diff --git a/dom/animation/Animation.cpp b/dom/animation/Animation.cpp index e479be06ea..b113aae924 100644 --- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -1204,8 +1204,7 @@ Animation::EffectEnd() const return StickyTimeDuration(0); } - return mEffect->SpecifiedTiming().mDelay - + mEffect->GetComputedTiming().mActiveDuration; + return mEffect->GetComputedTiming().mEndTime; } nsIDocument* diff --git a/dom/animation/AnimationEffectTiming.cpp b/dom/animation/AnimationEffectTiming.cpp index 37107ddc60..6fe676af43 100644 --- a/dom/animation/AnimationEffectTiming.cpp +++ b/dom/animation/AnimationEffectTiming.cpp @@ -18,6 +18,26 @@ AnimationEffectTiming::WrapObject(JSContext* aCx, JS::Handle aGivenPr return AnimationEffectTimingBinding::Wrap(aCx, this, aGivenProto); } +void +AnimationEffectTiming::NotifyTimingUpdate() +{ + if (mEffect) { + mEffect->NotifySpecifiedTimingUpdated(); + } +} + +void +AnimationEffectTiming::SetEndDelay(double aEndDelay) +{ + TimeDuration endDelay = TimeDuration::FromMilliseconds(aEndDelay); + if (mTiming.mEndDelay == endDelay) { + return; + } + mTiming.mEndDelay = endDelay; + + NotifyTimingUpdate(); +} + void AnimationEffectTiming::SetDuration(const UnrestrictedDoubleOrString& aDuration) { @@ -40,9 +60,7 @@ AnimationEffectTiming::SetDuration(const UnrestrictedDoubleOrString& aDuration) mTiming.mDuration.SetAsString() = aDuration.GetAsString(); } - if (mEffect) { - mEffect->NotifySpecifiedTimingUpdated(); - } + NotifyTimingUpdate(); } } // namespace dom diff --git a/dom/animation/AnimationEffectTiming.h b/dom/animation/AnimationEffectTiming.h index d6e9582cde..1f3afae6dc 100644 --- a/dom/animation/AnimationEffectTiming.h +++ b/dom/animation/AnimationEffectTiming.h @@ -24,9 +24,11 @@ public: void Unlink() override { mEffect = nullptr; } + void SetEndDelay(double aEndDelay); void SetDuration(const UnrestrictedDoubleOrString& aDuration); private: + void NotifyTimingUpdate(); KeyframeEffect* MOZ_NON_OWNING_REF mEffect; }; diff --git a/dom/animation/AnimationEffectTimingReadOnly.cpp b/dom/animation/AnimationEffectTimingReadOnly.cpp index 70ab956c89..1d61c6dbfc 100644 --- a/dom/animation/AnimationEffectTimingReadOnly.cpp +++ b/dom/animation/AnimationEffectTimingReadOnly.cpp @@ -13,111 +13,6 @@ #include "mozilla/dom/KeyframeEffectBinding.h" namespace mozilla { - -TimingParams::TimingParams(const dom::AnimationEffectTimingProperties& aRhs, - const dom::Element* aTarget) - : mDuration(aRhs.mDuration) - , mDelay(TimeDuration::FromMilliseconds(aRhs.mDelay)) - , mIterations(aRhs.mIterations) - , mDirection(aRhs.mDirection) - , mFill(aRhs.mFill) -{ - mFunction = AnimationUtils::ParseEasing(aTarget, aRhs.mEasing); -} - -TimingParams::TimingParams(double aDuration) -{ - mDuration.SetAsUnrestrictedDouble() = aDuration; -} - -template -static const dom::AnimationEffectTimingProperties& -GetTimingProperties(const OptionsType& aOptions); - -template <> -/* static */ const dom::AnimationEffectTimingProperties& -GetTimingProperties( - const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) -{ - MOZ_ASSERT(aOptions.IsKeyframeEffectOptions()); - return aOptions.GetAsKeyframeEffectOptions(); -} - -template <> -/* static */ const dom::AnimationEffectTimingProperties& -GetTimingProperties( - const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) -{ - MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions()); - return aOptions.GetAsKeyframeAnimationOptions(); -} - -template -static TimingParams -TimingParamsFromOptionsUnion( - const OptionsType& aOptions, - const Nullable& aTarget) -{ - if (aOptions.IsUnrestrictedDouble()) { - return TimingParams(aOptions.GetAsUnrestrictedDouble()); - } else { - // If aTarget is a pseudo element, we pass its parent element because - // TimingParams only needs its owner doc to parse easing and both pseudo - // element and its parent element should have the same owner doc. - // Bug 1246320: Avoid passing the element for parsing the timing function - RefPtr targetElement; - if (!aTarget.IsNull()) { - const dom::ElementOrCSSPseudoElement& target = aTarget.Value(); - MOZ_ASSERT(target.IsElement() || target.IsCSSPseudoElement(), - "Uninitialized target"); - if (target.IsElement()) { - targetElement = &target.GetAsElement(); - } else { - targetElement = target.GetAsCSSPseudoElement().ParentElement(); - } - } - return TimingParams(GetTimingProperties(aOptions), targetElement); - } -} - -/* static */ TimingParams -TimingParams::FromOptionsUnion( - const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, - const Nullable& aTarget) -{ - return TimingParamsFromOptionsUnion(aOptions, aTarget); -} - -/* static */ TimingParams -TimingParams::FromOptionsUnion( - const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, - const Nullable& aTarget) -{ - return TimingParamsFromOptionsUnion(aOptions, aTarget); -} - -bool -TimingParams::operator==(const TimingParams& aOther) const -{ - bool durationEqual; - if (mDuration.IsUnrestrictedDouble()) { - durationEqual = aOther.mDuration.IsUnrestrictedDouble() && - (mDuration.GetAsUnrestrictedDouble() == - aOther.mDuration.GetAsUnrestrictedDouble()); - } else { - // We consider all string values and uninitialized values as meaning "auto". - // Since mDuration is either a string or uninitialized, we consider it equal - // if aOther.mDuration is also either a string or uninitialized. - durationEqual = !aOther.mDuration.IsUnrestrictedDouble(); - } - return durationEqual && - mDelay == aOther.mDelay && - mIterations == aOther.mIterations && - mDirection == aOther.mDirection && - mFill == aOther.mFill && - mFunction == aOther.mFunction; -} - namespace dom { NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationEffectTimingReadOnly, mParent) diff --git a/dom/animation/AnimationEffectTimingReadOnly.h b/dom/animation/AnimationEffectTimingReadOnly.h index 889ad1fe37..2715530493 100644 --- a/dom/animation/AnimationEffectTimingReadOnly.h +++ b/dom/animation/AnimationEffectTimingReadOnly.h @@ -10,59 +10,13 @@ #include "js/TypeDecls.h" #include "mozilla/Attributes.h" #include "mozilla/ErrorResult.h" +#include "mozilla/TimingParams.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/UnionTypes.h" #include "nsCycleCollectionParticipant.h" #include "nsWrapperCache.h" -// X11 has a #define for None -#ifdef None -#undef None -#endif -#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for FillMode - // and PlaybackDirection - namespace mozilla { - -namespace dom { -struct AnimationEffectTimingProperties; -class Element; -class UnrestrictedDoubleOrKeyframeEffectOptions; -class UnrestrictedDoubleOrKeyframeAnimationOptions; -class ElementOrCSSPseudoElement; -} - -struct TimingParams -{ - TimingParams() = default; - TimingParams(const dom::AnimationEffectTimingProperties& aTimingProperties, - const dom::Element* aTarget); - explicit TimingParams(double aDuration); - - static TimingParams FromOptionsUnion( - const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, - const Nullable& aTarget); - static TimingParams FromOptionsUnion( - const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, - const Nullable& aTarget); - - // The unitialized state of mDuration represents "auto". - // Bug 1237173: We will replace this with Maybe. - dom::OwningUnrestrictedDoubleOrString mDuration; - TimeDuration mDelay; // Initializes to zero - double mIterations = 1.0; // Can be NaN, negative, +/-Infinity - dom::PlaybackDirection mDirection = dom::PlaybackDirection::Normal; - dom::FillMode mFill = dom::FillMode::Auto; - Maybe mFunction; - - bool operator==(const TimingParams& aOther) const; - bool operator!=(const TimingParams& aOther) const - { - return !(*this == aOther); - } -}; - - namespace dom { class AnimationEffectTimingReadOnly : public nsWrapperCache @@ -83,9 +37,9 @@ public: JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; double Delay() const { return mTiming.mDelay.ToMilliseconds(); } - double EndDelay() const { return 0.0; } + double EndDelay() const { return mTiming.mEndDelay.ToMilliseconds(); } FillMode Fill() const { return mTiming.mFill; } - double IterationStart() const { return 0.0; } + double IterationStart() const { return mTiming.mIterationStart; } double Iterations() const { return mTiming.mIterations; } void GetDuration(OwningUnrestrictedDoubleOrString& aRetVal) const { diff --git a/dom/animation/AnimationPerformanceWarning.cpp b/dom/animation/AnimationPerformanceWarning.cpp new file mode 100644 index 0000000000..21b91e3525 --- /dev/null +++ b/dom/animation/AnimationPerformanceWarning.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AnimationPerformanceWarning.h" + +#include "nsContentUtils.h" + +namespace mozilla { + +bool +AnimationPerformanceWarning::ToLocalizedString( + nsXPIDLString& aLocalizedString) const +{ + const char* key = nullptr; + + switch (mType) { + case Type::ContentTooLarge: + { + MOZ_ASSERT(mParams && mParams->Length() == 7, + "Parameter's length should be 7 for ContentTooLarge"); + + MOZ_ASSERT(mParams->Length() <= kMaxParamsForLocalization, + "Parameter's length should be less than " + "kMaxParamsForLocalization"); + // We can pass an array of parameters whose length is greater than 7 to + // nsContentUtils::FormatLocalizedString because + // nsTextFormatter drops those extra parameters in the end. + nsAutoString strings[kMaxParamsForLocalization]; + const char16_t* charParams[kMaxParamsForLocalization]; + + for (size_t i = 0, n = mParams->Length(); i < n; i++) { + strings[i].AppendInt((*mParams)[i]); + charParams[i] = strings[i].get(); + } + + nsresult rv = nsContentUtils::FormatLocalizedString( + nsContentUtils::eLAYOUT_PROPERTIES, + "AnimationWarningContentTooLarge", + charParams, + aLocalizedString); + return NS_SUCCEEDED(rv); + } + case Type::TransformBackfaceVisibilityHidden: + key = "AnimationWarningTransformBackfaceVisibilityHidden"; + break; + case Type::TransformPreserve3D: + key = "AnimationWarningTransformPreserve3D"; + break; + case Type::TransformSVG: + key = "AnimationWarningTransformSVG"; + break; + case Type::TransformFrameInactive: + key = "AnimationWarningTransformFrameInactive"; + break; + case Type::OpacityFrameInactive: + key = "AnimationWarningOpacityFrameInactive"; + break; + case Type::WithGeometricProperties: + key = "AnimationWarningWithGeometricProperties"; + break; + } + + nsresult rv = + nsContentUtils::GetLocalizedString(nsContentUtils::eLAYOUT_PROPERTIES, + key, aLocalizedString); + return NS_SUCCEEDED(rv); +} + +} // namespace mozilla diff --git a/dom/animation/AnimationPerformanceWarning.h b/dom/animation/AnimationPerformanceWarning.h new file mode 100644 index 0000000000..11481fe437 --- /dev/null +++ b/dom/animation/AnimationPerformanceWarning.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_AnimationPerformanceWarning_h +#define mozilla_dom_AnimationPerformanceWarning_h + +#include "mozilla/InitializerList.h" + +class nsXPIDLString; + +namespace mozilla { + +// Represents the reason why we can't run the CSS property on the compositor. +struct AnimationPerformanceWarning +{ + enum class Type : uint8_t { + ContentTooLarge, + TransformBackfaceVisibilityHidden, + TransformPreserve3D, + TransformSVG, + TransformFrameInactive, + OpacityFrameInactive, + WithGeometricProperties + }; + + explicit AnimationPerformanceWarning(Type aType) + : mType(aType) { } + + AnimationPerformanceWarning(Type aType, + std::initializer_list aParams) + : mType(aType) + { + // FIXME: Once std::initializer_list::size() become a constexpr function, + // we should use static_assert here. + MOZ_ASSERT(aParams.size() <= kMaxParamsForLocalization, + "The length of parameters should be less than " + "kMaxParamsForLocalization"); + mParams.emplace(aParams); + } + + // Maximum number of parameters passed to + // nsContentUtils::FormatLocalizedString to localize warning messages. + // + // NOTE: This constexpr can't be forward declared, so if you want to use + // this variable, please include this header file directly. + // This value is the same as the limit of nsStringBundle::FormatString. + // See the implementation of nsStringBundle::FormatString. + static MOZ_CONSTEXPR_VAR uint8_t kMaxParamsForLocalization = 10; + + // Indicates why this property could not be animated on the compositor. + Type mType; + + // Optional parameters that may be used for localization. + Maybe> mParams; + + bool ToLocalizedString(nsXPIDLString& aLocalizedString) const; + + bool operator==(const AnimationPerformanceWarning& aOther) const + { + return mType == aOther.mType && + mParams == aOther.mParams; + } + bool operator!=(const AnimationPerformanceWarning& aOther) const + { + return !(*this == aOther); + } +}; + +} // namespace mozilla + +#endif // mozilla_dom_AnimationPerformanceWarning_h diff --git a/dom/animation/EffectCompositor.cpp b/dom/animation/EffectCompositor.cpp index 5b6eb6f9b1..4c2c80e7ce 100644 --- a/dom/animation/EffectCompositor.cpp +++ b/dom/animation/EffectCompositor.cpp @@ -10,6 +10,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/KeyframeEffect.h" // For KeyframeEffectReadOnly #include "mozilla/AnimationUtils.h" +#include "mozilla/AnimationPerformanceWarning.h" #include "mozilla/EffectSet.h" #include "mozilla/InitializerList.h" #include "mozilla/LayerAnimationInfo.h" @@ -109,10 +110,14 @@ FindAnimationsForCompositor(const nsIFrame* aFrame, continue; } - if (effect->ShouldBlockCompositorAnimations(aFrame)) { + AnimationPerformanceWarning::Type warningType; + if (effect->ShouldBlockCompositorAnimations(aFrame, + warningType)) { if (aMatches) { aMatches->Clear(); } + effect->SetPerformanceWarning( + aProperty, AnimationPerformanceWarning(warningType)); return false; } @@ -716,6 +721,22 @@ EffectCompositor::GetPresContext(Element* aElement) return shell->GetPresContext(); } +/* static */ void +EffectCompositor::SetPerformanceWarning( + const nsIFrame *aFrame, + nsCSSProperty aProperty, + const AnimationPerformanceWarning& aWarning) +{ + EffectSet* effects = EffectSet::GetEffectSet(aFrame); + if (!effects) { + return; + } + + for (KeyframeEffectReadOnly* effect : *effects) { + effect->SetPerformanceWarning(aProperty, aWarning); + } +} + // --------------------------------------------------------- // // Nested class: AnimationStyleRuleProcessor diff --git a/dom/animation/EffectCompositor.h b/dom/animation/EffectCompositor.h index 679fbf3374..fbcbd1bc25 100644 --- a/dom/animation/EffectCompositor.h +++ b/dom/animation/EffectCompositor.h @@ -30,6 +30,7 @@ namespace mozilla { class EffectSet; class RestyleTracker; enum class CSSPseudoElementType : uint8_t; +struct AnimationPerformanceWarning; namespace dom { class Animation; @@ -187,6 +188,13 @@ public: static Maybe> GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame); + // Associates a performance warning with effects on |aFrame| that animates + // |aProperty|. + static void SetPerformanceWarning( + const nsIFrame* aFrame, + nsCSSProperty aProperty, + const AnimationPerformanceWarning& aWarning); + private: ~EffectCompositor() = default; diff --git a/dom/animation/KeyframeEffect.cpp b/dom/animation/KeyframeEffect.cpp index 3241a6e71a..bf73d13dcb 100644 --- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -34,18 +34,20 @@ GetComputedTimingDictionary(const ComputedTiming& aComputedTiming, { // AnimationEffectTimingProperties aRetVal.mDelay = aTiming.mDelay.ToMilliseconds(); + aRetVal.mEndDelay = aTiming.mEndDelay.ToMilliseconds(); aRetVal.mFill = aComputedTiming.mFill; aRetVal.mIterations = aComputedTiming.mIterations; + aRetVal.mIterationStart = aComputedTiming.mIterationStart; aRetVal.mDuration.SetAsUnrestrictedDouble() = aComputedTiming.mDuration.ToMilliseconds(); aRetVal.mDirection = aTiming.mDirection; // ComputedTimingProperties aRetVal.mActiveDuration = aComputedTiming.mActiveDuration.ToMilliseconds(); - aRetVal.mEndTime - = std::max(aRetVal.mDelay + aRetVal.mActiveDuration + aRetVal.mEndDelay, 0.0); + aRetVal.mEndTime = aComputedTiming.mEndTime.ToMilliseconds(); aRetVal.mLocalTime = AnimationUtils::TimeDurationToDouble(aLocalTime); aRetVal.mProgress = aComputedTiming.mProgress; + if (!aRetVal.mProgress.IsNull()) { // Convert the returned currentIteration into Infinity if we set // (uint64_t) aComputedTiming.mCurrentIteration to UINT64_MAX @@ -245,7 +247,11 @@ KeyframeEffectReadOnly::GetComputedTimingAt( result.mIterations = IsNaN(aTiming.mIterations) || aTiming.mIterations < 0.0f ? 1.0f : aTiming.mIterations; + result.mIterationStart = std::max(aTiming.mIterationStart, 0.0); + result.mActiveDuration = ActiveDuration(result.mDuration, result.mIterations); + result.mEndTime = aTiming.mDelay + result.mActiveDuration + + aTiming.mEndDelay; result.mFill = aTiming.mFill == dom::FillMode::Auto ? dom::FillMode::None : aTiming.mFill; @@ -264,7 +270,9 @@ KeyframeEffectReadOnly::GetComputedTimingAt( // Get the normalized time within the active interval. StickyTimeDuration activeTime; - if (localTime >= aTiming.mDelay + result.mActiveDuration) { + if (localTime >= + std::min(StickyTimeDuration(aTiming.mDelay + result.mActiveDuration), + result.mEndTime)) { result.mPhase = ComputedTiming::AnimationPhase::After; if (!result.FillsForwards()) { // The animation isn't active or filling at this time. @@ -272,11 +280,13 @@ KeyframeEffectReadOnly::GetComputedTimingAt( return result; } activeTime = result.mActiveDuration; - // Note that infinity == floor(infinity) so this will also be true when we - // have finished an infinitely repeating animation of zero duration. + double finiteProgress = + (IsInfinite(result.mIterations) ? 0.0 : result.mIterations) + + result.mIterationStart; isEndOfFinalIteration = result.mIterations != 0.0 && - result.mIterations == floor(result.mIterations); - } else if (localTime < aTiming.mDelay) { + fmod(finiteProgress, 1.0) == 0; + } else if (localTime < + std::min(StickyTimeDuration(aTiming.mDelay), result.mEndTime)) { result.mPhase = ComputedTiming::AnimationPhase::Before; if (!result.FillsBackwards()) { // The animation isn't active or filling at this time. @@ -291,49 +301,66 @@ KeyframeEffectReadOnly::GetComputedTimingAt( activeTime = localTime - aTiming.mDelay; } + // Calculate the scaled active time + // (We handle the case where the iterationStart is zero separately in case + // the duration is infinity, since 0 * Infinity is undefined.) + StickyTimeDuration startOffset = + result.mIterationStart == 0.0 + ? StickyTimeDuration(0) + : result.mDuration.MultDouble(result.mIterationStart); + StickyTimeDuration scaledActiveTime = activeTime + startOffset; + // Get the position within the current iteration. StickyTimeDuration iterationTime; - if (result.mDuration != zeroDuration) { + if (result.mDuration != zeroDuration && + scaledActiveTime != StickyTimeDuration::Forever()) { iterationTime = isEndOfFinalIteration ? result.mDuration - : activeTime % result.mDuration; - } /* else, iterationTime is zero */ + : scaledActiveTime % result.mDuration; + } /* else, either the duration is zero and iterationTime is zero, + or the scaledActiveTime is infinity in which case the iterationTime + should become infinity but we will not use the iterationTime in that + case so we just leave it as zero */ // Determine the 0-based index of the current iteration. - if (isEndOfFinalIteration) { + if (result.mPhase == ComputedTiming::AnimationPhase::Before || + result.mIterations == 0) { + result.mCurrentIteration = static_cast(result.mIterationStart); + } else if (result.mPhase == ComputedTiming::AnimationPhase::After) { result.mCurrentIteration = - IsInfinite(result.mIterations) // Positive Infinity? + IsInfinite(result.mIterations) ? UINT64_MAX // In GetComputedTimingDictionary(), we will convert this // into Infinity. - : static_cast(result.mIterations) - 1; - } else if (activeTime == zeroDuration) { - // If the active time is zero we're either in the first iteration - // (including filling backwards) or we have finished an animation with an - // iteration duration of zero that is filling forwards (but we're not at - // the exact end of an iteration since we deal with that above). - result.mCurrentIteration = - result.mPhase == ComputedTiming::AnimationPhase::After - ? static_cast(result.mIterations) // floor - : 0; + : static_cast(ceil(result.mIterations + + result.mIterationStart)) - 1; + } else if (result.mDuration == StickyTimeDuration::Forever()) { + result.mCurrentIteration = static_cast(result.mIterationStart); } else { result.mCurrentIteration = - static_cast(activeTime / result.mDuration); // floor + static_cast(scaledActiveTime / result.mDuration); // floor } // Normalize the iteration time into a fraction of the iteration duration. - if (result.mPhase == ComputedTiming::AnimationPhase::Before) { - result.mProgress.SetValue(0.0); + if (result.mPhase == ComputedTiming::AnimationPhase::Before || + result.mIterations == 0) { + double progress = fmod(result.mIterationStart, 1.0); + result.mProgress.SetValue(progress); } else if (result.mPhase == ComputedTiming::AnimationPhase::After) { - double progress = isEndOfFinalIteration - ? 1.0 - : fmod(result.mIterations, 1.0); + double progress; + if (isEndOfFinalIteration) { + progress = 1.0; + } else if (IsInfinite(result.mIterations)) { + progress = fmod(result.mIterationStart, 1.0); + } else { + progress = fmod(result.mIterations + result.mIterationStart, 1.0); + } result.mProgress.SetValue(progress); } else { // We are in the active phase so the iteration duration can't be zero. MOZ_ASSERT(result.mDuration != zeroDuration, "In the active phase of a zero-duration animation?"); double progress = result.mDuration == StickyTimeDuration::Forever() - ? 0.0 + ? fmod(result.mIterationStart, 1.0) : iterationTime / result.mDuration; result.mProgress.SetValue(progress); } @@ -368,18 +395,19 @@ KeyframeEffectReadOnly::GetComputedTimingAt( } StickyTimeDuration -KeyframeEffectReadOnly::ActiveDuration(const StickyTimeDuration& aIterationDuration, - double aIterationCount) +KeyframeEffectReadOnly::ActiveDuration( + const StickyTimeDuration& aIterationDuration, + double aIterationCount) { - if (IsInfinite(aIterationCount)) { - // An animation that repeats forever has an infinite active duration - // unless its iteration duration is zero, in which case it has a zero - // active duration. - const StickyTimeDuration zeroDuration; - return aIterationDuration == zeroDuration ? - zeroDuration : - StickyTimeDuration::Forever(); + // If either the iteration duration or iteration count is zero, + // Web Animations says that the active duration is zero. This is to + // ensure that the result is defined when the other argument is Infinity. + const StickyTimeDuration zeroDuration; + if (aIterationDuration == zeroDuration || + aIterationCount == 0.0) { + return zeroDuration; } + return aIterationDuration.MultDouble(aIterationCount); } @@ -609,6 +637,12 @@ KeyframeEffectReadOnly::SetIsRunningOnCompositor(nsCSSProperty aProperty, for (AnimationProperty& property : mProperties) { if (property.mProperty == aProperty) { property.mIsRunningOnCompositor = aIsRunning; + // We currently only set a performance warning message when animations + // cannot be run on the compositor, so if this animation is running + // on the compositor we don't need a message. + if (aIsRunning) { + property.mPerformanceWarning.reset(); + } return; } } @@ -1847,6 +1881,32 @@ KeyframeEffectReadOnly::GetFrames(JSContext*& aCx, } } + +void +KeyframeEffectReadOnly::GetPropertyState( + nsTArray& aStates) const +{ + for (const AnimationProperty& property : mProperties) { + // Bug 1252730: We should also expose this winsInCascade as well. + if (!property.mWinsInCascade) { + continue; + } + + AnimationPropertyState state; + state.mProperty.Construct( + NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty))); + state.mRunningOnCompositor.Construct(property.mIsRunningOnCompositor); + + nsXPIDLString localizedString; + if (property.mPerformanceWarning && + property.mPerformanceWarning->ToLocalizedString(localizedString)) { + state.mWarning.Construct(localizedString); + } + + aStates.AppendElement(state); + } +} + /* static */ const TimeDuration KeyframeEffectReadOnly::OverflowRegionRefreshInterval() { @@ -2045,42 +2105,29 @@ KeyframeEffectReadOnly::IsGeometricProperty( /* static */ bool KeyframeEffectReadOnly::CanAnimateTransformOnCompositor( const nsIFrame* aFrame, - const nsIContent* aContent) + AnimationPerformanceWarning::Type& aPerformanceWarning) { // Disallow OMTA for preserve-3d transform. Note that we check the style property // rather than Extend3DContext() since that can recurse back into this function - // via HasOpacity(). + // via HasOpacity(). See bug 779598. if (aFrame->Combines3DTransformWithAncestors() || aFrame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) { - if (aContent) { - nsCString message; - message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' " - "transforms is not supported. See bug 779598"); - AnimationUtils::LogAsyncAnimationFailure(message, aContent); - } + aPerformanceWarning = AnimationPerformanceWarning::Type::TransformPreserve3D; return false; } // Note that testing BackfaceIsHidden() is not a sufficient test for // what we need for animating backface-visibility correctly if we // remove the above test for Extend3DContext(); that would require - // looking at backface-visibility on descendants as well. + // looking at backface-visibility on descendants as well. See bug 1186204. if (aFrame->StyleDisplay()->BackfaceIsHidden()) { - if (aContent) { - nsCString message; - message.AppendLiteral("Gecko bug: Async animation of " - "'backface-visibility: hidden' transforms is not supported." - " See bug 1186204."); - AnimationUtils::LogAsyncAnimationFailure(message, aContent); - } + aPerformanceWarning = + AnimationPerformanceWarning::Type::TransformBackfaceVisibilityHidden; return false; } + // Async 'transform' animations of aFrames with SVG transforms is not + // supported. See bug 779599. if (aFrame->IsSVGTransformed()) { - if (aContent) { - nsCString message; - message.AppendLiteral("Gecko bug: Async 'transform' animations of " - "aFrames with SVG transforms is not supported. See bug 779599"); - AnimationUtils::LogAsyncAnimationFailure(message, aContent); - } + aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG; return false; } @@ -2088,8 +2135,9 @@ KeyframeEffectReadOnly::CanAnimateTransformOnCompositor( } bool -KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame* - aFrame) const +KeyframeEffectReadOnly::ShouldBlockCompositorAnimations( + const nsIFrame* aFrame, + AnimationPerformanceWarning::Type& aPerformanceWarning) const { // We currently only expect this method to be called when this effect // is attached to a playing Animation. If that ever changes we'll need @@ -2098,8 +2146,6 @@ KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame* // running on the compositor. MOZ_ASSERT(mAnimation && mAnimation->IsPlaying()); - bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled(); - for (const AnimationProperty& property : mProperties) { // If a property is overridden in the CSS cascade, it should not block other // animations from running on the compositor. @@ -2108,20 +2154,15 @@ KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame* } // Check for geometric properties if (IsGeometricProperty(property.mProperty)) { - if (shouldLog) { - nsCString message; - message.AppendLiteral("Performance warning: Async animation of " - "'transform' or 'opacity' not possible due to animation of geometric" - "properties on the same element"); - AnimationUtils::LogAsyncAnimationFailure(message, aFrame->GetContent()); - } + aPerformanceWarning = + AnimationPerformanceWarning::Type::WithGeometricProperties; return true; } // Check for unsupported transform animations if (property.mProperty == eCSSProperty_transform) { if (!CanAnimateTransformOnCompositor(aFrame, - shouldLog ? aFrame->GetContent() : nullptr)) { + aPerformanceWarning)) { return true; } } @@ -2130,6 +2171,28 @@ KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame* return false; } +void +KeyframeEffectReadOnly::SetPerformanceWarning( + nsCSSProperty aProperty, + const AnimationPerformanceWarning& aWarning) +{ + for (AnimationProperty& property : mProperties) { + if (property.mProperty == aProperty && + (!property.mPerformanceWarning || + *property.mPerformanceWarning != aWarning)) { + property.mPerformanceWarning = Some(aWarning); + + nsXPIDLString localizedString; + if (nsLayoutUtils::IsAnimationLoggingEnabled() && + property.mPerformanceWarning->ToLocalizedString(localizedString)) { + nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString); + AnimationUtils::LogAsyncAnimationFailure(logMessage, mTarget); + } + return; + } + } +} + //--------------------------------------------------------------------- // // KeyframeEffect diff --git a/dom/animation/KeyframeEffect.h b/dom/animation/KeyframeEffect.h index 33b67aa388..f66460de3c 100644 --- a/dom/animation/KeyframeEffect.h +++ b/dom/animation/KeyframeEffect.h @@ -11,6 +11,7 @@ #include "nsCycleCollectionParticipant.h" #include "nsIDocument.h" #include "nsWrapperCache.h" +#include "mozilla/AnimationPerformanceWarning.h" #include "mozilla/Attributes.h" #include "mozilla/ComputedTimingFunction.h" // ComputedTimingFunction #include "mozilla/LayerAnimationInfo.h" // LayerAnimations::kRecords @@ -18,8 +19,9 @@ #include "mozilla/StickyTimeDuration.h" #include "mozilla/StyleAnimationValue.h" #include "mozilla/TimeStamp.h" +#include "mozilla/TimingParams.h" #include "mozilla/dom/AnimationEffectReadOnly.h" -#include "mozilla/dom/AnimationEffectTimingReadOnly.h" // TimingParams +#include "mozilla/dom/AnimationEffectTimingReadOnly.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/KeyframeBinding.h" #include "mozilla/dom/Nullable.h" @@ -43,6 +45,7 @@ class OwningElementOrCSSPseudoElement; class UnrestrictedDoubleOrKeyframeEffectOptions; enum class IterationCompositeOperation : uint32_t; enum class CompositeOperation : uint32_t; +struct AnimationPropertyState; } /** @@ -55,6 +58,10 @@ struct ComputedTiming // Will equal StickyTimeDuration::Forever() if the animation repeats // indefinitely. StickyTimeDuration mActiveDuration; + // The effect end time in local time (i.e. an offset from the effect's + // start time). Will equal StickyTimeDuration::Forever() if the animation + // plays indefinitely. + StickyTimeDuration mEndTime; // Progress towards the end of the current iteration. If the effect is // being sampled backwards, this will go from 1.0 to 0.0. // Will be null if the animation is neither animating nor @@ -65,6 +72,7 @@ struct ComputedTiming // Unlike TimingParams::mIterations, this value is // guaranteed to be in the range [0, Infinity]. double mIterations = 1.0; + double mIterationStart = 0.0; StickyTimeDuration mDuration; // This is the computed fill mode so it is never auto @@ -140,6 +148,8 @@ struct AnimationProperty // objects for equality. bool mIsRunningOnCompositor = false; + Maybe mPerformanceWarning; + InfallibleTArray mSegments; // NOTE: This operator does *not* compare the mWinsInCascade member *or* the @@ -298,6 +308,8 @@ public: bool IsRunningOnCompositor() const; void SetIsRunningOnCompositor(nsCSSProperty aProperty, bool aIsRunning); + void GetPropertyState(nsTArray& aStates) const; + // Returns true if this effect, applied to |aFrame|, contains // properties that mean we shouldn't run *any* compositor animations on this // element. @@ -312,11 +324,24 @@ public: // // Bug 1218620 - It seems like we don't need to be this restrictive. Wouldn't // it be ok to do 'opacity' animations on the compositor in either case? - bool ShouldBlockCompositorAnimations(const nsIFrame* aFrame) const; + // + // When returning true, |aOutPerformanceWarning| stores the reason why + // we shouldn't run the compositor animations. + bool ShouldBlockCompositorAnimations( + const nsIFrame* aFrame, + AnimationPerformanceWarning::Type& aPerformanceWarning) const; nsIDocument* GetRenderedDocument() const; nsPresContext* GetPresContext() const; + // Associates a warning with the animated property on the specified frame + // indicating why, for example, the property could not be animated on the + // compositor. |aParams| and |aParamsLength| are optional parameters which + // will be used to generate a localized message for devtools. + void SetPerformanceWarning( + nsCSSProperty aProperty, + const AnimationPerformanceWarning& aWarning); + protected: KeyframeEffectReadOnly(nsIDocument* aDocument, Element* aTarget, @@ -378,11 +403,11 @@ private: bool CanThrottleTransformChanges(nsIFrame& aFrame) const; // Returns true unless Gecko limitations prevent performing transform - // animations for |aFrame|. Any limitations that are encountered are - // logged using |aContent| to describe the affected content. - // If |aContent| is nullptr, no logging is performed - static bool CanAnimateTransformOnCompositor(const nsIFrame* aFrame, - const nsIContent* aContent); + // animations for |aFrame|. When returning true, the reason for the + // limitation is stored in |aOutPerformanceWarning|. + static bool CanAnimateTransformOnCompositor( + const nsIFrame* aFrame, + AnimationPerformanceWarning::Type& aPerformanceWarning); static bool IsGeometricProperty(const nsCSSProperty aProperty); static const TimeDuration OverflowRegionRefreshInterval(); diff --git a/dom/animation/TimingParams.cpp b/dom/animation/TimingParams.cpp new file mode 100644 index 0000000000..dbb1df9f9d --- /dev/null +++ b/dom/animation/TimingParams.cpp @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/TimingParams.h" + +namespace mozilla { + +TimingParams::TimingParams(const dom::AnimationEffectTimingProperties& aRhs, + const dom::Element* aTarget) + : mDuration(aRhs.mDuration) + , mDelay(TimeDuration::FromMilliseconds(aRhs.mDelay)) + , mEndDelay(TimeDuration::FromMilliseconds(aRhs.mEndDelay)) + , mIterations(aRhs.mIterations) + , mIterationStart(aRhs.mIterationStart) + , mDirection(aRhs.mDirection) + , mFill(aRhs.mFill) +{ + mFunction = AnimationUtils::ParseEasing(aTarget, aRhs.mEasing); +} + +TimingParams::TimingParams(double aDuration) +{ + mDuration.SetAsUnrestrictedDouble() = aDuration; +} + +template +static const dom::AnimationEffectTimingProperties& +GetTimingProperties(const OptionsType& aOptions); + +template <> +/* static */ const dom::AnimationEffectTimingProperties& +GetTimingProperties( + const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) +{ + MOZ_ASSERT(aOptions.IsKeyframeEffectOptions()); + return aOptions.GetAsKeyframeEffectOptions(); +} + +template <> +/* static */ const dom::AnimationEffectTimingProperties& +GetTimingProperties( + const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) +{ + MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions()); + return aOptions.GetAsKeyframeAnimationOptions(); +} + +template +static TimingParams +TimingParamsFromOptionsUnion( + const OptionsType& aOptions, + const Nullable& aTarget) +{ + if (aOptions.IsUnrestrictedDouble()) { + return TimingParams(aOptions.GetAsUnrestrictedDouble()); + } else { + // If aTarget is a pseudo element, we pass its parent element because + // TimingParams only needs its owner doc to parse easing and both pseudo + // element and its parent element should have the same owner doc. + // Bug 1246320: Avoid passing the element for parsing the timing function + RefPtr targetElement; + if (!aTarget.IsNull()) { + const dom::ElementOrCSSPseudoElement& target = aTarget.Value(); + MOZ_ASSERT(target.IsElement() || target.IsCSSPseudoElement(), + "Uninitialized target"); + if (target.IsElement()) { + targetElement = &target.GetAsElement(); + } else { + targetElement = target.GetAsCSSPseudoElement().ParentElement(); + } + } + return TimingParams(GetTimingProperties(aOptions), targetElement); + } +} + +/* static */ TimingParams +TimingParams::FromOptionsUnion( + const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, + const Nullable& aTarget) +{ + return TimingParamsFromOptionsUnion(aOptions, aTarget); +} + +/* static */ TimingParams +TimingParams::FromOptionsUnion( + const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, + const Nullable& aTarget) +{ + return TimingParamsFromOptionsUnion(aOptions, aTarget); +} + +bool +TimingParams::operator==(const TimingParams& aOther) const +{ + bool durationEqual; + if (mDuration.IsUnrestrictedDouble()) { + durationEqual = aOther.mDuration.IsUnrestrictedDouble() && + (mDuration.GetAsUnrestrictedDouble() == + aOther.mDuration.GetAsUnrestrictedDouble()); + } else { + // We consider all string values and uninitialized values as meaning "auto". + // Since mDuration is either a string or uninitialized, we consider it equal + // if aOther.mDuration is also either a string or uninitialized. + durationEqual = !aOther.mDuration.IsUnrestrictedDouble(); + } + return durationEqual && + mDelay == aOther.mDelay && + mIterations == aOther.mIterations && + mIterationStart == aOther.mIterationStart && + mDirection == aOther.mDirection && + mFill == aOther.mFill && + mFunction == aOther.mFunction; +} + +} // namespace mozilla diff --git a/dom/animation/TimingParams.h b/dom/animation/TimingParams.h new file mode 100644 index 0000000000..442ebc482d --- /dev/null +++ b/dom/animation/TimingParams.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimingParams_h +#define mozilla_TimingParams_h + +#include "mozilla/dom/Nullable.h" +#include "mozilla/dom/UnionTypes.h" // For OwningUnrestrictedDoubleOrString +#include "mozilla/ComputedTimingFunction.h" +#include "mozilla/Maybe.h" +#include "mozilla/TimeStamp.h" // for TimeDuration + +// X11 has a #define for None +#ifdef None +#undef None +#endif +#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for FillMode + // and PlaybackDirection + +namespace mozilla { + +namespace dom { +struct AnimationEffectTimingProperties; +class Element; +class UnrestrictedDoubleOrKeyframeEffectOptions; +class UnrestrictedDoubleOrKeyframeAnimationOptions; +class ElementOrCSSPseudoElement; +} + +struct TimingParams +{ + TimingParams() = default; + TimingParams(const dom::AnimationEffectTimingProperties& aTimingProperties, + const dom::Element* aTarget); + explicit TimingParams(double aDuration); + + static TimingParams FromOptionsUnion( + const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, + const Nullable& aTarget); + static TimingParams FromOptionsUnion( + const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, + const Nullable& aTarget); + + // The unitialized state of mDuration represents "auto". + // Bug 1237173: We will replace this with Maybe. + dom::OwningUnrestrictedDoubleOrString mDuration; + TimeDuration mDelay; // Initializes to zero + TimeDuration mEndDelay; + double mIterations = 1.0; // Can be NaN, negative, +/-Infinity + double mIterationStart = 0.0; + dom::PlaybackDirection mDirection = dom::PlaybackDirection::Normal; + dom::FillMode mFill = dom::FillMode::Auto; + Maybe mFunction; + + bool operator==(const TimingParams& aOther) const; + bool operator!=(const TimingParams& aOther) const + { + return !(*this == aOther); + } +}; + +} // namespace mozilla + +#endif // mozilla_TimingParams_h diff --git a/dom/animation/moz.build b/dom/animation/moz.build index aa9aaba29c..1b1e7ee28a 100644 --- a/dom/animation/moz.build +++ b/dom/animation/moz.build @@ -20,6 +20,7 @@ EXPORTS.mozilla.dom += [ EXPORTS.mozilla += [ 'AnimationComparator.h', + 'AnimationPerformanceWarning.h', 'AnimationUtils.h', 'AnimValuesStyleRule.h', 'ComputedTimingFunction.h', @@ -27,6 +28,7 @@ EXPORTS.mozilla += [ 'EffectSet.h', 'PendingAnimationTracker.h', 'PseudoElementHashEntry.h', + 'TimingParams.h', ] UNIFIED_SOURCES += [ @@ -34,6 +36,7 @@ UNIFIED_SOURCES += [ 'AnimationEffectReadOnly.cpp', 'AnimationEffectTiming.cpp', 'AnimationEffectTimingReadOnly.cpp', + 'AnimationPerformanceWarning.cpp', 'AnimationTimeline.cpp', 'AnimationUtils.cpp', 'AnimValuesStyleRule.cpp', @@ -44,6 +47,7 @@ UNIFIED_SOURCES += [ 'EffectSet.cpp', 'KeyframeEffect.cpp', 'PendingAnimationTracker.cpp', + 'TimingParams.cpp', ] LOCAL_INCLUDES += [ diff --git a/dom/animation/test/chrome.ini b/dom/animation/test/chrome.ini index 7bff7243a2..ab725257ec 100644 --- a/dom/animation/test/chrome.ini +++ b/dom/animation/test/chrome.ini @@ -7,6 +7,7 @@ support-files = # file_animate_xrays.html needs to go in mochitest.ini since it is served # over HTTP [chrome/test_animation_observers.html] +[chrome/test_animation_property_state.html] [chrome/test_restyles.html] [chrome/test_running_on_compositor.html] skip-if = buildapp == 'b2g' diff --git a/dom/animation/test/chrome/test_animation_observers.html b/dom/animation/test/chrome/test_animation_observers.html index 3a5efa5d77..a6735396ee 100644 --- a/dom/animation/test/chrome/test_animation_observers.html +++ b/dom/animation/test/chrome/test_animation_observers.html @@ -1898,6 +1898,43 @@ addAsyncAnimTest("change_duration_and_currenttime", yield await_frame(); }); +addAsyncAnimTest("change_enddelay_and_currenttime", + { observe: div, subtree: true }, function*() { + var anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 100000 }); + yield await_frame(); + assert_records([{ added: [anim], changed: [], removed: [] }], + "records after animation is added"); + + anim.effect.timing.endDelay = 10000; + yield await_frame(); + assert_records([{ added: [], changed: [anim], removed: [] }], + "records after endDelay is changed"); + + anim.effect.timing.endDelay = 10000; + yield await_frame(); + assert_records([], "records after assigning same value"); + + anim.currentTime = 109000; + yield await_frame(); + assert_records([{ added: [], changed: [], removed: [anim] }], + "records after currentTime during endDelay"); + + anim.effect.timing.endDelay = -110000; + yield await_frame(); + assert_records([], "records after assigning negative value"); + + anim.cancel(); + yield await_frame(); +}); + +addAsyncAnimTest("change_enddelay_and_currenttime", + { observe: div, subtree: true }, function*() { + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 100, endDelay: -100 }); + yield await_frame(); + assert_records([], "records after animation is added"); +}); + // Run the tests. SimpleTest.requestLongerTimeout(2); SimpleTest.waitForExplicitFinish(); diff --git a/dom/animation/test/chrome/test_animation_property_state.html b/dom/animation/test/chrome/test_animation_property_state.html new file mode 100644 index 0000000000..0dcce0200d --- /dev/null +++ b/dom/animation/test/chrome/test_animation_property_state.html @@ -0,0 +1,330 @@ + + + +Bug 1196114 - Animation property which indicates + running on the compositor or not + + + + + + +Mozilla Bug 1196114 +
+ + + diff --git a/dom/animation/test/chrome/test_running_on_compositor.html b/dom/animation/test/chrome/test_running_on_compositor.html index 75edff3641..688e24137a 100644 --- a/dom/animation/test/chrome/test_running_on_compositor.html +++ b/dom/animation/test/chrome/test_running_on_compositor.html @@ -327,6 +327,72 @@ promise_test(function(t) { }, 'animation is added to compositor' + ' when timing.duration is made longer than the current time'); +promise_test(function(t) { + var div = addDiv(t); + var animation = div.animate({ opacity: [ 0, 1 ] }, 10000); + + return animation.ready.then(t.step_func(function() { + assert_equals(animation.isRunningOnCompositor, omtaEnabled, + 'Animation reports that it is running on the compositor'); + + animation.effect.timing.endDelay = 10000; + + assert_equals(animation.isRunningOnCompositor, omtaEnabled, + 'Animation reports that it is running on the compositor' + + ' when endDelay is changed'); + + animation.currentTime = 11000; + return waitForFrame(); + })).then(t.step_func(function() { + assert_equals(animation.isRunningOnCompositor, false, + 'Animation reports that it is NOT running on the compositor' + + ' when currentTime is during endDelay'); + })); +}, 'animation is removed from compositor' + + ' when current time is made longer than the duration even during endDelay'); + +promise_test(function(t) { + var div = addDiv(t); + var animation = div.animate({ opacity: [ 0, 1 ] }, 10000); + + return animation.ready.then(t.step_func(function() { + assert_equals(animation.isRunningOnCompositor, omtaEnabled, + 'Animation reports that it is running on the compositor'); + + animation.effect.timing.endDelay = -20000; + return waitForFrame(); + })).then(t.step_func(function() { + assert_equals(animation.isRunningOnCompositor, false, + 'Animation reports that it is NOT running on the compositor' + + ' when endTime is negative value'); + })); +}, 'animation is removed from compositor' + + ' when endTime is negative value'); + +promise_test(function(t) { + var div = addDiv(t); + var animation = div.animate({ opacity: [ 0, 1 ] }, 10000); + + return animation.ready.then(t.step_func(function() { + assert_equals(animation.isRunningOnCompositor, omtaEnabled, + 'Animation reports that it is running on the compositor'); + + animation.effect.timing.endDelay = -5000; + return waitForFrame(); + })).then(t.step_func(function() { + assert_equals(animation.isRunningOnCompositor, omtaEnabled, + 'Animation reports that it is running on the compositor' + + ' when endTime is positive and endDelay is negative'); + animation.currentTime = 6000; + return waitForFrame(); + })).then(t.step_func(function() { + assert_equals(animation.isRunningOnCompositor, false, + 'Animation reports that it is NOT running on the compositor' + + ' when currentTime is after endTime'); + })); +}, 'animation is NOT running on compositor' + + 'when endTime is positive and endDelay is negative'); + diff --git a/dom/animation/test/css-animations/file_animation-computed-timing.html b/dom/animation/test/css-animations/file_animation-computed-timing.html index 6a94b23a4d..3fae01c34b 100644 --- a/dom/animation/test/css-animations/file_animation-computed-timing.html +++ b/dom/animation/test/css-animations/file_animation-computed-timing.html @@ -188,11 +188,11 @@ test(function(t) { }, 'endTime of an infinitely repeating zero-duration animation'); test(function(t) { - // Fill forwards so div.getAnimations()[0] wouldn't return + // Fill forwards so div.getAnimations()[0] won't return an // undefined value. var div = addDiv(t, {style: 'animation: moveAnimation 10s -100s forwards'}); var effect = div.getAnimations()[0].effect; - assert_equals(effect.getComputedTiming().endTime, 0, + assert_equals(effect.getComputedTiming().endTime, -90000, 'Initial value of endTime'); }, 'endTime of an animation that finishes before its startTime'); diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index 1ea34c947e..91c42c65f0 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -308,6 +308,7 @@ GK_ATOM(disableOutputEscaping, "disable-output-escaping") GK_ATOM(disabled, "disabled") GK_ATOM(disablehistory, "disablehistory") GK_ATOM(display, "display") +GK_ATOM(displayMode, "display-mode") GK_ATOM(distinct, "distinct") GK_ATOM(div, "div") GK_ATOM(dl, "dl") diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 4b04b1a7b5..a967d1fa7b 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -48,6 +48,7 @@ #include "nsSandboxFlags.h" #include "xpcpublic.h" #include "nsIFrame.h" +#include "nsDisplayList.h" namespace mozilla { @@ -453,7 +454,7 @@ EventListenerManager::ProcessApzAwareEventListenerAdd() } } - if (doc) { + if (doc && nsDisplayListBuilder::LayerEventRegionsEnabled()) { nsIPresShell* ps = doc->GetShell(); if (ps) { nsIFrame* f = ps->GetRootFrame(); diff --git a/dom/indexedDB/IDBKeyRange.cpp b/dom/indexedDB/IDBKeyRange.cpp index 0dde2649ef..ac8fe7a06e 100644 --- a/dom/indexedDB/IDBKeyRange.cpp +++ b/dom/indexedDB/IDBKeyRange.cpp @@ -336,6 +336,55 @@ IDBKeyRange::GetUpper(JSContext* aCx, JS::MutableHandle aResult, aResult.set(mCachedUpperVal); } +bool +IDBKeyRange::Includes(JSContext* aCx, + JS::Handle aValue, + ErrorResult& aRv) const +{ + Key key; + aRv = GetKeyFromJSVal(aCx, aValue, key); + if (aRv.Failed()) { + return false; + } + + switch (Key::CompareKeys(Lower(), key)) { + case 1: + return false; + case 0: + // Identical keys. + if (LowerOpen()) { + return false; + } + break; + case -1: + if (IsOnly()) { + return false; + } + break; + default: + MOZ_CRASH(); + } + + if (!IsOnly()) { + switch (Key::CompareKeys(key, Upper())) { + case 1: + return false; + case 0: + // Identical keys. + if (UpperOpen()) { + return false; + } + break; + case -1: + break; + } + } else { + MOZ_ASSERT(key == Lower()); + } + + return true; +} + // static already_AddRefed IDBKeyRange::Only(const GlobalObject& aGlobal, diff --git a/dom/indexedDB/IDBKeyRange.h b/dom/indexedDB/IDBKeyRange.h index 3de6b4b9c1..6371c03964 100644 --- a/dom/indexedDB/IDBKeyRange.h +++ b/dom/indexedDB/IDBKeyRange.h @@ -126,6 +126,11 @@ public: return mIsOnly ? mLower : mUpper; } + bool + Includes(JSContext* aCx, + JS::Handle aKey, + ErrorResult& aRv) const; + bool IsOnly() const { diff --git a/dom/indexedDB/Key.h b/dom/indexedDB/Key.h index 159c5c0beb..2744dfb5d1 100644 --- a/dom/indexedDB/Key.h +++ b/dom/indexedDB/Key.h @@ -235,7 +235,7 @@ public: SetFromValueArray(mozIStorageValueArray* aValues, uint32_t aIndex); static int16_t - CompareKeys(Key& aFirst, Key& aSecond) + CompareKeys(const Key& aFirst, const Key& aSecond) { int32_t result = Compare(aFirst.mBuffer, aSecond.mBuffer); diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 5f18018fbd..6c0f9f21a0 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -519,7 +519,8 @@ child: TextureFactoryIdentifier textureFactoryIdentifier, uint64_t layersId, nullable PRenderFrame renderFrame, - bool parentIsActive); + bool parentIsActive, + nsSizeMode sizeMode); async LoadURL(nsCString uri, BrowserConfiguration config, ShowInfo info); @@ -527,10 +528,12 @@ child: async CacheFileDescriptor(nsString path, FileDescriptor fd); - async UpdateDimensions(CSSRect rect, CSSSize size, nsSizeMode sizeMode, + async UpdateDimensions(CSSRect rect, CSSSize size, ScreenOrientationInternal orientation, LayoutDeviceIntPoint chromeDisp) compressall; + async SizeModeChanged(nsSizeMode sizeMode); + /** * Sending an activate message moves focus to the child. */ diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 84a47dd2f0..a99c70d8ca 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -1480,7 +1480,7 @@ TabChild::DoFakeShow(const TextureFactoryIdentifier& aTextureFactoryIdentifier, PRenderFrameChild* aRenderFrame, const ShowInfo& aShowInfo) { RecvShow(ScreenIntSize(0, 0), aShowInfo, aTextureFactoryIdentifier, - aLayersId, aRenderFrame, mParentIsActive); + aLayersId, aRenderFrame, mParentIsActive, nsSizeMode_Normal); mDidFakeShow = true; } @@ -1592,10 +1592,12 @@ TabChild::RecvShow(const ScreenIntSize& aSize, const TextureFactoryIdentifier& aTextureFactoryIdentifier, const uint64_t& aLayersId, PRenderFrameChild* aRenderFrame, - const bool& aParentIsActive) + const bool& aParentIsActive, + const nsSizeMode& aSizeMode) { MOZ_ASSERT((!mDidFakeShow && aRenderFrame) || (mDidFakeShow && !aRenderFrame)); + mPuppetWidget->SetSizeMode(aSizeMode); if (mDidFakeShow) { ApplyShowInfo(aInfo); RecvParentActivated(aParentIsActive); @@ -1631,7 +1633,6 @@ TabChild::RecvShow(const ScreenIntSize& aSize, bool TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size, - const nsSizeMode& sizeMode, const ScreenOrientationInternal& orientation, const LayoutDeviceIntPoint& chromeDisp) { @@ -1658,7 +1659,6 @@ TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size, baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height, true); - mPuppetWidget->SetSizeMode(sizeMode); mPuppetWidget->Resize(screenRect.x + chromeDisp.x, screenRect.y + chromeDisp.y, screenSize.width, screenSize.height, true); @@ -1666,6 +1666,22 @@ TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size, return true; } +bool +TabChild::RecvSizeModeChanged(const nsSizeMode& aSizeMode) +{ + mPuppetWidget->SetSizeMode(aSizeMode); + nsCOMPtr document(GetDocument()); + nsCOMPtr presShell = document->GetShell(); + if (presShell) { + nsPresContext* presContext = presShell->GetPresContext(); + if (presContext) { + presContext->MediaFeatureValuesChangedAllDocuments(eRestyle_Subtree, + NS_STYLE_HINT_REFLOW); + } + } + return true; +} + bool TabChild::UpdateFrame(const FrameMetrics& aFrameMetrics) { diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index 28bef0d433..8d5f298959 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -191,11 +191,12 @@ public: virtual ScreenIntSize GetInnerSize() = 0; + // Get the Document for the top-level window in this tab. + already_AddRefed GetDocument() const; + protected: virtual ~TabChildBase(); - // Get the Document for the top-level window in this tab. - already_AddRefed GetDocument() const; // Get the pres-shell of the document for the top-level window in this tab. already_AddRefed GetPresShell() const; @@ -319,14 +320,17 @@ public: const TextureFactoryIdentifier& aTextureFactoryIdentifier, const uint64_t& aLayersId, PRenderFrameChild* aRenderFrame, - const bool& aParentIsActive) override; + const bool& aParentIsActive, + const nsSizeMode& aSizeMode) override; virtual bool RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size, - const nsSizeMode& sizeMode, const ScreenOrientationInternal& orientation, const LayoutDeviceIntPoint& chromeDisp) override; + virtual bool + RecvSizeModeChanged(const nsSizeMode& aSizeMode) override; + virtual bool RecvActivate() override; virtual bool RecvDeactivate() override; diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 55650f5648..fb696ddeae 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -277,6 +277,7 @@ TabParent::TabParent(nsIContentParent* aManager, , mDPI(0) , mDefaultScale(0) , mUpdatedDimensions(false) + , mSizeMode(nsSizeMode_Normal) , mManager(aManager) , mDocShellIsActive(false) , mMarkedDestroying(false) @@ -875,8 +876,14 @@ TabParent::Show(const ScreenIntSize& size, bool aParentIsActive) } } + nsCOMPtr container = mFrameElement->OwnerDoc()->GetContainer(); + nsCOMPtr baseWindow = do_QueryInterface(container); + nsCOMPtr mainWidget; + baseWindow->GetMainWidget(getter_AddRefs(mainWidget)); + mSizeMode = mainWidget ? mainWidget->SizeMode() : nsSizeMode_Normal; + Unused << SendShow(size, GetShowInfo(), textureFactoryIdentifier, - layersId, renderFrame, aParentIsActive); + layersId, renderFrame, aParentIsActive, mSizeMode); } bool @@ -887,7 +894,6 @@ TabParent::RecvSetDimensions(const uint32_t& aFlags, MOZ_ASSERT(!(aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_INNER), "We should never see DIM_FLAGS_SIZE_INNER here!"); - nsCOMPtr widget = GetWidget(); NS_ENSURE_TRUE(mFrameElement, true); nsCOMPtr docShell = mFrameElement->OwnerDoc()->GetDocShell(); NS_ENSURE_TRUE(docShell, true); @@ -971,11 +977,19 @@ TabParent::UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size) CSSRect unscaledRect = devicePixelRect / widgetScale; CSSSize unscaledSize = devicePixelSize / widgetScale; Unused << SendUpdateDimensions(unscaledRect, unscaledSize, - widget->SizeMode(), orientation, chromeOffset); } } +void +TabParent::SizeModeChanged(const nsSizeMode& aSizeMode) +{ + if (!mIsDestroyed && aSizeMode != mSizeMode) { + mSizeMode = aSizeMode; + Unused << SendSizeModeChanged(aSizeMode); + } +} + void TabParent::UIResolutionChanged() { diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index cf077bf1a6..943d1e119a 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -337,6 +337,8 @@ public: void UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size); + void SizeModeChanged(const nsSizeMode& aSizeMode); + void UIResolutionChanged(); void ThemeChanged(); @@ -599,6 +601,7 @@ protected: float mDPI; CSSToLayoutDeviceScale mDefaultScale; bool mUpdatedDimensions; + nsSizeMode mSizeMode; LayoutDeviceIntPoint mChromeOffset; private: diff --git a/dom/locales/en-US/chrome/layout/layout_errors.properties b/dom/locales/en-US/chrome/layout/layout_errors.properties index 11acea9ebb..7ae736b25d 100644 --- a/dom/locales/en-US/chrome/layout/layout_errors.properties +++ b/dom/locales/en-US/chrome/layout/layout_errors.properties @@ -10,3 +10,25 @@ ImageMapPolyOddNumberOfCoords=The "coords" attribute of the TablePartRelPosWarning=Relative positioning of table rows and row groups is now supported. This site may need to be updated because it may depend on this feature having no effect. ScrollLinkedEffectFound2=This site appears to use a scroll-linked positioning effect. This may not work well with asynchronous panning; see https://developer.mozilla.org/docs/Mozilla/Performance/ScrollLinkedEffects for further details and to join the discussion on related tools and features! + +## LOCALIZATION NOTE(AnimationWarningContentTooLarge): +## (%1$S, %2$S) is a pair of integer values of the frame size +## (%3$S, %4$S) is a pair of integer values of the viewport size +## (%5$S, %6$S) is a pair of integer values of the visual rectangle size +## (%7$S) is an integer value +AnimationWarningContentTooLarge=Async animation disabled because frame size (%1$S, %2$S) is bigger than the viewport (%3$S, %4$S) or the visual rectangle (%5$S, %6$S) is larger than the max allowed value (%7$S) +## LOCALIZATION NOTE(AnimationWarningTransformBackfaceVisibilityHidde): +## 'backface-visibility: hidden' is a CSS property, don't translate it. +AnimationWarningTransformBackfaceVisibilityHidden=Async animation of 'backface-visibility: hidden' transforms is not supported +## LOCALIZATION NOTE(AnimationWarningTransformPreserve3D): +## 'transform-style: preserve-3d' is a CSS property, don't translate it. +AnimationWarningTransformPreserve3D=Async animation of 'transform-style: preserve-3d' transforms is not supported +## LOCALIZATION NOTE(AnimationWarningTransformSVG, +## AnimationWarningTransformFrameInactive, +## AnimationWarningOpacityFrameInactive, +## AnimationWarningWithGeometricProperties): +## 'transform' and 'opacity' mean CSS property names, don't translate it. +AnimationWarningTransformSVG=Async 'transform' animations of aFrames with SVG transforms is not supported +AnimationWarningTransformFrameInactive=Async animation disabled because frame was not marked active for 'transform' animation +AnimationWarningOpacityFrameInactive=Async animation disabled because frame was not marked active for 'opacity' animation +AnimationWarningWithGeometricProperties=Async animation of 'transform' or 'opacity' not possible due to animation of geometric properties on the same element diff --git a/dom/plugins/base/PluginPRLibrary.cpp b/dom/plugins/base/PluginPRLibrary.cpp index 89a7a140c3..6c0160340d 100644 --- a/dom/plugins/base/PluginPRLibrary.cpp +++ b/dom/plugins/base/PluginPRLibrary.cpp @@ -316,4 +316,17 @@ PluginPRLibrary::EndUpdateBackground(NPP instance, const nsIntRect&) return NS_ERROR_NOT_AVAILABLE; } +#if defined(XP_WIN) +nsresult +PluginPRLibrary::GetScrollCaptureContainer(NPP aInstance, ImageContainer** aContainer) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +nsresult +PluginPRLibrary::UpdateScrollState(NPP aInstance, bool aIsScrolling) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +#endif + } // namespace mozilla diff --git a/dom/plugins/base/PluginPRLibrary.h b/dom/plugins/base/PluginPRLibrary.h index 8c4122acdf..774e0f7703 100644 --- a/dom/plugins/base/PluginPRLibrary.h +++ b/dom/plugins/base/PluginPRLibrary.h @@ -125,6 +125,10 @@ public: virtual void GetLibraryPath(nsACString& aPath) { aPath.Assign(mFilePath); } virtual nsresult GetRunID(uint32_t* aRunID) override { return NS_ERROR_NOT_IMPLEMENTED; } virtual void SetHasLocalInstance() override { } +#if defined(XP_WIN) + virtual nsresult GetScrollCaptureContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) override; + virtual nsresult UpdateScrollState(NPP aInstance, bool aIsScrolling) override; +#endif private: NP_InitializeFunc mNP_Initialize; diff --git a/dom/plugins/base/android/ANPOpenGL.cpp b/dom/plugins/base/android/ANPOpenGL.cpp deleted file mode 100644 index 60c5325055..0000000000 --- a/dom/plugins/base/android/ANPOpenGL.cpp +++ /dev/null @@ -1,81 +0,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/. */ - -#include -#include -#include "AndroidBridge.h" -#include "ANPBase.h" -#include "GLContextProvider.h" -#include "nsNPAPIPluginInstance.h" -#include "nsPluginInstanceOwner.h" -#include "GLContextProvider.h" -#include "GLContextEGL.h" - -#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) -#define ASSIGN(obj, name) (obj)->name = anp_opengl_##name - -using namespace mozilla; -using namespace mozilla::gl; - -typedef nsNPAPIPluginInstance::TextureInfo TextureInfo; - -static ANPEGLContext anp_opengl_acquireContext(NPP instance) { - nsNPAPIPluginInstance* pinst = static_cast(instance->ndata); - - GLContext* context = pinst->GLContext(); - if (!context) - return nullptr; - - context->MakeCurrent(); - return GLContextEGL::Cast(context)->GetEGLContext(); -} - -static ANPTextureInfo anp_opengl_lockTexture(NPP instance) { - nsNPAPIPluginInstance* pinst = static_cast(instance->ndata); - - TextureInfo pluginInfo = pinst->LockContentTexture(); - - ANPTextureInfo info; - info.textureId = pluginInfo.mTexture; - info.width = pluginInfo.mWidth; - info.height = pluginInfo.mHeight; - - // It looks like we should be passing whatever - // internal format Flash told us it used previously - // (e.g., the value of pluginInfo.mInternalFormat), - // but if we do that it doesn't upload to the texture - // for some reason. - info.internalFormat = 0; - - return info; -} - -static void anp_opengl_releaseTexture(NPP instance, const ANPTextureInfo* info) { - nsNPAPIPluginInstance* pinst = static_cast(instance->ndata); - - TextureInfo pluginInfo(info->textureId, info->width, info->height, info->internalFormat); - pinst->ReleaseContentTexture(pluginInfo); - pinst->RedrawPlugin(); -} - -static void anp_opengl_invertPluginContent(NPP instance, bool isContentInverted) { - // OpenGL is BottomLeft if uninverted. - gl::OriginPos newOriginPos = gl::OriginPos::BottomLeft; - if (isContentInverted) - newOriginPos = gl::OriginPos::TopLeft; - - nsNPAPIPluginInstance* pinst = static_cast(instance->ndata); - - pinst->SetOriginPos(newOriginPos); - pinst->RedrawPlugin(); -} - -/////////////////////////////////////////////////////////////////////////////// - -void InitOpenGLInterface(ANPOpenGLInterfaceV0* i) { - ASSIGN(i, acquireContext); - ASSIGN(i, lockTexture); - ASSIGN(i, releaseTexture); - ASSIGN(i, invertPluginContent); -} diff --git a/dom/plugins/base/android/moz.build b/dom/plugins/base/android/moz.build index d6f9be778d..58dd996af7 100644 --- a/dom/plugins/base/android/moz.build +++ b/dom/plugins/base/android/moz.build @@ -16,7 +16,6 @@ SOURCES += [ 'ANPLog.cpp', 'ANPMatrix.cpp', 'ANPNativeWindow.cpp', - 'ANPOpenGL.cpp', 'ANPSurface.cpp', 'ANPSystem.cpp', 'ANPVideo.cpp', diff --git a/dom/plugins/base/npapi.h b/dom/plugins/base/npapi.h index 1b256163c1..798ee20aad 100644 --- a/dom/plugins/base/npapi.h +++ b/dom/plugins/base/npapi.h @@ -412,6 +412,8 @@ typedef enum { NPNVdocumentOrigin = 22, + NPNVCSSZoomFactor = 23, + NPNVpluginDrawingModel = 1000 /* Get the current drawing model (NPDrawingModel) */ #if defined(XP_MACOSX) , NPNVcontentsScaleFactor = 1001 diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index 439f87d2a7..62ce27d92b 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -2171,6 +2171,14 @@ _getvalue(NPP npp, NPNVariable variable, void *result) } #endif + case NPNVCSSZoomFactor: { + nsNPAPIPluginInstance *inst = + (nsNPAPIPluginInstance *) (npp ? npp->ndata : nullptr); + double scaleFactor = inst ? inst->GetCSSZoomFactor() : 1.0; + *(double*)result = scaleFactor; + return NPERR_NO_ERROR; + } + #ifdef MOZ_WIDGET_ANDROID case kLogInterfaceV0_ANPGetValue: { LOG("get log interface"); @@ -2288,9 +2296,7 @@ _getvalue(NPP npp, NPNVariable variable, void *result) case kOpenGLInterfaceV0_ANPGetValue: { LOG("get openGL interface"); - ANPOpenGLInterfaceV0 *i = (ANPOpenGLInterfaceV0*) result; - InitOpenGLInterface(i); - return NPERR_NO_ERROR; + return NPERR_GENERIC_ERROR; } case kWindowInterfaceV1_ANPGetValue: { diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp index 09988c69e1..f249df97d8 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.cpp +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -98,67 +98,6 @@ static bool EnsureGLContext() return sPluginContext != nullptr; } -class SharedPluginTexture final { -public: - NS_INLINE_DECL_REFCOUNTING(SharedPluginTexture) - - SharedPluginTexture() : mLock("SharedPluginTexture.mLock") - { - } - - nsNPAPIPluginInstance::TextureInfo Lock() - { - if (!EnsureGLContext()) { - mTextureInfo.mTexture = 0; - return mTextureInfo; - } - - if (!mTextureInfo.mTexture && sPluginContext->MakeCurrent()) { - sPluginContext->fGenTextures(1, &mTextureInfo.mTexture); - } - - mLock.Lock(); - return mTextureInfo; - } - - void Release(nsNPAPIPluginInstance::TextureInfo& aTextureInfo) - { - mTextureInfo = aTextureInfo; - mLock.Unlock(); - } - - EGLImage CreateEGLImage() - { - MutexAutoLock lock(mLock); - - if (!EnsureGLContext()) - return 0; - - if (mTextureInfo.mWidth == 0 || mTextureInfo.mHeight == 0) - return 0; - - GLuint& tex = mTextureInfo.mTexture; - EGLImage image = gl::CreateEGLImage(sPluginContext, tex); - - // We want forget about this now, so delete the texture. Assigning it to zero - // ensures that we create a new one in Lock() - sPluginContext->fDeleteTextures(1, &tex); - tex = 0; - - return image; - } - -private: - // Private destructor, to discourage deletion outside of Release(): - ~SharedPluginTexture() - { - } - - nsNPAPIPluginInstance::TextureInfo mTextureInfo; - - Mutex mLock; -}; - static std::map sPluginNPPMap; #endif @@ -259,7 +198,6 @@ nsNPAPIPluginInstance::Destroy() if (mContentSurface) mContentSurface->SetFrameAvailableCallback(nullptr); - mContentTexture = nullptr; mContentSurface = nullptr; std::map::iterator it; @@ -930,12 +868,6 @@ void nsNPAPIPluginInstance::SetWakeLock(bool aLocked) hal::WAKE_LOCK_NO_CHANGE); } -void nsNPAPIPluginInstance::EnsureSharedTexture() -{ - if (!mContentTexture) - mContentTexture = new SharedPluginTexture(); -} - GLContext* nsNPAPIPluginInstance::GLContext() { if (!EnsureGLContext()) @@ -944,18 +876,6 @@ GLContext* nsNPAPIPluginInstance::GLContext() return sPluginContext; } -nsNPAPIPluginInstance::TextureInfo nsNPAPIPluginInstance::LockContentTexture() -{ - EnsureSharedTexture(); - return mContentTexture->Lock(); -} - -void nsNPAPIPluginInstance::ReleaseContentTexture(nsNPAPIPluginInstance::TextureInfo& aTextureInfo) -{ - EnsureSharedTexture(); - mContentTexture->Release(aTextureInfo); -} - already_AddRefed nsNPAPIPluginInstance::CreateSurfaceTexture() { if (!EnsureGLContext()) @@ -994,15 +914,6 @@ void* nsNPAPIPluginInstance::AcquireContentWindow() return mContentSurface->NativeWindow()->Handle(); } -EGLImage -nsNPAPIPluginInstance::AsEGLImage() -{ - if (!mContentTexture) - return 0; - - return mContentTexture->CreateEGLImage(); -} - AndroidSurfaceTexture* nsNPAPIPluginInstance::AsSurfaceTexture() { @@ -1090,7 +1001,8 @@ nsresult nsNPAPIPluginInstance::IsRemoteDrawingCoreAnimation(bool* aDrawing) #endif } -nsresult nsNPAPIPluginInstance::ContentsScaleFactorChanged(double aContentsScaleFactor) +nsresult +nsNPAPIPluginInstance::ContentsScaleFactorChanged(double aContentsScaleFactor) { #ifdef XP_MACOSX if (!mPlugin) @@ -1110,6 +1022,31 @@ nsresult nsNPAPIPluginInstance::ContentsScaleFactorChanged(double aContentsScale #endif } +nsresult +nsNPAPIPluginInstance::CSSZoomFactorChanged(float aCSSZoomFactor) +{ + if (RUNNING != mRunning) + return NS_OK; + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance informing plugin of CSS Zoom Factor change this=%p\n",this)); + + if (!mPlugin || !mPlugin->GetLibrary()) + return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + if (!pluginFunctions->setvalue) + return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(this); + + NPError error; + double value = static_cast(aCSSZoomFactor); + NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->setvalue)(&mNPP, NPNVCSSZoomFactor, &value), this, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + return (error == NPERR_NO_ERROR) ? NS_OK : NS_ERROR_FAILURE; +} + nsresult nsNPAPIPluginInstance::GetJSObject(JSContext *cx, JSObject** outObject) { @@ -1210,6 +1147,29 @@ nsNPAPIPluginInstance::GetImageSize(nsIntSize* aSize) return !library ? NS_ERROR_FAILURE : library->GetImageSize(&mNPP, aSize); } +#if defined(XP_WIN) +nsresult +nsNPAPIPluginInstance::GetScrollCaptureContainer(ImageContainer**aContainer) +{ + *aContainer = nullptr; + + if (RUNNING != mRunning) + return NS_OK; + + AutoPluginLibraryCall library(this); + return !library ? NS_ERROR_FAILURE : library->GetScrollCaptureContainer(&mNPP, aContainer); +} +nsresult +nsNPAPIPluginInstance::UpdateScrollState(bool aIsScrolling) +{ + if (RUNNING != mRunning) + return NS_OK; + + AutoPluginLibraryCall library(this); + return !library ? NS_ERROR_FAILURE : library->UpdateScrollState(&mNPP, aIsScrolling); +} +#endif + void nsNPAPIPluginInstance::DidComposite() { @@ -1805,6 +1765,16 @@ nsNPAPIPluginInstance::GetContentsScaleFactor() return scaleFactor; } +float +nsNPAPIPluginInstance::GetCSSZoomFactor() +{ + float zoomFactor = 1.0; + if (mOwner) { + mOwner->GetCSSZoomFactor(&zoomFactor); + } + return zoomFactor; +} + nsresult nsNPAPIPluginInstance::GetRunID(uint32_t* aRunID) { diff --git a/dom/plugins/base/nsNPAPIPluginInstance.h b/dom/plugins/base/nsNPAPIPluginInstance.h index 8ec4679c50..ee517672b5 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.h +++ b/dom/plugins/base/nsNPAPIPluginInstance.h @@ -26,7 +26,6 @@ #include "AndroidBridge.h" #include class PluginEventRunnable; -class SharedPluginTexture; #endif #include "mozilla/TimeStamp.h" @@ -102,6 +101,7 @@ public: nsresult GetDrawingModel(int32_t* aModel); nsresult IsRemoteDrawingCoreAnimation(bool* aDrawing); nsresult ContentsScaleFactorChanged(double aContentsScaleFactor); + nsresult CSSZoomFactorChanged(float aCSSZoomFactor); nsresult GetJSObject(JSContext *cx, JSObject** outObject); bool ShouldCache(); nsresult IsWindowless(bool* isWindowless); @@ -122,6 +122,10 @@ public: nsresult InvalidateRegion(NPRegion invalidRegion); nsresult GetMIMEType(const char* *result); nsresult GetJSContext(JSContext* *outContext); +#if defined(XP_WIN) + nsresult GetScrollCaptureContainer(mozilla::layers::ImageContainer **aContainer); + nsresult UpdateScrollState(bool aIsScrolling); +#endif nsPluginInstanceOwner* GetOwner(); void SetOwner(nsPluginInstanceOwner *aOwner); void DidComposite(); @@ -206,13 +210,9 @@ public: GLuint mInternalFormat; }; - TextureInfo LockContentTexture(); - void ReleaseContentTexture(TextureInfo& aTextureInfo); - // For ANPNativeWindow void* AcquireContentWindow(); - EGLImage AsEGLImage(); mozilla::gl::AndroidSurfaceTexture* AsSurfaceTexture(); // For ANPVideo @@ -308,6 +308,9 @@ public: // Returns the contents scale factor of the screen the plugin is drawn on. double GetContentsScaleFactor(); + // Returns the css zoom factor of the document the plugin is drawn on. + float GetCSSZoomFactor(); + nsresult GetRunID(uint32_t *aRunID); static bool InPluginCallUnsafeForReentry() { return gInUnsafePluginCalls > 0; } @@ -357,7 +360,6 @@ protected: bool mFullScreen; mozilla::gl::OriginPos mOriginPos; - RefPtr mContentTexture; RefPtr mContentSurface; #endif @@ -408,7 +410,6 @@ private: mozilla::TimeStamp mStopTime; #ifdef MOZ_WIDGET_ANDROID - void EnsureSharedTexture(); already_AddRefed CreateSurfaceTexture(); std::map mVideos; diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index 884dd77260..973749d09a 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -169,27 +169,6 @@ nsPluginInstanceOwner::NotifyPaintWaiter(nsDisplayListBuilder* aBuilder) } #if MOZ_WIDGET_ANDROID -static void -AttachToContainerAsEGLImage(ImageContainer* container, - nsNPAPIPluginInstance* instance, - const LayoutDeviceRect& rect, - RefPtr* out_image) -{ - MOZ_ASSERT(out_image); - MOZ_ASSERT(!*out_image); - - EGLImage image = instance->AsEGLImage(); - if (!image) { - return; - } - - RefPtr img = new EGLImageImage( - image, nullptr, - gfx::IntSize(rect.width, rect.height), instance->OriginPos(), - false /* owns */); - *out_image = img; -} - static void AttachToContainerAsSurfaceTexture(ImageContainer* container, nsNPAPIPluginInstance* instance, @@ -212,6 +191,21 @@ AttachToContainerAsSurfaceTexture(ImageContainer* container, } #endif +bool +nsPluginInstanceOwner::NeedsScrollImageLayer() +{ +#if defined(XP_WIN) + // If this is a windowed plugin and we're doing layout in the content + // process, force the creation of an image layer for the plugin. We'll + // paint to this when scrolling. + return XRE_IsContentProcess() && + mPluginWindow && + mPluginWindow->type == NPWindowTypeWindow; +#else + return false; +#endif +} + already_AddRefed nsPluginInstanceOwner::GetImageContainer() { @@ -238,15 +232,21 @@ nsPluginInstanceOwner::GetImageContainer() // Try to get it as an EGLImage first. RefPtr img; - AttachToContainerAsEGLImage(container, mInstance, r, &img); - if (!img) { - AttachToContainerAsSurfaceTexture(container, mInstance, r, &img); - } - MOZ_ASSERT(img); + AttachToContainerAsSurfaceTexture(container, mInstance, r, &img); - container->SetCurrentImageInTransaction(img); + if (img) { + container->SetCurrentImageInTransaction(img); + } #else - mInstance->GetImageContainer(getter_AddRefs(container)); + if (NeedsScrollImageLayer()) { + // windowed plugin under e10s +#if defined(XP_WIN) + mInstance->GetScrollCaptureContainer(getter_AddRefs(container)); +#endif + } else { + // async windowless rendering + mInstance->GetImageContainer(getter_AddRefs(container)); + } #endif return container.forget(); @@ -326,6 +326,21 @@ nsPluginInstanceOwner::GetCurrentImageSize() return size; } +bool +nsPluginInstanceOwner::UpdateScrollState(bool aIsScrolling) +{ +#if defined(XP_WIN) + if (!mInstance) { + return false; + } + mScrollState = aIsScrolling; + nsresult rv = mInstance->UpdateScrollState(aIsScrolling); + return NS_SUCCEEDED(rv); +#else + return false; +#endif +} + nsPluginInstanceOwner::nsPluginInstanceOwner() { // create nsPluginNativeWindow object, it is derived from NPWindow @@ -346,6 +361,7 @@ nsPluginInstanceOwner::nsPluginInstanceOwner() mLastScaleFactor = 1.0; mShouldBlurOnActivate = false; #endif + mLastCSSZoomFactor = 1.0; mContentFocused = false; mWidgetVisible = true; mPluginWindowVisible = false; @@ -373,6 +389,7 @@ nsPluginInstanceOwner::nsPluginInstanceOwner() mGotCompositionData = false; mSentStartComposition = false; mPluginDidNotHandleIMEComposition = false; + mScrollState = false; #endif } @@ -613,14 +630,18 @@ NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRect(NPRect *invalidRect) #endif #ifndef XP_MACOSX - // Windowed plugins should not be calling NPN_InvalidateRect, but - // Silverlight does and expects it to "work" + // Silverlight calls invalidate for windowed plugins so this needs to work. if (mWidget) { mWidget->Invalidate( LayoutDeviceIntRect(invalidRect->left, invalidRect->top, invalidRect->right - invalidRect->left, invalidRect->bottom - invalidRect->top)); - return NS_OK; + // Plugin instances also call invalidate when plugin windows are hidden + // during scrolling. In this case fall through so we invalidate the + // underlying layer. + if (!NeedsScrollImageLayer()) { + return NS_OK; + } } #endif nsIntRect rect(invalidRect->left, @@ -972,9 +993,9 @@ nsPluginInstanceOwner::RequestCommitOrCancel(bool aCommitted) } if (aCommitted) { - widget->NotifyIME(widget::REQUEST_TO_COMMIT_COMPOSITION); + widget->NotifyIME(widget::REQUEST_TO_COMMIT_COMPOSITION); } else { - widget->NotifyIME(widget::REQUEST_TO_CANCEL_COMPOSITION); + widget->NotifyIME(widget::REQUEST_TO_CANCEL_COMPOSITION); } return true; } @@ -3325,6 +3346,15 @@ NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void) } #endif // XP_MACOSX } + +#ifndef XP_MACOSX + // A failure here is terminal since we can't fall back on the non-e10s code + // path below. + if (!mWidget && XRE_IsContentProcess()) { + return NS_ERROR_UNEXPECTED; + } +#endif // XP_MACOSX + if (!mWidget) { // native (single process) mWidget = do_CreateInstance(kWidgetCID, &rv); @@ -3507,17 +3537,6 @@ nsPluginInstanceOwner::SendWindowFocusChanged(bool aIsActive) NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); } -void -nsPluginInstanceOwner::ResolutionMayHaveChanged() -{ - double scaleFactor = 1.0; - GetContentsScaleFactor(&scaleFactor); - if (scaleFactor != mLastScaleFactor) { - ContentsScaleFactorChanged(scaleFactor); - mLastScaleFactor = scaleFactor; - } -} - void nsPluginInstanceOwner::HidePluginWindow() { @@ -3588,6 +3607,28 @@ nsPluginInstanceOwner::UpdateWindowVisibility(bool aVisible) } #endif // XP_MACOSX +void +nsPluginInstanceOwner::ResolutionMayHaveChanged() +{ +#ifdef XP_MACOSX + double scaleFactor = 1.0; + GetContentsScaleFactor(&scaleFactor); + if (scaleFactor != mLastScaleFactor) { + ContentsScaleFactorChanged(scaleFactor); + mLastScaleFactor = scaleFactor; + } +#endif + float zoomFactor = 1.0; + GetCSSZoomFactor(&zoomFactor); + if (zoomFactor != mLastCSSZoomFactor) { + if (mInstance) { + mInstance->CSSZoomFactorChanged(zoomFactor); + } + mLastCSSZoomFactor = zoomFactor; + } + +} + void nsPluginInstanceOwner::UpdateDocumentActiveState(bool aIsActive) { @@ -3667,6 +3708,18 @@ nsPluginInstanceOwner::GetContentsScaleFactor(double *result) return NS_OK; } +void +nsPluginInstanceOwner::GetCSSZoomFactor(float *result) +{ + nsCOMPtr content = do_QueryReferent(mContent); + nsIPresShell* presShell = nsContentUtils::FindPresShellForDocument(content->OwnerDoc()); + if (presShell) { + *result = presShell->GetPresContext()->DeviceContext()->GetFullZoom(); + } else { + *result = 1.0; + } +} + void nsPluginInstanceOwner::SetFrame(nsPluginFrame *aFrame) { // Don't do anything if the frame situation hasn't changed. diff --git a/dom/plugins/base/nsPluginInstanceOwner.h b/dom/plugins/base/nsPluginInstanceOwner.h index 69bb08da94..1b8d9c8ca4 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.h +++ b/dom/plugins/base/nsPluginInstanceOwner.h @@ -138,7 +138,6 @@ public: enum { ePluginPaintEnable, ePluginPaintDisable }; void WindowFocusMayHaveChanged(); - void ResolutionMayHaveChanged(); bool WindowIsActive(); void SendWindowFocusChanged(bool aIsActive); @@ -160,6 +159,7 @@ public: void UpdateWindowVisibility(bool aVisible); #endif // XP_MACOSX + void ResolutionMayHaveChanged(); void UpdateDocumentActiveState(bool aIsActive); void SetFrame(nsPluginFrame *aFrame); @@ -228,6 +228,11 @@ public: // Returns the image container that has our currently displayed image. already_AddRefed GetImageContainer(); + // Returns true if this is windowed plugin that can return static captures + // for scroll operations. + bool NeedsScrollImageLayer(); + // Notification we receive from nsPluginFrame about scroll state. + bool UpdateScrollState(bool aIsScrolling); void DidComposite(); @@ -273,6 +278,7 @@ public: const mozilla::widget::CandidateWindowPosition& aPosition); bool RequestCommitOrCancel(bool aCommitted); + void GetCSSZoomFactor(float *result); private: virtual ~nsPluginInstanceOwner(); @@ -323,7 +329,7 @@ private: // True if, the next time the window is activated, we should blur ourselves. bool mShouldBlurOnActivate; #endif - + double mLastCSSZoomFactor; // Initially, the event loop nesting level we were created on, it's updated // if we detect the appshell is on a lower level as long as we're not stopped. // We delay DoStopPlugin() until the appshell reaches this level or lower. @@ -397,6 +403,9 @@ private: #endif bool mWaitingForPaint; +#if defined(XP_WIN) + bool mScrollState; +#endif }; #endif // nsPluginInstanceOwner_h_ diff --git a/dom/plugins/ipc/PPluginInstance.ipdl b/dom/plugins/ipc/PPluginInstance.ipdl index ff09c7f882..a5381f58fb 100644 --- a/dom/plugins/ipc/PPluginInstance.ipdl +++ b/dom/plugins/ipc/PPluginInstance.ipdl @@ -97,6 +97,8 @@ child: intr NPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId() returns (nsCString plug_id, NPError result); + intr NPP_SetValue_NPNVCSSZoomFactor(double value) returns (NPError result); + intr NPP_SetValue_NPNVmuteAudioBool(bool muted) returns (NPError result); intr NPP_HandleEvent(NPRemoteEvent event) diff --git a/dom/plugins/ipc/PluginInstanceChild.cpp b/dom/plugins/ipc/PluginInstanceChild.cpp index 27b6634040..63a74fee74 100644 --- a/dom/plugins/ipc/PluginInstanceChild.cpp +++ b/dom/plugins/ipc/PluginInstanceChild.cpp @@ -543,6 +543,10 @@ PluginInstanceChild::NPN_GetValue(NPNVariable aVar, } #endif /* XP_MACOSX */ + case NPNVCSSZoomFactor: { + *static_cast(aValue) = mCSSZoomFactor; + return NPERR_NO_ERROR; + } #ifdef DEBUG case NPNVjavascriptEnabledBool: case NPNVasdEnabledBool: @@ -817,6 +821,21 @@ PluginInstanceChild::AnswerNPP_SetValue_NPNVprivateModeBool(const bool& value, return true; } +bool +PluginInstanceChild::AnswerNPP_SetValue_NPNVCSSZoomFactor(const double& value, + NPError* result) +{ + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return true; + } + + mCSSZoomFactor = value; + double v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVCSSZoomFactor, &v); + return true; +} + bool PluginInstanceChild::AnswerNPP_SetValue_NPNVmuteAudioBool(const bool& value, NPError* result) diff --git a/dom/plugins/ipc/PluginInstanceChild.h b/dom/plugins/ipc/PluginInstanceChild.h index 6ddd751219..d04954439e 100644 --- a/dom/plugins/ipc/PluginInstanceChild.h +++ b/dom/plugins/ipc/PluginInstanceChild.h @@ -89,6 +89,8 @@ protected: AnswerNPP_SetValue_NPNVprivateModeBool(const bool& value, NPError* result) override; virtual bool AnswerNPP_SetValue_NPNVmuteAudioBool(const bool& value, NPError* result) override; + virtual bool + AnswerNPP_SetValue_NPNVCSSZoomFactor(const double& value, NPError* result) override; virtual bool AnswerNPP_HandleEvent(const NPRemoteEvent& event, int16_t* handled) override; @@ -403,6 +405,7 @@ private: #if defined(XP_DARWIN) double mContentsScaleFactor; #endif + double mCSSZoomFactor; int16_t mDrawingModel; NPAsyncSurface* mCurrentDirectSurface; diff --git a/dom/plugins/ipc/PluginInstanceParent.cpp b/dom/plugins/ipc/PluginInstanceParent.cpp index 34e6547abc..3c8c231fda 100644 --- a/dom/plugins/ipc/PluginInstanceParent.cpp +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -60,6 +60,7 @@ #include "nsIWidget.h" #include "nsPluginNativeWindow.h" #include "PluginQuirks.h" +#include "nsWindowsHelpers.h" extern const wchar_t* kFlashFullscreenClass; #elif defined(MOZ_WIDGET_GTK) #include "mozilla/dom/ContentChild.h" @@ -76,6 +77,13 @@ using namespace mozilla::plugins; using namespace mozilla::layers; using namespace mozilla::gl; +#if defined(XP_WIN) +// Delays associated with attempting an e10s window capture for scrolling. +const int kScrollCaptureDelayMs = 100; +const int kInitScrollCaptureDelayMs = 1000; +const uint32_t kScrollCaptureFillColor = 0xFFa0a0a0; // gray +#endif + void StreamNotifyParent::ActorDestroy(ActorDestroyReason aWhy) { @@ -113,6 +121,13 @@ PluginInstanceParent::LookupPluginInstanceByID(uintptr_t aId) } #endif +template<> +struct RunnableMethodTraits +{ + static void RetainCallee(PluginInstanceParent* obj) { } + static void ReleaseCallee(PluginInstanceParent* obj) { } +}; + PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent, NPP npp, const nsCString& aMimeType, @@ -139,6 +154,11 @@ PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent, , mShHeight(0) , mShColorSpace(nullptr) #endif +#if defined(XP_WIN) + , mCaptureRefreshTask(nullptr) + , mValidFirstCapture(false) + , mIsScrolling(false) +#endif { #if defined(OS_WIN) if (!sPluginInstanceList) { @@ -163,6 +183,9 @@ PluginInstanceParent::~PluginInstanceParent() if (mShColorSpace) ::CGColorSpaceRelease(mShColorSpace); #endif +#if defined(XP_WIN) + CancelScheduledScrollCapture(); +#endif } bool @@ -440,6 +463,12 @@ PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginWindow( // non-pointer-sized integer. *result = mNPNIface->setvalue(mNPP, NPPVpluginWindowBool, (void*)(intptr_t)windowed); + +#if defined(XP_WIN) + if (windowed) { + ScheduleScrollCapture(kScrollCaptureDelayMs); + } +#endif return true; } @@ -1187,6 +1216,203 @@ PluginInstanceParent::EndUpdateBackground(const nsIntRect& aRect) return NS_OK; } +#if defined(XP_WIN) +//#define CAPTURE_LOG(...) printf_stderr("CAPTURE [%X]: ", this);printf_stderr(__VA_ARGS__);printf_stderr("\n"); +#define CAPTURE_LOG(...) + +void +PluginInstanceParent::ScheduleScrollCapture(int aTimeout) +{ + if (mCaptureRefreshTask) { + return; + } + CAPTURE_LOG("delayed scroll capture requested."); + mCaptureRefreshTask = + NewRunnableMethod(this, &PluginInstanceParent::ScheduledUpdateScrollCaptureCallback); + MessageLoop::current()->PostDelayedTask(FROM_HERE, mCaptureRefreshTask, + kScrollCaptureDelayMs); +} + +void +PluginInstanceParent::ScheduledUpdateScrollCaptureCallback() +{ + CAPTURE_LOG("taking delayed scrollcapture."); + mCaptureRefreshTask = nullptr; + bool retrigger = false; + UpdateScrollCapture(retrigger); + if (retrigger) { + // reset the async request + ScheduleScrollCapture(kScrollCaptureDelayMs); + } +} + +void +PluginInstanceParent::CancelScheduledScrollCapture() +{ + CAPTURE_LOG("delayed scroll capture cancelled."); + if (mCaptureRefreshTask) { + mCaptureRefreshTask->Cancel(); + mCaptureRefreshTask = nullptr; + } +} + +bool +PluginInstanceParent::UpdateScrollCapture(bool& aRequestNewCapture) +{ + aRequestNewCapture = false; + if (!::IsWindow(mChildPluginHWND)) { + CAPTURE_LOG("invalid window"); + aRequestNewCapture = true; + return false; + } + + nsAutoHDC windowDC(::GetDC(mChildPluginHWND)); + if (!windowDC) { + CAPTURE_LOG("no windowdc"); + aRequestNewCapture = true; + return false; + } + + RECT bounds = {0}; + ::GetWindowRect(mChildPluginHWND, &bounds); + if ((bounds.left == bounds.right && bounds.top == bounds.bottom) || + (!mWindowSize.width && !mWindowSize.height)) { + CAPTURE_LOG("empty bounds"); + // Lots of null window plugins in content, don't capture. + return false; + } + + // If we need to init mScrollCapture do so, also reset it if the size of the + // plugin window changes. + if (!mScrollCapture || mScrollCapture->GetSize() != mWindowSize) { + mValidFirstCapture = false; + mScrollCapture = + gfxPlatform::GetPlatform()->CreateOffscreenSurface(mWindowSize, + SurfaceFormat::X8R8G8B8_UINT32); + } + + // Check clipping, we don't want to capture windows that are clipped by + // the viewport. + RECT clip = {0}; + int rgnType = ::GetWindowRgnBox(mPluginHWND, &clip); + bool clipCorrect = !clip.left && !clip.top && + clip.right == mWindowSize.width && + clip.bottom == mWindowSize.height; + + bool isVisible = ::IsWindowVisible(mChildPluginHWND); + + CAPTURE_LOG("validcap=%d visible=%d region=%d clip=%d:%dx%dx%dx%d", + mValidFirstCapture, isVisible, rgnType, clipCorrect + clip.left, clip.top, clip.right, clip.bottom); + + // We have a good capture and can't update so keep using the existing + // capture image. Otherwise fall through so we paint the fill color to + // the layer. + if (mValidFirstCapture && (!isVisible || !clipCorrect)) { + return true; + } + + // On Windows we'll need a native bitmap for BitBlt. + RefPtr nativeScrollCapture; + + // Copy the plugin window if it's visible and there's no clipping, otherwise + // use a default fill color. + if (isVisible && clipCorrect) { + CAPTURE_LOG("capturing window"); + nativeScrollCapture = + new gfxWindowsSurface(mWindowSize, SurfaceFormat::X8R8G8B8_UINT32); + if (!::BitBlt(nativeScrollCapture->GetDC(), 0, 0, mWindowSize.width, + mWindowSize.height, windowDC, 0, 0, SRCCOPY)) { + CAPTURE_LOG("blt failure??"); + return false; + } + ::GdiFlush(); + mValidFirstCapture = true; + } + + IntSize targetSize = mScrollCapture->GetSize(); + RefPtr dt = + gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mScrollCapture, + targetSize); + + if (nativeScrollCapture) { + // Copy the native capture image over to a remotable gfx surface. + RefPtr sourceSurface = + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, + nativeScrollCapture); + dt->CopySurface(sourceSurface, + IntRect(0, 0, targetSize.width, targetSize.height), + IntPoint()); + } else { + CAPTURE_LOG("using fill color"); + dt->FillRect(gfx::Rect(0, 0, targetSize.width, targetSize.height), + gfx::ColorPattern(gfx::Color::FromABGR(kScrollCaptureFillColor)), + gfx::DrawOptions(1.f, CompositionOp::OP_SOURCE)); + aRequestNewCapture = true; + } + dt->Flush(); + + // Get a source for mScrollCapture and load it into the image container. + RefPtr cachedSource = + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(dt, + mScrollCapture); + RefPtr image = + new SourceSurfaceImage(cachedSource->GetSize(), cachedSource); + + ImageContainer::NonOwningImage holder(image); + holder.mFrameID = ++mFrameID; + + AutoTArray imageList; + imageList.AppendElement(holder); + + // inits mImageContainer + ImageContainer *container = GetImageContainer(); + container->SetCurrentImages(imageList); + + // Invalidate our area in the page so the image gets flushed. + NPRect nprect = {0, 0, targetSize.width, targetSize.height}; + RecvNPN_InvalidateRect(nprect); + + return true; +} + +nsresult +PluginInstanceParent::GetScrollCaptureContainer(ImageContainer** aContainer) +{ + if (!aContainer || !::IsWindow(mPluginHWND)) { + return NS_ERROR_FAILURE; + } + + if (!mImageContainer) { + ScheduleScrollCapture(kInitScrollCaptureDelayMs); + return NS_ERROR_FAILURE; + } + + ImageContainer *container = GetImageContainer(); + NS_IF_ADDREF(container); + *aContainer = container; + + return NS_OK; +} + +nsresult +PluginInstanceParent::UpdateScrollState(bool aIsScrolling) +{ + bool scrollStateChanged = (mIsScrolling != aIsScrolling); + mIsScrolling = aIsScrolling; + if (scrollStateChanged && !aIsScrolling) { + // At the end of a dom scroll operation capturing now will attempt to + // capture a window that is still hidden due to the current scroll + // operation. (The browser process will update visibility after layer + // updates get pushed over.) So we delay our attempt for a bit. This + // shouldn't hurt our chances of capturing with APZ scroll since the + // delay is short. + ScheduleScrollCapture(kScrollCaptureDelayMs); + } + return NS_OK; +} +#endif // XP_WIN + PluginAsyncSurrogate* PluginInstanceParent::GetAsyncSurrogate() { @@ -1339,6 +1565,9 @@ PluginInstanceParent::NPP_SetWindow(const NPWindow* aWindow) window.type = aWindow->type; #endif + mWindowSize.width = window.width; + mWindowSize.height = window.height; + #if defined(XP_MACOSX) double floatScaleFactor = 1.0; mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &floatScaleFactor); @@ -1383,6 +1612,12 @@ PluginInstanceParent::NPP_SetWindow(const NPWindow* aWindow) } RecordDrawingModel(); + +#if defined(XP_WIN) + if (!mCaptureRefreshTask) { + ScheduleScrollCapture(kScrollCaptureDelayMs); + } +#endif return NPERR_NO_ERROR; } @@ -1500,6 +1735,13 @@ PluginInstanceParent::NPP_SetValue(NPNVariable variable, void* value) return result; + case NPNVCSSZoomFactor: + if (!CallNPP_SetValue_NPNVCSSZoomFactor(*static_cast(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + default: NS_ERROR("Unhandled NPNVariable in NPP_SetValue"); MOZ_LOG(GetPluginLog(), LogLevel::Warning, diff --git a/dom/plugins/ipc/PluginInstanceParent.h b/dom/plugins/ipc/PluginInstanceParent.h index 751cc72574..8d3a2ab29a 100644 --- a/dom/plugins/ipc/PluginInstanceParent.h +++ b/dom/plugins/ipc/PluginInstanceParent.h @@ -342,6 +342,10 @@ public: nsresult BeginUpdateBackground(const nsIntRect& aRect, DrawTarget** aDrawTarget); nsresult EndUpdateBackground(const nsIntRect& aRect); +#if defined(XP_WIN) + nsresult GetScrollCaptureContainer(mozilla::layers::ImageContainer** aContainer); + nsresult UpdateScrollState(bool aIsScrolling); +#endif void DidComposite(); bool IsUsingDirectDrawing(); @@ -401,6 +405,7 @@ private: bool mIsWhitelistedForShumway; NPWindowType mWindowType; int16_t mDrawingModel; + IntSize mWindowSize; // Since plugins may request different drawing models to find a compatible // one, we only record the drawing model after a SetWindow call and if the @@ -465,6 +470,18 @@ private: RefPtr mBackground; RefPtr mImageContainer; + +#if defined(XP_WIN) + void ScheduleScrollCapture(int aTimeout); + void ScheduledUpdateScrollCaptureCallback(); + bool UpdateScrollCapture(bool& aRequestNewCapture); + void CancelScheduledScrollCapture(); + + RefPtr mScrollCapture; + CancelableTask* mCaptureRefreshTask; + bool mValidFirstCapture; + bool mIsScrolling; +#endif }; diff --git a/dom/plugins/ipc/PluginLibrary.h b/dom/plugins/ipc/PluginLibrary.h index f9ace1da4d..c38d26d253 100644 --- a/dom/plugins/ipc/PluginLibrary.h +++ b/dom/plugins/ipc/PluginLibrary.h @@ -78,6 +78,10 @@ public: virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) = 0; virtual nsresult ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) = 0; #endif +#if defined(XP_WIN) + virtual nsresult GetScrollCaptureContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) = 0; + virtual nsresult UpdateScrollState(NPP aInstance, bool aIsScrolling) = 0; +#endif /** * The next three methods are the third leg in the trip to diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 504931b87a..aebb84a2d5 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -2000,6 +2000,22 @@ PluginModuleParent::EndUpdateBackground(NPP instance, const nsIntRect& aRect) return i->EndUpdateBackground(aRect); } +#if defined(XP_WIN) +nsresult +PluginModuleParent::GetScrollCaptureContainer(NPP aInstance, + mozilla::layers::ImageContainer** aContainer) +{ + PluginInstanceParent* inst = PluginInstanceParent::Cast(aInstance); + return !inst ? NS_ERROR_FAILURE : inst->GetScrollCaptureContainer(aContainer); +} +nsresult +PluginModuleParent::UpdateScrollState(NPP aInstance, bool aIsScrolling) +{ + PluginInstanceParent* inst = PluginInstanceParent::Cast(aInstance); + return !inst ? NS_ERROR_FAILURE : inst->UpdateScrollState(aIsScrolling); +} +#endif + void PluginModuleParent::OnInitFailure() { diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h index a549a1874c..29b3bb1892 100644 --- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -253,6 +253,11 @@ protected: virtual nsresult EndUpdateBackground(NPP instance, const nsIntRect& aRect) override; +#if defined(XP_WIN) + virtual nsresult GetScrollCaptureContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) override; + virtual nsresult UpdateScrollState(NPP aInstance, bool aIsScrolling); +#endif + #if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) override; #else diff --git a/dom/security/test/csp/file_child-src_service_worker.html b/dom/security/test/csp/file_child-src_service_worker.html index c970dc4f48..b291a4a4e8 100644 --- a/dom/security/test/csp/file_child-src_service_worker.html +++ b/dom/security/test/csp/file_child-src_service_worker.html @@ -9,13 +9,14 @@ try { if ('serviceWorker' in navigator) { navigator.serviceWorker.register( - 'file_child-src_service_worker.js' - + "#" - + page_id + 'file_child-src_service_worker.js', + { scope: './' + page_id + '/' } ).then(function(reg) { // registration worked - window.parent.postMessage({id:page_id, message:"allowed"}, 'http://mochi.test:8888'); + reg.unregister().then(function() { + window.parent.postMessage({id:page_id, message:"allowed"}, 'http://mochi.test:8888'); + }); }).catch(function(error) { // registration failed window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888'); diff --git a/dom/tests/mochitest/fetch/test_fetch_cors.html b/dom/tests/mochitest/fetch/test_fetch_cors.html index b9a6992a58..b01789c989 100644 --- a/dom/tests/mochitest/fetch/test_fetch_cors.html +++ b/dom/tests/mochitest/fetch/test_fetch_cors.html @@ -16,6 +16,10 @@ diff --git a/dom/webidl/AnimationEffectTiming.webidl b/dom/webidl/AnimationEffectTiming.webidl index 474f0180a0..0aa8c968f8 100644 --- a/dom/webidl/AnimationEffectTiming.webidl +++ b/dom/webidl/AnimationEffectTiming.webidl @@ -14,8 +14,7 @@ interface AnimationEffectTiming : AnimationEffectTimingReadOnly { //Bug 1244633 - implement AnimationEffectTiming delay //inherit attribute double delay; - //Bug 1244635 - implement AnimationEffectTiming endDelay - //inherit attribute double endDelay; + inherit attribute double endDelay; //Bug 1244637 - implement AnimationEffectTiming fill //inherit attribute FillMode fill; //Bug 1244638 - implement AnimationEffectTiming iterationStart diff --git a/dom/webidl/IDBKeyRange.webidl b/dom/webidl/IDBKeyRange.webidl index ddf9f4469a..8bbb1e025f 100644 --- a/dom/webidl/IDBKeyRange.webidl +++ b/dom/webidl/IDBKeyRange.webidl @@ -19,6 +19,10 @@ interface IDBKeyRange { readonly attribute boolean lowerOpen; [Constant] readonly attribute boolean upperOpen; + [Throws] + boolean includes(any key); + + [NewObject, Throws] static IDBKeyRange only (any value); [NewObject, Throws] diff --git a/dom/webidl/KeyframeEffect.webidl b/dom/webidl/KeyframeEffect.webidl index af2e9ea98b..2d748b21fa 100644 --- a/dom/webidl/KeyframeEffect.webidl +++ b/dom/webidl/KeyframeEffect.webidl @@ -45,6 +45,16 @@ interface KeyframeEffectReadOnly : AnimationEffectReadOnly { [Throws] sequence getFrames(); }; +// Non-standard extensions +dictionary AnimationPropertyState { + DOMString property; + boolean runningOnCompositor; + DOMString? warning; +}; + +partial interface KeyframeEffectReadOnly { + [ChromeOnly] sequence getPropertyState(); +}; [Func="nsDocument::IsWebAnimationsEnabled", Constructor ((Element or CSSPseudoElement)? target, diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 4d924e45e1..5bc6f90d70 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -319,7 +319,13 @@ PopulateRegistrationData(nsIPrincipal* aPrincipal, } aData.scope() = aRegistration->mScope; - aData.scriptSpec() = aRegistration->mScriptSpec; + + RefPtr newest = aRegistration->Newest(); + if (NS_WARN_IF(!newest)) { + return NS_ERROR_FAILURE; + } + + aData.scriptSpec() = newest->ScriptSpec(); if (aRegistration->mActiveWorker) { aData.currentWorkerURL() = aRegistration->mActiveWorker->ScriptSpec(); @@ -460,7 +466,10 @@ NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetScriptSpec(nsAString& aScriptSpec) { AssertIsOnMainThread(); - CopyUTF8toUTF16(mScriptSpec, aScriptSpec); + RefPtr newest = Newest(); + if (newest) { + CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec); + } return NS_OK; } @@ -932,22 +941,21 @@ class ServiceWorkerJobBase : public ServiceWorkerJob public: ServiceWorkerJobBase(ServiceWorkerJobQueue* aQueue, ServiceWorkerJob::Type aJobType, - ServiceWorkerUpdateFinishCallback* aCallback) - : ServiceWorkerJobBase(aQueue, aJobType, aCallback, nullptr, nullptr) - { } - - ServiceWorkerJobBase(ServiceWorkerJobQueue* aQueue, - ServiceWorkerJob::Type aJobType, + nsIPrincipal* aPrincipal, + const nsACString& aScope, + const nsACString& aScriptSpec, ServiceWorkerUpdateFinishCallback* aCallback, - ServiceWorkerRegistrationInfo* aRegistration, ServiceWorkerInfo* aServiceWorkerInfo) : ServiceWorkerJob(aQueue, aJobType) + , mPrincipal(aPrincipal) + , mScope(aScope) + , mScriptSpec(aScriptSpec) , mCallback(aCallback) - , mCanceled(false) - , mRegistration(aRegistration) , mUpdateAndInstallInfo(aServiceWorkerInfo) + , mCanceled(false) { AssertIsOnMainThread(); + MOZ_ASSERT(aPrincipal); } void @@ -958,14 +966,49 @@ public: } protected: + nsCOMPtr mPrincipal; + const nsCString mScope; + const nsCString mScriptSpec; RefPtr mCallback; - bool mCanceled; RefPtr mRegistration; RefPtr mUpdateAndInstallInfo; + bool mCanceled; ~ServiceWorkerJobBase() { } + // Ensure that mRegistration is set for the job. Also, if mRegistration was + // already set, ensure that a new registration object has not replaced it in + // the ServiceWorkerManager. This can happen when jobs race such that the + // registration is cleared and recreated while an update job is executing. + nsresult + EnsureAndVerifyRegistration() + { + AssertIsOnMainThread(); + + RefPtr swm = ServiceWorkerManager::GetInstance(); + if (NS_WARN_IF(!swm)) { + mRegistration = nullptr; + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr registration = + swm->GetRegistration(mPrincipal, mScope); + + if (NS_WARN_IF(!registration)) { + mRegistration = nullptr; + return NS_ERROR_NOT_AVAILABLE; + } + + if (NS_WARN_IF(mRegistration && registration != mRegistration)) { + mRegistration = nullptr; + return NS_ERROR_NOT_AVAILABLE; + } + + mRegistration = registration.forget(); + return NS_OK; + } + void Succeed() { @@ -976,31 +1019,15 @@ protected: mCallback = nullptr; } } -}; - -// Base type for jobs that work with a specific service worker script. -class ServiceWorkerScriptJobBase : public ServiceWorkerJobBase -{ -protected: - ServiceWorkerScriptJobBase(ServiceWorkerJobQueue* aQueue, - ServiceWorkerJob::Type aJobType, - ServiceWorkerUpdateFinishCallback* aCallback, - ServiceWorkerRegistrationInfo* aRegistration, - ServiceWorkerInfo* aServiceWorkerInfo) - : ServiceWorkerJobBase(aQueue, aJobType, aCallback, aRegistration, - aServiceWorkerInfo) - { - } // This MUST only be called when the job is still performing actions related // to registration or update. After the spec resolves the update promise, use // Done() with the failure code instead. // Callers MUST hold a strong ref before calling this! void - Fail(ErrorResult& aRv) + FailWithErrorResult(ErrorResult& aRv) { AssertIsOnMainThread(); - MOZ_ASSERT(mRegistration); // With cancellation support, we may only be running with one reference // from another object like a stream loader or something. @@ -1017,8 +1044,8 @@ protected: // Remove the old error code so we can replace it with a TypeError. aRv.SuppressException(); - NS_ConvertUTF8toUTF16 scriptSpec(mRegistration->mScriptSpec); - NS_ConvertUTF8toUTF16 scope(mRegistration->mScope); + NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec); + NS_ConvertUTF8toUTF16 scope(mScope); // Throw the type error with a generic error message. aRv.ThrowTypeError(scriptSpec, scope); @@ -1032,8 +1059,14 @@ protected: aRv.SuppressException(); mUpdateAndInstallInfo = nullptr; + + if (!mRegistration) { + Done(origStatus); + return; + } + if (mRegistration->mInstallingWorker) { - nsresult rv = serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal, + nsresult rv = serviceWorkerScriptCache::PurgeCache(mPrincipal, mRegistration->mInstallingWorker->CacheName()); if (NS_FAILED(rv)) { NS_WARNING("Failed to purge the installing worker cache."); @@ -1051,23 +1084,31 @@ protected: Fail(nsresult aRv) { ErrorResult rv(aRv); - Fail(rv); + FailWithErrorResult(rv); } }; -class ServiceWorkerInstallJob final : public ServiceWorkerScriptJobBase +class ServiceWorkerInstallJob final : public ServiceWorkerJobBase { friend class ContinueInstallTask; public: + enum InstallType { + UpdateSameScript, + OverwriteScript + }; + ServiceWorkerInstallJob(ServiceWorkerJobQueue* aQueue, + nsIPrincipal* aPrincipal, + const nsACString& aScope, + const nsACString& aScriptSpec, ServiceWorkerUpdateFinishCallback* aCallback, - ServiceWorkerRegistrationInfo* aRegistration, - ServiceWorkerInfo* aServiceWorkerInfo) - : ServiceWorkerScriptJobBase(aQueue, Type::InstallJob, aCallback, - aRegistration, aServiceWorkerInfo) + ServiceWorkerInfo* aServiceWorkerInfo, + InstallType aType) + : ServiceWorkerJobBase(aQueue, Type::InstallJob, aPrincipal, aScope, + aScriptSpec, aCallback, aServiceWorkerInfo) + , mType(aType) { - MOZ_ASSERT(aRegistration); } void @@ -1083,10 +1124,25 @@ public: Install() { RefPtr kungFuDeathGrip = this; + if (mCanceled) { return Fail(NS_ERROR_DOM_ABORT_ERR); } - MOZ_ASSERT(mRegistration); + + nsresult rv = EnsureAndVerifyRegistration(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Fail(NS_ERROR_DOM_ABORT_ERR); + } + + // If we are trying to install an update for an existing script, then + // make sure we don't overwrite a recent script change or resurrect a + // dead registration. + if (mType == UpdateSameScript) { + RefPtr newest = mRegistration->Newest(); + if (!newest || !mScriptSpec.Equals(newest->ScriptSpec())) { + return Fail(NS_ERROR_DOM_ABORT_ERR); + } + } // Begin [[Install]] atomic step 3. if (mRegistration->mInstallingWorker) { @@ -1128,8 +1184,8 @@ public: // which sends the install event to the worker. ServiceWorkerPrivate* workerPrivate = mRegistration->mInstallingWorker->WorkerPrivate(); - nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"), - callback, failRunnable); + rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"), + callback, failRunnable); if (NS_WARN_IF(NS_FAILED(rv))) { ContinueAfterInstallEvent(false /* aSuccess */); @@ -1143,6 +1199,11 @@ public: return Done(NS_ERROR_DOM_ABORT_ERR); } + nsresult rv = EnsureAndVerifyRegistration(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Fail(NS_ERROR_DOM_ABORT_ERR); + } + if (!mRegistration->mInstallingWorker) { NS_WARNING("mInstallingWorker was null."); return Done(NS_ERROR_DOM_ABORT_ERR); @@ -1176,28 +1237,24 @@ public: mRegistration->mWaitingWorker = mRegistration->mInstallingWorker.forget(); mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Installed); mRegistration->NotifyListenersOnChange(); + swm->StoreRegistration(mPrincipal, mRegistration); swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER); - // "If registration's waiting worker's skip waiting flag is set" - if (mRegistration->mWaitingWorker->SkipWaitingFlag()) { - mRegistration->PurgeActiveWorker(); - } - Done(NS_OK); // Activate() is invoked out of band of atomic. - mRegistration->TryToActivate(); + mRegistration->TryToActivateAsync(); } + +private: + const InstallType mType; }; -class ServiceWorkerRegisterJob final : public ServiceWorkerScriptJobBase, +class ServiceWorkerRegisterJob final : public ServiceWorkerJobBase, public serviceWorkerScriptCache::CompareCallback { friend class ContinueUpdateRunnable; - nsCString mScope; - nsCString mScriptSpec; - nsCOMPtr mPrincipal; nsCOMPtr mLoadGroup; ~ServiceWorkerRegisterJob() @@ -1208,16 +1265,13 @@ public: // [[Register]] ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue, - const nsCString& aScope, - const nsCString& aScriptSpec, - ServiceWorkerUpdateFinishCallback* aCallback, nsIPrincipal* aPrincipal, + const nsACString& aScope, + const nsACString& aScriptSpec, + ServiceWorkerUpdateFinishCallback* aCallback, nsILoadGroup* aLoadGroup) - : ServiceWorkerScriptJobBase(aQueue, Type::RegisterJob, aCallback, nullptr, - nullptr) - , mScope(aScope) - , mScriptSpec(aScriptSpec) - , mPrincipal(aPrincipal) + : ServiceWorkerJobBase(aQueue, Type::RegisterJob, aPrincipal, aScope, + aScriptSpec, aCallback, nullptr) , mLoadGroup(aLoadGroup) { AssertIsOnMainThread(); @@ -1227,10 +1281,12 @@ public: // [[Update]] ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue, - ServiceWorkerRegistrationInfo* aRegistration, + nsIPrincipal* aPrincipal, + const nsACString& aScope, + const nsACString& aScriptSpec, ServiceWorkerUpdateFinishCallback* aCallback) - : ServiceWorkerScriptJobBase(aQueue, Type::UpdateJob, aCallback, - aRegistration, nullptr) + : ServiceWorkerJobBase(aQueue, Type::UpdateJob, aPrincipal, aScope, + aScriptSpec, aCallback, nullptr) { AssertIsOnMainThread(); } @@ -1250,14 +1306,13 @@ public: } if (mJobType == RegisterJob) { + MOZ_ASSERT(!mRegistration); mRegistration = swm->GetRegistration(mPrincipal, mScope); if (mRegistration) { mRegistration->mPendingUninstall = false; RefPtr newest = mRegistration->Newest(); - if (newest && mScriptSpec.Equals(newest->ScriptSpec()) && - mScriptSpec.Equals(mRegistration->mScriptSpec)) { - swm->StoreRegistration(mPrincipal, mRegistration); + if (newest && mScriptSpec.Equals(newest->ScriptSpec())) { Succeed(); // Done() must always be called async from Start() @@ -1273,12 +1328,33 @@ public: } else { mRegistration = swm->CreateNewRegistration(mScope, mPrincipal); } - - mRegistration->mScriptSpec = mScriptSpec; - mRegistration->NotifyListenersOnChange(); - swm->StoreRegistration(mPrincipal, mRegistration); } else { MOZ_ASSERT(mJobType == UpdateJob); + + nsresult rv = EnsureAndVerifyRegistration(); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Do nothing here, but since mRegistration is nullptr we will + // trigger the async Fail() call below. + MOZ_ASSERT(!mRegistration); + } + + // If a different script spec has been registered between when this update + // was scheduled and it running now, then simply abort. + RefPtr newest = mRegistration ? mRegistration->Newest() + : nullptr; + if (!mRegistration || + (newest && !mScriptSpec.Equals(newest->ScriptSpec()))) { + + // Done() must always be called async from Start() + nsCOMPtr runnable = + NS_NewRunnableMethodWithArg( + this, + &ServiceWorkerRegisterJob::Fail, + NS_ERROR_DOM_ABORT_ERR); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(runnable))); + + return; + } } Update(); @@ -1290,6 +1366,7 @@ public: const nsACString& aMaxScope) override { RefPtr kungFuDeathGrip = this; + if (NS_WARN_IF(mCanceled)) { Fail(NS_ERROR_DOM_ABORT_ERR); return; @@ -1300,6 +1377,11 @@ public: return; } + nsresult rv = EnsureAndVerifyRegistration(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Fail(NS_ERROR_DOM_ABORT_ERR); + } + if (aInCacheAndEqual) { Succeed(); Done(NS_OK); @@ -1312,7 +1394,7 @@ public: RefPtr swm = ServiceWorkerManager::GetInstance(); nsCOMPtr scriptURI; - nsresult rv = NS_NewURI(getter_AddRefs(scriptURI), mRegistration->mScriptSpec); + rv = NS_NewURI(getter_AddRefs(scriptURI), mScriptSpec); if (NS_WARN_IF(NS_FAILED(rv))) { Fail(NS_ERROR_DOM_SECURITY_ERR); return; @@ -1362,8 +1444,7 @@ public: MOZ_ASSERT(!mUpdateAndInstallInfo); mUpdateAndInstallInfo = - new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec, - aNewCacheName); + new ServiceWorkerInfo(mRegistration, mScriptSpec, aNewCacheName); RefPtr upcasted = this; nsMainThreadPtrHandle handle( @@ -1386,7 +1467,12 @@ private: ContinueInstall(bool aScriptEvaluationResult) { AssertIsOnMainThread(); - MOZ_ASSERT(mRegistration); + + nsresult rv = EnsureAndVerifyRegistration(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Fail(NS_ERROR_DOM_ABORT_ERR); + } + mRegistration->mUpdating = false; RefPtr kungFuDeathGrip = this; @@ -1397,15 +1483,28 @@ private: if (NS_WARN_IF(!aScriptEvaluationResult)) { ErrorResult error; - NS_ConvertUTF8toUTF16 scriptSpec(mRegistration->mScriptSpec); + NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec); NS_ConvertUTF8toUTF16 scope(mRegistration->mScope); error.ThrowTypeError(scriptSpec, scope); - return Fail(error); + return FailWithErrorResult(error); } + // For updates we want to make sure our install job does not end up + // changing the script for the registration. Since a registration + // script change can be queued in an install job, we can not + // conclusively verify that the update install should proceed here. + // Instead, we have to pass a flag into our install job indicating + // if a script change is allowed or not. This can then be used to + // check the current script after all previous install jobs have been + // flushed. + ServiceWorkerInstallJob::InstallType installType = + mJobType == UpdateJob ? ServiceWorkerInstallJob::UpdateSameScript + : ServiceWorkerInstallJob::OverwriteScript; + RefPtr job = - new ServiceWorkerInstallJob(mQueue, mCallback, - mRegistration, mUpdateAndInstallInfo); + new ServiceWorkerInstallJob(mQueue, mPrincipal, mScope, mScriptSpec, + mCallback, mUpdateAndInstallInfo, + installType); mQueue->Append(job); Done(NS_OK); } @@ -1432,10 +1531,16 @@ private: { AssertIsOnMainThread(); RefPtr kungFuDeathGrip = this; + if (mCanceled) { return Fail(NS_ERROR_DOM_ABORT_ERR); } + nsresult rv = EnsureAndVerifyRegistration(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Fail(NS_ERROR_DOM_ABORT_ERR); + } + if (mRegistration->mInstallingWorker) { mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker(); @@ -1448,14 +1553,13 @@ private: // 9.2.20 If newestWorker is not null, and newestWorker's script url is // equal to registration's registering script url and response is a // byte-for-byte match with the script resource of newestWorker... - if (workerInfo && workerInfo->ScriptSpec().Equals(mRegistration->mScriptSpec)) { + if (workerInfo && workerInfo->ScriptSpec().Equals(mScriptSpec)) { cacheName = workerInfo->CacheName(); } - nsresult rv = - serviceWorkerScriptCache::Compare(mRegistration, mRegistration->mPrincipal, cacheName, - NS_ConvertUTF8toUTF16(mRegistration->mScriptSpec), - this, mLoadGroup); + rv = serviceWorkerScriptCache::Compare(mRegistration, mPrincipal, cacheName, + NS_ConvertUTF8toUTF16(mScriptSpec), + this, mLoadGroup); if (NS_WARN_IF(NS_FAILED(rv))) { return Fail(rv); } @@ -1696,7 +1800,8 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow, MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); RefPtr job = - new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb, documentPrincipal, loadGroup); + new ServiceWorkerRegisterJob(queue, documentPrincipal, cleanedScope, spec, + cb, loadGroup); queue->Append(job); AssertIsOnMainThread(); @@ -1733,10 +1838,24 @@ ServiceWorkerManager::AppendPendingOperation(nsIRunnable* aRunnable) } } +void +ServiceWorkerRegistrationInfo::TryToActivateAsync() +{ + nsCOMPtr r = + NS_NewRunnableMethod(this, + &ServiceWorkerRegistrationInfo::TryToActivate); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r))); +} + +/* + * TryToActivate should not be called directly, use TryToACtivateAsync instead. + */ void ServiceWorkerRegistrationInfo::TryToActivate() { - if (!IsControllingDocuments() || mWaitingWorker->SkipWaitingFlag()) { + if (!IsControllingDocuments() || + // Waiting worker will be removed if the registration is removed + (mWaitingWorker && mWaitingWorker->SkipWaitingFlag())) { Activate(); } } @@ -2791,14 +2910,15 @@ ServiceWorkerManager::LoadRegistration( GetRegistration(principal, aRegistration.scope()); if (!registration) { registration = CreateNewRegistration(aRegistration.scope(), principal); - } else if (registration->mScriptSpec == aRegistration.scriptSpec() && - !!registration->mActiveWorker == aRegistration.currentWorkerURL().IsEmpty()) { - // No needs for updates. - return; + } else { + RefPtr newest = registration->Newest(); + if (newest && newest->ScriptSpec() == aRegistration.scriptSpec() && + !!registration->mActiveWorker == aRegistration.currentWorkerURL().IsEmpty()) { + // No needs for updates. + return; + } } - registration->mScriptSpec = aRegistration.scriptSpec(); - const nsCString& currentWorkerURL = aRegistration.currentWorkerURL(); if (!currentWorkerURL.IsEmpty()) { registration->mActiveWorker = @@ -3107,6 +3227,18 @@ ServiceWorkerManager::RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* data->mUpdateTimers.Remove(aRegistration->mScope); } + // The registration should generally only be removed if there are no controlled + // documents, but mControlledDocuments can contain references to potentially + // controlled docs. This happens when the service worker is not active yet. + // We must purge these references since we are evicting the registration. + for (auto iter = swm->mControlledDocuments.Iter(); !iter.Done(); iter.Next()) { + ServiceWorkerRegistrationInfo* reg = iter.UserData(); + MOZ_ASSERT(reg); + if (reg->mScope.Equals(aRegistration->mScope)) { + iter.Remove(); + } + } + RefPtr info; data->mInfos.Get(aRegistration->mScope, getter_AddRefs(info)); @@ -3219,7 +3351,7 @@ ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aR aRegistration->mActiveWorker->WorkerPrivate(); serviceWorkerPrivate->NoteStoppedControllingDocuments(); } - aRegistration->TryToActivate(); + aRegistration->TryToActivateAsync(); } } } @@ -3672,6 +3804,19 @@ ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes, const nsACString& aScope) { AssertIsOnMainThread(); + + nsCOMPtr scopeURI; + nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr principal = + BasePrincipal::CreateCodebasePrincipal(scopeURI, aOriginAttributes); + if (NS_WARN_IF(!principal)) { + return; + } + nsAutoCString scopeKey; aOriginAttributes.CreateSuffix(scopeKey); @@ -3699,9 +3844,6 @@ ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes, return; } - // "Set registration's registering script url to newestWorker's script url." - registration->mScriptSpec = newest->ScriptSpec(); - // "If the registration queue for registration is empty, invoke Update algorithm, // or its equivalent, with client, registration as its argument." // TODO(catalinb): We don't implement the force bypass cache flag. @@ -3711,7 +3853,8 @@ ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes, MOZ_ASSERT(queue); RefPtr job = - new ServiceWorkerRegisterJob(queue, registration, nullptr); + new ServiceWorkerRegisterJob(queue, principal, registration->mScope, + newest->ScriptSpec(), nullptr); queue->Append(job); } } @@ -3755,9 +3898,6 @@ ServiceWorkerManager::Update(nsIPrincipal* aPrincipal, return; } - // "Set registration's registering script url to newestWorker's script url." - registration->mScriptSpec = newest->ScriptSpec(); - ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scopeKey, aScope); MOZ_ASSERT(queue); @@ -3765,7 +3905,8 @@ ServiceWorkerManager::Update(nsIPrincipal* aPrincipal, // "Invoke Update algorithm, or its equivalent, with client, registration as // its argument." RefPtr job = - new ServiceWorkerRegisterJob(queue, registration, aCallback); + new ServiceWorkerRegisterJob(queue, aPrincipal, registration->mScope, + newest->ScriptSpec(), aCallback); queue->Append(job); } @@ -4002,7 +4143,7 @@ ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal, (registration->mWaitingWorker->ID() == aServiceWorkerID)) { registration->mWaitingWorker->SetSkipWaitingFlag(); if (registration->mWaitingWorker->State() == ServiceWorkerState::Installed) { - registration->TryToActivate(); + registration->TryToActivateAsync(); } } else { NS_WARNING("Failed to set skipWaiting flag, no matching worker."); diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h index 5670d151d6..115c1ac82c 100644 --- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -73,9 +73,6 @@ public: NS_DECL_NSISERVICEWORKERREGISTRATIONINFO nsCString mScope; - // The scriptURL for the registration. This may be completely different from - // the URLs of the following three workers. - nsCString mScriptSpec; nsCOMPtr mPrincipal; @@ -100,7 +97,7 @@ public: nsIPrincipal* aPrincipal); already_AddRefed - Newest() + Newest() const { RefPtr newest; if (mInstallingWorker) { @@ -142,6 +139,9 @@ public: void PurgeActiveWorker(); + void + TryToActivateAsync(); + void TryToActivate(); diff --git a/dom/workers/test/serviceworkers/test_claim_oninstall.html b/dom/workers/test/serviceworkers/test_claim_oninstall.html index a2bd4f2157..ac52ed15dc 100644 --- a/dom/workers/test/serviceworkers/test_claim_oninstall.html +++ b/dom/workers/test/serviceworkers/test_claim_oninstall.html @@ -38,13 +38,18 @@ var p = new Promise(function(res, rej) { registration.installing.onstatechange = function(e) { + ok(registration.waiting, "Worker should be in waitinging state"); + // The worker will become active only if claim will reject inside the // install handler. + registration.waiting.onstatechange = function(e) { + ok(registration.active, "Claim should reject if the worker is not active"); + ok(navigator.serviceWorker.controller === null, "Client is not controlled."); + e.target.onstatechange = null; + res(); + } - ok(registration.active, "Claim should reject if the worker is not active"); - ok(navigator.serviceWorker.controller === null, "Client is not controlled."); e.target.onstatechange = null; - res(); } }); diff --git a/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul b/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul index 0c63a3b2cd..c879dc01be 100644 --- a/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul +++ b/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul @@ -47,43 +47,15 @@ return waitForServiceWorkerRegistrationChange(registration, function () { is(registration.scriptSpec, EXAMPLE_URL + "worker.js"); - - return waitForServiceWorkerRegistrationChange(registration, function () { - ok(registration.installingWorker !== null); - ok(registration.waitingWorker === null); - ok(registration.activeWorker === null); - - return waitForServiceWorkerRegistrationChange(registration, function () { - ok(registration.installingWorker === null); - ok(registration.waitingWorker !== null); - ok(registration.activeWorker === null); - - return waitForServiceWorkerRegistrationChange(registration, function () { - ok(registration.installingWorker === null); - ok(registration.waitingWorker === null); - ok(registration.activeWorker !== null); - - return registration; - }); - }); - }); - }); - }); - iframe.contentWindow.postMessage("register", "*"); - let registration = yield promise; - - promise = waitForServiceWorkerRegistrationChange(registration, function () { - is(registration.scriptSpec, EXAMPLE_URL + "worker2.js"); - - return waitForServiceWorkerRegistrationChange(registration, function () { ok(registration.installingWorker !== null); + is(registration.installingWorker.scriptSpec, EXAMPLE_URL + "worker.js"); ok(registration.waitingWorker === null); - ok(registration.activeWorker !== null); + ok(registration.activeWorker === null); return waitForServiceWorkerRegistrationChange(registration, function () { ok(registration.installingWorker === null); ok(registration.waitingWorker !== null); - ok(registration.activeWorker !== null); + ok(registration.activeWorker === null); return waitForServiceWorkerRegistrationChange(registration, function () { ok(registration.installingWorker === null); @@ -93,7 +65,30 @@ return registration; }); }); + }); + }); + iframe.contentWindow.postMessage("register", "*"); + let registration = yield promise; + promise = waitForServiceWorkerRegistrationChange(registration, function () { + is(registration.scriptSpec, EXAMPLE_URL + "worker2.js"); + ok(registration.installingWorker !== null); + is(registration.installingWorker.scriptSpec, EXAMPLE_URL + "worker2.js"); + ok(registration.waitingWorker === null); + ok(registration.activeWorker !== null); + + return waitForServiceWorkerRegistrationChange(registration, function () { + ok(registration.installingWorker === null); + ok(registration.waitingWorker !== null); + ok(registration.activeWorker !== null); + + return waitForServiceWorkerRegistrationChange(registration, function () { + ok(registration.installingWorker === null); + ok(registration.waitingWorker === null); + ok(registration.activeWorker !== null); + + return registration; + }); }); }); iframe.contentWindow.postMessage("register", "*"); diff --git a/gfx/ipc/GfxMessageUtils.h b/gfx/ipc/GfxMessageUtils.h index f19948145f..3c7bcb5068 100644 --- a/gfx/ipc/GfxMessageUtils.h +++ b/gfx/ipc/GfxMessageUtils.h @@ -783,7 +783,6 @@ struct ParamTraits static void Write(Message* aMsg, const paramType& aParam) { WriteParam(aMsg, aParam.mParentBackend); - WriteParam(aMsg, aParam.mSupportedBlendModes.serialize()); WriteParam(aMsg, aParam.mMaxTextureSize); WriteParam(aMsg, aParam.mSupportsTextureBlitting); WriteParam(aMsg, aParam.mSupportsPartialUploads); @@ -792,14 +791,11 @@ struct ParamTraits static bool Read(const Message* aMsg, void** aIter, paramType* aResult) { - uint32_t supportedBlendModes = 0; bool result = ReadParam(aMsg, aIter, &aResult->mParentBackend) && - ReadParam(aMsg, aIter, &supportedBlendModes) && ReadParam(aMsg, aIter, &aResult->mMaxTextureSize) && ReadParam(aMsg, aIter, &aResult->mSupportsTextureBlitting) && ReadParam(aMsg, aIter, &aResult->mSupportsPartialUploads) && ReadParam(aMsg, aIter, &aResult->mSyncHandle); - aResult->mSupportedBlendModes.deserialize(supportedBlendModes); return result; } }; diff --git a/gfx/layers/CompositorTypes.h b/gfx/layers/CompositorTypes.h index ca0f72ad46..4096398791 100644 --- a/gfx/layers/CompositorTypes.h +++ b/gfx/layers/CompositorTypes.h @@ -161,7 +161,6 @@ struct TextureFactoryIdentifier { LayersBackend mParentBackend; GeckoProcessType mParentProcessId; - EnumSet mSupportedBlendModes; int32_t mMaxTextureSize; bool mSupportsTextureBlitting; bool mSupportsPartialUploads; @@ -175,7 +174,6 @@ struct TextureFactoryIdentifier SyncHandle aSyncHandle = 0) : mParentBackend(aLayersBackend) , mParentProcessId(aParentProcessId) - , mSupportedBlendModes(gfx::CompositionOp::OP_OVER) , mMaxTextureSize(aMaxTextureSize) , mSupportsTextureBlitting(aSupportsTextureBlitting) , mSupportsPartialUploads(aSupportsPartialUploads) diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 013f3b1cdb..0f9c026e09 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -1387,10 +1387,17 @@ ContainerLayer::DefaultComputeEffectiveTransforms(const Matrix4x4& aTransformToS useIntermediateSurface = true; #endif } else { + /* Don't use an intermediate surface for opacity when it's within a 3d + * context, since we'd rather keep the 3d effects. This matches the + * WebKit/blink behaviour, but is changing in the latest spec. + */ float opacity = GetEffectiveOpacity(); CompositionOp blendMode = GetEffectiveMixBlendMode(); - if (((opacity != 1.0f || blendMode != CompositionOp::OP_OVER) && (HasMultipleChildren() || Creates3DContextWithExtendingChildren())) || - (!idealTransform.Is2D() && Creates3DContextWithExtendingChildren())) { + if ((HasMultipleChildren() || Creates3DContextWithExtendingChildren()) && + ((opacity != 1.0f && !Extend3DContext()) || + (blendMode != CompositionOp::OP_OVER))) { + useIntermediateSurface = true; + } else if (!idealTransform.Is2D() && Creates3DContextWithExtendingChildren()) { useIntermediateSurface = true; } else { useIntermediateSurface = false; diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index 14a3b6d84d..97fbfe10bc 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -640,17 +640,6 @@ public: mRegionToClear = aRegion; } - virtual bool SupportsMixBlendModes(EnumSet& aMixBlendModes) - { - return false; - } - - bool SupportsMixBlendMode(gfx::CompositionOp aMixBlendMode) - { - EnumSet modes(aMixBlendMode); - return SupportsMixBlendModes(modes); - } - virtual float RequestProperty(const nsAString& property) { return -1; } const TimeStamp& GetAnimationReadyTime() const { diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 40716479ea..018502010e 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -278,6 +278,11 @@ using mozilla::gfx::PointTyped; * Units: screen pixels (for distance) * screen pixels per millisecond (for velocity) * + * \li\b apz.paint_skipping.enabled + * When APZ is scrolling and sending repaint requests to the main thread, often + * the main thread doesn't actually need to do a repaint. This pref allows the + * main thread to skip doing those repaints in cases where it doesn't need to. + * * \li\b apz.record_checkerboarding * Whether or not to record detailed info on checkerboarding events. * diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp index 1d64d89f9e..81e92ab85a 100644 --- a/gfx/layers/apz/util/APZCCallbackHelper.cpp +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp @@ -900,9 +900,19 @@ APZCCallbackHelper::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrol } void -APZCCallbackHelper::NotifyFlushComplete() +APZCCallbackHelper::NotifyFlushComplete(nsIPresShell* aShell) { MOZ_ASSERT(NS_IsMainThread()); + // In some cases, flushing the APZ state to the main thread doesn't actually + // trigger a flush and repaint (this is an intentional optimization - the stuff + // visible to the user is still correct). However, reftests update their + // snapshot based on invalidation events that are emitted during paints, + // so we ensure that we kick off a paint when an APZ flush is done. Note that + // only chrome/testing code can trigger this behaviour. + if (aShell && aShell->GetRootFrame()) { + aShell->GetRootFrame()->SchedulePaint(); + } + nsCOMPtr observerService = mozilla::services::GetObserverService(); MOZ_ASSERT(observerService); observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr); diff --git a/gfx/layers/apz/util/APZCCallbackHelper.h b/gfx/layers/apz/util/APZCCallbackHelper.h index 463f5dc1b3..0b21337c40 100644 --- a/gfx/layers/apz/util/APZCCallbackHelper.h +++ b/gfx/layers/apz/util/APZCCallbackHelper.h @@ -161,7 +161,7 @@ public: static void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent); /* Notify content that the repaint flush is complete. */ - static void NotifyFlushComplete(); + static void NotifyFlushComplete(nsIPresShell* aShell); private: static uint64_t sLastTargetAPZCNotificationInputBlock; diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp index b42ba6eab3..54a2834284 100644 --- a/gfx/layers/apz/util/ChromeProcessController.cpp +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -232,5 +232,5 @@ void ChromeProcessController::NotifyFlushComplete() { MOZ_ASSERT(NS_IsMainThread()); - APZCCallbackHelper::NotifyFlushComplete(); + APZCCallbackHelper::NotifyFlushComplete(GetPresShell()); } diff --git a/gfx/layers/basic/BasicCompositor.cpp b/gfx/layers/basic/BasicCompositor.cpp index 8ec9d0948c..8fc744b563 100644 --- a/gfx/layers/basic/BasicCompositor.cpp +++ b/gfx/layers/basic/BasicCompositor.cpp @@ -133,11 +133,6 @@ BasicCompositor::GetTextureFactoryIdentifier() TextureFactoryIdentifier ident(LayersBackend::LAYERS_BASIC, XRE_GetProcessType(), GetMaxTextureSize()); - - // All composition ops are supported in software. - for (uint8_t op = 0; op < uint8_t(CompositionOp::OP_COUNT); op++) { - ident.mSupportedBlendModes += CompositionOp(op); - } return ident; } diff --git a/gfx/layers/basic/BasicContainerLayer.cpp b/gfx/layers/basic/BasicContainerLayer.cpp index c43b95c95f..ee155e3073 100644 --- a/gfx/layers/basic/BasicContainerLayer.cpp +++ b/gfx/layers/basic/BasicContainerLayer.cpp @@ -83,7 +83,7 @@ BasicContainerLayer::ComputeEffectiveTransforms(const Matrix4x4& aTransformToSur GetMaskLayer() || GetForceIsolatedGroup() || (GetMixBlendMode() != CompositionOp::OP_OVER && HasMultipleChildren()) || - (GetEffectiveOpacity() != 1.0 && (HasMultipleChildren() || hasSingleBlendingChild)); + (GetEffectiveOpacity() != 1.0 && ((HasMultipleChildren() && !Extend3DContext()) || hasSingleBlendingChild)); if (!Extend3DContext()) { idealTransform.ProjectTo2D(); diff --git a/gfx/layers/basic/BasicLayers.h b/gfx/layers/basic/BasicLayers.h index 46e8735ee0..623c3d6280 100644 --- a/gfx/layers/basic/BasicLayers.h +++ b/gfx/layers/basic/BasicLayers.h @@ -160,8 +160,6 @@ public: virtual int32_t GetMaxTextureSize() const override { return INT32_MAX; } bool CompositorMightResample() { return mCompositorMightResample; } - virtual bool SupportsMixBlendModes(EnumSet& aMixBlendModes) override { return true; } - protected: enum TransactionPhase { PHASE_NONE, PHASE_CONSTRUCTION, PHASE_DRAWING, PHASE_FORWARD diff --git a/gfx/layers/client/ClientLayerManager.h b/gfx/layers/client/ClientLayerManager.h index a3616afd46..4128e17d67 100644 --- a/gfx/layers/client/ClientLayerManager.h +++ b/gfx/layers/client/ClientLayerManager.h @@ -203,11 +203,6 @@ public: const mozilla::TimeStamp& aCompositeStart, const mozilla::TimeStamp& aCompositeEnd); - virtual bool SupportsMixBlendModes(EnumSet& aMixBlendModes) override - { - return (GetTextureFactoryIdentifier().mSupportedBlendModes & aMixBlendModes) == aMixBlendModes; - } - virtual bool AreComponentAlphaLayersEnabled() override; // Log APZ test data for the current paint. We supply the paint sequence diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp index 1a0130df64..1016df3969 100644 --- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -589,6 +589,7 @@ SampleAnimations(Layer* aLayer, TimeStamp aPoint) // into their start time, hence the delay is effectively zero. timing.mDelay = TimeDuration(0); timing.mIterations = animation.iterations(); + timing.mIterationStart = animation.iterationStart(); timing.mDirection = static_cast(animation.direction()); // Animations typically only run on the compositor during their active diff --git a/gfx/layers/d3d11/CompositorD3D11.cpp b/gfx/layers/d3d11/CompositorD3D11.cpp index d4300e3d90..995c1d417e 100644 --- a/gfx/layers/d3d11/CompositorD3D11.cpp +++ b/gfx/layers/d3d11/CompositorD3D11.cpp @@ -461,11 +461,6 @@ CompositorD3D11::GetTextureFactoryIdentifier() ident.mParentProcessId = XRE_GetProcessType(); ident.mParentBackend = LayersBackend::LAYERS_D3D11; ident.mSyncHandle = mAttachments->mSyncHandle; - for (uint8_t op = 0; op < uint8_t(gfx::CompositionOp::OP_COUNT); op++) { - if (BlendOpIsMixBlendMode(gfx::CompositionOp(op))) { - ident.mSupportedBlendModes += gfx::CompositionOp(op); - } - } return ident; } diff --git a/gfx/layers/d3d9/CompositorD3D9.cpp b/gfx/layers/d3d9/CompositorD3D9.cpp index ddfe9b4fc4..605b440b4a 100644 --- a/gfx/layers/d3d9/CompositorD3D9.cpp +++ b/gfx/layers/d3d9/CompositorD3D9.cpp @@ -74,11 +74,6 @@ CompositorD3D9::GetTextureFactoryIdentifier() ident.mMaxTextureSize = GetMaxTextureSize(); ident.mParentBackend = LayersBackend::LAYERS_D3D9; ident.mParentProcessId = XRE_GetProcessType(); - for (uint8_t op = 0; op < uint8_t(gfx::CompositionOp::OP_COUNT); op++) { - if (BlendOpIsMixBlendMode(gfx::CompositionOp(op))) { - ident.mSupportedBlendModes += gfx::CompositionOp(op); - } - } return ident; } diff --git a/gfx/layers/ipc/APZChild.cpp b/gfx/layers/ipc/APZChild.cpp index 4e5fe7fe28..f7e00448f8 100644 --- a/gfx/layers/ipc/APZChild.cpp +++ b/gfx/layers/ipc/APZChild.cpp @@ -151,7 +151,11 @@ APZChild::RecvNotifyAPZStateChange(const ViewID& aViewId, bool APZChild::RecvNotifyFlushComplete() { - APZCCallbackHelper::NotifyFlushComplete(); + nsCOMPtr shell; + if (nsCOMPtr doc = mBrowser->GetDocument()) { + shell = doc->GetShell(); + } + APZCCallbackHelper::NotifyFlushComplete(shell.get()); return true; } diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh index 62d23f64b1..040858a596 100644 --- a/gfx/layers/ipc/LayersMessages.ipdlh +++ b/gfx/layers/ipc/LayersMessages.ipdlh @@ -195,6 +195,7 @@ struct Animation { // Values <= 0 mean the animation will not play (although events are still // dispatched on the main thread). float iterations; + float iterationStart; // This uses the NS_STYLE_ANIMATION_DIRECTION_* constants. int32_t direction; nsCSSProperty property; diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h index 06947781d6..302f903af4 100644 --- a/gfx/layers/opengl/CompositorOGL.h +++ b/gfx/layers/opengl/CompositorOGL.h @@ -215,12 +215,6 @@ public: GetMaxTextureSize(), mFBOTextureTarget == LOCAL_GL_TEXTURE_2D, SupportsPartialTextureUpdate()); - result.mSupportedBlendModes += gfx::CompositionOp::OP_SOURCE; - for (uint8_t op = 0; op < uint8_t(gfx::CompositionOp::OP_COUNT); op++) { - if (BlendOpIsMixBlendMode(gfx::CompositionOp(op))) { - result.mSupportedBlendModes += gfx::CompositionOp(op); - } - } return result; } diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index c482844b30..bc62dfe9c8 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -175,6 +175,7 @@ private: DECL_GFX_PREF(Live, "apz.overscroll.stop_distance_threshold", APZOverscrollStopDistanceThreshold, float, 5.0f); DECL_GFX_PREF(Live, "apz.overscroll.stop_velocity_threshold", APZOverscrollStopVelocityThreshold, float, 0.01f); DECL_GFX_PREF(Live, "apz.overscroll.stretch_factor", APZOverscrollStretchFactor, float, 0.5f); + DECL_GFX_PREF(Live, "apz.paint_skipping.enabled", APZPaintSkipping, bool, true); DECL_GFX_PREF(Live, "apz.printtree", APZPrintTree, bool, false); DECL_GFX_PREF(Live, "apz.record_checkerboarding", APZRecordCheckerboarding, bool, false); DECL_GFX_PREF(Live, "apz.test.logging_enabled", APZTestLoggingEnabled, bool, false); @@ -360,6 +361,7 @@ private: DECL_GFX_PREF(Live, "layers.low-precision-buffer", UseLowPrecisionBuffer, bool, false); DECL_GFX_PREF(Live, "layers.low-precision-opacity", LowPrecisionOpacity, float, 1.0f); DECL_GFX_PREF(Live, "layers.low-precision-resolution", LowPrecisionResolution, float, 0.25f); + DECL_GFX_PREF(Live, "layers.max-active", MaxActiveLayers, int32_t, -1); DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.enabled", LayersOffMainThreadCompositionEnabled, bool, false); DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-enabled", LayersOffMainThreadCompositionForceEnabled, bool, false); DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1); diff --git a/image/ClippedImage.cpp b/image/ClippedImage.cpp index 468eb65d01..57da47e9a2 100644 --- a/image/ClippedImage.cpp +++ b/image/ClippedImage.cpp @@ -137,11 +137,18 @@ private: }; ClippedImage::ClippedImage(Image* aImage, - nsIntRect aClip) + nsIntRect aClip, + const Maybe& aSVGViewportSize) : ImageWrapper(aImage) , mClip(aClip) { MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image"); + MOZ_ASSERT_IF(aSVGViewportSize, + aImage->GetType() == imgIContainer::TYPE_VECTOR); + if (aSVGViewportSize) { + mSVGViewportSize = Some(aSVGViewportSize->ToNearestPixels( + nsPresContext::AppUnitsPerCSSPixel())); + } } ClippedImage::~ClippedImage() @@ -162,6 +169,15 @@ ClippedImage::ShouldClip() // If there's a problem with the inner image we'll let it handle // everything. mShouldClip.emplace(false); + } else if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { + // Clamp the clipping region to the size of the SVG viewport. + nsIntRect svgViewportRect(nsIntPoint(0,0), *mSVGViewportSize); + + mClip = mClip.Intersect(svgViewportRect); + + // If the clipping region is the same size as the SVG viewport size + // we don't have to do anything. + mShouldClip.emplace(!mClip.IsEqualInterior(svgViewportRect)); } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 && NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) { // Clamp the clipping region to the size of the underlying image. @@ -420,8 +436,19 @@ ClippedImage::DrawSingleTile(gfxContext* aContext, gfxRect clip(mClip.x, mClip.y, mClip.width, mClip.height); nsIntSize size(aSize), innerSize(aSize); - if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) && - NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) { + bool needScale = false; + if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { + innerSize = *mSVGViewportSize; + needScale = true; + } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) && + NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) { + needScale = true; + } else { + MOZ_ASSERT_UNREACHABLE( + "If ShouldClip() led us to draw then we should never get here"); + } + + if (needScale) { double scaleX = aSize.width / clip.width; double scaleY = aSize.height / clip.height; @@ -429,9 +456,6 @@ ClippedImage::DrawSingleTile(gfxContext* aContext, clip.Scale(scaleX, scaleY); size = innerSize; size.Scale(scaleX, scaleY); - } else { - MOZ_ASSERT(false, - "If ShouldClip() led us to draw then we should never get here"); } // We restrict our drawing to only the clipping region, and translate so that @@ -478,8 +502,17 @@ ClippedImage::OptimalImageSizeForDest(const gfxSize& aDest, } int32_t imgWidth, imgHeight; - if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) && - NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) { + bool needScale = false; + if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { + imgWidth = mSVGViewportSize->width; + imgHeight = mSVGViewportSize->height; + needScale = true; + } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) && + NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) { + needScale = true; + } + + if (needScale) { // To avoid ugly sampling artifacts, ClippedImage needs the image size to // be chosen such that the clipping region lies on pixel boundaries. @@ -501,12 +534,12 @@ ClippedImage::OptimalImageSizeForDest(const gfxSize& aDest, nsIntSize finalScale(ceil(double(innerDesiredSize.width) / imgWidth), ceil(double(innerDesiredSize.height) / imgHeight)); return mClip.Size() * finalScale; - } else { - MOZ_ASSERT(false, - "If ShouldClip() led us to draw then we should never get here"); - return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, aFilter, - aFlags); } + + MOZ_ASSERT(false, + "If ShouldClip() led us to draw then we should never get here"); + return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, aFilter, + aFlags); } NS_IMETHODIMP_(nsIntRect) diff --git a/image/ClippedImage.h b/image/ClippedImage.h index 6b16f57496..3e947652b0 100644 --- a/image/ClippedImage.h +++ b/image/ClippedImage.h @@ -64,7 +64,8 @@ public: uint32_t aFlags) override; protected: - ClippedImage(Image* aImage, nsIntRect aClip); + ClippedImage(Image* aImage, nsIntRect aClip, + const Maybe& aSVGViewportSize); virtual ~ClippedImage(); @@ -86,9 +87,10 @@ private: // If we are forced to draw a temporary surface, we cache it here. UniquePtr mCachedSurface; - nsIntRect mClip; // The region to clip to. - Maybe mShouldClip; // Memoized ShouldClip() if present. - + nsIntRect mClip; // The region to clip to. + Maybe mShouldClip; // Memoized ShouldClip() if present. + Maybe mSVGViewportSize; // If we're clipping a VectorImage, this + // is the size of viewport of that image. friend class DrawSingleTileCallback; friend class ImageOps; }; diff --git a/image/ImageOps.cpp b/image/ImageOps.cpp index 76a7bb968b..db00e59e26 100644 --- a/image/ImageOps.cpp +++ b/image/ImageOps.cpp @@ -40,17 +40,19 @@ ImageOps::Freeze(imgIContainer* aImage) } /* static */ already_AddRefed -ImageOps::Clip(Image* aImage, nsIntRect aClip) +ImageOps::Clip(Image* aImage, nsIntRect aClip, + const Maybe& aSVGViewportSize) { - RefPtr clippedImage = new ClippedImage(aImage, aClip); + RefPtr clippedImage = new ClippedImage(aImage, aClip, aSVGViewportSize); return clippedImage.forget(); } /* static */ already_AddRefed -ImageOps::Clip(imgIContainer* aImage, nsIntRect aClip) +ImageOps::Clip(imgIContainer* aImage, nsIntRect aClip, + const Maybe& aSVGViewportSize) { nsCOMPtr clippedImage = - new ClippedImage(static_cast(aImage), aClip); + new ClippedImage(static_cast(aImage), aClip, aSVGViewportSize); return clippedImage.forget(); } diff --git a/image/ImageOps.h b/image/ImageOps.h index 9dcfc286f8..7a8e19be34 100644 --- a/image/ImageOps.h +++ b/image/ImageOps.h @@ -40,12 +40,19 @@ public: /** * Creates a clipped version of an existing image. Animation is unaffected. * - * @param aImage The existing image. - * @param aClip The rectangle to clip the image against. + * @param aImage The existing image. + * @param aClip The rectangle to clip the image against. + * @param aSVGViewportSize The specific viewort size of aImage. Unless aImage + * is a vector image without intrinsic size, this + * argument should be pass as Nothing(). */ - static already_AddRefed Clip(Image* aImage, nsIntRect aClip); + static already_AddRefed Clip(Image* aImage, nsIntRect aClip, + const Maybe& aSVGViewportSize = + Nothing()); static already_AddRefed Clip(imgIContainer* aImage, - nsIntRect aClip); + nsIntRect aClip, + const Maybe& aSVGViewportSize = + Nothing()); /** * Creates a version of an existing image which is rotated and/or flipped to diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp index 0703f18c29..913940c55a 100644 --- a/image/VectorImage.cpp +++ b/image/VectorImage.cpp @@ -763,7 +763,6 @@ struct SVGDrawingParameters uint32_t aFlags) : context(aContext) , size(aSize.width, aSize.height) - , imageRect(0, 0, aSize.width, aSize.height) , region(aRegion) , filter(aFilter) , svgContext(aSVGContext) @@ -780,7 +779,6 @@ struct SVGDrawingParameters gfxContext* context; IntSize size; - IntRect imageRect; ImageRegion region; Filter filter; const Maybe& svgContext; @@ -828,15 +826,32 @@ VectorImage::Draw(gfxContext* aContext, AutoRestore autoRestoreIsDrawing(mIsDrawing); mIsDrawing = true; + Maybe svgContext; + // If FLAG_FORCE_PRESERVEASPECTRATIO_NONE bit is set, that mean we should + // overwrite SVG preserveAspectRatio attibute of this image with none, and + // always stretch this image to viewport non-uniformly. + // And we can do this only if the caller pass in the the SVG viewport, via + // aSVGContext. + if ((aFlags & FLAG_FORCE_PRESERVEASPECTRATIO_NONE) && aSVGContext.isSome()) { + Maybe aspectRatio = + Some(SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE, + SVG_MEETORSLICE_UNKNOWN)); + svgContext = + Some(SVGImageContext(aSVGContext->GetViewportSize(), + aspectRatio)); + } else { + svgContext = aSVGContext; + } + float animTime = (aWhichFrame == FRAME_FIRST) ? 0.0f : mSVGDocumentWrapper->GetCurrentTime(); - AutoSVGRenderingState autoSVGState(aSVGContext, animTime, + AutoSVGRenderingState autoSVGState(svgContext, animTime, mSVGDocumentWrapper->GetRootSVGElem()); - // Pack up the drawing parameters. + SVGDrawingParameters params(aContext, aSize, aRegion, aFilter, - aSVGContext, animTime, aFlags); + svgContext, animTime, aFlags); if (aFlags & FLAG_BYPASS_SURFACE_CACHE) { CreateSurfaceAndShow(params); diff --git a/image/imgIContainer.idl b/image/imgIContainer.idl index 950ab0cda6..7fa7ff6c75 100644 --- a/image/imgIContainer.idl +++ b/image/imgIContainer.idl @@ -181,6 +181,16 @@ interface imgIContainer : nsISupports * cached rendering from the surface cache. This is used when we are printing, * for example, where we want the vector commands from VectorImages to end up * in the PDF output rather than a cached rendering at screen resolution. + * + * FLAG_FORCE_PRESERVEASPECTRATIO_NONE: Force scaling this image + * non-uniformly if necessary. This flag is for vector image only. A raster + * image should ignore this flag. While drawing a vector image with this + * flag, do not force uniform scaling even if its root node has a + * preserveAspectRatio attribute that would otherwise require uniform + * scaling , such as xMinYMin/ xMidYMin. Always scale the graphic content of + * the given image non-uniformly if necessary such that the image's + * viewBox (if specified or implied by height/width attributes) exactly + * matches the viewport rectangle. */ const unsigned long FLAG_NONE = 0x0; const unsigned long FLAG_SYNC_DECODE = 0x1; @@ -192,6 +202,7 @@ interface imgIContainer : nsISupports const unsigned long FLAG_HIGH_QUALITY_SCALING = 0x40; const unsigned long FLAG_WANT_DATA_SURFACE = 0x80; const unsigned long FLAG_BYPASS_SURFACE_CACHE = 0x100; + const unsigned long FLAG_FORCE_PRESERVEASPECTRATIO_NONE = 0x200; /** * A constant specifying the default set of decode flags (i.e., the default diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h index ba9fcbcf0c..b411a26138 100644 --- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -56,7 +56,8 @@ const size_t ChunkMarkBitmapBits = 129024; const size_t ChunkRuntimeOffset = ChunkSize - sizeof(void*); const size_t ChunkTrailerSize = 2 * sizeof(uintptr_t) + sizeof(uint64_t); const size_t ChunkLocationOffset = ChunkSize - ChunkTrailerSize; -const size_t ArenaZoneOffset = 0; +const size_t ArenaZoneOffset = sizeof(size_t); +const size_t ArenaHeaderSize = sizeof(size_t) + 2 * sizeof(uintptr_t) + sizeof(size_t); /* * Live objects are marked black. How many other additional colors are available diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index e08bfe8ffb..09856da1b4 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -786,6 +786,8 @@ GCState(JSContext* cx, unsigned argc, Value* vp) state = "mark"; else if (globalState == gc::SWEEP) state = "sweep"; + else if (globalState == gc::FINALIZE) + state = "finalize"; else if (globalState == gc::COMPACT) state = "compact"; else @@ -1642,7 +1644,7 @@ DisplayName(JSContext* cx, unsigned argc, Value* vp) } static JSObject* -ShellObjectMetadataCallback(JSContext* cx, JSObject*) +ShellObjectMetadataCallback(JSContext* cx, HandleObject) { AutoEnterOOMUnsafeRegion oomUnsafe; diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index b409dfa4a1..97cc601713 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -92,7 +92,7 @@ GCRuntime::checkIncrementalZoneState(ExclusiveContext* cx, T* t) Zone* zone = cx->asJSContext()->zone(); MOZ_ASSERT_IF(t && zone->wasGCStarted() && (zone->isGCMarking() || zone->isGCSweeping()), - t->asTenured().arenaHeader()->allocatedDuringIncremental); + t->asTenured().arena()->allocatedDuringIncremental); #endif } @@ -150,6 +150,8 @@ template JSObject* GCRuntime::tryNewNurseryObject(JSContext* cx, size_t thingSize, size_t nDynamicSlots, const Class* clasp) { + MOZ_ASSERT(isNurseryAllocAllowed()); + MOZ_ASSERT(!cx->zone()->usedByExclusiveThread); MOZ_ASSERT(!IsAtomsCompartment(cx->compartment())); JSObject* obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp); if (obj) @@ -270,7 +272,7 @@ GCRuntime::tryNewTenuredThing(ExclusiveContext* cx, AllocKind kind, size_t thing /* static */ void* GCRuntime::refillFreeListFromAnyThread(ExclusiveContext* cx, AllocKind thingKind, size_t thingSize) { - MOZ_ASSERT(cx->arenas()->freeLists[thingKind].isEmpty()); + cx->arenas()->checkEmptyFreeList(thingKind); if (cx->isJSContext()) return refillFreeListFromMainThread(cx->asJSContext(), thingKind, thingSize); @@ -321,12 +323,12 @@ ArenaLists::allocateFromArena(JS::Zone* zone, AllocKind thingKind, maybeLock.emplace(rt); ArenaList& al = arenaLists[thingKind]; - ArenaHeader* aheader = al.takeNextArena(); - if (aheader) { + Arena* arena = al.takeNextArena(); + if (arena) { // Empty arenas should be immediately freed. - MOZ_ASSERT(!aheader->isEmpty()); + MOZ_ASSERT(!arena->isEmpty()); - return allocateFromArenaInner(zone, aheader, thingKind); + return allocateFromArenaInner(zone, arena, thingKind); } // Parallel threads have their own ArenaLists, but chunks are shared; @@ -340,44 +342,33 @@ ArenaLists::allocateFromArena(JS::Zone* zone, AllocKind thingKind, // Although our chunk should definitely have enough space for another arena, // there are other valid reasons why Chunk::allocateArena() may fail. - aheader = rt->gc.allocateArena(chunk, zone, thingKind, maybeLock.ref()); - if (!aheader) + arena = rt->gc.allocateArena(chunk, zone, thingKind, maybeLock.ref()); + if (!arena) return nullptr; MOZ_ASSERT(!maybeLock->wasUnlocked()); MOZ_ASSERT(al.isCursorAtEnd()); - al.insertAtCursor(aheader); + al.insertBeforeCursor(arena); - return allocateFromArenaInner(zone, aheader, thingKind); + return allocateFromArenaInner(zone, arena, thingKind); } -template -TenuredCell* -ArenaLists::allocateFromArenaInner(JS::Zone* zone, ArenaHeader* aheader, AllocKind kind) +inline TenuredCell* +ArenaLists::allocateFromArenaInner(JS::Zone* zone, Arena* arena, AllocKind kind) { size_t thingSize = Arena::thingSize(kind); - FreeSpan span; - if (hasFreeThings) { - MOZ_ASSERT(aheader->hasFreeThings()); - span = aheader->getFirstFreeSpan(); - aheader->setAsFullyUsed(); - } else { - MOZ_ASSERT(!aheader->hasFreeThings()); - Arena* arena = aheader->getArena(); - span.initFinal(arena->thingsStart(kind), arena->thingsEnd() - thingSize, thingSize); - } - freeLists[kind].setHead(&span); + freeLists[kind] = arena->getFirstFreeSpan(); if (MOZ_UNLIKELY(zone->wasGCStarted())) - zone->runtimeFromAnyThread()->gc.arenaAllocatedDuringGC(zone, aheader); - TenuredCell* thing = freeLists[kind].allocate(thingSize); + zone->runtimeFromAnyThread()->gc.arenaAllocatedDuringGC(zone, arena); + TenuredCell* thing = freeLists[kind]->allocate(thingSize); MOZ_ASSERT(thing); // This allocation is infallible. return thing; } void -GCRuntime::arenaAllocatedDuringGC(JS::Zone* zone, ArenaHeader* arena) +GCRuntime::arenaAllocatedDuringGC(JS::Zone* zone, Arena* arena) { if (zone->needsIncrementalBarrier()) { arena->allocatedDuringIncremental = true; @@ -387,5 +378,3 @@ GCRuntime::arenaAllocatedDuringGC(JS::Zone* zone, ArenaHeader* arena) arenasAllocatedDuringSweep = arena; } } - - diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index ebfdda69fb..9b34a3207b 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -19,16 +19,6 @@ namespace js { namespace gc { -class MOZ_RAII AutoCopyFreeListToArenas -{ - JSRuntime* runtime; - ZoneSelector selector; - - public: - AutoCopyFreeListToArenas(JSRuntime* rt, ZoneSelector selector); - ~AutoCopyFreeListToArenas(); -}; - struct MOZ_RAII AutoFinishGC { explicit AutoFinishGC(JSRuntime* rt); @@ -60,7 +50,6 @@ struct MOZ_RAII AutoPrepareForTracing { AutoFinishGC finish; AutoTraceSession session; - AutoCopyFreeListToArenas copy; AutoPrepareForTracing(JSRuntime* rt, ZoneSelector selector); }; diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index df07f78aa7..a141a90973 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -708,6 +708,13 @@ class GCRuntime --noGCOrAllocationCheck; } + bool isNurseryAllocAllowed() { return noNurseryAllocationCheck == 0; } + void disallowNurseryAlloc() { ++noNurseryAllocationCheck; } + void allowNurseryAlloc() { + MOZ_ASSERT(!isNurseryAllocAllowed()); + --noNurseryAllocationCheck; + } + bool isInsideUnsafeRegion() { return inUnsafeRegion != 0; } void enterUnsafeRegion() { ++inUnsafeRegion; } void leaveUnsafeRegion() { @@ -840,7 +847,7 @@ class GCRuntime void freeAllLifoBlocksAfterSweeping(LifoAlloc* lifo); // Public here for ReleaseArenaLists and FinalizeTypedArenas. - void releaseArena(ArenaHeader* aheader, const AutoLockGC& lock); + void releaseArena(Arena* arena, const AutoLockGC& lock); void releaseHeldRelocatedArenas(); void releaseHeldRelocatedArenasWithoutUnlocking(const AutoLockGC& lock); @@ -871,8 +878,8 @@ class GCRuntime friend class ArenaLists; Chunk* pickChunk(const AutoLockGC& lock, AutoMaybeStartBackgroundAllocation& maybeStartBGAlloc); - ArenaHeader* allocateArena(Chunk* chunk, Zone* zone, AllocKind kind, const AutoLockGC& lock); - void arenaAllocatedDuringGC(JS::Zone* zone, ArenaHeader* arena); + Arena* allocateArena(Chunk* chunk, Zone* zone, AllocKind kind, const AutoLockGC& lock); + void arenaAllocatedDuringGC(JS::Zone* zone, Arena* arena); // Allocator internals bool gcIfNeededPerAllocation(JSContext* cx); @@ -949,20 +956,20 @@ class GCRuntime void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks, ThreadType threadType); void assertBackgroundSweepingFinished(); bool shouldCompact(); - IncrementalProgress beginCompactPhase(); + void beginCompactPhase(); IncrementalProgress compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget); void endCompactPhase(JS::gcreason::Reason reason); void sweepTypesAfterCompacting(Zone* zone); void sweepZoneAfterCompacting(Zone* zone); - bool relocateArenas(Zone* zone, JS::gcreason::Reason reason, ArenaHeader*& relocatedListOut, + bool relocateArenas(Zone* zone, JS::gcreason::Reason reason, Arena*& relocatedListOut, SliceBudget& sliceBudget); void updateAllCellPointersParallel(MovingTracer* trc, Zone* zone); void updateAllCellPointersSerial(MovingTracer* trc, Zone* zone); void updatePointersToRelocatedCells(Zone* zone); - void protectAndHoldArenas(ArenaHeader* arenaList); + void protectAndHoldArenas(Arena* arenaList); void unprotectHeldRelocatedArenas(); - void releaseRelocatedArenas(ArenaHeader* arenaList); - void releaseRelocatedArenasWithoutUnlocking(ArenaHeader* arenaList, const AutoLockGC& lock); + void releaseRelocatedArenas(Arena* arenaList); + void releaseRelocatedArenasWithoutUnlocking(Arena* arenaList, const AutoLockGC& lock); void finishCollection(JS::gcreason::Reason reason); void computeNonIncrementalMarkingForValidation(); @@ -1170,14 +1177,14 @@ class GCRuntime /* * List head of arenas allocated during the sweep phase. */ - js::gc::ArenaHeader* arenasAllocatedDuringSweep; + js::gc::Arena* arenasAllocatedDuringSweep; /* * Incremental compacting state. */ bool startedCompacting; js::gc::ZoneList zonesToMaybeCompact; - ArenaHeader* relocatedArenasToRelease; + Arena* relocatedArenasToRelease; #ifdef JS_GC_ZEAL js::gc::MarkingValidator* markingValidator; @@ -1312,6 +1319,7 @@ class GCRuntime int inUnsafeRegion; size_t noGCOrAllocationCheck; + size_t noNurseryAllocationCheck; #endif /* Synchronize GC heap access between main thread and GCHelperState. */ diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 30a8f646cd..eb34749303 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -10,6 +10,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/Atomics.h" #include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" #include "mozilla/EnumeratedArray.h" #include "mozilla/EnumeratedRange.h" #include "mozilla/PodOperations.h" @@ -59,10 +60,9 @@ TraceManuallyBarrieredGenericPointerEdge(JSTracer* trc, gc::Cell** thingp, const namespace gc { -struct Arena; +class Arena; class ArenaList; class SortedArenaList; -struct ArenaHeader; struct Chunk; /* @@ -250,7 +250,7 @@ struct Cell }; // A GC TenuredCell gets behaviors that are valid for things in the Tenured -// heap, such as access to the arena header and mark bits. +// heap, such as access to the arena and mark bits. class TenuredCell : public Cell { public: @@ -268,8 +268,8 @@ class TenuredCell : public Cell // used tagged. static MOZ_ALWAYS_INLINE bool isNullLike(const Cell* thing) { return !thing; } - // Access to the arena header. - inline ArenaHeader* arenaHeader() const; + // Access to the arena. + inline Arena* arena() const; inline AllocKind getAllocKind() const; inline JS::TraceKind getTraceKind() const; inline JS::Zone* zone() const; @@ -322,49 +322,43 @@ const size_t ArenaBitmapWords = ArenaBitmapBits / JS_BITS_PER_WORD; */ class FreeSpan { + friend class Arena; friend class ArenaCellIterImpl; - friend class CompactFreeSpan; - friend class FreeList; - uintptr_t first; - uintptr_t last; + uint16_t first; + uint16_t last; public: // This inits just |first| and |last|; if the span is non-empty it doesn't // do anything with the next span stored at |last|. - void initBoundsUnchecked(uintptr_t first, uintptr_t last) { - this->first = first; - this->last = last; - } - - void initBounds(uintptr_t first, uintptr_t last) { - initBoundsUnchecked(first, last); - checkSpan(); + void initBounds(uintptr_t firstArg, uintptr_t lastArg, const Arena* arena) { + checkRange(firstArg, lastArg, arena); + first = firstArg; + last = lastArg; } void initAsEmpty() { first = 0; last = 0; - MOZ_ASSERT(isEmpty()); } // This sets |first| and |last|, and also sets the next span stored at // |last| as empty. (As a result, |firstArg| and |lastArg| cannot represent // an empty span.) - void initFinal(uintptr_t firstArg, uintptr_t lastArg, size_t thingSize) { - first = firstArg; - last = lastArg; - FreeSpan* lastSpan = reinterpret_cast(last); - lastSpan->initAsEmpty(); - MOZ_ASSERT(!isEmpty()); - checkSpan(thingSize); + void initFinal(uintptr_t firstArg, uintptr_t lastArg, const Arena* arena) { + initBounds(firstArg, lastArg, arena); + FreeSpan* last = nextSpanUnchecked(arena); + last->initAsEmpty(); + checkSpan(arena); } bool isEmpty() const { - checkSpan(); return !first; } + Arena* getArenaUnchecked() { return reinterpret_cast(this); } + inline Arena* getArena(); + static size_t offsetOfFirst() { return offsetof(FreeSpan, first); } @@ -374,275 +368,176 @@ class FreeSpan } // Like nextSpan(), but no checking of the following span is done. - FreeSpan* nextSpanUnchecked() const { - return reinterpret_cast(last); + FreeSpan* nextSpanUnchecked(const Arena* arena) const { + MOZ_ASSERT(arena && !isEmpty()); + return reinterpret_cast(uintptr_t(arena) + last); } - const FreeSpan* nextSpan() const { - MOZ_ASSERT(!isEmpty()); - return nextSpanUnchecked(); + const FreeSpan* nextSpan(const Arena* arena) const { + checkSpan(arena); + return nextSpanUnchecked(arena); } - uintptr_t arenaAddress() const { - MOZ_ASSERT(!isEmpty()); - return first & ~ArenaMask; - } - -#ifdef DEBUG - bool isWithinArena(uintptr_t arenaAddr) const { - MOZ_ASSERT(!(arenaAddr & ArenaMask)); - MOZ_ASSERT(!isEmpty()); - return arenaAddress() == arenaAddr; - } -#endif - - size_t length(size_t thingSize) const { - checkSpan(); - MOZ_ASSERT((last - first) % thingSize == 0); - return (last - first) / thingSize + 1; - } - - bool inFreeList(uintptr_t thing) { - for (const FreeSpan* span = this; !span->isEmpty(); span = span->nextSpan()) { - /* If the thing comes before the current span, it's not free. */ - if (thing < span->first) - return false; - - /* If we find it before the end of the span, it's free. */ - if (thing <= span->last) - return true; - } - return false; - } - - private: - // Some callers can pass in |thingSize| easily, and we can do stronger - // checking in that case. - void checkSpan(size_t thingSize = 0) const { -#ifdef DEBUG - if (!first || !last) { - MOZ_ASSERT(!first && !last); - // An empty span. - return; - } - - // |first| and |last| must be ordered appropriately, belong to the same - // arena, and be suitably aligned. - MOZ_ASSERT(first <= last); - MOZ_ASSERT((first & ~ArenaMask) == (last & ~ArenaMask)); - MOZ_ASSERT((last - first) % (thingSize ? thingSize : CellSize) == 0); - - // If there's a following span, it must be from the same arena, it must - // have a higher address, and the gap must be at least 2*thingSize. - FreeSpan* next = reinterpret_cast(last); - if (next->first) { - MOZ_ASSERT(next->last); - MOZ_ASSERT((first & ~ArenaMask) == (next->first & ~ArenaMask)); - MOZ_ASSERT(thingSize - ? last + 2 * thingSize <= next->first - : last < next->first); - } -#endif - } -}; - -class CompactFreeSpan -{ - uint16_t firstOffset_; - uint16_t lastOffset_; - - public: - CompactFreeSpan(size_t firstOffset, size_t lastOffset) - : firstOffset_(firstOffset) - , lastOffset_(lastOffset) - {} - - void initAsEmpty() { - firstOffset_ = 0; - lastOffset_ = 0; - } - - bool operator==(const CompactFreeSpan& other) const { - return firstOffset_ == other.firstOffset_ && - lastOffset_ == other.lastOffset_; - } - - void compact(FreeSpan span) { - if (span.isEmpty()) { - initAsEmpty(); - } else { - static_assert(ArenaShift < 16, "Check that we can pack offsets into uint16_t."); - uintptr_t arenaAddr = span.arenaAddress(); - firstOffset_ = span.first - arenaAddr; - lastOffset_ = span.last - arenaAddr; - } - } - - bool isEmpty() const { - MOZ_ASSERT(!!firstOffset_ == !!lastOffset_); - return !firstOffset_; - } - - FreeSpan decompact(uintptr_t arenaAddr) const { - MOZ_ASSERT(!(arenaAddr & ArenaMask)); - FreeSpan decodedSpan; - if (isEmpty()) { - decodedSpan.initAsEmpty(); - } else { - MOZ_ASSERT(firstOffset_ <= lastOffset_); - MOZ_ASSERT(lastOffset_ < ArenaSize); - decodedSpan.initBounds(arenaAddr + firstOffset_, arenaAddr + lastOffset_); - } - return decodedSpan; - } -}; - -class FreeList -{ - // Although |head| is private, it is exposed to the JITs via the - // offsetOf{First,Last}() and addressOfFirstLast() methods below. - // Therefore, any change in the representation of |head| will require - // updating the relevant JIT code. - FreeSpan head; - - public: - FreeList() {} - - static size_t offsetOfFirst() { - return offsetof(FreeList, head) + offsetof(FreeSpan, first); - } - - static size_t offsetOfLast() { - return offsetof(FreeList, head) + offsetof(FreeSpan, last); - } - - void* addressOfFirst() const { - return (void*)&head.first; - } - - void* addressOfLast() const { - return (void*)&head.last; - } - - void initAsEmpty() { - head.initAsEmpty(); - } - - FreeSpan* getHead() { return &head; } - void setHead(FreeSpan* span) { head = *span; } - - bool isEmpty() const { - return head.isEmpty(); - } - -#ifdef DEBUG - uintptr_t arenaAddress() const { - MOZ_ASSERT(!isEmpty()); - return head.arenaAddress(); - } -#endif - - ArenaHeader* arenaHeader() const { - MOZ_ASSERT(!isEmpty()); - return reinterpret_cast(head.arenaAddress()); - } - -#ifdef DEBUG - bool isSameNonEmptySpan(const FreeSpan& another) const { - MOZ_ASSERT(!isEmpty()); - MOZ_ASSERT(!another.isEmpty()); - return head.first == another.first && head.last == another.last; - } -#endif - MOZ_ALWAYS_INLINE TenuredCell* allocate(size_t thingSize) { - MOZ_ASSERT(thingSize % CellSize == 0); - head.checkSpan(thingSize); - uintptr_t thing = head.first; - if (thing < head.last) { - // We have two or more things in the free list head, so we can do a - // simple bump-allocate. - head.first = thing + thingSize; - } else if (MOZ_LIKELY(thing)) { - // We have one thing in the free list head. Use it, but first - // update the free list head to point to the subseqent span (which - // may be empty). - setHead(reinterpret_cast(thing)); + // Eschew the usual checks, because this might be the placeholder span. + // If this is somehow an invalid, non-empty span, checkSpan() will catch it. + Arena* arena = getArenaUnchecked(); + checkSpan(arena); + uintptr_t thing = uintptr_t(arena) + first; + if (first < last) { + // We have space for at least two more things, so do a simple bump-allocate. + first += thingSize; + } else if (MOZ_LIKELY(first)) { + // The last space points to the next free span (which may be empty). + const FreeSpan* next = nextSpan(arena); + first = next->first; + last = next->last; } else { - // The free list head is empty. - return nullptr; + return nullptr; // The span is empty. } - head.checkSpan(thingSize); + checkSpan(arena); JS_EXTRA_POISON(reinterpret_cast(thing), JS_ALLOCATED_TENURED_PATTERN, thingSize); MemProfiler::SampleTenured(reinterpret_cast(thing), thingSize); return reinterpret_cast(thing); } + + inline void checkSpan(const Arena* arena) const; + inline void checkRange(uintptr_t first, uintptr_t last, const Arena* arena) const; }; -/* Every arena has a header. */ -struct ArenaHeader +/* + * Arenas are the allocation units of the tenured heap in the GC. An arena + * is 4kiB in size and 4kiB-aligned. It starts with several header fields + * followed by some bytes of padding. The remainder of the arena is filled + * with GC things of a particular AllocKind. The padding ensures that the + * GC thing array ends exactly at the end of the arena: + * + * <----------------------------------------------> = ArenaSize bytes + * +---------------+---------+----+----+-----+----+ + * | header fields | padding | T0 | T1 | ... | Tn | + * +---------------+---------+----+----+-----+----+ + * <-------------------------> = first thing offset + */ +class Arena { - friend struct FreeLists; + static JS_FRIEND_DATA(const uint32_t) ThingSizes[]; + static JS_FRIEND_DATA(const uint32_t) FirstThingOffsets[]; + static JS_FRIEND_DATA(const uint32_t) ThingsPerArena[]; + /* + * The first span of free things in the arena. Most of these spans are + * stored as offsets in free regions of the data array, and most operations + * on FreeSpans take an Arena pointer for safety. However, the FreeSpans + * used for allocation are stored here, at the start of an Arena, and use + * their own address to grab the next span within the same Arena. + */ + FreeSpan firstFreeSpan; + + public: + /* + * The zone that this Arena is contained within, when allocated. The offset + * of this field must match the ArenaZoneOffset stored in js/HeapAPI.h, + * as is statically asserted below. + */ JS::Zone* zone; /* - * ArenaHeader::next has two purposes: when unallocated, it points to the - * next available Arena's header. When allocated, it points to the next - * arena of the same size class and compartment. + * Arena::next has two purposes: when unallocated, it points to the next + * available Arena. When allocated, it points to the next Arena in the same + * zone and with the same alloc kind. */ - ArenaHeader* next; + Arena* next; private: /* - * The first span of free things in the arena. We encode it as a - * CompactFreeSpan rather than a FreeSpan to minimize the header size. - */ - CompactFreeSpan firstFreeSpan; - - /* - * One of AllocKind constants or AllocKind::LIMIT when the arena does not - * contain any GC things and is on the list of empty arenas in the GC + * One of the AllocKind constants or AllocKind::LIMIT when the arena does + * not contain any GC things and is on the list of empty arenas in the GC * chunk. * - * We use 8 bits for the allocKind so the compiler can use byte-level memory - * instructions to access it. + * We use 8 bits for the alloc kind so the compiler can use byte-level + * memory instructions to access it. */ size_t allocKind : 8; + public: /* * When collecting we sometimes need to keep an auxillary list of arenas, - * for which we use the following fields. This happens for several reasons: + * for which we use the following fields. This happens for several reasons: * - * When recursive marking uses too much stack the marking is delayed and the - * corresponding arenas are put into a stack. To distinguish the bottom of - * the stack from the arenas not present in the stack we use the + * When recursive marking uses too much stack, the marking is delayed and + * the corresponding arenas are put into a stack. To distinguish the bottom + * of the stack from the arenas not present in the stack we use the * markOverflow flag to tag arenas on the stack. * * Delayed marking is also used for arenas that we allocate into during an * incremental GC. In this case, we intend to mark all the objects in the * arena, and it's faster to do this marking in bulk. * - * When sweeping we keep track of which arenas have been allocated since the - * end of the mark phase. This allows us to tell whether a pointer to an - * unmarked object is yet to be finalized or has already been reallocated. - * We set the allocatedDuringIncremental flag for this and clear it at the - * end of the sweep phase. + * When sweeping we keep track of which arenas have been allocated since + * the end of the mark phase. This allows us to tell whether a pointer to + * an unmarked object is yet to be finalized or has already been + * reallocated. We set the allocatedDuringIncremental flag for this and + * clear it at the end of the sweep phase. * - * To minimize the ArenaHeader size we record the next linkage as - * arenaAddress() >> ArenaShift and pack it with the allocKind field and the - * flags. + * To minimize the size of the header fields we record the next linkage as + * address() >> ArenaShift and pack it with the allocKind and the flags. */ - public: - size_t hasDelayedMarking : 1; - size_t allocatedDuringIncremental : 1; - size_t markOverflow : 1; - size_t auxNextLink : JS_BITS_PER_WORD - 8 - 1 - 1 - 1; + size_t hasDelayedMarking : 1; + size_t allocatedDuringIncremental : 1; + size_t markOverflow : 1; + size_t auxNextLink : JS_BITS_PER_WORD - 8 - 1 - 1 - 1; static_assert(ArenaShift >= 8 + 1 + 1 + 1, - "ArenaHeader::auxNextLink packing assumes that ArenaShift has enough bits to " - "cover allocKind and hasDelayedMarking."); + "Arena::auxNextLink packing assumes that ArenaShift has " + "enough bits to cover allocKind and hasDelayedMarking."); + /* + * The size of data should be |ArenaSize - offsetof(data)|, but the offset + * is not yet known to the compiler, so we do it by hand. |firstFreeSpan| + * takes up 8 bytes on 64-bit due to alignment requirements; the rest are + * obvious. This constant is stored in js/HeapAPI.h. + */ + uint8_t data[ArenaSize - ArenaHeaderSize]; + + void init(JS::Zone* zoneArg, AllocKind kind) { + MOZ_ASSERT(firstFreeSpan.isEmpty()); + MOZ_ASSERT(!zone); + MOZ_ASSERT(!allocated()); + MOZ_ASSERT(!hasDelayedMarking); + MOZ_ASSERT(!allocatedDuringIncremental); + MOZ_ASSERT(!markOverflow); + MOZ_ASSERT(!auxNextLink); + + zone = zoneArg; + allocKind = size_t(kind); + setAsFullyUnused(); + } + + // Sets |firstFreeSpan| to the Arena's entire valid range, and + // also sets the next span stored at |firstFreeSpan.last| as empty. + void setAsFullyUnused() { + AllocKind kind = getAllocKind(); + firstFreeSpan.first = firstThingOffset(kind); + firstFreeSpan.last = lastThingOffset(kind); + FreeSpan* last = firstFreeSpan.nextSpanUnchecked(this); + last->initAsEmpty(); + } + + void setAsNotAllocated() { + firstFreeSpan.initAsEmpty(); + zone = nullptr; + allocKind = size_t(AllocKind::LIMIT); + hasDelayedMarking = 0; + allocatedDuringIncremental = 0; + markOverflow = 0; + auxNextLink = 0; + } + + uintptr_t address() const { + checkAddress(); + return uintptr_t(this); + } + + inline void checkAddress() const; - inline uintptr_t address() const; inline Chunk* chunk() const; bool allocated() const { @@ -650,152 +545,178 @@ struct ArenaHeader return IsValidAllocKind(AllocKind(allocKind)); } - void init(JS::Zone* zoneArg, AllocKind kind) { - MOZ_ASSERT(!allocated()); - MOZ_ASSERT(!markOverflow); - MOZ_ASSERT(!allocatedDuringIncremental); - MOZ_ASSERT(!hasDelayedMarking); - zone = zoneArg; - - static_assert(size_t(AllocKind::LIMIT) <= 255, - "We must be able to fit the allockind into uint8_t."); - allocKind = size_t(kind); - - /* - * The firstFreeSpan is initially marked as empty (and thus the arena - * is marked as full). See allocateFromArenaInline(). - */ - firstFreeSpan.initAsEmpty(); - } - - void setAsNotAllocated() { - allocKind = size_t(AllocKind::LIMIT); - markOverflow = 0; - allocatedDuringIncremental = 0; - hasDelayedMarking = 0; - auxNextLink = 0; - } - - inline uintptr_t arenaAddress() const; - inline Arena* getArena(); - AllocKind getAllocKind() const { MOZ_ASSERT(allocated()); return AllocKind(allocKind); } - inline size_t getThingSize() const; + FreeSpan* getFirstFreeSpan() { return &firstFreeSpan; } - bool hasFreeThings() const { - return !firstFreeSpan.isEmpty(); + static size_t thingSize(AllocKind kind) { return ThingSizes[size_t(kind)]; } + static size_t thingsPerArena(AllocKind kind) { return ThingsPerArena[size_t(kind)]; } + static size_t thingsSpan(AllocKind kind) { return thingsPerArena(kind) * thingSize(kind); } + + static size_t firstThingOffset(AllocKind kind) { return FirstThingOffsets[size_t(kind)]; } + static size_t lastThingOffset(AllocKind kind) { return ArenaSize - thingSize(kind); } + + size_t getThingSize() const { return thingSize(getAllocKind()); } + size_t getThingsPerArena() const { return thingsPerArena(getAllocKind()); } + size_t getThingsSpan() const { return getThingsPerArena() * getThingSize(); } + + uintptr_t thingsStart() const { return address() + firstThingOffset(getAllocKind()); } + uintptr_t thingsEnd() const { return address() + ArenaSize; } + + bool isEmpty() const { + // Arena is empty if its first span covers the whole arena. + firstFreeSpan.checkSpan(this); + AllocKind kind = getAllocKind(); + return firstFreeSpan.first == firstThingOffset(kind) && + firstFreeSpan.last == lastThingOffset(kind); } - inline bool isEmpty() const; + bool hasFreeThings() const { return !firstFreeSpan.isEmpty(); } - void setAsFullyUsed() { - firstFreeSpan.initAsEmpty(); + size_t numFreeThings(size_t thingSize) const { + firstFreeSpan.checkSpan(this); + size_t numFree = 0; + const FreeSpan* span = &firstFreeSpan; + for (; !span->isEmpty(); span = span->nextSpan(this)) + numFree += (span->last - span->first) / thingSize + 1; + return numFree; } - inline FreeSpan getFirstFreeSpan() const; - inline void setFirstFreeSpan(const FreeSpan* span); + size_t countFreeCells() { return numFreeThings(getThingSize()); } + size_t countUsedCells() { return getThingsPerArena() - countFreeCells(); } -#ifdef DEBUG - void checkSynchronizedWithFreeList() const; -#endif + bool inFreeList(uintptr_t thing) { + uintptr_t base = address(); + const FreeSpan* span = &firstFreeSpan; + for (; !span->isEmpty(); span = span->nextSpan(this)) { + /* If the thing comes before the current span, it's not free. */ + if (thing < base + span->first) + return false; - inline ArenaHeader* getNextDelayedMarking() const; - inline void setNextDelayedMarking(ArenaHeader* aheader); - inline void unsetDelayedMarking(); - - inline ArenaHeader* getNextAllocDuringSweep() const; - inline void setNextAllocDuringSweep(ArenaHeader* aheader); - inline void unsetAllocDuringSweep(); - - inline void setNextArenaToUpdate(ArenaHeader* aheader); - inline ArenaHeader* getNextArenaToUpdateAndUnlink(); - - void unmarkAll(); - - size_t countUsedCells(); - size_t countFreeCells(); -}; -static_assert(ArenaZoneOffset == offsetof(ArenaHeader, zone), - "The hardcoded API zone offset must match the actual offset."); - -struct Arena -{ - /* - * Layout of an arena: - * An arena is 4K in size and 4K-aligned. It starts with the ArenaHeader - * descriptor followed by some pad bytes. The remainder of the arena is - * filled with the array of T things. The pad bytes ensure that the thing - * array ends exactly at the end of the arena. - * - * +-------------+-----+----+----+-----+----+ - * | ArenaHeader | pad | T0 | T1 | ... | Tn | - * +-------------+-----+----+----+-----+----+ - * - * <----------------------------------------> = ArenaSize bytes - * <-------------------> = first thing offset - */ - ArenaHeader aheader; - uint8_t data[ArenaSize - sizeof(ArenaHeader)]; - - private: - static JS_FRIEND_DATA(const uint32_t) ThingSizes[]; - static JS_FRIEND_DATA(const uint32_t) FirstThingOffsets[]; - static const uint32_t ThingsPerArena[]; - - public: - static void staticAsserts(); - - static size_t thingSize(AllocKind kind) { - return ThingSizes[size_t(kind)]; - } - - static size_t firstThingOffset(AllocKind kind) { - return FirstThingOffsets[size_t(kind)]; - } - - static size_t thingsPerArena(AllocKind kind) { - return ThingsPerArena[size_t(kind)]; - } - - static size_t thingsSpan(AllocKind kind) { - return thingsPerArena(kind) * thingSize(kind); + /* If we find it before the end of the span, it's free. */ + if (thing <= base + span->last) + return true; + } + return false; } static bool isAligned(uintptr_t thing, size_t thingSize) { /* Things ends at the arena end. */ - uintptr_t tailOffset = (ArenaSize - thing) & ArenaMask; + uintptr_t tailOffset = ArenaSize - (thing & ArenaMask); return tailOffset % thingSize == 0; } - uintptr_t address() const { - return aheader.address(); + Arena* getNextDelayedMarking() const { + MOZ_ASSERT(hasDelayedMarking); + return reinterpret_cast(auxNextLink << ArenaShift); } - uintptr_t thingsStart(AllocKind thingKind) { - return address() + firstThingOffset(thingKind); + void setNextDelayedMarking(Arena* arena) { + MOZ_ASSERT(!(uintptr_t(arena) & ArenaMask)); + MOZ_ASSERT(!auxNextLink && !hasDelayedMarking); + hasDelayedMarking = 1; + if (arena) + auxNextLink = arena->address() >> ArenaShift; } - uintptr_t thingsEnd() { - return address() + ArenaSize; + void unsetDelayedMarking() { + MOZ_ASSERT(hasDelayedMarking); + hasDelayedMarking = 0; + auxNextLink = 0; } - void setAsFullyUnused(AllocKind thingKind); + Arena* getNextAllocDuringSweep() const { + MOZ_ASSERT(allocatedDuringIncremental); + return reinterpret_cast(auxNextLink << ArenaShift); + } + + void setNextAllocDuringSweep(Arena* arena) { + MOZ_ASSERT(!(uintptr_t(arena) & ArenaMask)); + MOZ_ASSERT(!auxNextLink && !allocatedDuringIncremental); + allocatedDuringIncremental = 1; + if (arena) + auxNextLink = arena->address() >> ArenaShift; + } + + void unsetAllocDuringSweep() { + MOZ_ASSERT(allocatedDuringIncremental); + allocatedDuringIncremental = 0; + auxNextLink = 0; + } + + Arena* getNextArenaToUpdateAndUnlink() { + MOZ_ASSERT(!hasDelayedMarking && !allocatedDuringIncremental && !markOverflow); + Arena* next = reinterpret_cast(auxNextLink << ArenaShift); + auxNextLink = 0; + return next; + } + + void setNextArenaToUpdate(Arena* arena) { + MOZ_ASSERT(!(uintptr_t(arena) & ArenaMask)); + MOZ_ASSERT(!hasDelayedMarking && !allocatedDuringIncremental && !markOverflow); + MOZ_ASSERT(!auxNextLink); + auxNextLink = arena->address() >> ArenaShift; + } template size_t finalize(FreeOp* fop, AllocKind thingKind, size_t thingSize); + + static void staticAsserts(); + + void unmarkAll(); }; -static_assert(sizeof(Arena) == ArenaSize, "The hardcoded arena size must match the struct size."); +static_assert(ArenaZoneOffset == offsetof(Arena, zone), + "The hardcoded API zone offset must match the actual offset."); -inline size_t -ArenaHeader::getThingSize() const +static_assert(sizeof(Arena) == ArenaSize, "The hardcoded API header size (ArenaHeaderSize) " + "must match the actual size of the header fields."); + +inline Arena* +FreeSpan::getArena() { - MOZ_ASSERT(allocated()); - return Arena::thingSize(getAllocKind()); + Arena* arena = getArenaUnchecked(); + arena->checkAddress(); + return arena; +} + +inline void +FreeSpan::checkSpan(const Arena* arena) const +{ +#ifdef DEBUG + if (!first) { + MOZ_ASSERT(!first && !last); + return; + } + + arena->checkAddress(); + checkRange(first, last, arena); + + // If there's a following span, it must have a higher address, + // and the gap must be at least 2 * thingSize. + const FreeSpan* next = nextSpanUnchecked(arena); + if (next->first) { + checkRange(next->first, next->last, arena); + size_t thingSize = arena->getThingSize(); + MOZ_ASSERT(last + 2 * thingSize <= next->first); + } +#endif +} + +inline void +FreeSpan::checkRange(uintptr_t first, uintptr_t last, const Arena* arena) const +{ +#ifdef DEBUG + MOZ_ASSERT(arena); + MOZ_ASSERT(first <= last); + AllocKind thingKind = arena->getAllocKind(); + MOZ_ASSERT(first >= Arena::firstThingOffset(thingKind)); + MOZ_ASSERT(last <= Arena::lastThingOffset(thingKind)); + MOZ_ASSERT((last - first) % Arena::thingSize(thingKind) == 0); +#endif } /* @@ -844,8 +765,8 @@ struct ChunkInfo Chunk* prev; public: - /* Free arenas are linked together with aheader.next. */ - ArenaHeader* freeArenasHead; + /* Free arenas are linked together with arena.next. */ + Arena* freeArenasHead; #if JS_BITS_PER_WORD == 32 /* @@ -970,14 +891,14 @@ struct ChunkBitmap memset((void*)bitmap, 0, sizeof(bitmap)); } - uintptr_t* arenaBits(ArenaHeader* aheader) { + uintptr_t* arenaBits(Arena* arena) { static_assert(ArenaBitmapBits == ArenaBitmapWords * JS_BITS_PER_WORD, "We assume that the part of the bitmap corresponding to the arena " "has the exact number of words so we do not need to deal with a word " "that covers bits from two arenas."); uintptr_t* word, unused; - getMarkWordAndMask(reinterpret_cast(aheader->address()), BLACK, &word, &unused); + getMarkWordAndMask(reinterpret_cast(arena->address()), BLACK, &word, &unused); return word; } }; @@ -1048,12 +969,10 @@ struct Chunk return info.trailer.storeBuffer; } - ArenaHeader* allocateArena(JSRuntime* rt, JS::Zone* zone, AllocKind kind, - const AutoLockGC& lock); + Arena* allocateArena(JSRuntime* rt, JS::Zone* zone, AllocKind kind, const AutoLockGC& lock); - void releaseArena(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock); - void recycleArena(ArenaHeader* aheader, SortedArenaList& dest, AllocKind thingKind, - size_t thingsPerArena); + void releaseArena(JSRuntime* rt, Arena* arena, const AutoLockGC& lock); + void recycleArena(Arena* arena, SortedArenaList& dest, size_t thingsPerArena); bool decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock); void decommitAllArenasWithoutUnlocking(const AutoLockGC& lock); @@ -1067,17 +986,17 @@ struct Chunk /* Search for a decommitted arena to allocate. */ unsigned findDecommittedArenaOffset(); - ArenaHeader* fetchNextDecommittedArena(); + Arena* fetchNextDecommittedArena(); - void addArenaToFreeList(JSRuntime* rt, ArenaHeader* aheader); - void addArenaToDecommittedList(JSRuntime* rt, const ArenaHeader* aheader); + void addArenaToFreeList(JSRuntime* rt, Arena* arena); + void addArenaToDecommittedList(JSRuntime* rt, const Arena* arena); void updateChunkListAfterAlloc(JSRuntime* rt, const AutoLockGC& lock); void updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock); public: /* Unlink and return the freeArenasHead. */ - inline ArenaHeader* fetchNextFreeArena(JSRuntime* rt); + inline Arena* fetchNextFreeArena(JSRuntime* rt); }; static_assert(sizeof(Chunk) == ChunkSize, @@ -1141,133 +1060,27 @@ class HeapUsage } }; -inline uintptr_t -ArenaHeader::address() const +inline void +Arena::checkAddress() const { - uintptr_t addr = reinterpret_cast(this); + mozilla::DebugOnly addr = uintptr_t(this); MOZ_ASSERT(addr); MOZ_ASSERT(!(addr & ArenaMask)); MOZ_ASSERT(Chunk::withinValidRange(addr)); - return addr; } inline Chunk* -ArenaHeader::chunk() const +Arena::chunk() const { return Chunk::fromAddress(address()); } -inline uintptr_t -ArenaHeader::arenaAddress() const -{ - return address(); -} - -inline Arena* -ArenaHeader::getArena() -{ - return reinterpret_cast(arenaAddress()); -} - -inline bool -ArenaHeader::isEmpty() const -{ - /* Arena is empty if its first span covers the whole arena. */ - MOZ_ASSERT(allocated()); - size_t firstThingOffset = Arena::firstThingOffset(getAllocKind()); - size_t lastThingOffset = ArenaSize - getThingSize(); - const CompactFreeSpan emptyCompactSpan(firstThingOffset, lastThingOffset); - return firstFreeSpan == emptyCompactSpan; -} - -FreeSpan -ArenaHeader::getFirstFreeSpan() const -{ -#ifdef DEBUG - checkSynchronizedWithFreeList(); -#endif - return firstFreeSpan.decompact(arenaAddress()); -} - -void -ArenaHeader::setFirstFreeSpan(const FreeSpan* span) -{ - MOZ_ASSERT_IF(!span->isEmpty(), span->isWithinArena(arenaAddress())); - firstFreeSpan.compact(*span); -} - -inline ArenaHeader* -ArenaHeader::getNextDelayedMarking() const -{ - MOZ_ASSERT(hasDelayedMarking); - return &reinterpret_cast(auxNextLink << ArenaShift)->aheader; -} - -inline void -ArenaHeader::setNextDelayedMarking(ArenaHeader* aheader) -{ - MOZ_ASSERT(!(uintptr_t(aheader) & ArenaMask)); - MOZ_ASSERT(!auxNextLink && !hasDelayedMarking); - hasDelayedMarking = 1; - if (aheader) - auxNextLink = aheader->arenaAddress() >> ArenaShift; -} - -inline void -ArenaHeader::unsetDelayedMarking() -{ - MOZ_ASSERT(hasDelayedMarking); - hasDelayedMarking = 0; - auxNextLink = 0; -} - -inline ArenaHeader* -ArenaHeader::getNextAllocDuringSweep() const -{ - MOZ_ASSERT(allocatedDuringIncremental); - return &reinterpret_cast(auxNextLink << ArenaShift)->aheader; -} - -inline void -ArenaHeader::setNextAllocDuringSweep(ArenaHeader* aheader) -{ - MOZ_ASSERT(!auxNextLink && !allocatedDuringIncremental); - allocatedDuringIncremental = 1; - if (aheader) - auxNextLink = aheader->arenaAddress() >> ArenaShift; -} - -inline void -ArenaHeader::unsetAllocDuringSweep() -{ - MOZ_ASSERT(allocatedDuringIncremental); - allocatedDuringIncremental = 0; - auxNextLink = 0; -} - -inline ArenaHeader* -ArenaHeader::getNextArenaToUpdateAndUnlink() -{ - MOZ_ASSERT(!hasDelayedMarking && !allocatedDuringIncremental && !markOverflow); - ArenaHeader* next = &reinterpret_cast(auxNextLink << ArenaShift)->aheader; - auxNextLink = 0; - return next; -} - -inline void -ArenaHeader::setNextArenaToUpdate(ArenaHeader* aheader) -{ - MOZ_ASSERT(!hasDelayedMarking && !allocatedDuringIncremental && !markOverflow); - MOZ_ASSERT(!auxNextLink); - auxNextLink = aheader->arenaAddress() >> ArenaShift; -} - static void AssertValidColor(const TenuredCell* thing, uint32_t color) { #ifdef DEBUG - ArenaHeader* aheader = thing->arenaHeader(); - MOZ_ASSERT(color < aheader->getThingSize() / CellSize); + Arena* arena = thing->arena(); + MOZ_ASSERT(color < arena->getThingSize() / CellSize); #endif } @@ -1342,17 +1155,11 @@ Cell::getTraceKind() const } inline bool -InFreeList(ArenaHeader* aheader, void* thing) +InFreeList(Arena* arena, void* thing) { - if (!aheader->hasFreeThings()) - return false; - - FreeSpan firstSpan(aheader->getFirstFreeSpan()); uintptr_t addr = reinterpret_cast(thing); - - MOZ_ASSERT(Arena::isAligned(addr, aheader->getThingSize())); - - return firstSpan.inFreeList(addr); + MOZ_ASSERT(Arena::isAligned(addr, arena->getThingSize())); + return arena->inFreeList(addr); } /* static */ MOZ_ALWAYS_INLINE bool @@ -1377,7 +1184,7 @@ TenuredCell::fromPointer(const void* ptr) bool TenuredCell::isMarked(uint32_t color /* = BLACK */) const { - MOZ_ASSERT(arenaHeader()->allocated()); + MOZ_ASSERT(arena()->allocated()); AssertValidColor(this, color); return chunk()->bitmap.isMarked(this, color); } @@ -1405,19 +1212,19 @@ TenuredCell::copyMarkBitsFrom(const TenuredCell* src) bitmap.copyMarkBit(this, src, GRAY); } -inline ArenaHeader* -TenuredCell::arenaHeader() const +inline Arena* +TenuredCell::arena() const { MOZ_ASSERT(isTenured()); uintptr_t addr = address(); addr &= ~ArenaMask; - return reinterpret_cast(addr); + return reinterpret_cast(addr); } AllocKind TenuredCell::getAllocKind() const { - return arenaHeader()->getAllocKind(); + return arena()->getAllocKind(); } JS::TraceKind @@ -1429,7 +1236,7 @@ TenuredCell::getTraceKind() const JS::Zone* TenuredCell::zone() const { - JS::Zone* zone = arenaHeader()->zone; + JS::Zone* zone = arena()->zone; MOZ_ASSERT(CurrentThreadCanAccessZone(zone)); return zone; } @@ -1437,13 +1244,13 @@ TenuredCell::zone() const JS::Zone* TenuredCell::zoneFromAnyThread() const { - return arenaHeader()->zone; + return arena()->zone; } bool TenuredCell::isInsideZone(JS::Zone* zone) const { - return zone == arenaHeader()->zone; + return zone == arena()->zone; } /* static */ MOZ_ALWAYS_INLINE void @@ -1512,7 +1319,7 @@ Cell::isAligned() const bool TenuredCell::isAligned() const { - return Arena::isAligned(address(), arenaHeader()->getThingSize()); + return Arena::isAligned(address(), arena()->getThingSize()); } #endif diff --git a/js/src/gc/Iteration.cpp b/js/src/gc/Iteration.cpp index 45168fd432..5b060996d6 100644 --- a/js/src/gc/Iteration.cpp +++ b/js/src/gc/Iteration.cpp @@ -43,9 +43,9 @@ IterateCompartmentsArenasCells(JSRuntime* rt, Zone* zone, void* data, size_t thingSize = Arena::thingSize(thingKind); for (ArenaIter aiter(zone, thingKind); !aiter.done(); aiter.next()) { - ArenaHeader* aheader = aiter.get(); - (*arenaCallback)(rt, data, aheader->getArena(), traceKind, thingSize); - for (ArenaCellIterUnderGC iter(aheader); !iter.done(); iter.next()) + Arena* arena = aiter.get(); + (*arenaCallback)(rt, data, arena, traceKind, thingSize); + for (ArenaCellIterUnderGC iter(arena); !iter.done(); iter.next()) (*cellCallback)(rt, data, iter.getCell(), traceKind, thingSize); } } diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 86bbdc0e05..da820d0ac3 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -240,13 +240,11 @@ js::CheckTracedThing(JSTracer* trc, T* thing) /* * Try to assert that the thing is allocated. This is complicated by the * fact that allocated things may still contain the poison pattern if that - * part has not been overwritten, and that the free span list head in the - * ArenaHeader may not be synced with the real one in ArenaLists. Also, - * background sweeping may be running and concurrently modifiying the free - * list. + * part has not been overwritten. Also, background sweeping may be running + * and concurrently modifiying the free list. */ MOZ_ASSERT_IF(IsThingPoisoned(thing) && rt->isHeapBusy() && !rt->gc.isBackgroundSweeping(), - !InFreeList(thing->asTenured().arenaHeader(), thing)); + !InFreeList(thing->asTenured().arena(), thing)); #endif } @@ -740,7 +738,7 @@ MustSkipMarking(JSObject* obj) return true; // Don't mark things outside a zone if we are in a per-zone GC. It is - // faster to check our own arena header, which we can do since we know that + // faster to check our own arena, which we can do since we know that // the object is tenured. return !TenuredCell::fromPointer(obj)->zone()->isGCMarking(); } @@ -1830,13 +1828,13 @@ GCMarker::reset() MOZ_ASSERT(isMarkStackEmpty()); while (unmarkedArenaStackTop) { - ArenaHeader* aheader = unmarkedArenaStackTop; - MOZ_ASSERT(aheader->hasDelayedMarking); + Arena* arena = unmarkedArenaStackTop; + MOZ_ASSERT(arena->hasDelayedMarking); MOZ_ASSERT(markLaterArenas); - unmarkedArenaStackTop = aheader->getNextDelayedMarking(); - aheader->unsetDelayedMarking(); - aheader->markOverflow = 0; - aheader->allocatedDuringIncremental = 0; + unmarkedArenaStackTop = arena->getNextDelayedMarking(); + arena->unsetDelayedMarking(); + arena->markOverflow = 0; + arena->allocatedDuringIncremental = 0; markLaterArenas--; } MOZ_ASSERT(isDrained()); @@ -1881,27 +1879,27 @@ GCMarker::leaveWeakMarkingMode() } void -GCMarker::markDelayedChildren(ArenaHeader* aheader) +GCMarker::markDelayedChildren(Arena* arena) { - if (aheader->markOverflow) { - bool always = aheader->allocatedDuringIncremental; - aheader->markOverflow = 0; + if (arena->markOverflow) { + bool always = arena->allocatedDuringIncremental; + arena->markOverflow = 0; - for (ArenaCellIterUnderGC i(aheader); !i.done(); i.next()) { + for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) { TenuredCell* t = i.getCell(); if (always || t->isMarked()) { t->markIfUnmarked(); - js::TraceChildren(this, t, MapAllocToTraceKind(aheader->getAllocKind())); + js::TraceChildren(this, t, MapAllocToTraceKind(arena->getAllocKind())); } } } else { - MOZ_ASSERT(aheader->allocatedDuringIncremental); - PushArena(this, aheader); + MOZ_ASSERT(arena->allocatedDuringIncremental); + PushArena(this, arena); } - aheader->allocatedDuringIncremental = 0; + arena->allocatedDuringIncremental = 0; /* * Note that during an incremental GC we may still be allocating into - * aheader. However, prepareForIncrementalGC sets the + * the arena. However, prepareForIncrementalGC sets the * allocatedDuringIncremental flag if we continue marking. */ } @@ -1919,13 +1917,13 @@ GCMarker::markDelayedChildren(SliceBudget& budget) * marking of its things. For that we pop arena from the stack and * clear its hasDelayedMarking flag before we begin the marking. */ - ArenaHeader* aheader = unmarkedArenaStackTop; - MOZ_ASSERT(aheader->hasDelayedMarking); + Arena* arena = unmarkedArenaStackTop; + MOZ_ASSERT(arena->hasDelayedMarking); MOZ_ASSERT(markLaterArenas); - unmarkedArenaStackTop = aheader->getNextDelayedMarking(); - aheader->unsetDelayedMarking(); + unmarkedArenaStackTop = arena->getNextDelayedMarking(); + arena->unsetDelayedMarking(); markLaterArenas--; - markDelayedChildren(aheader); + markDelayedChildren(arena); budget.step(150); if (budget.isOverBudget()) @@ -1938,22 +1936,23 @@ GCMarker::markDelayedChildren(SliceBudget& budget) template static void -PushArenaTyped(GCMarker* gcmarker, ArenaHeader* aheader) +PushArenaTyped(GCMarker* gcmarker, Arena* arena) { - for (ArenaCellIterUnderGC i(aheader); !i.done(); i.next()) + for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) gcmarker->traverse(i.get()); } struct PushArenaFunctor { - template void operator()(GCMarker* gcmarker, ArenaHeader* aheader) { - PushArenaTyped(gcmarker, aheader); + template void operator()(GCMarker* gcmarker, Arena* arena) { + PushArenaTyped(gcmarker, arena); } }; void -gc::PushArena(GCMarker* gcmarker, ArenaHeader* aheader) +gc::PushArena(GCMarker* gcmarker, Arena* arena) { - DispatchTraceKindTyped(PushArenaFunctor(), MapAllocToTraceKind(aheader->getAllocKind()), gcmarker, aheader); + DispatchTraceKindTyped(PushArenaFunctor(), + MapAllocToTraceKind(arena->getAllocKind()), gcmarker, arena); } #ifdef DEBUG @@ -2427,7 +2426,7 @@ js::gc::IsAboutToBeFinalizedDuringSweep(TenuredCell& tenured) MOZ_ASSERT(!IsInsideNursery(&tenured)); MOZ_ASSERT(!tenured.runtimeFromAnyThread()->isHeapMinorCollecting()); MOZ_ASSERT(tenured.zoneFromAnyThread()->isGCSweeping()); - if (tenured.arenaHeader()->allocatedDuringIncremental) + if (tenured.arena()->allocatedDuringIncremental) return false; return !tenured.isMarked(); } diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index ea8d32982f..8fdb354d08 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -32,7 +32,7 @@ class NativeObject; class ObjectGroup; class WeakMapBase; namespace gc { -struct ArenaHeader; +class Arena; } // namespace gc namespace jit { class JitCode; @@ -214,9 +214,9 @@ class GCMarker : public JSTracer linearWeakMarkingDisabled_ = true; } - void delayMarkingArena(gc::ArenaHeader* aheader); + void delayMarkingArena(gc::Arena* arena); void delayMarkingChildren(const void* thing); - void markDelayedChildren(gc::ArenaHeader* aheader); + void markDelayedChildren(gc::Arena* arena); bool markDelayedChildren(SliceBudget& budget); bool hasDelayedChildren() const { return !!unmarkedArenaStackTop; @@ -283,7 +283,7 @@ class GCMarker : public JSTracer void eagerlyMarkChildren(Shape* shape); void lazilyMarkChildren(ObjectGroup* group); - // We may not have concrete types yet, so this has to be out of the header. + // We may not have concrete types yet, so this has to be outside the header. template void dispatchToTraceChildren(T* thing); @@ -331,7 +331,7 @@ class GCMarker : public JSTracer uint32_t color; /* Pointer to the top of the stack of arenas we are delaying marking on. */ - js::gc::ArenaHeader* unmarkedArenaStackTop; + js::gc::Arena* unmarkedArenaStackTop; /* * If the weakKeys table OOMs, disable the linear algorithm and fall back @@ -364,7 +364,7 @@ namespace gc { /*** Special Cases ***/ void -PushArena(GCMarker* gcmarker, ArenaHeader* aheader); +PushArena(GCMarker* gcmarker, Arena* arena); /*** Liveness ***/ diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index fc0e14b2dc..33002dfc91 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -250,7 +250,7 @@ oom: static bool IsMarkedOrAllocated(TenuredCell* cell) { - return cell->isMarked() || cell->arenaHeader()->allocatedDuringIncremental; + return cell->isMarked() || cell->arena()->allocatedDuringIncremental; } struct CheckEdgeTracer : public JS::CallbackTracer { diff --git a/js/src/jit-test/tests/gc/bug-1137341.js b/js/src/jit-test/tests/gc/bug-1137341.js index 41e7e6bede..ec172aee33 100644 --- a/js/src/jit-test/tests/gc/bug-1137341.js +++ b/js/src/jit-test/tests/gc/bug-1137341.js @@ -1,6 +1,9 @@ if (helperThreadCount() == 0) quit(); +gczeal(0); +gc(); + schedulegc(this); startgc(0, "shrinking"); var g = newGlobal(); diff --git a/js/src/jit-test/tests/gc/bug-1138390.js b/js/src/jit-test/tests/gc/bug-1138390.js index b0310e278a..bc9faf49b5 100644 --- a/js/src/jit-test/tests/gc/bug-1138390.js +++ b/js/src/jit-test/tests/gc/bug-1138390.js @@ -8,8 +8,8 @@ if (!("startgc" in this && if (helperThreadCount() == 0) quit(); -if ("gczeal" in this) - gczeal(0); +gczeal(0); +gc(); // Start an incremental GC that includes the atoms zone startgc(0); diff --git a/js/src/jit-test/tests/gc/bug-939499.js b/js/src/jit-test/tests/gc/bug-939499.js index 9b97e71637..b0f683b584 100644 --- a/js/src/jit-test/tests/gc/bug-939499.js +++ b/js/src/jit-test/tests/gc/bug-939499.js @@ -1,2 +1,4 @@ +gczeal(0); +gc(); verifyprebarriers(); gcparam('markStackLimit', 5); diff --git a/js/src/jit-test/tests/gc/incremental-AccessorShape-barrier.js b/js/src/jit-test/tests/gc/incremental-AccessorShape-barrier.js index 9dde938ecd..0055e2c51f 100644 --- a/js/src/jit-test/tests/gc/incremental-AccessorShape-barrier.js +++ b/js/src/jit-test/tests/gc/incremental-AccessorShape-barrier.js @@ -1,3 +1,6 @@ +gczeal(0); +gc(); + var o = {}; function foo() { var i = 0; diff --git a/js/src/jit-test/tests/gc/incremental-abort.js b/js/src/jit-test/tests/gc/incremental-abort.js index b649c41caf..a4bead260a 100644 --- a/js/src/jit-test/tests/gc/incremental-abort.js +++ b/js/src/jit-test/tests/gc/incremental-abort.js @@ -3,6 +3,9 @@ if (!("gcstate" in this && "gczeal" in this && "abortgc" in this)) quit(); +gczeal(0); +gc(); + function testAbort(zoneCount, objectCount, sliceCount, abortState) { // Allocate objectCount objects in zoneCount zones and run a incremental diff --git a/js/src/jit-test/tests/gc/incremental-state.js b/js/src/jit-test/tests/gc/incremental-state.js index 89e4b63c8b..5c167971c1 100644 --- a/js/src/jit-test/tests/gc/incremental-state.js +++ b/js/src/jit-test/tests/gc/incremental-state.js @@ -1,48 +1,58 @@ -/* - * Test expected state changes during collection. - */ +// Test expected state changes during collection. +if (!("gcstate" in this)) + quit(); -if ("gcstate" in this) { - assertEq(gcstate(), "none"); +gczeal(0); - /* Non-incremental GC. */ - gc(); - assertEq(gcstate(), "none"); +// Non-incremental GC. +gc(); +assertEq(gcstate(), "none"); - /* Incremental GC in one slice. */ - gcslice(1000000); - assertEq(gcstate(), "none"); +// Incremental GC in minimal slice. Note that finalization always uses zero- +// sized slices while background finalization is on-going, so we need to loop. +gcslice(1000000); +while (gcstate() == "finalize") { gcslice(1); } +assertEq(gcstate(), "none"); - /* - * Incremental GC in multiple slices: if marking takes more than one slice, - * we yield before we start sweeping. - */ - gczeal(0); - gcslice(1); - assertEq(gcstate(), "mark"); - gcslice(1000000); - assertEq(gcstate(), "mark"); - gcslice(1000000); - assertEq(gcstate(), "none"); +// Incremental GC in multiple slices: if marking takes more than one slice, +// we yield before we start sweeping. +gczeal(0); +gcslice(1); +assertEq(gcstate(), "mark"); +gcslice(1000000); +assertEq(gcstate(), "mark"); +gcslice(1000000); +while (gcstate() == "finalize") { gcslice(1); } +assertEq(gcstate(), "none"); - /* Zeal mode 8: Incremental GC in two slices: 1) mark roots 2) finish collection. */ - gczeal(8); - gcslice(1); - assertEq(gcstate(), "mark"); - gcslice(1); - assertEq(gcstate(), "none"); +// Zeal mode 8: Incremental GC in two main slices: +// 1) mark roots +// 2) mark and sweep +// *) finalize. +gczeal(8); +gcslice(1); +assertEq(gcstate(), "mark"); +gcslice(1); +while (gcstate() == "finalize") { gcslice(1); } +assertEq(gcstate(), "none"); - /* Zeal mode 9: Incremental GC in two slices: 1) mark all 2) new marking and finish. */ - gczeal(9); - gcslice(1); - assertEq(gcstate(), "mark"); - gcslice(1); - assertEq(gcstate(), "none"); +// Zeal mode 9: Incremental GC in two main slices: +// 1) mark roots and marking +// 2) new marking and sweeping +// *) finalize. +gczeal(9); +gcslice(1); +assertEq(gcstate(), "mark"); +gcslice(1); +while (gcstate() == "finalize") { gcslice(1); } +assertEq(gcstate(), "none"); - /* Zeal mode 10: Incremental GC in multiple slices (always yeilds before sweeping). */ - gczeal(10); - gcslice(1000000); - assertEq(gcstate(), "sweep"); - gcslice(1000000); - assertEq(gcstate(), "none"); -} +// Zeal mode 10: Incremental GC in multiple slices (always yeilds before +// sweeping). This test uses long slices to prove that this zeal mode yields +// in sweeping, where normal IGC (above) does not. +gczeal(10); +gcslice(1000000); +assertEq(gcstate(), "sweep"); +gcslice(1000000); +while (gcstate() == "finalize") { gcslice(1); } +assertEq(gcstate(), "none"); diff --git a/js/src/jit/CompileWrappers.cpp b/js/src/jit/CompileWrappers.cpp index 202529e60d..9de0aa0924 100644 --- a/js/src/jit/CompileWrappers.cpp +++ b/js/src/jit/CompileWrappers.cpp @@ -215,15 +215,9 @@ CompileZone::addressOfNeedsIncrementalBarrier() } const void* -CompileZone::addressOfFreeListFirst(gc::AllocKind allocKind) +CompileZone::addressOfFreeList(gc::AllocKind allocKind) { - return zone()->arenas.getFreeList(allocKind)->addressOfFirst(); -} - -const void* -CompileZone::addressOfFreeListLast(gc::AllocKind allocKind) -{ - return zone()->arenas.getFreeList(allocKind)->addressOfLast(); + return zone()->arenas.addressOfFreeList(allocKind); } JSCompartment* diff --git a/js/src/jit/CompileWrappers.h b/js/src/jit/CompileWrappers.h index b0af936c62..af80945e63 100644 --- a/js/src/jit/CompileWrappers.h +++ b/js/src/jit/CompileWrappers.h @@ -97,9 +97,7 @@ class CompileZone const void* addressOfNeedsIncrementalBarrier(); - // arenas.getFreeList(allocKind) - const void* addressOfFreeListFirst(gc::AllocKind allocKind); - const void* addressOfFreeListLast(gc::AllocKind allocKind); + const void* addressOfFreeList(gc::AllocKind allocKind); }; class JitCompartment; diff --git a/js/src/jit/JitcodeMap.cpp b/js/src/jit/JitcodeMap.cpp index 1ef357ab28..a580e6bcc1 100644 --- a/js/src/jit/JitcodeMap.cpp +++ b/js/src/jit/JitcodeMap.cpp @@ -870,7 +870,7 @@ bool JitcodeGlobalEntry::BaseEntry::isJitcodeMarkedFromAnyThread() { return IsMarkedUnbarriered(&jitcode_) || - jitcode_->arenaHeader()->allocatedDuringIncremental; + jitcode_->arena()->allocatedDuringIncremental; } bool @@ -900,7 +900,7 @@ bool JitcodeGlobalEntry::BaselineEntry::isMarkedFromAnyThread() { return IsMarkedUnbarriered(&script_) || - script_->arenaHeader()->allocatedDuringIncremental; + script_->arena()->allocatedDuringIncremental; } template @@ -968,7 +968,7 @@ JitcodeGlobalEntry::IonEntry::isMarkedFromAnyThread() { for (unsigned i = 0; i < numScripts(); i++) { if (!IsMarkedUnbarriered(&sizedScriptList()->pairs[i].script) && - !sizedScriptList()->pairs[i].script->arenaHeader()->allocatedDuringIncremental) + !sizedScriptList()->pairs[i].script->arena()->allocatedDuringIncremental) { return false; } diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 38400c151e..a73dcfe9ab 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -777,7 +777,7 @@ MacroAssembler::nurseryAllocate(Register result, Register temp, gc::AllocKind al } } -// Inlined version of FreeList::allocate. This does not fill in slots_. +// Inlined version of FreeSpan::allocate. This does not fill in slots_. void MacroAssembler::freeListAllocate(Register result, Register temp, gc::AllocKind allocKind, Label* fail) { @@ -787,24 +787,33 @@ MacroAssembler::freeListAllocate(Register result, Register temp, gc::AllocKind a Label fallback; Label success; - // Load FreeList::head::first of |zone|'s freeLists for |allocKind|. If - // there is no room remaining in the span, fall back to get the next one. - loadPtr(AbsoluteAddress(zone->addressOfFreeListFirst(allocKind)), result); - branchPtr(Assembler::BelowOrEqual, AbsoluteAddress(zone->addressOfFreeListLast(allocKind)), result, &fallback); - computeEffectiveAddress(Address(result, thingSize), temp); - storePtr(temp, AbsoluteAddress(zone->addressOfFreeListFirst(allocKind))); + // Load the first and last offsets of |zone|'s free list for |allocKind|. + // If there is no room remaining in the span, fall back to get the next one. + loadPtr(AbsoluteAddress(zone->addressOfFreeList(allocKind)), temp); + load16ZeroExtend(Address(temp, js::gc::FreeSpan::offsetOfFirst()), result); + load16ZeroExtend(Address(temp, js::gc::FreeSpan::offsetOfLast()), temp); + branch32(Assembler::AboveOrEqual, result, temp, &fallback); + + // Bump the offset for the next allocation. + add32(Imm32(thingSize), result); + loadPtr(AbsoluteAddress(zone->addressOfFreeList(allocKind)), temp); + store16(result, Address(temp, js::gc::FreeSpan::offsetOfFirst())); + sub32(Imm32(thingSize), result); + addPtr(temp, result); // Turn the offset into a pointer. jump(&success); bind(&fallback); - // If there are no FreeSpans left, we bail to finish the allocation. The - // interpreter will call |refillFreeLists|, setting up a new FreeList so - // that we can continue allocating in the jit. - branchPtr(Assembler::Equal, result, ImmPtr(0), fail); - // Point the free list head at the subsequent span (which may be empty). - loadPtr(Address(result, js::gc::FreeSpan::offsetOfFirst()), temp); - storePtr(temp, AbsoluteAddress(zone->addressOfFreeListFirst(allocKind))); - loadPtr(Address(result, js::gc::FreeSpan::offsetOfLast()), temp); - storePtr(temp, AbsoluteAddress(zone->addressOfFreeListLast(allocKind))); + // If there are no free spans left, we bail to finish the allocation. The + // interpreter will call the GC allocator to set up a new arena to allocate + // from, after which we can resume allocating in the jit. + branchTest32(Assembler::Zero, result, result, fail); + loadPtr(AbsoluteAddress(zone->addressOfFreeList(allocKind)), temp); + addPtr(temp, result); // Turn the offset into a pointer. + Push(result); + // Update the free list to point to the next span (which may be empty). + load32(Address(result, 0), result); + store32(result, Address(temp, js::gc::FreeSpan::offsetOfFirst())); + Pop(result); bind(&success); } diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index f603b49cd6..45219dbb67 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -44,6 +44,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared public: using MacroAssemblerX86Shared::load32; using MacroAssemblerX86Shared::store32; + using MacroAssemblerX86Shared::store16; MacroAssemblerX64() { @@ -654,6 +655,15 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared store32(src, Address(scratch, 0x0)); } } + void store16(Register src, AbsoluteAddress address) { + if (X86Encoding::IsAddressImmediate(address.addr)) { + movw(src, Operand(address)); + } else { + ScratchRegisterScope scratch(asMasm()); + mov(ImmPtr(address.addr), scratch); + store16(src, Address(scratch, 0x0)); + } + } void store64(Register64 src, Address address) { movq(src.reg, Operand(address)); } diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index 8da217010a..91f5e6e0a3 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -52,6 +52,7 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared public: using MacroAssemblerX86Shared::load32; using MacroAssemblerX86Shared::store32; + using MacroAssemblerX86Shared::store16; using MacroAssemblerX86Shared::call; MacroAssemblerX86() @@ -650,6 +651,9 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared void store32(Register src, AbsoluteAddress address) { movl(src, Operand(address)); } + void store16(Register src, AbsoluteAddress address) { + movw(src, Operand(address)); + } void store64(Register64 src, Address address) { movl(src.low, Operand(address)); movl(src.high, Operand(Address(address.base, address.offset + 4))); diff --git a/js/src/jsapi-tests/testGCFinalizeCallback.cpp b/js/src/jsapi-tests/testGCFinalizeCallback.cpp index a89a081246..dce53dfcb0 100644 --- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp +++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp @@ -25,6 +25,10 @@ BEGIN_TEST(testGCFinalizeCallback) FinalizeCalls = 0; JS::PrepareForFullGC(rt); JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000); + while (rt->gc.isIncrementalGCInProgress()) { + JS::PrepareForFullGC(rt); + JS::IncrementalGCSlice(rt, JS::gcreason::API, 1000000); + } CHECK(!rt->gc.isIncrementalGCInProgress()); CHECK(rt->gc.isFullGc()); CHECK(checkMultipleGroups()); @@ -62,6 +66,10 @@ BEGIN_TEST(testGCFinalizeCallback) FinalizeCalls = 0; JS::PrepareZoneForGC(global1->zone()); JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000); + while (rt->gc.isIncrementalGCInProgress()) { + JS::PrepareZoneForGC(global1->zone()); + JS::IncrementalGCSlice(rt, JS::gcreason::API, 1000000); + } CHECK(!rt->gc.isIncrementalGCInProgress()); CHECK(!rt->gc.isFullGc()); CHECK(checkSingleGroup()); @@ -74,6 +82,12 @@ BEGIN_TEST(testGCFinalizeCallback) JS::PrepareZoneForGC(global2->zone()); JS::PrepareZoneForGC(global3->zone()); JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000); + while (rt->gc.isIncrementalGCInProgress()) { + JS::PrepareZoneForGC(global1->zone()); + JS::PrepareZoneForGC(global2->zone()); + JS::PrepareZoneForGC(global3->zone()); + JS::IncrementalGCSlice(rt, JS::gcreason::API, 1000000); + } CHECK(!rt->gc.isIncrementalGCInProgress()); CHECK(!rt->gc.isFullGc()); CHECK(checkMultipleGroups()); @@ -95,6 +109,8 @@ BEGIN_TEST(testGCFinalizeCallback) JS::RootedObject global4(cx, createTestGlobal()); budget = js::SliceBudget(js::WorkBudget(1)); rt->gc.debugGCSlice(budget); + while (rt->gc.isIncrementalGCInProgress()) + rt->gc.debugGCSlice(budget); CHECK(!rt->gc.isIncrementalGCInProgress()); CHECK(!rt->gc.isFullGc()); CHECK(checkMultipleGroups()); diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp index 4a32a7ad1b..c12c240235 100644 --- a/js/src/jsapi-tests/testWeakMap.cpp +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -99,7 +99,8 @@ BEGIN_TEST(testWeakMap_keyDelegates) CHECK(newCCW(map, delegateRoot)); js::SliceBudget budget(js::WorkBudget(1000000)); rt->gc.startDebugGC(GC_NORMAL, budget); - CHECK(!JS::IsIncrementalGCInProgress(rt)); + while (JS::IsIncrementalGCInProgress(rt)) + rt->gc.debugGCSlice(budget); #ifdef DEBUG CHECK(map->zone()->lastZoneGroupIndex() < delegateRoot->zone()->lastZoneGroupIndex()); #endif @@ -114,7 +115,8 @@ BEGIN_TEST(testWeakMap_keyDelegates) CHECK(newCCW(map, delegateRoot)); budget = js::SliceBudget(js::WorkBudget(100000)); rt->gc.startDebugGC(GC_NORMAL, budget); - CHECK(!JS::IsIncrementalGCInProgress(rt)); + while (JS::IsIncrementalGCInProgress(rt)) + rt->gc.debugGCSlice(budget); CHECK(checkSize(map, 1)); /* diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 6e5f2f3fa6..0a9a733bf5 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -883,7 +883,7 @@ JSCompartment::clearObjectMetadata() } void -JSCompartment::setNewObjectMetadata(JSContext* cx, JSObject* obj) +JSCompartment::setNewObjectMetadata(JSContext* cx, HandleObject obj) { assertSameCompartment(cx, this, obj); @@ -1157,13 +1157,13 @@ AutoSetNewObjectMetadata::~AutoSetNewObjectMetadata() return; if (!cx_->isExceptionPending() && cx_->compartment()->hasObjectPendingMetadata()) { - JSObject* obj = cx_->compartment()->objectMetadataState.as(); + RootedObject obj(cx_, cx_->compartment()->objectMetadataState.as()); // Make sure to restore the previous state before setting the object's // metadata. SetNewObjectMetadata asserts that the state is not // PendingMetadata in order to ensure that metadata callbacks are called // in order. cx_->compartment()->objectMetadataState = prevState_; - SetNewObjectMetadata(cx_, obj); + obj = SetNewObjectMetadata(cx_, obj); } else { cx_->compartment()->objectMetadataState = prevState_; } diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index f27586287c..a37561868d 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -588,7 +588,7 @@ struct JSCompartment void forgetObjectMetadataCallback() { objectMetadataCallback = nullptr; } - void setNewObjectMetadata(JSContext* cx, JSObject* obj); + void setNewObjectMetadata(JSContext* cx, JS::HandleObject obj); void clearObjectMetadata(); const void* addressOfMetadataCallback() const { return &objectMetadataCallback; diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index e579d8e34f..12f262d357 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -999,7 +999,7 @@ DumpHeapVisitArena(JSRuntime* rt, void* data, gc::Arena* arena, { DumpHeapTracer* dtrc = static_cast(data); fprintf(dtrc->output, "# arena allockind=%u size=%u\n", - unsigned(arena->aheader.getAllocKind()), unsigned(thingSize)); + unsigned(arena->getAllocKind()), unsigned(thingSize)); } static void diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index d34e402239..e4b3fef245 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -2633,7 +2633,7 @@ class MOZ_RAII JS_FRIEND_API(AutoCTypesActivityCallback) { }; typedef JSObject* -(* ObjectMetadataCallback)(JSContext* cx, JSObject* obj); +(* ObjectMetadataCallback)(JSContext* cx, JS::HandleObject obj); /** * Specify a callback to invoke when creating each JS object in the current diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index b3d89927ca..f22a59477a 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -302,10 +302,12 @@ const uint32_t Arena::ThingSizes[] = CHECK_THING_SIZE( sizeof(jit::JitCode), /* AllocKind::JITCODE */ ); +FreeSpan ArenaLists::placeholder; + #undef CHECK_THING_SIZE_INNER #undef CHECK_THING_SIZE -#define OFFSET(type) uint32_t(sizeof(ArenaHeader) + (ArenaSize - sizeof(ArenaHeader)) % sizeof(type)) +#define OFFSET(type) uint32_t(ArenaHeaderSize + (ArenaSize - ArenaHeaderSize) % sizeof(type)) const uint32_t Arena::FirstThingOffsets[] = { OFFSET(JSFunction), /* AllocKind::FUNCTION */ @@ -337,7 +339,7 @@ const uint32_t Arena::FirstThingOffsets[] = { #undef OFFSET -#define COUNT(type) uint32_t((ArenaSize - sizeof(ArenaHeader)) / sizeof(type)) +#define COUNT(type) uint32_t((ArenaSize - ArenaHeaderSize) / sizeof(type)) const uint32_t Arena::ThingsPerArena[] = { COUNT(JSFunction), /* AllocKind::FUNCTION */ @@ -449,41 +451,8 @@ ArenaCellIterImpl::get() const return reinterpret_cast(getCell()); } -#ifdef DEBUG void -ArenaHeader::checkSynchronizedWithFreeList() const -{ - /* - * Do not allow to access the free list when its real head is still stored - * in FreeLists and is not synchronized with this one. - */ - MOZ_ASSERT(allocated()); - - /* - * We can be called from the background finalization thread when the free - * list in the zone can mutate at any moment. We cannot do any - * checks in this case. - */ - if (IsBackgroundFinalized(getAllocKind()) && zone->runtimeFromAnyThread()->gc.onBackgroundThread()) - return; - - FreeSpan firstSpan = firstFreeSpan.decompact(arenaAddress()); - if (firstSpan.isEmpty()) - return; - const FreeList* freeList = zone->arenas.getFreeList(getAllocKind()); - if (freeList->isEmpty() || firstSpan.arenaAddress() != freeList->arenaAddress()) - return; - - /* - * Here this arena has free things, FreeList::lists[thingKind] is not - * empty and also points to this arena. Thus they must be the same. - */ - MOZ_ASSERT(freeList->isSameNonEmptySpan(firstSpan)); -} -#endif - -void -ArenaHeader::unmarkAll() +Arena::unmarkAll() { uintptr_t* word = chunk()->bitmap.arenaBits(this); memset(word, 0, ArenaBitmapWords * sizeof(uintptr_t)); @@ -492,6 +461,8 @@ ArenaHeader::unmarkAll() /* static */ void Arena::staticAsserts() { + static_assert(size_t(AllocKind::LIMIT) <= 255, + "We must be able to fit the allockind into uint8_t."); static_assert(JS_ARRAY_LENGTH(ThingSizes) == size_t(AllocKind::LIMIT), "We haven't defined all thing sizes."); static_assert(JS_ARRAY_LENGTH(FirstThingOffsets) == size_t(AllocKind::LIMIT), @@ -500,15 +471,6 @@ Arena::staticAsserts() "We haven't defined all counts."); } -void -Arena::setAsFullyUnused(AllocKind thingKind) -{ - FreeSpan fullSpan; - size_t thingSize = Arena::thingSize(thingKind); - fullSpan.initFinal(thingsStart(thingKind), thingsEnd() - thingSize, thingSize); - aheader.setFirstFreeSpan(&fullSpan); -} - template inline size_t Arena::finalize(FreeOp* fop, AllocKind thingKind, size_t thingSize) @@ -517,39 +479,39 @@ Arena::finalize(FreeOp* fop, AllocKind thingKind, size_t thingSize) MOZ_ASSERT(thingSize % CellSize == 0); MOZ_ASSERT(thingSize <= 255); - MOZ_ASSERT(aheader.allocated()); - MOZ_ASSERT(thingKind == aheader.getAllocKind()); - MOZ_ASSERT(thingSize == aheader.getThingSize()); - MOZ_ASSERT(!aheader.hasDelayedMarking); - MOZ_ASSERT(!aheader.markOverflow); - MOZ_ASSERT(!aheader.allocatedDuringIncremental); + MOZ_ASSERT(allocated()); + MOZ_ASSERT(thingKind == getAllocKind()); + MOZ_ASSERT(thingSize == getThingSize()); + MOZ_ASSERT(!hasDelayedMarking); + MOZ_ASSERT(!markOverflow); + MOZ_ASSERT(!allocatedDuringIncremental); - uintptr_t firstThing = thingsStart(thingKind); - uintptr_t firstThingOrSuccessorOfLastMarkedThing = firstThing; - uintptr_t lastThing = thingsEnd() - thingSize; + uint_fast16_t firstThing = firstThingOffset(thingKind); + uint_fast16_t firstThingOrSuccessorOfLastMarkedThing = firstThing; + uint_fast16_t lastThing = ArenaSize - thingSize; FreeSpan newListHead; FreeSpan* newListTail = &newListHead; size_t nmarked = 0; if (MOZ_UNLIKELY(MemProfiler::enabled())) { - for (ArenaCellIterUnderFinalize i(&aheader); !i.done(); i.next()) { + for (ArenaCellIterUnderFinalize i(this); !i.done(); i.next()) { T* t = i.get(); if (t->asTenured().isMarked()) MemProfiler::MarkTenured(reinterpret_cast(t)); } } - for (ArenaCellIterUnderFinalize i(&aheader); !i.done(); i.next()) { + for (ArenaCellIterUnderFinalize i(this); !i.done(); i.next()) { T* t = i.get(); if (t->asTenured().isMarked()) { - uintptr_t thing = reinterpret_cast(t); + uint_fast16_t thing = uintptr_t(t) & ArenaMask; if (thing != firstThingOrSuccessorOfLastMarkedThing) { // We just finished passing over one or more free things, // so record a new FreeSpan. - newListTail->initBoundsUnchecked(firstThingOrSuccessorOfLastMarkedThing, - thing - thingSize); - newListTail = newListTail->nextSpanUnchecked(); + newListTail->initBounds(firstThingOrSuccessorOfLastMarkedThing, + thing - thingSize, this); + newListTail = newListTail->nextSpanUnchecked(this); } firstThingOrSuccessorOfLastMarkedThing = thing + thingSize; nmarked++; @@ -561,30 +523,28 @@ Arena::finalize(FreeOp* fop, AllocKind thingKind, size_t thingSize) } if (nmarked == 0) { - // Do nothing. The caller will update the arena header appropriately. + // Do nothing. The caller will update the arena appropriately. MOZ_ASSERT(newListTail == &newListHead); JS_EXTRA_POISON(data, JS_SWEPT_TENURED_PATTERN, sizeof(data)); return nmarked; } MOZ_ASSERT(firstThingOrSuccessorOfLastMarkedThing != firstThing); - uintptr_t lastMarkedThing = firstThingOrSuccessorOfLastMarkedThing - thingSize; + uint_fast16_t lastMarkedThing = firstThingOrSuccessorOfLastMarkedThing - thingSize; if (lastThing == lastMarkedThing) { // If the last thing was marked, we will have already set the bounds of // the final span, and we just need to terminate the list. newListTail->initAsEmpty(); } else { // Otherwise, end the list with a span that covers the final stretch of free things. - newListTail->initFinal(firstThingOrSuccessorOfLastMarkedThing, lastThing, thingSize); + newListTail->initFinal(firstThingOrSuccessorOfLastMarkedThing, lastThing, this); } + firstFreeSpan = newListHead; #ifdef DEBUG - size_t nfree = 0; - for (const FreeSpan* span = &newListHead; !span->isEmpty(); span = span->nextSpan()) - nfree += span->length(thingSize); + size_t nfree = numFreeThings(thingSize); MOZ_ASSERT(nfree + nmarked == thingsPerArena(thingKind)); #endif - aheader.setFirstFreeSpan(&newListHead); return nmarked; } @@ -594,7 +554,7 @@ Arena::finalize(FreeOp* fop, AllocKind thingKind, size_t thingSize) template static inline bool FinalizeTypedArenas(FreeOp* fop, - ArenaHeader** src, + Arena** src, SortedArenaList& dest, AllocKind thingKind, SliceBudget& budget, @@ -612,17 +572,17 @@ FinalizeTypedArenas(FreeOp* fop, size_t thingSize = Arena::thingSize(thingKind); size_t thingsPerArena = Arena::thingsPerArena(thingKind); - while (ArenaHeader* aheader = *src) { - *src = aheader->next; - size_t nmarked = aheader->getArena()->finalize(fop, thingKind, thingSize); + while (Arena* arena = *src) { + *src = arena->next; + size_t nmarked = arena->finalize(fop, thingKind, thingSize); size_t nfree = thingsPerArena - nmarked; if (nmarked) - dest.insertAt(aheader, nfree); + dest.insertAt(arena, nfree); else if (keepArenas == ArenaLists::KEEP_ARENAS) - aheader->chunk()->recycleArena(aheader, dest, thingKind, thingsPerArena); + arena->chunk()->recycleArena(arena, dest, thingsPerArena); else - fop->runtime()->gc.releaseArena(aheader, maybeLock.ref()); + fop->runtime()->gc.releaseArena(arena, maybeLock.ref()); budget.step(thingsPerArena); if (budget.isOverBudget()) @@ -638,7 +598,7 @@ FinalizeTypedArenas(FreeOp* fop, */ static bool FinalizeArenas(FreeOp* fop, - ArenaHeader** src, + Arena** src, SortedArenaList& dest, AllocKind thingKind, SliceBudget& budget, @@ -902,7 +862,7 @@ Chunk::findDecommittedArenaOffset() MOZ_CRASH("No decommitted arenas found."); } -ArenaHeader* +Arena* Chunk::fetchNextDecommittedArena() { MOZ_ASSERT(info.numArenasFreeCommitted == 0); @@ -915,9 +875,9 @@ Chunk::fetchNextDecommittedArena() Arena* arena = &arenas[offset]; MarkPagesInUse(arena, ArenaSize); - arena->aheader.setAsNotAllocated(); + arena->setAsNotAllocated(); - return &arena->aheader; + return arena; } inline void @@ -927,30 +887,30 @@ GCRuntime::updateOnFreeArenaAlloc(const ChunkInfo& info) --numArenasFreeCommitted; } -inline ArenaHeader* +inline Arena* Chunk::fetchNextFreeArena(JSRuntime* rt) { MOZ_ASSERT(info.numArenasFreeCommitted > 0); MOZ_ASSERT(info.numArenasFreeCommitted <= info.numArenasFree); - ArenaHeader* aheader = info.freeArenasHead; - info.freeArenasHead = aheader->next; + Arena* arena = info.freeArenasHead; + info.freeArenasHead = arena->next; --info.numArenasFreeCommitted; --info.numArenasFree; rt->gc.updateOnFreeArenaAlloc(info); - return aheader; + return arena; } -ArenaHeader* +Arena* Chunk::allocateArena(JSRuntime* rt, Zone* zone, AllocKind thingKind, const AutoLockGC& lock) { - ArenaHeader* aheader = info.numArenasFreeCommitted > 0 - ? fetchNextFreeArena(rt) - : fetchNextDecommittedArena(); - aheader->init(zone, thingKind); + Arena* arena = info.numArenasFreeCommitted > 0 + ? fetchNextFreeArena(rt) + : fetchNextDecommittedArena(); + arena->init(zone, thingKind); updateChunkListAfterAlloc(rt, lock); - return aheader; + return arena; } inline void @@ -960,40 +920,38 @@ GCRuntime::updateOnArenaFree(const ChunkInfo& info) } void -Chunk::addArenaToFreeList(JSRuntime* rt, ArenaHeader* aheader) +Chunk::addArenaToFreeList(JSRuntime* rt, Arena* arena) { - MOZ_ASSERT(!aheader->allocated()); - MOZ_RELEASE_ASSERT(uintptr_t(info.freeArenasHead) != uintptr_t(UINT64_C(0x4b4b4b4b4b4b4b4b))); - aheader->next = info.freeArenasHead; - info.freeArenasHead = aheader; + MOZ_ASSERT(!arena->allocated()); + arena->next = info.freeArenasHead; + info.freeArenasHead = arena; ++info.numArenasFreeCommitted; ++info.numArenasFree; rt->gc.updateOnArenaFree(info); } void -Chunk::addArenaToDecommittedList(JSRuntime* rt, const ArenaHeader* aheader) +Chunk::addArenaToDecommittedList(JSRuntime* rt, const Arena* arena) { ++info.numArenasFree; - decommittedArenas.set(Chunk::arenaIndex(aheader->arenaAddress())); + decommittedArenas.set(Chunk::arenaIndex(arena->address())); } void -Chunk::recycleArena(ArenaHeader* aheader, SortedArenaList& dest, AllocKind thingKind, - size_t thingsPerArena) +Chunk::recycleArena(Arena* arena, SortedArenaList& dest, size_t thingsPerArena) { - aheader->getArena()->setAsFullyUnused(thingKind); - dest.insertAt(aheader, thingsPerArena); + arena->setAsFullyUnused(); + dest.insertAt(arena, thingsPerArena); } void -Chunk::releaseArena(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock) +Chunk::releaseArena(JSRuntime* rt, Arena* arena, const AutoLockGC& lock) { - MOZ_ASSERT(aheader->allocated()); - MOZ_ASSERT(!aheader->hasDelayedMarking); + MOZ_ASSERT(arena->allocated()); + MOZ_ASSERT(!arena->hasDelayedMarking); - aheader->setAsNotAllocated(); - addArenaToFreeList(rt, aheader); + arena->setAsNotAllocated(); + addArenaToFreeList(rt, arena); updateChunkListAfterFree(rt, lock); } @@ -1001,19 +959,19 @@ bool Chunk::decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock) { MOZ_ASSERT(info.numArenasFreeCommitted > 0); - ArenaHeader* aheader = fetchNextFreeArena(rt); + Arena* arena = fetchNextFreeArena(rt); updateChunkListAfterAlloc(rt, lock); bool ok; { AutoUnlockGC unlock(lock); - ok = MarkPagesUnused(aheader->getArena(), ArenaSize); + ok = MarkPagesUnused(arena, ArenaSize); } if (ok) - addArenaToDecommittedList(rt, aheader); + addArenaToDecommittedList(rt, arena); else - addArenaToFreeList(rt, aheader); + addArenaToFreeList(rt, arena); updateChunkListAfterFree(rt, lock); return ok; @@ -1023,7 +981,7 @@ void Chunk::decommitAllArenasWithoutUnlocking(const AutoLockGC& lock) { for (size_t i = 0; i < ArenasPerChunk; ++i) { - if (decommittedArenas.get(i) || arenas[i].aheader.allocated()) + if (decommittedArenas.get(i) || arenas[i].allocated()) continue; if (MarkPagesUnused(&arenas[i], ArenaSize)) { @@ -1112,7 +1070,7 @@ GCRuntime::pickChunk(const AutoLockGC& lock, return chunk; } -ArenaHeader* +Arena* GCRuntime::allocateArena(Chunk* chunk, Zone* zone, AllocKind thingKind, const AutoLockGC& lock) { MOZ_ASSERT(chunk->hasAvailableArenas()); @@ -1125,23 +1083,23 @@ GCRuntime::allocateArena(Chunk* chunk, Zone* zone, AllocKind thingKind, const Au return nullptr; } - ArenaHeader* aheader = chunk->allocateArena(rt, zone, thingKind, lock); + Arena* arena = chunk->allocateArena(rt, zone, thingKind, lock); zone->usage.addGCArena(); // Trigger an incremental slice if needed. if (!rt->isHeapMinorCollecting() && !isHeapCompacting()) maybeAllocTriggerZoneGC(zone, lock); - return aheader; + return arena; } void -GCRuntime::releaseArena(ArenaHeader* aheader, const AutoLockGC& lock) +GCRuntime::releaseArena(Arena* arena, const AutoLockGC& lock) { - aheader->zone->usage.removeGCArena(); + arena->zone->usage.removeGCArena(); if (isBackgroundSweeping()) - aheader->zone->threshold.updateForRemovedArena(tunables); - return aheader->chunk()->releaseArena(rt, aheader, lock); + arena->zone->threshold.updateForRemovedArena(tunables); + return arena->chunk()->releaseArena(rt, arena, lock); } GCRuntime::GCRuntime(JSRuntime* rt) : @@ -1218,6 +1176,7 @@ GCRuntime::GCRuntime(JSRuntime* rt) : #ifdef DEBUG inUnsafeRegion(0), noGCOrAllocationCheck(0), + noNurseryAllocationCheck(0), #endif lock(nullptr), allocTask(rt, emptyChunks_), @@ -1971,14 +1930,14 @@ ZoneHeapThreshold::updateForRemovedArena(const GCSchedulingTunables& tunables) } void -GCMarker::delayMarkingArena(ArenaHeader* aheader) +GCMarker::delayMarkingArena(Arena* arena) { - if (aheader->hasDelayedMarking) { + if (arena->hasDelayedMarking) { /* Arena already scheduled to be marked later */ return; } - aheader->setNextDelayedMarking(unmarkedArenaStackTop); - unmarkedArenaStackTop = aheader; + arena->setNextDelayedMarking(unmarkedArenaStackTop); + unmarkedArenaStackTop = arena; markLaterArenas++; } @@ -1986,19 +1945,23 @@ void GCMarker::delayMarkingChildren(const void* thing) { const TenuredCell* cell = TenuredCell::fromPointer(thing); - cell->arenaHeader()->markOverflow = 1; - delayMarkingArena(cell->arenaHeader()); + cell->arena()->markOverflow = 1; + delayMarkingArena(cell->arena()); } inline void ArenaLists::prepareForIncrementalGC(JSRuntime* rt) { for (auto i : AllAllocKinds()) { - FreeList* freeList = &freeLists[i]; - if (!freeList->isEmpty()) { - ArenaHeader* aheader = freeList->arenaHeader(); - aheader->allocatedDuringIncremental = true; - rt->gc.marker.delayMarkingArena(aheader); + FreeSpan* span = freeLists[i]; + if (span != &placeholder) { + if (!span->isEmpty()) { + Arena* arena = span->getArena(); + arena->allocatedDuringIncremental = true; + rt->gc.marker.delayMarkingArena(arena); + } else { + freeLists[i] = &placeholder; + } } } } @@ -2061,31 +2024,16 @@ CanRelocateAllocKind(AllocKind kind) return IsObjectAllocKind(kind); } -size_t ArenaHeader::countFreeCells() -{ - size_t count = 0; - size_t thingSize = getThingSize(); - FreeSpan firstSpan(getFirstFreeSpan()); - for (const FreeSpan* span = &firstSpan; !span->isEmpty(); span = span->nextSpan()) - count += span->length(thingSize); - return count; -} - -size_t ArenaHeader::countUsedCells() -{ - return Arena::thingsPerArena(getAllocKind()) - countFreeCells(); -} - -ArenaHeader* -ArenaList::removeRemainingArenas(ArenaHeader** arenap) +Arena* +ArenaList::removeRemainingArenas(Arena** arenap) { // This is only ever called to remove arenas that are after the cursor, so // we don't need to update it. #ifdef DEBUG - for (ArenaHeader* arena = *arenap; arena; arena = arena->next) + for (Arena* arena = *arenap; arena; arena = arena->next) MOZ_ASSERT(cursorp_ != &arena->next); #endif - ArenaHeader* remainingArenas = *arenap; + Arena* remainingArenas = *arenap; *arenap = nullptr; check(); return remainingArenas; @@ -2101,7 +2049,7 @@ ShouldRelocateAllArenas(JS::gcreason::Reason reason) * Choose which arenas to relocate all cells from. Return an arena cursor that * can be passed to removeRemainingArenas(). */ -ArenaHeader** +Arena** ArenaList::pickArenasToRelocate(size_t& arenaTotalOut, size_t& relocTotalOut) { // Relocate the greatest number of arenas such that the number of used cells @@ -2119,17 +2067,17 @@ ArenaList::pickArenasToRelocate(size_t& arenaTotalOut, size_t& relocTotalOut) if (isCursorAtEnd()) return nullptr; - ArenaHeader** arenap = cursorp_; // Next arena to consider for relocation. - size_t previousFreeCells = 0; // Count of free cells before arenap. - size_t followingUsedCells = 0; // Count of used cells after arenap. - size_t fullArenaCount = 0; // Number of full arenas (not relocated). - size_t nonFullArenaCount = 0; // Number of non-full arenas (considered for relocation). - size_t arenaIndex = 0; // Index of the next arena to consider. + Arena** arenap = cursorp_; // Next arena to consider for relocation. + size_t previousFreeCells = 0; // Count of free cells before arenap. + size_t followingUsedCells = 0; // Count of used cells after arenap. + size_t fullArenaCount = 0; // Number of full arenas (not relocated). + size_t nonFullArenaCount = 0; // Number of non-full arenas (considered for relocation). + size_t arenaIndex = 0; // Index of the next arena to consider. - for (ArenaHeader* arena = head_; arena != *cursorp_; arena = arena->next) + for (Arena* arena = head_; arena != *cursorp_; arena = arena->next) fullArenaCount++; - for (ArenaHeader* arena = *cursorp_; arena; arena = arena->next) { + for (Arena* arena = *cursorp_; arena; arena = arena->next) { followingUsedCells += arena->countUsedCells(); nonFullArenaCount++; } @@ -2138,7 +2086,7 @@ ArenaList::pickArenasToRelocate(size_t& arenaTotalOut, size_t& relocTotalOut) size_t cellsPerArena = Arena::thingsPerArena((*arenap)->getAllocKind()); while (*arenap) { - ArenaHeader* arena = *arenap; + Arena* arena = *arenap; if (followingUsedCells <= previousFreeCells) break; @@ -2241,25 +2189,25 @@ RelocateCell(Zone* zone, TenuredCell* src, AllocKind thingKind, size_t thingSize } static void -RelocateArena(ArenaHeader* aheader, SliceBudget& sliceBudget) +RelocateArena(Arena* arena, SliceBudget& sliceBudget) { - MOZ_ASSERT(aheader->allocated()); - MOZ_ASSERT(!aheader->hasDelayedMarking); - MOZ_ASSERT(!aheader->markOverflow); - MOZ_ASSERT(!aheader->allocatedDuringIncremental); + MOZ_ASSERT(arena->allocated()); + MOZ_ASSERT(!arena->hasDelayedMarking); + MOZ_ASSERT(!arena->markOverflow); + MOZ_ASSERT(!arena->allocatedDuringIncremental); - Zone* zone = aheader->zone; + Zone* zone = arena->zone; - AllocKind thingKind = aheader->getAllocKind(); - size_t thingSize = aheader->getThingSize(); + AllocKind thingKind = arena->getAllocKind(); + size_t thingSize = arena->getThingSize(); - for (ArenaCellIterUnderFinalize i(aheader); !i.done(); i.next()) { + for (ArenaCellIterUnderFinalize i(arena); !i.done(); i.next()) { RelocateCell(zone, i.getCell(), thingKind, thingSize); sliceBudget.step(); } #ifdef DEBUG - for (ArenaCellIterUnderFinalize i(aheader); !i.done(); i.next()) { + for (ArenaCellIterUnderFinalize i(arena); !i.done(); i.next()) { TenuredCell* src = i.getCell(); MOZ_ASSERT(RelocationOverlay::isCellForwarded(src)); TenuredCell* dest = Forwarded(src); @@ -2286,17 +2234,16 @@ ShouldProtectRelocatedArenas(JS::gcreason::Reason reason) * Relocate all arenas identified by pickArenasToRelocate: for each arena, * relocate each cell within it, then add it to a list of relocated arenas. */ -ArenaHeader* -ArenaList::relocateArenas(ArenaHeader* toRelocate, ArenaHeader* relocated, SliceBudget& sliceBudget, +Arena* +ArenaList::relocateArenas(Arena* toRelocate, Arena* relocated, SliceBudget& sliceBudget, gcstats::Statistics& stats) { check(); - while (ArenaHeader* arena = toRelocate) { + while (Arena* arena = toRelocate) { toRelocate = arena->next; RelocateArena(arena, sliceBudget); // Prepend to list of relocated arenas - MOZ_RELEASE_ASSERT(uintptr_t(relocated) != uintptr_t(UINT64_C(0x4b4b4b4b4b4b4b4b))); arena->next = relocated; relocated = arena; stats.count(gcstats::STAT_ARENA_RELOCATED); @@ -2331,7 +2278,7 @@ ShouldRelocateZone(size_t arenaCount, size_t relocCount, JS::gcreason::Reason re } bool -ArenaLists::relocateArenas(Zone* zone, ArenaHeader*& relocatedListOut, JS::gcreason::Reason reason, +ArenaLists::relocateArenas(Zone* zone, Arena*& relocatedListOut, JS::gcreason::Reason reason, SliceBudget& sliceBudget, gcstats::Statistics& stats) { // This is only called from the main thread while we are doing a GC, so @@ -2340,16 +2287,15 @@ ArenaLists::relocateArenas(Zone* zone, ArenaHeader*& relocatedListOut, JS::gcrea MOZ_ASSERT(runtime_->gc.isHeapCompacting()); MOZ_ASSERT(!runtime_->gc.isBackgroundSweeping()); - // Flush all the freeLists back into the arena headers + // Clear all the free lists. purge(); - checkEmptyFreeLists(); if (ShouldRelocateAllArenas(reason)) { zone->prepareForCompacting(); for (auto i : AllAllocKinds()) { if (CanRelocateAllocKind(i)) { ArenaList& al = arenaLists[i]; - ArenaHeader* allArenas = al.head(); + Arena* allArenas = al.head(); al.clear(); relocatedListOut = al.relocateArenas(allArenas, relocatedListOut, sliceBudget, stats); } @@ -2357,7 +2303,7 @@ ArenaLists::relocateArenas(Zone* zone, ArenaHeader*& relocatedListOut, JS::gcrea } else { size_t arenaCount = 0; size_t relocCount = 0; - AllAllocKindArray toRelocate; + AllAllocKindArray toRelocate; for (auto i : AllAllocKinds()) { toRelocate[i] = nullptr; @@ -2372,24 +2318,17 @@ ArenaLists::relocateArenas(Zone* zone, ArenaHeader*& relocatedListOut, JS::gcrea for (auto i : AllAllocKinds()) { if (toRelocate[i]) { ArenaList& al = arenaLists[i]; - ArenaHeader* arenas = al.removeRemainingArenas(toRelocate[i]); + Arena* arenas = al.removeRemainingArenas(toRelocate[i]); relocatedListOut = al.relocateArenas(arenas, relocatedListOut, sliceBudget, stats); } } } - // When we allocate new locations for cells, we use - // allocateFromFreeList(). Reset the free list again so that - // AutoCopyFreeListToArenasForGC doesn't complain that the free lists are - // different now. - purge(); - checkEmptyFreeLists(); - return true; } bool -GCRuntime::relocateArenas(Zone* zone, JS::gcreason::Reason reason, ArenaHeader*& relocatedListOut, +GCRuntime::relocateArenas(Zone* zone, JS::gcreason::Reason reason, Arena*& relocatedListOut, SliceBudget& sliceBudget) { gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT_MOVE); @@ -2410,7 +2349,7 @@ GCRuntime::relocateArenas(Zone* zone, JS::gcreason::Reason reason, ArenaHeader*& if (CanRelocateAllocKind(i)) { ArenaList& al = zone->arenas.arenaLists[i]; size_t freeCells = 0; - for (ArenaHeader* arena = al.arenaAfterCursor(); arena; arena = arena->next) + for (Arena* arena = al.arenaAfterCursor(); arena; arena = arena->next) freeCells += arena->countFreeCells(); MOZ_ASSERT(freeCells < thingsPerArena); } @@ -2483,7 +2422,7 @@ GCRuntime::sweepZoneAfterCompacting(Zone* zone) template static void -UpdateCellPointersTyped(MovingTracer* trc, ArenaHeader* arena, JS::TraceKind traceKind) +UpdateCellPointersTyped(MovingTracer* trc, Arena* arena, JS::TraceKind traceKind) { for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) { T* cell = reinterpret_cast(i.getCell()); @@ -2496,7 +2435,7 @@ UpdateCellPointersTyped(MovingTracer* trc, ArenaHeader* arena, JS::TraceKind tra * Update the interal pointers for all cells in an arena. */ static void -UpdateCellPointers(MovingTracer* trc, ArenaHeader* arena) +UpdateCellPointers(MovingTracer* trc, Arena* arena) { AllocKind kind = arena->getAllocKind(); JS::TraceKind traceKind = MapAllocToTraceKind(kind); @@ -2556,17 +2495,17 @@ struct ArenasToUpdate }; ArenasToUpdate(Zone* zone, KindsToUpdate kinds); bool done() { return kind == AllocKind::LIMIT; } - ArenaHeader* getArenasToUpdate(AutoLockHelperThreadState& lock, unsigned max); + Arena* getArenasToUpdate(AutoLockHelperThreadState& lock, unsigned max); private: KindsToUpdate kinds; // Selects which thing kinds to iterate Zone* zone; // Zone to process AllocKind kind; // Current alloc kind to process - ArenaHeader* arena; // Next arena to process + Arena* arena; // Next arena to process AllocKind nextAllocKind(AllocKind i) { return AllocKind(uint8_t(i) + 1); } bool shouldProcessKind(AllocKind kind); - ArenaHeader* next(AutoLockHelperThreadState& lock); + Arena* next(AutoLockHelperThreadState& lock); }; bool ArenasToUpdate::shouldProcessKind(AllocKind kind) @@ -2604,7 +2543,7 @@ ArenasToUpdate::ArenasToUpdate(Zone* zone, KindsToUpdate kinds) MOZ_ASSERT(kinds && !(kinds & ~ALL)); } -ArenaHeader* +Arena* ArenasToUpdate::next(AutoLockHelperThreadState& lock) { // Find the next arena to update. @@ -2629,17 +2568,17 @@ ArenasToUpdate::next(AutoLockHelperThreadState& lock) return nullptr; } -ArenaHeader* +Arena* ArenasToUpdate::getArenasToUpdate(AutoLockHelperThreadState& lock, unsigned count) { if (done()) return nullptr; - ArenaHeader* head = nullptr; - ArenaHeader* tail = nullptr; + Arena* head = nullptr; + Arena* tail = nullptr; for (unsigned i = 0; i < count; ++i) { - ArenaHeader* arena = next(lock); + Arena* arena = next(lock); if (!arena) break; @@ -2669,7 +2608,7 @@ struct UpdateCellPointersTask : public GCParallelTask private: JSRuntime* rt_; ArenasToUpdate* source_; - ArenaHeader* arenaList_; + Arena* arenaList_; virtual void run() override; void getArenasToUpdate(AutoLockHelperThreadState& lock); @@ -2694,7 +2633,7 @@ void UpdateCellPointersTask::updateArenas() { MovingTracer trc(rt_); - for (ArenaHeader* arena = arenaList_; + for (Arena* arena = arenaList_; arena; arena = arena->getNextArenaToUpdateAndUnlink()) { @@ -2838,15 +2777,13 @@ GCRuntime::updatePointersToRelocatedCells(Zone* zone) } void -GCRuntime::protectAndHoldArenas(ArenaHeader* arenaList) +GCRuntime::protectAndHoldArenas(Arena* arenaList) { - for (ArenaHeader* arena = arenaList; arena; ) { + for (Arena* arena = arenaList; arena; ) { MOZ_ASSERT(arena->allocated()); - ArenaHeader* next = arena->next; + Arena* next = arena->next; if (!next) { // Prepend to hold list before we protect the memory. - MOZ_RELEASE_ASSERT( - uintptr_t(relocatedArenasToRelease) != uintptr_t(UINT64_C(0x4b4b4b4b4b4b4b4b))); arena->next = relocatedArenasToRelease; relocatedArenasToRelease = arenaList; } @@ -2858,14 +2795,14 @@ GCRuntime::protectAndHoldArenas(ArenaHeader* arenaList) void GCRuntime::unprotectHeldRelocatedArenas() { - for (ArenaHeader* arena = relocatedArenasToRelease; arena; arena = arena->next) { + for (Arena* arena = relocatedArenasToRelease; arena; arena = arena->next) { UnprotectPages(arena, ArenaSize); MOZ_ASSERT(arena->allocated()); } } void -GCRuntime::releaseRelocatedArenas(ArenaHeader* arenaList) +GCRuntime::releaseRelocatedArenas(Arena* arenaList) { AutoLockGC lock(rt); releaseRelocatedArenasWithoutUnlocking(arenaList, lock); @@ -2873,31 +2810,26 @@ GCRuntime::releaseRelocatedArenas(ArenaHeader* arenaList) } void -GCRuntime::releaseRelocatedArenasWithoutUnlocking(ArenaHeader* arenaList, const AutoLockGC& lock) +GCRuntime::releaseRelocatedArenasWithoutUnlocking(Arena* arenaList, const AutoLockGC& lock) { // Release the relocated arenas, now containing only forwarding pointers unsigned count = 0; while (arenaList) { - ArenaHeader* aheader = arenaList; + Arena* arena = arenaList; arenaList = arenaList->next; // Clear the mark bits - aheader->unmarkAll(); + arena->unmarkAll(); // Mark arena as empty - AllocKind thingKind = aheader->getAllocKind(); - size_t thingSize = aheader->getThingSize(); - Arena* arena = aheader->getArena(); - FreeSpan fullSpan; - fullSpan.initFinal(arena->thingsStart(thingKind), arena->thingsEnd() - thingSize, thingSize); - aheader->setFirstFreeSpan(&fullSpan); + arena->setAsFullyUnused(); #if defined(JS_CRASH_DIAGNOSTICS) || defined(JS_GC_ZEAL) - JS_POISON(reinterpret_cast(arena->thingsStart(thingKind)), - JS_MOVED_TENURED_PATTERN, Arena::thingsSpan(thingKind)); + JS_POISON(reinterpret_cast(arena->thingsStart()), + JS_MOVED_TENURED_PATTERN, arena->getThingsSpan()); #endif - releaseArena(aheader, lock); + releaseArena(arena, lock); ++count; } } @@ -2927,12 +2859,12 @@ GCRuntime::releaseHeldRelocatedArenasWithoutUnlocking(const AutoLockGC& lock) } void -ReleaseArenaList(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock) +ReleaseArenaList(JSRuntime* rt, Arena* arena, const AutoLockGC& lock) { - ArenaHeader* next; - for (; aheader; aheader = next) { - next = aheader->next; - rt->gc.releaseArena(aheader, lock); + Arena* next; + for (; arena; arena = next) { + next = arena->next; + rt->gc.releaseArena(arena, lock); } } @@ -2964,18 +2896,19 @@ ArenaLists::finalizeNow(FreeOp* fop, const FinalizePhase& phase) } void -ArenaLists::finalizeNow(FreeOp* fop, AllocKind thingKind, KeepArenasEnum keepArenas, ArenaHeader** empty) +ArenaLists::finalizeNow(FreeOp* fop, AllocKind thingKind, KeepArenasEnum keepArenas, Arena** empty) { MOZ_ASSERT(!IsBackgroundFinalized(thingKind)); forceFinalizeNow(fop, thingKind, keepArenas, empty); } void -ArenaLists::forceFinalizeNow(FreeOp* fop, AllocKind thingKind, KeepArenasEnum keepArenas, ArenaHeader** empty) +ArenaLists::forceFinalizeNow(FreeOp* fop, AllocKind thingKind, + KeepArenasEnum keepArenas, Arena** empty) { MOZ_ASSERT(backgroundFinalizeState[thingKind] == BFS_DONE); - ArenaHeader* arenas = arenaLists[thingKind].head(); + Arena* arenas = arenaLists[thingKind].head(); if (!arenas) return; arenaLists[thingKind].clear(); @@ -3041,7 +2974,7 @@ ArenaLists::queueForBackgroundSweep(FreeOp* fop, AllocKind thingKind) } /*static*/ void -ArenaLists::backgroundFinalize(FreeOp* fop, ArenaHeader* listHead, ArenaHeader** empty) +ArenaLists::backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty) { MOZ_ASSERT(listHead); MOZ_ASSERT(empty); @@ -3161,7 +3094,7 @@ GCRuntime::refillFreeListInGC(Zone* zone, AllocKind thingKind) * Called by compacting GC to refill a free list while we are in a GC. */ - MOZ_ASSERT(zone->arenas.freeLists[thingKind].isEmpty()); + zone->arenas.checkEmptyFreeList(thingKind); mozilla::DebugOnly rt = zone->runtimeFromMainThread(); MOZ_ASSERT(rt->isHeapMajorCollecting()); MOZ_ASSERT(!rt->gc.isBackgroundSweeping()); @@ -3469,14 +3402,14 @@ GCRuntime::sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks, ThreadT return; // We must finalize thing kinds in the order specified by BackgroundFinalizePhases. - ArenaHeader* emptyArenas = nullptr; + Arena* emptyArenas = nullptr; FreeOp fop(rt, threadType); for (unsigned phase = 0 ; phase < ArrayLength(BackgroundFinalizePhases) ; ++phase) { for (Zone* zone = zones.front(); zone; zone = zone->nextZone()) { for (unsigned index = 0 ; index < BackgroundFinalizePhases[phase].length ; ++index) { AllocKind kind = BackgroundFinalizePhases[phase].kinds[index]; - ArenaHeader* arenas = zone->arenas.arenaListsToSweep[kind]; - MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(UINT64_C(0x4b4b4b4b4b4b4b4b))); + Arena* arenas = zone->arenas.arenaListsToSweep[kind]; + MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1)); if (arenas) ArenaLists::backgroundFinalize(&fop, arenas, &emptyArenas); } @@ -3833,7 +3766,7 @@ GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime) if (rt->gc.numActiveZoneIters) return; - AutoLockGC lock(rt); // Avoid race with background sweeping. + assertBackgroundSweepingFinished(); JSZoneCallback callback = rt->destroyZoneCallback; @@ -3848,15 +3781,16 @@ GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime) Zone* zone = *read++; if (zone->wasGCStarted()) { - if ((!zone->isQueuedForBackgroundSweep() && - zone->arenas.arenaListsAreEmpty() && - !zone->hasMarkedCompartments()) || destroyingRuntime) + MOZ_ASSERT(!zone->isQueuedForBackgroundSweep()); + const bool zoneIsDead = zone->arenas.arenaListsAreEmpty() && + !zone->hasMarkedCompartments(); + if (zoneIsDead || destroyingRuntime) { zone->arenas.checkEmptyFreeLists(); - AutoUnlockGC unlock(lock); if (callback) callback(zone); + zone->sweepCompartments(fop, false, destroyingRuntime); MOZ_ASSERT(zone->compartments.empty()); fop->delete_(zone); @@ -4531,15 +4465,15 @@ js::gc::MarkingValidator::validate() if (chunk->decommittedArenas.get(i)) continue; Arena* arena = &chunk->arenas[i]; - if (!arena->aheader.allocated()) + if (!arena->allocated()) continue; - if (!arena->aheader.zone->isGCSweeping()) + if (!arena->zone->isGCSweeping()) continue; - if (arena->aheader.allocatedDuringIncremental) + if (arena->allocatedDuringIncremental) continue; - AllocKind kind = arena->aheader.getAllocKind(); - uintptr_t thing = arena->thingsStart(kind); + AllocKind kind = arena->getAllocKind(); + uintptr_t thing = arena->thingsStart(); uintptr_t end = arena->thingsEnd(); while (thing < end) { Cell* cell = (Cell*)thing; @@ -5377,7 +5311,7 @@ GCRuntime::endSweepingZoneGroup() sweepBackgroundThings(zones, freeLifoAlloc, MainThread); /* Reset the list of arenas marked as being allocated during sweep phase. */ - while (ArenaHeader* arena = arenasAllocatedDuringSweep) { + while (Arena* arena = arenasAllocatedDuringSweep) { arenasAllocatedDuringSweep = arena->getNextAllocDuringSweep(); arena->unsetAllocDuringSweep(); } @@ -5481,9 +5415,9 @@ SweepThing(ObjectGroup* group, AutoClearTypeInferenceStateOnOOM* oom) template static bool -SweepArenaList(ArenaHeader** arenasToSweep, SliceBudget& sliceBudget, Args... args) +SweepArenaList(Arena** arenasToSweep, SliceBudget& sliceBudget, Args... args) { - while (ArenaHeader* arena = *arenasToSweep) { + while (Arena* arena = *arenasToSweep) { for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) SweepThing(i.get(), args...); @@ -5657,13 +5591,6 @@ GCRuntime::endSweepPhase(bool destroyingRuntime) jitRuntime->execAlloc().purge(); jitRuntime->backedgeExecAlloc().purge(); } - - /* - * This removes compartments from rt->compartment, so we do it last to make - * sure we don't miss sweeping any compartments. - */ - if (!destroyingRuntime) - sweepZones(&fop, destroyingRuntime); } { @@ -5691,10 +5618,6 @@ GCRuntime::endSweepPhase(bool destroyingRuntime) AutoLockGC lock(rt); expireChunksAndArenas(invocationKind == GC_SHRINK, lock); } - - /* Ensure the compartments get swept if it's the last GC. */ - if (destroyingRuntime) - sweepZones(&fop, destroyingRuntime); } finishMarkingValidation(); @@ -5719,19 +5642,12 @@ GCRuntime::endSweepPhase(bool destroyingRuntime) #endif } -GCRuntime::IncrementalProgress +void GCRuntime::beginCompactPhase() { - gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT); + MOZ_ASSERT(!isBackgroundSweeping()); - if (isIncremental) { - // Poll for end of background sweeping - AutoLockGC lock(rt); - if (isBackgroundSweeping()) - return NotFinished; - } else { - waitBackgroundSweepEnd(); - } + gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT); MOZ_ASSERT(zonesToMaybeCompact.isEmpty()); for (GCZonesIter zone(rt); !zone.done(); zone.next()) { @@ -5741,7 +5657,6 @@ GCRuntime::beginCompactPhase() MOZ_ASSERT(!relocatedArenasToRelease); startedCompacting = true; - return Finished; } GCRuntime::IncrementalProgress @@ -5756,7 +5671,7 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget) while (!zonesToMaybeCompact.isEmpty()) { Zone* zone = zonesToMaybeCompact.front(); MOZ_ASSERT(zone->isGCFinished()); - ArenaHeader* relocatedArenas = nullptr; + Arena* relocatedArenas = nullptr; if (relocateArenas(zone, reason, relocatedArenas, sliceBudget)) { zone->setGCState(Zone::Compact); updatePointersToRelocatedCells(zone); @@ -5885,36 +5800,6 @@ AutoTraceSession::~AutoTraceSession() } } -AutoCopyFreeListToArenas::AutoCopyFreeListToArenas(JSRuntime* rt, ZoneSelector selector) - : runtime(rt), - selector(selector) -{ - for (ZonesIter zone(rt, selector); !zone.done(); zone.next()) - zone->arenas.copyFreeListsToArenas(); -} - -AutoCopyFreeListToArenas::~AutoCopyFreeListToArenas() -{ - for (ZonesIter zone(runtime, selector); !zone.done(); zone.next()) - zone->arenas.clearFreeListsInArenas(); -} - -class AutoCopyFreeListToArenasForGC -{ - JSRuntime* runtime; - - public: - explicit AutoCopyFreeListToArenasForGC(JSRuntime* rt) : runtime(rt) { - MOZ_ASSERT(rt->currentThreadHasExclusiveAccess()); - for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) - zone->arenas.copyFreeListsToArenas(); - } - ~AutoCopyFreeListToArenasForGC() { - for (ZonesIter zone(runtime, WithAtoms); !zone.done(); zone.next()) - zone->arenas.clearFreeListsInArenas(); - } -}; - void GCRuntime::resetIncrementalGC(const char* reason) { @@ -5924,8 +5809,6 @@ GCRuntime::resetIncrementalGC(const char* reason) case MARK: { /* Cancel any ongoing marking. */ - AutoCopyFreeListToArenasForGC copy(rt); - marker.reset(); marker.stop(); clearBufferedGrayRoots(); @@ -5973,12 +5856,24 @@ GCRuntime::resetIncrementalGC(const char* reason) break; } - case COMPACT: { + case FINALIZE: { { gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD); rt->gc.waitBackgroundSweepOrAllocEnd(); } + bool wasCompacting = isCompacting; + isCompacting = false; + + auto unlimited = SliceBudget::unlimited(); + incrementalCollectSlice(unlimited, JS::gcreason::RESET); + + isCompacting = wasCompacting; + + break; + } + + case COMPACT: { bool wasCompacting = isCompacting; isCompacting = true; @@ -6094,7 +5989,6 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea { MOZ_ASSERT(rt->currentThreadHasExclusiveAccess()); - AutoCopyFreeListToArenasForGC copy(rt); AutoGCSlice slice(rt); bool destroyingRuntime = (reason == JS::gcreason::DESTROY_RUNTIME); @@ -6207,8 +6101,7 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea endSweepPhase(destroyingRuntime); - incrementalState = COMPACT; - MOZ_ASSERT(!startedCompacting); + incrementalState = FINALIZE; /* Yield before compacting since it is not incremental. */ if (isCompacting && isIncremental) @@ -6216,10 +6109,40 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea MOZ_FALLTHROUGH; + case FINALIZE: + { + gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD); + + // Yield until background finalization is done. + if (isIncremental) { + // Poll for end of background sweeping + AutoLockGC lock(rt); + if (isBackgroundSweeping()) + break; + } else { + waitBackgroundSweepEnd(); + } + } + + { + // Re-sweep the zones list, now that background finalization is + // finished to actually remove and free dead zones. + gcstats::AutoPhase ap1(stats, gcstats::PHASE_SWEEP); + gcstats::AutoPhase ap2(stats, gcstats::PHASE_DESTROY); + AutoSetThreadIsSweeping threadIsSweeping; + FreeOp fop(rt); + sweepZones(&fop, destroyingRuntime); + } + + MOZ_ASSERT(!startedCompacting); + incrementalState = COMPACT; + + MOZ_FALLTHROUGH; + case COMPACT: if (isCompacting) { - if (!startedCompacting && beginCompactPhase() == NotFinished) - break; + if (!startedCompacting) + beginCompactPhase(); if (compactPhase(reason, budget) == NotFinished) break; @@ -6815,8 +6738,7 @@ AutoFinishGC::AutoFinishGC(JSRuntime* rt) AutoPrepareForTracing::AutoPrepareForTracing(JSRuntime* rt, ZoneSelector selector) : finish(rt), - session(rt), - copy(rt, selector) + session(rt) { } @@ -6960,8 +6882,8 @@ gc::MergeCompartments(JSCompartment* source, JSCompartment* target) for (auto thingKind : AllAllocKinds()) { for (ArenaIter aiter(source->zone(), thingKind); !aiter.done(); aiter.next()) { - ArenaHeader* aheader = aiter.get(); - aheader->zone = target->zone(); + Arena* arena = aiter.get(); + arena->zone = target->zone(); } } @@ -7176,13 +7098,13 @@ ArenaLists::adoptArenas(JSRuntime* rt, ArenaLists* fromArenaLists) ArenaList* toList = &arenaLists[thingKind]; fromList->check(); toList->check(); - ArenaHeader* next; - for (ArenaHeader* fromHeader = fromList->head(); fromHeader; fromHeader = next) { - // Copy fromHeader->next before releasing/reinserting. - next = fromHeader->next; + Arena* next; + for (Arena* fromArena = fromList->head(); fromArena; fromArena = next) { + // Copy fromArena->next before releasing/reinserting. + next = fromArena->next; - MOZ_ASSERT(!fromHeader->isEmpty()); - toList->insertAtCursor(fromHeader); + MOZ_ASSERT(!fromArena->isEmpty()); + toList->insertAtCursor(fromArena); } fromList->clear(); toList->check(); @@ -7190,12 +7112,12 @@ ArenaLists::adoptArenas(JSRuntime* rt, ArenaLists* fromArenaLists) } bool -ArenaLists::containsArena(JSRuntime* rt, ArenaHeader* needle) +ArenaLists::containsArena(JSRuntime* rt, Arena* needle) { AutoLockGC lock(rt); ArenaList& list = arenaLists[needle->getAllocKind()]; - for (ArenaHeader* aheader = list.head(); aheader; aheader = aheader->next) { - if (aheader == needle) + for (Arena* arena = list.head(); arena; arena = arena->next) { + if (arena == needle) return true; } return false; @@ -7338,6 +7260,17 @@ JS::AutoAssertNoAlloc::~AutoAssertNoAlloc() if (gc) gc->allowAlloc(); } + +AutoAssertNoNurseryAlloc::AutoAssertNoNurseryAlloc(JSRuntime* rt) + : gc(rt->gc) +{ + gc.disallowNurseryAlloc(); +} + +AutoAssertNoNurseryAlloc::~AutoAssertNoNurseryAlloc() +{ + gc.allowNurseryAlloc(); +} #endif JS::AutoAssertGCCallback::AutoAssertGCCallback(JSObject* obj) diff --git a/js/src/jsgc.h b/js/src/jsgc.h index eec0b9e113..691113ad6d 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -48,6 +48,7 @@ enum State { MARK_ROOTS, MARK, SWEEP, + FINALIZE, COMPACT }; @@ -290,8 +291,8 @@ class AutoMaybeStartBackgroundAllocation; */ struct SortedArenaListSegment { - ArenaHeader* head; - ArenaHeader** tailp; + Arena* head; + Arena** tailp; void clear() { head = nullptr; @@ -302,23 +303,21 @@ struct SortedArenaListSegment return tailp == &head; } - // Appends |aheader| to this segment. - void append(ArenaHeader* aheader) { - MOZ_ASSERT(aheader); - MOZ_ASSERT_IF(head, head->getAllocKind() == aheader->getAllocKind()); - MOZ_RELEASE_ASSERT(uintptr_t(aheader) != uintptr_t(UINT64_C(0x4b4b4b4b4b4b4b4b))); - *tailp = aheader; - tailp = &aheader->next; + // Appends |arena| to this segment. + void append(Arena* arena) { + MOZ_ASSERT(arena); + MOZ_ASSERT_IF(head, head->getAllocKind() == arena->getAllocKind()); + *tailp = arena; + tailp = &arena->next; } - // Points the tail of this segment at |aheader|, which may be null. Note + // Points the tail of this segment at |arena|, which may be null. Note // that this does not change the tail itself, but merely which arena // follows it. This essentially turns the tail into a cursor (see also the // description of ArenaList), but from the perspective of a SortedArenaList // this makes no difference. - void linkTo(ArenaHeader* aheader) { - MOZ_RELEASE_ASSERT(uintptr_t(aheader) != uintptr_t(UINT64_C(0x4b4b4b4b4b4b4b4b))); - *tailp = aheader; + void linkTo(Arena* arena) { + *tailp = arena; } }; @@ -327,15 +326,12 @@ struct SortedArenaListSegment * boundaries, i.e. before the first arena, between two arenas, or after the * last arena. * - * Normally the arena following the cursor is the first arena in the list with - * some free things and all arenas before the cursor are fully allocated. (And - * if the cursor is at the end of the list, then all the arenas are full.) - * - * However, the arena currently being allocated from is considered full while - * its list of free spans is moved into the freeList. Therefore, during GC or - * cell enumeration, when an unallocated freeList is moved back to the arena, - * we can see an arena with some free cells before the cursor. - * + * Arenas are usually sorted in order of increasing free space, with the cursor + * following the Arena currently being allocated from. This ordering should not + * be treated as an invariant, however, as the free lists may be cleared, + * leaving arenas previously used for allocation partially full. Sorting order + * is restored during sweeping. + * Arenas following the cursor should not be full. */ class ArenaList { @@ -361,8 +357,8 @@ class ArenaList { // // |cursorp_| is never null. // - ArenaHeader* head_; - ArenaHeader** cursorp_; + Arena* head_; + Arena** cursorp_; void copy(const ArenaList& other) { other.check(); @@ -398,7 +394,7 @@ class ArenaList { MOZ_ASSERT_IF(!head_, cursorp_ == &head_); // If there's an arena following the cursor, it must not be full. - ArenaHeader* cursor = *cursorp_; + Arena* cursor = *cursorp_; MOZ_ASSERT_IF(cursor, cursor->hasFreeThings()); #endif } @@ -421,7 +417,7 @@ class ArenaList { } // This returns nullptr if the list is empty. - ArenaHeader* head() const { + Arena* head() const { check(); return head_; } @@ -437,28 +433,27 @@ class ArenaList { } // This can return nullptr. - ArenaHeader* arenaAfterCursor() const { + Arena* arenaAfterCursor() const { check(); return *cursorp_; } // This returns the arena after the cursor and moves the cursor past it. - ArenaHeader* takeNextArena() { + Arena* takeNextArena() { check(); - ArenaHeader* aheader = *cursorp_; - if (!aheader) + Arena* arena = *cursorp_; + if (!arena) return nullptr; - cursorp_ = &aheader->next; + cursorp_ = &arena->next; check(); - return aheader; + return arena; } // This does two things. // - Inserts |a| at the cursor. // - Leaves the cursor sitting just before |a|, if |a| is not full, or just // after |a|, if |a| is full. - // - void insertAtCursor(ArenaHeader* a) { + void insertAtCursor(Arena* a) { check(); a->next = *cursorp_; *cursorp_ = a; @@ -469,6 +464,15 @@ class ArenaList { check(); } + // Inserts |a| at the cursor, then moves the cursor past it. + void insertBeforeCursor(Arena* a) { + check(); + a->next = *cursorp_; + *cursorp_ = a; + cursorp_ = &a->next; + check(); + } + // This inserts |other|, which must be full, at the cursor of |this|. ArenaList& insertListWithCursorAtEnd(const ArenaList& other) { check(); @@ -484,10 +488,10 @@ class ArenaList { return *this; } - ArenaHeader* removeRemainingArenas(ArenaHeader** arenap); - ArenaHeader** pickArenasToRelocate(size_t& arenaTotalOut, size_t& relocTotalOut); - ArenaHeader* relocateArenas(ArenaHeader* toRelocate, ArenaHeader* relocated, - SliceBudget& sliceBudget, gcstats::Statistics& stats); + Arena* removeRemainingArenas(Arena** arenap); + Arena** pickArenasToRelocate(size_t& arenaTotalOut, size_t& relocTotalOut); + Arena* relocateArenas(Arena* toRelocate, Arena* relocated, + SliceBudget& sliceBudget, gcstats::Statistics& stats); }; /* @@ -509,14 +513,14 @@ class SortedArenaList private: // The maximum number of GC things that an arena can hold. - static const size_t MaxThingsPerArena = (ArenaSize - sizeof(ArenaHeader)) / MinThingSize; + static const size_t MaxThingsPerArena = (ArenaSize - ArenaHeaderSize) / MinThingSize; size_t thingsPerArena_; SortedArenaListSegment segments[MaxThingsPerArena + 1]; // Convenience functions to get the nth head and tail. - ArenaHeader* headAt(size_t n) { return segments[n].head; } - ArenaHeader** tailAt(size_t n) { return segments[n].tailp; } + Arena* headAt(size_t n) { return segments[n].head; } + Arena** tailAt(size_t n) { return segments[n].tailp; } public: explicit SortedArenaList(size_t thingsPerArena = MaxThingsPerArena) { @@ -536,17 +540,16 @@ class SortedArenaList segments[i].clear(); } - // Inserts a header, which has room for |nfree| more things, in its segment. - void insertAt(ArenaHeader* aheader, size_t nfree) { + // Inserts an arena, which has room for |nfree| more things, in its segment. + void insertAt(Arena* arena, size_t nfree) { MOZ_ASSERT(nfree <= thingsPerArena_); - segments[nfree].append(aheader); + segments[nfree].append(arena); } // Remove all empty arenas, inserting them as a linked list. - void extractEmpty(ArenaHeader** empty) { + void extractEmpty(Arena** empty) { SortedArenaListSegment& segment = segments[thingsPerArena_]; if (segment.head) { - MOZ_RELEASE_ASSERT(uintptr_t(*empty) != uintptr_t(UINT64_C(0x4b4b4b4b4b4b4b4b))); *segment.tailp = *empty; *empty = segment.head; segment.clear(); @@ -556,7 +559,7 @@ class SortedArenaList // Links up the tail of each non-empty segment to the head of the next // non-empty segment, creating a contiguous list that is returned as an // ArenaList. This is not a destructive operation: neither the head nor tail - // of any segment is modified. However, note that the ArenaHeaders in the + // of any segment is modified. However, note that the Arenas in the // resulting ArenaList should be treated as read-only unless the // SortedArenaList is no longer needed: inserting or removing arenas would // invalidate the SortedArenaList. @@ -587,11 +590,16 @@ class ArenaLists * free things. Initially all the spans are initialized as empty. After we * find a new arena with available things we move its first free span into * the list and set the arena as fully allocated. way we do not need to - * update the arena header after the initial allocation. When starting the + * update the arena after the initial allocation. When starting the * GC we only move the head of the of the list of spans back to the arena * only for the arena that was not fully allocated. */ - AllAllocKindArray freeLists; + AllAllocKindArray freeLists; + + // Because the JITs can allocate from the free lists, they cannot be null. + // We use a placeholder FreeSpan that is empty (and wihout an associated + // Arena) so the JITs can fall back gracefully. + static FreeSpan placeholder; AllAllocKindArray arenaLists; @@ -604,7 +612,7 @@ class ArenaLists AllAllocKindArray backgroundFinalizeState; /* For each arena kind, a list of arenas remaining to be swept. */ - AllAllocKindArray arenaListsToSweep; + AllAllocKindArray arenaListsToSweep; /* During incremental sweeping, a list of the arenas already swept. */ AllocKind incrementalSweptArenaKind; @@ -612,22 +620,22 @@ class ArenaLists // Arena lists which have yet to be swept, but need additional foreground // processing before they are swept. - ArenaHeader* gcShapeArenasToUpdate; - ArenaHeader* gcAccessorShapeArenasToUpdate; - ArenaHeader* gcScriptArenasToUpdate; - ArenaHeader* gcObjectGroupArenasToUpdate; + Arena* gcShapeArenasToUpdate; + Arena* gcAccessorShapeArenasToUpdate; + Arena* gcScriptArenasToUpdate; + Arena* gcObjectGroupArenasToUpdate; // While sweeping type information, these lists save the arenas for the // objects which have already been finalized in the foreground (which must // happen at the beginning of the GC), so that type sweeping can determine // which of the object pointers are marked. ObjectAllocKindArray savedObjectArenas; - ArenaHeader* savedEmptyObjectArenas; + Arena* savedEmptyObjectArenas; public: explicit ArenaLists(JSRuntime* rt) : runtime_(rt) { for (auto i : AllAllocKinds()) - freeLists[i].initAsEmpty(); + freeLists[i] = &placeholder; for (auto i : AllAllocKinds()) backgroundFinalizeState[i] = BFS_DONE; for (auto i : AllAllocKinds()) @@ -642,30 +650,25 @@ class ArenaLists ~ArenaLists(); - static uintptr_t getFreeListOffset(AllocKind thingKind) { - uintptr_t offset = offsetof(ArenaLists, freeLists); - return offset + size_t(thingKind) * sizeof(FreeList); + const void* addressOfFreeList(AllocKind thingKind) const { + return reinterpret_cast(&freeLists[thingKind]); } - const FreeList* getFreeList(AllocKind thingKind) const { - return &freeLists[thingKind]; - } - - ArenaHeader* getFirstArena(AllocKind thingKind) const { + Arena* getFirstArena(AllocKind thingKind) const { return arenaLists[thingKind].head(); } - ArenaHeader* getFirstArenaToSweep(AllocKind thingKind) const { + Arena* getFirstArenaToSweep(AllocKind thingKind) const { return arenaListsToSweep[thingKind]; } - ArenaHeader* getFirstSweptArena(AllocKind thingKind) const { + Arena* getFirstSweptArena(AllocKind thingKind) const { if (thingKind != incrementalSweptArenaKind) return nullptr; return incrementalSweptArenas.head(); } - ArenaHeader* getArenaAfterCursor(AllocKind thingKind) const { + Arena* getArenaAfterCursor(AllocKind thingKind) const { return arenaLists[thingKind].arenaAfterCursor(); } @@ -687,8 +690,8 @@ class ArenaLists for (auto i : AllAllocKinds()) { /* The background finalization must have stopped at this point. */ MOZ_ASSERT(backgroundFinalizeState[i] == BFS_DONE); - for (ArenaHeader* aheader = arenaLists[i].head(); aheader; aheader = aheader->next) - aheader->unmarkAll(); + for (Arena* arena = arenaLists[i].head(); arena; arena = arena->next) + arena->unmarkAll(); } } @@ -701,93 +704,23 @@ class ArenaLists } /* - * Return the free list back to the arena so the GC finalization will not - * run the finalizers over unitialized bytes from free things. + * Clear the free lists so we won't try to allocate from swept arenas. */ void purge() { for (auto i : AllAllocKinds()) - purge(i); - } - - void purge(AllocKind i) { - FreeList* freeList = &freeLists[i]; - if (!freeList->isEmpty()) { - ArenaHeader* aheader = freeList->arenaHeader(); - aheader->setFirstFreeSpan(freeList->getHead()); - freeList->initAsEmpty(); - } + freeLists[i] = &placeholder; } inline void prepareForIncrementalGC(JSRuntime* rt); - /* - * Temporarily copy the free list heads to the arenas so the code can see - * the proper value in ArenaHeader::freeList when accessing the latter - * outside the GC. - */ - void copyFreeListsToArenas() { - for (auto i : AllAllocKinds()) - copyFreeListToArena(i); - } - - void copyFreeListToArena(AllocKind thingKind) { - FreeList* freeList = &freeLists[thingKind]; - if (!freeList->isEmpty()) { - ArenaHeader* aheader = freeList->arenaHeader(); - MOZ_ASSERT(!aheader->hasFreeThings()); - aheader->setFirstFreeSpan(freeList->getHead()); - } - } - - /* - * Clear the free lists in arenas that were temporarily set there using - * copyToArenas. - */ - void clearFreeListsInArenas() { - for (auto i : AllAllocKinds()) - clearFreeListInArena(i); - } - - void clearFreeListInArena(AllocKind kind) { - FreeList* freeList = &freeLists[kind]; - if (!freeList->isEmpty()) { - ArenaHeader* aheader = freeList->arenaHeader(); - MOZ_ASSERT(freeList->isSameNonEmptySpan(aheader->getFirstFreeSpan())); - aheader->setAsFullyUsed(); - } - } - - /* - * Check that the free list is either empty or were synchronized with the - * arena using copyToArena(). - */ - bool isSynchronizedFreeList(AllocKind kind) { - FreeList* freeList = &freeLists[kind]; - if (freeList->isEmpty()) - return true; - ArenaHeader* aheader = freeList->arenaHeader(); - if (aheader->hasFreeThings()) { - /* - * If the arena has a free list, it must be the same as one in - * lists. - */ - MOZ_ASSERT(freeList->isSameNonEmptySpan(aheader->getFirstFreeSpan())); - return true; - } - return false; - } - - /* Check if |aheader|'s arena is in use. */ - bool arenaIsInUse(ArenaHeader* aheader, AllocKind kind) const { - MOZ_ASSERT(aheader); - const FreeList& freeList = freeLists[kind]; - if (freeList.isEmpty()) - return false; - return aheader == freeList.arenaHeader(); + /* Check if this arena is in use. */ + bool arenaIsInUse(Arena* arena, AllocKind kind) const { + MOZ_ASSERT(arena); + return arena == freeLists[kind]->getArenaUnchecked(); } MOZ_ALWAYS_INLINE TenuredCell* allocateFromFreeList(AllocKind thingKind, size_t thingSize) { - return freeLists[thingKind].allocate(thingSize); + return freeLists[thingKind]->allocate(thingSize); } /* @@ -795,8 +728,8 @@ class ArenaLists */ void adoptArenas(JSRuntime* runtime, ArenaLists* fromArenaLists); - /* True if the ArenaHeader in question is found in this ArenaLists */ - bool containsArena(JSRuntime* runtime, ArenaHeader* arenaHeader); + /* True if the Arena in question is found in this ArenaLists */ + bool containsArena(JSRuntime* runtime, Arena* arena); void checkEmptyFreeLists() { #ifdef DEBUG @@ -806,10 +739,10 @@ class ArenaLists } void checkEmptyFreeList(AllocKind kind) { - MOZ_ASSERT(freeLists[kind].isEmpty()); + MOZ_ASSERT(freeLists[kind]->isEmpty()); } - bool relocateArenas(Zone* zone, ArenaHeader*& relocatedListOut, JS::gcreason::Reason reason, + bool relocateArenas(Zone* zone, Arena*& relocatedListOut, JS::gcreason::Reason reason, SliceBudget& sliceBudget, gcstats::Statistics& stats); void queueForegroundObjectsForSweep(FreeOp* fop); @@ -819,7 +752,7 @@ class ArenaLists bool foregroundFinalize(FreeOp* fop, AllocKind thingKind, SliceBudget& sliceBudget, SortedArenaList& sweepList); - static void backgroundFinalize(FreeOp* fop, ArenaHeader* listHead, ArenaHeader** empty); + static void backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty); // When finalizing arenas, whether to keep empty arenas on the list or // release them immediately. @@ -834,19 +767,16 @@ class ArenaLists inline void queueForBackgroundSweep(FreeOp* fop, const FinalizePhase& phase); inline void finalizeNow(FreeOp* fop, AllocKind thingKind, - KeepArenasEnum keepArenas, ArenaHeader** empty = nullptr); + KeepArenasEnum keepArenas, Arena** empty = nullptr); inline void forceFinalizeNow(FreeOp* fop, AllocKind thingKind, - KeepArenasEnum keepArenas, ArenaHeader** empty = nullptr); + KeepArenasEnum keepArenas, Arena** empty = nullptr); inline void queueForForegroundSweep(FreeOp* fop, AllocKind thingKind); inline void queueForBackgroundSweep(FreeOp* fop, AllocKind thingKind); inline void mergeSweptArenas(AllocKind thingKind); TenuredCell* allocateFromArena(JS::Zone* zone, AllocKind thingKind, AutoMaybeStartBackgroundAllocation& maybeStartBGAlloc); - - enum ArenaAllocMode { HasFreeThings = true, IsEmpty = false }; - template - TenuredCell* allocateFromArenaInner(JS::Zone* zone, ArenaHeader* aheader, AllocKind kind); + inline TenuredCell* allocateFromArenaInner(JS::Zone* zone, Arena* arena, AllocKind kind); inline void normalizeBackgroundFinalizeState(AllocKind thingKind); @@ -1353,6 +1283,19 @@ class ZoneList JSObject* NewMemoryStatisticsObject(JSContext* cx); +struct MOZ_RAII AutoAssertNoNurseryAlloc +{ +#ifdef DEBUG + explicit AutoAssertNoNurseryAlloc(JSRuntime* rt); + ~AutoAssertNoNurseryAlloc(); + + private: + gc::GCRuntime& gc; +#else + explicit AutoAssertNoNurseryAlloc(JSRuntime* rt) {} +#endif +}; + } /* namespace gc */ #ifdef DEBUG diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index 734df87e24..d6feaeb92a 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -9,6 +9,8 @@ #include "jsgc.h" +#include "mozilla/DebugOnly.h" + #include "gc/GCTrace.h" #include "gc/Zone.h" @@ -40,68 +42,64 @@ GCRuntime::poke() class ArenaIter { - ArenaHeader* aheader; - ArenaHeader* unsweptHeader; - ArenaHeader* sweptHeader; + Arena* arena; + Arena* unsweptArena; + Arena* sweptArena; + mozilla::DebugOnly initialized; public: - ArenaIter() { - aheader = nullptr; - unsweptHeader = nullptr; - sweptHeader = nullptr; - } + ArenaIter() + : arena(nullptr), unsweptArena(nullptr), sweptArena(nullptr), initialized(false) {} - ArenaIter(JS::Zone* zone, AllocKind kind) { - init(zone, kind); - } + ArenaIter(JS::Zone* zone, AllocKind kind) : initialized(false) { init(zone, kind); } void init(JS::Zone* zone, AllocKind kind) { - aheader = zone->arenas.getFirstArena(kind); - unsweptHeader = zone->arenas.getFirstArenaToSweep(kind); - sweptHeader = zone->arenas.getFirstSweptArena(kind); - if (!unsweptHeader) { - unsweptHeader = sweptHeader; - sweptHeader = nullptr; + MOZ_ASSERT(!initialized); + MOZ_ASSERT(zone); + initialized = true; + arena = zone->arenas.getFirstArena(kind); + unsweptArena = zone->arenas.getFirstArenaToSweep(kind); + sweptArena = zone->arenas.getFirstSweptArena(kind); + if (!unsweptArena) { + unsweptArena = sweptArena; + sweptArena = nullptr; } - if (!aheader) { - aheader = unsweptHeader; - unsweptHeader = sweptHeader; - sweptHeader = nullptr; + if (!arena) { + arena = unsweptArena; + unsweptArena = sweptArena; + sweptArena = nullptr; } } bool done() const { - return !aheader; + MOZ_ASSERT(initialized); + return !arena; } - ArenaHeader* get() const { - return aheader; + Arena* get() const { + MOZ_ASSERT(!done()); + return arena; } void next() { MOZ_ASSERT(!done()); - aheader = aheader->next; - if (!aheader) { - aheader = unsweptHeader; - unsweptHeader = sweptHeader; - sweptHeader = nullptr; + arena = arena->next; + if (!arena) { + arena = unsweptArena; + unsweptArena = sweptArena; + sweptArena = nullptr; } } }; class ArenaCellIterImpl { - // These three are set in initUnsynchronized(). size_t firstThingOffset; size_t thingSize; -#ifdef DEBUG - bool isInited; -#endif - - // These three are set in reset() (which is called by init()). + Arena* arenaAddr; FreeSpan span; - uintptr_t thing; - uintptr_t limit; + uint_fast16_t thing; + mozilla::DebugOnly initialized; // Upon entry, |thing| points to any thing (free or used) and finds the // first used thing, which may be |thing|. @@ -114,54 +112,46 @@ class ArenaCellIterImpl // never need to move forward. if (thing == span.first) { thing = span.last + thingSize; - span = *span.nextSpan(); + span = *span.nextSpan(arenaAddr); } } public: ArenaCellIterImpl() - : firstThingOffset(0) // Squelch - , thingSize(0) // warnings - , limit(0) - { - } + : firstThingOffset(0), thingSize(0), arenaAddr(nullptr), thing(0), initialized(false) {} - void initUnsynchronized(ArenaHeader* aheader) { - AllocKind kind = aheader->getAllocKind(); -#ifdef DEBUG - isInited = true; -#endif + explicit ArenaCellIterImpl(Arena* arena) : initialized(false) { init(arena); } + + void init(Arena* arena) { + MOZ_ASSERT(!initialized); + MOZ_ASSERT(arena); + initialized = true; + AllocKind kind = arena->getAllocKind(); firstThingOffset = Arena::firstThingOffset(kind); thingSize = Arena::thingSize(kind); - reset(aheader); - } - - void init(ArenaHeader* aheader) { -#ifdef DEBUG - AllocKind kind = aheader->getAllocKind(); - MOZ_ASSERT(aheader->zone->arenas.isSynchronizedFreeList(kind)); -#endif - initUnsynchronized(aheader); + reset(arena); } // Use this to move from an Arena of a particular kind to another Arena of // the same kind. - void reset(ArenaHeader* aheader) { - MOZ_ASSERT(isInited); - span = aheader->getFirstFreeSpan(); - uintptr_t arenaAddr = aheader->arenaAddress(); - thing = arenaAddr + firstThingOffset; - limit = arenaAddr + ArenaSize; + void reset(Arena* arena) { + MOZ_ASSERT(initialized); + MOZ_ASSERT(arena); + arenaAddr = arena; + span = *arena->getFirstFreeSpan(); + thing = firstThingOffset; moveForwardIfFree(); } bool done() const { - return thing == limit; + MOZ_ASSERT(initialized); + MOZ_ASSERT(thing <= ArenaSize); + return thing == ArenaSize; } TenuredCell* getCell() const { MOZ_ASSERT(!done()); - return reinterpret_cast(thing); + return reinterpret_cast(uintptr_t(arenaAddr) + thing); } template T* get() const { @@ -172,7 +162,7 @@ class ArenaCellIterImpl void next() { MOZ_ASSERT(!done()); thing += thingSize; - if (thing < limit) + if (thing < ArenaSize) moveForwardIfFree(); } }; @@ -184,30 +174,32 @@ ArenaCellIterImpl::get() const; class ArenaCellIterUnderGC : public ArenaCellIterImpl { public: - explicit ArenaCellIterUnderGC(ArenaHeader* aheader) { - MOZ_ASSERT(aheader->zone->runtimeFromAnyThread()->isHeapBusy()); - init(aheader); + explicit ArenaCellIterUnderGC(Arena* arena) : ArenaCellIterImpl(arena) { + MOZ_ASSERT(arena->zone->runtimeFromAnyThread()->isHeapBusy()); } }; class ArenaCellIterUnderFinalize : public ArenaCellIterImpl { public: - explicit ArenaCellIterUnderFinalize(ArenaHeader* aheader) { - initUnsynchronized(aheader); - } + explicit ArenaCellIterUnderFinalize(Arena* arena) : ArenaCellIterImpl(arena) {} }; class ZoneCellIterImpl { ArenaIter arenaIter; ArenaCellIterImpl cellIter; + mozilla::DebugOnly initialized; protected: - ZoneCellIterImpl() {} + ZoneCellIterImpl() : initialized(false) {} + + ZoneCellIterImpl(JS::Zone* zone, AllocKind kind) : initialized(false) { init(zone, kind); } void init(JS::Zone* zone, AllocKind kind) { - MOZ_ASSERT(zone->arenas.isSynchronizedFreeList(kind)); + MOZ_ASSERT(!initialized); + MOZ_ASSERT(zone); + initialized = true; arenaIter.init(zone, kind); if (!arenaIter.done()) cellIter.init(arenaIter.get()); @@ -215,6 +207,7 @@ class ZoneCellIterImpl public: bool done() const { + MOZ_ASSERT(initialized); return arenaIter.done(); } @@ -243,24 +236,18 @@ class ZoneCellIterImpl class ZoneCellIterUnderGC : public ZoneCellIterImpl { public: - ZoneCellIterUnderGC(JS::Zone* zone, AllocKind kind) { + ZoneCellIterUnderGC(JS::Zone* zone, AllocKind kind) : ZoneCellIterImpl(zone, kind) { MOZ_ASSERT(zone->runtimeFromAnyThread()->gc.nursery.isEmpty()); MOZ_ASSERT(zone->runtimeFromAnyThread()->isHeapBusy()); - init(zone, kind); } }; class ZoneCellIter : public ZoneCellIterImpl { JS::AutoAssertNoAlloc noAlloc; - ArenaLists* lists; - AllocKind kind; public: - ZoneCellIter(JS::Zone* zone, AllocKind kind) - : lists(&zone->arenas), - kind(kind) - { + ZoneCellIter(JS::Zone* zone, AllocKind kind) { JSRuntime* rt = zone->runtimeFromMainThread(); /* @@ -278,23 +265,11 @@ class ZoneCellIter : public ZoneCellIterImpl /* Evict the nursery before iterating so we can see all things. */ rt->gc.evictNursery(); - if (lists->isSynchronizedFreeList(kind)) { - lists = nullptr; - } else { - MOZ_ASSERT(!rt->isHeapBusy()); - lists->copyFreeListToArena(kind); - } - /* Assert that no GCs can occur while a ZoneCellIter is live. */ noAlloc.disallowAlloc(rt); init(zone, kind); } - - ~ZoneCellIter() { - if (lists) - lists->clearFreeListInArena(kind); - } }; class GCZonesIter @@ -303,9 +278,7 @@ class GCZonesIter ZonesIter zone; public: - explicit GCZonesIter(JSRuntime* rt, ZoneSelector selector = WithAtoms) - : zone(rt, selector) - { + explicit GCZonesIter(JSRuntime* rt, ZoneSelector selector = WithAtoms) : zone(rt, selector) { if (!zone->isCollecting()) next(); } diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index c76aecd47e..967e416bcc 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -275,7 +275,17 @@ ClassCanHaveFixedData(const Class* clasp) || js::IsTypedArrayClass(clasp); } -static MOZ_ALWAYS_INLINE void +// This function is meant to be called from allocation fast paths. +// +// If we do have an allocation metadata hook, it can cause a GC, so the object +// must be rooted. The usual way to do this would be to make our callers pass a +// HandleObject, but that would require them to pay the cost of rooting the +// object unconditionally, even though collecting metadata is rare. Instead, +// SetNewObjectMetadata's contract is that the caller must use the pointer +// returned in place of the pointer passed. If a GC occurs, the returned pointer +// may be the passed pointer, relocated by GC. If no GC could occur, it's just +// passed through. We root nothing unless necessary. +static MOZ_ALWAYS_INLINE MOZ_WARN_UNUSED_RESULT JSObject* SetNewObjectMetadata(ExclusiveContext* cxArg, JSObject* obj) { MOZ_ASSERT(!cxArg->compartment()->hasObjectPendingMetadata()); @@ -290,10 +300,13 @@ SetNewObjectMetadata(ExclusiveContext* cxArg, JSObject* obj) // callback, and any reentering of JS via Invoke() etc. AutoEnterAnalysis enter(cx); - RootedObject hobj(cx, obj); - cx->compartment()->setNewObjectMetadata(cx, hobj); + RootedObject rooted(cx, obj); + cx->compartment()->setNewObjectMetadata(cx, rooted); + return rooted; } } + + return obj; } } // namespace js @@ -357,7 +370,7 @@ JSObject::create(js::ExclusiveContext* cx, js::gc::AllocKind kind, js::gc::Initi if (group->clasp()->shouldDelayMetadataCallback()) cx->compartment()->setObjectPendingMetadata(cx, obj); else - SetNewObjectMetadata(cx, obj); + obj = SetNewObjectMetadata(cx, obj); js::gc::TraceCreateObject(obj); diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index 253d86c907..9b5d548161 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -164,7 +164,7 @@ PropertyTree::getChild(ExclusiveContext* cx, Shape* parentArg, HandlebarrierTracer(), &tmp, "read barrier"); MOZ_ASSERT(tmp == existingShape); } else if (zone->isGCSweeping() && !existingShape->isMarked() && - !existingShape->arenaHeader()->allocatedDuringIncremental) + !existingShape->arena()->allocatedDuringIncremental) { /* * The shape we've found is unreachable and due to be finalized, so diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 131c577f73..9fdc6841f2 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -460,6 +460,7 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio // Suppress GC so that calls below do not trigger a new incremental GC // which could require barriers on the atoms compartment. gc::AutoSuppressGC nogc(cx); + gc::AutoAssertNoNurseryAlloc noNurseryAlloc(cx->runtime()); JSObject* global = CreateGlobalForOffThreadParse(cx, ParseTaskKind::Script, nogc); if (!global) diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 6be0fde81c..ea1190149b 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -14,6 +14,7 @@ #include "jsobj.h" #include "jsscript.h" +#include "gc/Heap.h" #include "jit/BaselineJIT.h" #include "jit/Ion.h" #include "vm/ArrayObject.h" @@ -354,9 +355,9 @@ StatsArenaCallback(JSRuntime* rt, void* data, gc::Arena* arena, { RuntimeStats* rtStats = static_cast(data)->rtStats; - // The admin space includes (a) the header and (b) the padding between the - // end of the header and the start of the first GC thing. - size_t allocationSpace = Arena::thingsSpan(arena->aheader.getAllocKind()); + // The admin space includes (a) the header fields and (b) the padding + // between the end of the header fields and the first GC thing. + size_t allocationSpace = gc::Arena::thingsSpan(arena->getAllocKind()); rtStats->currZoneStats->gcHeapArenaAdmin += gc::ArenaSize - allocationSpace; // We don't call the callback on unused things. So we compute the diff --git a/js/src/vm/Runtime-inl.h b/js/src/vm/Runtime-inl.h index d63c04f2b0..afc98fe441 100644 --- a/js/src/vm/Runtime-inl.h +++ b/js/src/vm/Runtime-inl.h @@ -70,7 +70,7 @@ NewObjectCache::newObjectFromHit(JSContext* cx, EntryIndex entryIndex, gc::Initi if (group->clasp()->shouldDelayMetadataCallback()) cx->compartment()->setObjectPendingMetadata(cx, obj); else - SetNewObjectMetadata(cx, obj); + obj = static_cast(SetNewObjectMetadata(cx, obj)); probes::CreateObject(cx, obj); gc::TraceCreateObject(obj); diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index 1e534ef779..0d0ba9b6da 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -1411,7 +1411,7 @@ SavedStacks::chooseSamplingProbability(JSCompartment* compartment) } JSObject* -SavedStacksMetadataCallback(JSContext* cx, JSObject* target) +SavedStacksMetadataCallback(JSContext* cx, HandleObject target) { RootedObject obj(cx, target); diff --git a/js/src/vm/SavedStacks.h b/js/src/vm/SavedStacks.h index 207b049807..023c9ab197 100644 --- a/js/src/vm/SavedStacks.h +++ b/js/src/vm/SavedStacks.h @@ -149,7 +149,7 @@ namespace js { class SavedStacks { friend class SavedFrame; - friend JSObject* SavedStacksMetadataCallback(JSContext* cx, JSObject* target); + friend JSObject* SavedStacksMetadataCallback(JSContext* cx, HandleObject target); friend bool JS::ubi::ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFrame& ubiFrame, MutableHandleObject outSavedFrameStack); @@ -300,7 +300,7 @@ class SavedStacks { bool getLocation(JSContext* cx, const FrameIter& iter, MutableHandle locationp); }; -JSObject* SavedStacksMetadataCallback(JSContext* cx, JSObject* target); +JSObject* SavedStacksMetadataCallback(JSContext* cx, HandleObject target); template <> class RootedBase diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 9c60b473c3..352789afb3 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -772,10 +772,10 @@ TypeSet::IsTypeAllocatedDuringIncremental(TypeSet::Type v) bool rv; if (v.isSingletonUnchecked()) { JSObject* obj = v.singletonNoBarrier(); - rv = obj->isTenured() && obj->asTenured().arenaHeader()->allocatedDuringIncremental; + rv = obj->isTenured() && obj->asTenured().arena()->allocatedDuringIncremental; } else if (v.isGroupUnchecked()) { ObjectGroup* group = v.groupNoBarrier(); - rv = group->arenaHeader()->allocatedDuringIncremental; + rv = group->arena()->allocatedDuringIncremental; } else { rv = false; } diff --git a/layout/base/DisplayItemScrollClip.cpp b/layout/base/DisplayItemScrollClip.cpp index ce96e9e6d7..4b2a527c55 100644 --- a/layout/base/DisplayItemScrollClip.cpp +++ b/layout/base/DisplayItemScrollClip.cpp @@ -18,7 +18,7 @@ DisplayItemScrollClip::IsAncestor(const DisplayItemScrollClip* aAncestor, return true; } - for (const DisplayItemScrollClip* sc = aDescendant; sc; sc = sc->mCrossStackingContextParent) { + for (const DisplayItemScrollClip* sc = aDescendant; sc; sc = sc->mParent) { if (sc == aAncestor) { return true; } @@ -33,9 +33,10 @@ DisplayItemScrollClip::ToString(const DisplayItemScrollClip* aScrollClip) nsAutoCString str; for (const DisplayItemScrollClip* scrollClip = aScrollClip; scrollClip; scrollClip = scrollClip->mParent) { - str.AppendPrintf("<%s>", scrollClip->mClip ? scrollClip->mClip->ToString().get() : "null"); + str.AppendPrintf("<%s>%s", scrollClip->mClip ? scrollClip->mClip->ToString().get() : "null", + scrollClip->mIsAsyncScrollable ? " [async-scrollable]" : ""); if (scrollClip->mParent) { - str.Append(" "); + str.Append(", "); } } return str; diff --git a/layout/base/DisplayItemScrollClip.h b/layout/base/DisplayItemScrollClip.h index a5486bee64..7f270f7854 100644 --- a/layout/base/DisplayItemScrollClip.h +++ b/layout/base/DisplayItemScrollClip.h @@ -29,8 +29,7 @@ class DisplayItemClip; * in that case, mIsAsyncScrollable on the scroll clip will be false. * When async-scrollable scroll frames are nested, the inner display items * can walk the whole chain of scroll clips via the DisplayItemScrollClip's - * mParent pointer, or its mCrossStackingContextParent pointer if they need - * access to the complete chain. + * mParent pointer. * Storing scroll clips on display items allows easy access of all scroll * frames that affect a certain display item, and it allows some display list * operations to compute more accurate clips, for example when computing the @@ -40,7 +39,6 @@ class DisplayItemClip; class DisplayItemScrollClip { public: DisplayItemScrollClip(const DisplayItemScrollClip* aParent, - const DisplayItemScrollClip* aCrossStackingContextParent, nsIScrollableFrame* aScrollableFrame, const DisplayItemClip* aClip, bool aIsAsyncScrollable) @@ -48,22 +46,20 @@ public: , mScrollableFrame(aScrollableFrame) , mClip(aClip) , mIsAsyncScrollable(aIsAsyncScrollable) - , mCrossStackingContextParent(aCrossStackingContextParent) - , mCrossStackingContextDepth(aCrossStackingContextParent ? - aCrossStackingContextParent->mCrossStackingContextDepth + 1 : 1) + , mDepth(aParent ? aParent->mDepth + 1 : 1) { MOZ_ASSERT(mScrollableFrame); } /** - * "Pick innermost" is analogous to the intersection operation on regular + * "Pick descendant" is analogous to the intersection operation on regular * clips: In some cases, there are multiple candidate clips that can apply to * an item, one of them being the ancestor of the other. This method picks * the descendant. * Both aClip1 and aClip2 are allowed to be null. */ static const DisplayItemScrollClip* - PickInnermost(const DisplayItemScrollClip* aClip1, + PickDescendant(const DisplayItemScrollClip* aClip1, const DisplayItemScrollClip* aClip2) { MOZ_ASSERT(IsAncestor(aClip1, aClip2) || IsAncestor(aClip2, aClip1), @@ -71,6 +67,16 @@ public: return Depth(aClip1) > Depth(aClip2) ? aClip1 : aClip2; } + static const DisplayItemScrollClip* + PickAncestor(const DisplayItemScrollClip* aClip1, + const DisplayItemScrollClip* aClip2) + { + MOZ_ASSERT(IsAncestor(aClip1, aClip2) || IsAncestor(aClip2, aClip1), + "one of the scroll clips must be an ancestor of the other"); + return Depth(aClip1) < Depth(aClip2) ? aClip1 : aClip2; + } + + /** * Returns whether aAncestor is an ancestor scroll clip of aDescendant. * A scroll clip that's null is considered the root scroll clip. @@ -108,14 +114,9 @@ public: private: static uint32_t Depth(const DisplayItemScrollClip* aSC) - { return aSC ? aSC->mCrossStackingContextDepth : 0; } + { return aSC ? aSC->mDepth : 0; } - /** - * The previous (outer) scroll clip, across stacking contexts, or null. - */ - const DisplayItemScrollClip* mCrossStackingContextParent; - - const uint32_t mCrossStackingContextDepth; + const uint32_t mDepth; }; } // namespace mozilla diff --git a/layout/base/DisplayListClipState.cpp b/layout/base/DisplayListClipState.cpp index a83b8d2504..faac0a37f5 100644 --- a/layout/base/DisplayListClipState.cpp +++ b/layout/base/DisplayListClipState.cpp @@ -111,7 +111,7 @@ DisplayListClipState::ClipContainingBlockDescendantsToContentBox(nsDisplayListBu const DisplayItemScrollClip* DisplayListClipState::GetCurrentInnermostScrollClip() { - return DisplayItemScrollClip::PickInnermost( + return DisplayItemScrollClip::PickDescendant( mScrollClipContentDescendants, mScrollClipContainingBlockDescendants); } @@ -120,12 +120,8 @@ DisplayListClipState::TurnClipIntoScrollClipForContentDescendants( nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame) { const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip(); - const DisplayItemScrollClip* crossStackingContextParent = parent; - if (!crossStackingContextParent) { - crossStackingContextParent = mCrossStackingContextParentScrollClip; - } mScrollClipContentDescendants = - aBuilder->AllocateDisplayItemScrollClip(parent, crossStackingContextParent, + aBuilder->AllocateDisplayItemScrollClip(parent, aScrollableFrame, GetCurrentCombinedClip(aBuilder), true); Clear(); @@ -136,12 +132,8 @@ DisplayListClipState::TurnClipIntoScrollClipForContainingBlockDescendants( nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame) { const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip(); - const DisplayItemScrollClip* crossStackingContextParent = parent; - if (!crossStackingContextParent) { - crossStackingContextParent = mCrossStackingContextParentScrollClip; - } mScrollClipContainingBlockDescendants = - aBuilder->AllocateDisplayItemScrollClip(parent, crossStackingContextParent, + aBuilder->AllocateDisplayItemScrollClip(parent, aScrollableFrame, GetCurrentCombinedClip(aBuilder), true); Clear(); @@ -170,12 +162,8 @@ DisplayListClipState::CreateInactiveScrollClip( WithoutRoundedCorners(aBuilder, GetCurrentCombinedClip(aBuilder)); const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip(); - const DisplayItemScrollClip* crossStackingContextParent = parent; - if (!crossStackingContextParent) { - crossStackingContextParent = mCrossStackingContextParentScrollClip; - } DisplayItemScrollClip* scrollClip = - aBuilder->AllocateDisplayItemScrollClip(parent, crossStackingContextParent, + aBuilder->AllocateDisplayItemScrollClip(parent, aScrollableFrame, rectClip, false); return scrollClip; @@ -204,8 +192,11 @@ DisplayListClipState::InsertInactiveScrollClipForContainingBlockDescendants( DisplayListClipState::AutoSaveRestore::AutoSaveRestore(nsDisplayListBuilder* aBuilder) : mState(aBuilder->ClipState()) , mSavedState(aBuilder->ClipState()) +#ifdef DEBUG , mClipUsed(false) , mRestored(false) +#endif + , mClearedForStackingContextContents(false) {} } // namespace mozilla diff --git a/layout/base/DisplayListClipState.h b/layout/base/DisplayListClipState.h index b31fe4ef34..521e912e8d 100644 --- a/layout/base/DisplayListClipState.h +++ b/layout/base/DisplayListClipState.h @@ -7,6 +7,7 @@ #define DISPLAYLISTCLIPSTATE_H_ #include "DisplayItemClip.h" +#include "DisplayItemScrollClip.h" #include "mozilla/DebugOnly.h" @@ -16,8 +17,6 @@ class nsDisplayListBuilder; namespace mozilla { -class DisplayItemScrollClip; - /** * All clip coordinates are in appunits relative to the reference frame * for the display item we're building. @@ -30,7 +29,7 @@ public: , mCurrentCombinedClip(nullptr) , mScrollClipContentDescendants(nullptr) , mScrollClipContainingBlockDescendants(nullptr) - , mCrossStackingContextParentScrollClip(nullptr) + , mStackingContextAncestorSC(nullptr) {} /** @@ -50,6 +49,11 @@ public: const DisplayItemScrollClip* GetCurrentInnermostScrollClip(); + const DisplayItemScrollClip* CurrentAncestorScrollClipForStackingContextContents() + { + return mStackingContextAncestorSC; + } + class AutoSaveRestore; friend class AutoSaveRestore; @@ -73,6 +77,7 @@ private: void SetScrollClipForContainingBlockDescendants(const DisplayItemScrollClip* aScrollClip) { mScrollClipContainingBlockDescendants = aScrollClip; + mStackingContextAncestorSC = DisplayItemScrollClip::PickAncestor(mStackingContextAncestorSC, aScrollClip); } void Clear() @@ -83,24 +88,18 @@ private: // We do not clear scroll clips. } - void ClearForStackingContextContents() + void EnterStackingContextContents(bool aClear) { - mClipContentDescendants = nullptr; - mClipContainingBlockDescendants = nullptr; - mCurrentCombinedClip = nullptr; - mCrossStackingContextParentScrollClip = GetCurrentInnermostScrollClip(); - mScrollClipContentDescendants = nullptr; - mScrollClipContainingBlockDescendants = nullptr; - } - - void ClearIncludingScrollClip() - { - mClipContentDescendants = nullptr; - mClipContainingBlockDescendants = nullptr; - mCurrentCombinedClip = nullptr; - mCrossStackingContextParentScrollClip = nullptr; - mScrollClipContentDescendants = nullptr; - mScrollClipContainingBlockDescendants = nullptr; + if (aClear) { + mClipContentDescendants = nullptr; + mClipContainingBlockDescendants = nullptr; + mCurrentCombinedClip = nullptr; + mScrollClipContentDescendants = nullptr; + mScrollClipContainingBlockDescendants = nullptr; + mStackingContextAncestorSC = nullptr; + } else { + mStackingContextAncestorSC = GetCurrentInnermostScrollClip(); + } } /** @@ -183,7 +182,13 @@ private: */ const DisplayItemScrollClip* mScrollClipContentDescendants; const DisplayItemScrollClip* mScrollClipContainingBlockDescendants; - const DisplayItemScrollClip* mCrossStackingContextParentScrollClip; + + /** + * A scroll clip that is an ancestor of all the scroll clips that were + * "current" on this clip state since EnterStackingContextContents was + * called. + */ + const DisplayItemScrollClip* mStackingContextAncestorSC; }; /** @@ -198,55 +203,97 @@ public: explicit AutoSaveRestore(nsDisplayListBuilder* aBuilder); void Restore() { + if (!mClearedForStackingContextContents) { + // Forward along the ancestor scroll clip to the original clip state. + mSavedState.mStackingContextAncestorSC = + DisplayItemScrollClip::PickAncestor(mSavedState.mStackingContextAncestorSC, + mState.mStackingContextAncestorSC); + } mState = mSavedState; +#ifdef DEBUG mRestored = true; +#endif } ~AutoSaveRestore() { - mState = mSavedState; + Restore(); } void Clear() { NS_ASSERTION(!mRestored, "Already restored!"); mState.Clear(); +#ifdef DEBUG mClipUsed = false; +#endif } - void ClearForStackingContextContents() + void EnterStackingContextContents(bool aClear) { NS_ASSERTION(!mRestored, "Already restored!"); - mState.ClearForStackingContextContents(); - mClipUsed = false; + mState.EnterStackingContextContents(aClear); + mClearedForStackingContextContents = aClear; } - - void ClearIncludingScrollClip() + void ExitStackingContextContents(const DisplayItemScrollClip** aOutContainerSC) { - NS_ASSERTION(!mRestored, "Already restored!"); - mState.ClearIncludingScrollClip(); - mClipUsed = false; + if (mClearedForStackingContextContents) { + // If we cleared the scroll clip, then the scroll clip that was current + // just before we cleared it is the one that needs to be set on the + // container item. + *aOutContainerSC = mSavedState.GetCurrentInnermostScrollClip(); + } else { + // If we didn't clear the scroll clip, then the container item needs to + // get a scroll clip that's an ancestor of all its direct child items' + // scroll clips. + // The simplest way to satisfy this requirement would be to just take the + // root scroll clip (i.e. nullptr). However, this can cause the bounds of + // the container items to be enlarged unnecessarily, so instead we try to + // take the "deepest" scroll clip that satisfies the requirement. + // Usually this is the scroll clip that was current before we entered + // the stacking context contents (call that the "initial scroll clip"). + // There are two cases in which the container scroll clip *won't* be the + // initial scroll clip (instead the container scroll clip will be a + // proper ancestor of the initial scroll clip): + // (1) If SetScrollClipForContainingBlockDescendants was called with an + // ancestor scroll clip of the initial scroll clip while we were + // building our direct child items. This happens if we entered a + // position:absolute or position:fixed element whose containing + // block is an ancestor of the frame that generated the initial + // scroll clip. Then the "ancestor scroll clip for stacking context + // contents" will be set to that scroll clip. + // (2) If one of our direct child items is a container item for which + // (1) or (2) happened. + *aOutContainerSC = mState.CurrentAncestorScrollClipForStackingContextContents(); + } + Restore(); } void TurnClipIntoScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame) { NS_ASSERTION(!mRestored, "Already restored!"); mState.TurnClipIntoScrollClipForContentDescendants(aBuilder, aScrollableFrame); +#ifdef DEBUG mClipUsed = true; +#endif } void TurnClipIntoScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame) { NS_ASSERTION(!mRestored, "Already restored!"); mState.TurnClipIntoScrollClipForContainingBlockDescendants(aBuilder, aScrollableFrame); +#ifdef DEBUG mClipUsed = true; +#endif } DisplayItemScrollClip* InsertInactiveScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame) { NS_ASSERTION(!mRestored, "Already restored!"); DisplayItemScrollClip* scrollClip = mState.InsertInactiveScrollClipForContentDescendants(aBuilder, aScrollableFrame); +#ifdef DEBUG mClipUsed = true; +#endif return scrollClip; } @@ -254,7 +301,9 @@ public: { NS_ASSERTION(!mRestored, "Already restored!"); DisplayItemScrollClip* scrollClip = mState.InsertInactiveScrollClipForContainingBlockDescendants(aBuilder, aScrollableFrame); +#ifdef DEBUG mClipUsed = true; +#endif return scrollClip; } @@ -268,7 +317,9 @@ public: { NS_ASSERTION(!mRestored, "Already restored!"); NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG mClipUsed = true; +#endif mState.ClipContainingBlockDescendants(aRect, aRadii, mClip); } @@ -277,7 +328,9 @@ public: { NS_ASSERTION(!mRestored, "Already restored!"); NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG mClipUsed = true; +#endif mState.ClipContentDescendants(aRect, aRadii, mClip); } @@ -287,7 +340,9 @@ public: { NS_ASSERTION(!mRestored, "Already restored!"); NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG mClipUsed = true; +#endif mState.ClipContentDescendants(aRect, aRoundedRect, aRadii, mClip); } @@ -305,7 +360,9 @@ public: { NS_ASSERTION(!mRestored, "Already restored!"); NS_ASSERTION(!mClipUsed, "mClip already used"); +#ifdef DEBUG mClipUsed = true; +#endif mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, mClip, aFlags); } @@ -313,8 +370,11 @@ protected: DisplayListClipState& mState; DisplayListClipState mSavedState; DisplayItemClip mClip; - DebugOnly mClipUsed; - DebugOnly mRestored; +#ifdef DEBUG + bool mClipUsed; + bool mRestored; +#endif + bool mClearedForStackingContextContents; }; class DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox : public AutoSaveRestore { @@ -324,7 +384,9 @@ public: uint32_t aFlags = 0) : AutoSaveRestore(aBuilder) { +#ifdef DEBUG mClipUsed = true; +#endif mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, mClip, aFlags); } }; @@ -338,7 +400,9 @@ class DisplayListClipState::AutoClipMultiple : public AutoSaveRestore { public: explicit AutoClipMultiple(nsDisplayListBuilder* aBuilder) : AutoSaveRestore(aBuilder) +#ifdef DEBUG , mExtraClipUsed(false) +#endif {} /** @@ -364,13 +428,17 @@ public: { NS_ASSERTION(!mRestored, "Already restored!"); NS_ASSERTION(!mExtraClipUsed, "mExtraClip already used"); +#ifdef DEBUG mExtraClipUsed = true; +#endif mState.ClipContainingBlockDescendants(aRect, aRadii, mExtraClip); } protected: DisplayItemClip mExtraClip; - DebugOnly mExtraClipUsed; +#ifdef DEBUG + bool mExtraClipUsed; +#endif }; } // namespace mozilla diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 0422156345..5fae8f90b3 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -1365,7 +1365,9 @@ protected: ContainerLayer* mContainerLayer; nsRect mContainerBounds; const DisplayItemScrollClip* mContainerScrollClip; - DebugOnly mAccumulatedChildBounds; +#ifdef DEBUG + nsRect mAccumulatedChildBounds; +#endif ContainerLayerParameters mParameters; /** * The region of PaintedLayers that should be invalidated every time @@ -3727,7 +3729,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) } } - int32_t maxLayers = nsDisplayItem::MaxActiveLayers(); + int32_t maxLayers = gfxPrefs::MaxActiveLayers(); int layerCount = 0; nsDisplayList savedItems; @@ -3801,19 +3803,6 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) } const DisplayItemScrollClip* itemScrollClip = item->ScrollClip(); - if (itemType == nsDisplayItem::TYPE_OPACITY && layerState == LAYER_INACTIVE) { - // This is an unfortunate hack. For opacity items, we usually want to - // apply clips and scroll transforms to their descendants. However, if - // an opacity is inactive and gets painted into a PaintedLayer, we can't - // apply async scrolling offsets to the inactive layer manager contents; - // instead, we need to move the opacity item itself, by moving the - // PaintedLayer that it gets painted into. - itemScrollClip = InnermostScrollClipApplicableToAGR( - static_cast(item)->ScrollClipForSameAGRChildren(), - animatedGeometryRootForClip); - item->SetScrollClip(itemScrollClip); - item->UpdateBounds(mBuilder); - } // Now we need to separate the item's scroll clip chain into those scroll // clips that can be applied to the whole layer (i.e. to all items // sharing the item's animated geometry root), and those that need to be @@ -3878,11 +3867,17 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) } } bounds = fixedToViewportClip.ApplyNonRoundedIntersection(bounds); - for (const DisplayItemScrollClip* scrollClip = itemScrollClip; - scrollClip && scrollClip != mContainerScrollClip; - scrollClip = scrollClip->mParent) { - if (scrollClip->mClip) { - bounds = scrollClip->mClip->ApplyNonRoundedIntersection(bounds); + if (!bounds.IsEmpty()) { + for (const DisplayItemScrollClip* scrollClip = itemScrollClip; + scrollClip && scrollClip != mContainerScrollClip; + scrollClip = scrollClip->mParent) { + if (scrollClip->mClip) { + if (scrollClip->mIsAsyncScrollable) { + bounds = scrollClip->mClip->GetClipRect(); + } else { + bounds = scrollClip->mClip->ApplyNonRoundedIntersection(bounds); + } + } } } ((nsRect&)mAccumulatedChildBounds).UnionRect(mAccumulatedChildBounds, bounds); @@ -3986,7 +3981,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) } mParameters.mBackgroundColor = uniformColor; - mParameters.mScrollClip = DisplayItemScrollClip::PickInnermost(agrScrollClip, mContainerScrollClip); + mParameters.mScrollClip = agrScrollClip; // Just use its layer. // Set layerContentsVisibleRect.width/height to -1 to indicate we diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 425a365fe6..ede9a1c9b2 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -737,8 +737,7 @@ nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext, joinedBorderArea.width, joinedBorderArea.height); // start drawing - gfxContext* ctx = aRenderingContext.ThebesContext(); - ctx->Save(); + bool needToPopClip = false; if (::IsBoxDecorationSlice(aStyleBorder)) { if (joinedBorderArea.IsEqualEdges(aBorderArea)) { @@ -747,10 +746,11 @@ nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext, } else { // We're drawing borders around the joined continuation boxes so we need // to clip that to the slice that we want for this frame. - aRenderingContext.ThebesContext()-> - Clip(NSRectToSnappedRect(aBorderArea, - aForFrame->PresContext()->AppUnitsPerDevPixel(), - aDrawTarget)); + aDrawTarget.PushClipRect( + NSRectToSnappedRect(aBorderArea, + aForFrame->PresContext()->AppUnitsPerDevPixel(), + aDrawTarget)); + needToPopClip = true; } } else { MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea), @@ -807,7 +807,9 @@ nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext, bgColor); br.DrawBorders(); - ctx->Restore(); + if (needToPopClip) { + aDrawTarget.PopClip(); + } PrintAsStringNewline(); @@ -1650,7 +1652,8 @@ nsCSSRendering::PaintBackground(nsPresContext* aPresContext, const nsRect& aBorderArea, uint32_t aFlags, nsRect* aBGClipRect, - int32_t aLayer) + int32_t aLayer, + CompositionOp aCompositonOp) { PROFILER_LABEL("nsCSSRendering", "PaintBackground", js::ProfileEntry::Category::GRAPHICS); @@ -1680,7 +1683,7 @@ nsCSSRendering::PaintBackground(nsPresContext* aPresContext, return PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea, sc, *aForFrame->StyleBorder(), aFlags, - aBGClipRect, aLayer); + aBGClipRect, aLayer, aCompositonOp); } static bool @@ -2860,11 +2863,16 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext, const nsStyleBorder& aBorder, uint32_t aFlags, nsRect* aBGClipRect, - int32_t aLayer) + int32_t aLayer, + CompositionOp aCompositonOp) { NS_PRECONDITION(aForFrame, "Frame is expected to be provided to PaintBackground"); + // If we're drawing all layers, aCompositonOp is ignored, so make sure that + // it was left at its default value. + MOZ_ASSERT_IF(aLayer == -1, aCompositonOp == CompositionOp::OP_OVER); + DrawResult result = DrawResult::SUCCESS; // Check to see if we have an appearance defined. If so, we let the theme @@ -3035,20 +3043,24 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext, } if ((aLayer < 0 || i == (uint32_t)startLayer) && !clipState.mDirtyRectGfx.IsEmpty()) { + // When we're drawing a single layer, use the specified composition op, + // otherwise get the compositon op from the image layer. + CompositionOp co = (aLayer >= 0) ? aCompositonOp : + (paintMask ? GetGFXCompositeMode(layer.mComposite) : + GetGFXBlendMode(layer.mBlendMode)); nsBackgroundLayerState state = PrepareImageLayer(aPresContext, aForFrame, aFlags, paintBorderArea, clipState.mBGClipArea, - layer, paintMask); + layer, co); result &= state.mImageRenderer.PrepareResult(); if (!state.mFillArea.IsEmpty()) { // Always using OP_OVER mode while drawing the bottom mask layer. bool isBottomMaskLayer = paintMask ? (i == (layers.mImageCount - 1)) : false; - if (state.mCompositionOp != CompositionOp::OP_OVER && - !isBottomMaskLayer) { + if (co != CompositionOp::OP_OVER && !isBottomMaskLayer) { NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER, "It is assumed the initial op is OP_OVER, when it is restored later"); - ctx->SetOp(state.mCompositionOp); + ctx->SetOp(co); } result &= @@ -3058,7 +3070,7 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext, clipState.mDirtyRect, state.mRepeatSize); - if (state.mCompositionOp != CompositionOp::OP_OVER) { + if (co != CompositionOp::OP_OVER) { ctx->SetOp(CompositionOp::OP_OVER); } } @@ -3300,7 +3312,7 @@ nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext, const nsRect& aBorderArea, const nsRect& aBGClipRect, const nsStyleImageLayers::Layer& aLayer, - bool aMask) + CompositionOp aCompositonOp) { /* * The properties we need to keep in mind when drawing style image @@ -3493,9 +3505,6 @@ nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext, state.mFillArea.IntersectRect(state.mFillArea, bgClipRect); - state.mCompositionOp = aMask ? GetGFXCompositeMode(aLayer.mComposite) : - GetGFXBlendMode(aLayer.mBlendMode); - return state; } @@ -3808,7 +3817,16 @@ DrawBorderImage(nsPresContext* aPresContext, nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]); nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]); + if (subArea.IsEmpty()) + continue; + nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel()); + // intrinsicSize.CanComputeConcreteSize() return false means we can not + // read intrinsic size from aStyleBorder.mBorderImageSource. + // In this condition, we pass imageSize(a resolved size comes from + // default sizing algorithm) to renderer as the viewport size. + Maybe svgViewportSize = intrinsicSize.CanComputeConcreteSize() ? + Nothing() : Some(imageSize); result &= renderer.DrawBorderImageComponent(aPresContext, @@ -3818,7 +3836,8 @@ DrawBorderImage(nsPresContext* aPresContext, intSubArea.width, intSubArea.height), fillStyleH, fillStyleV, - unitSize, j * (RIGHT + 1) + i); + unitSize, j * (RIGHT + 1) + i, + svgViewportSize); } } @@ -4896,7 +4915,9 @@ nsImageRenderer::PrepareImage() // The cropped image is identical to the source image mImageContainer.swap(srcImage); } else { - nsCOMPtr subImage = ImageOps::Clip(srcImage, actualCropRect); + nsCOMPtr subImage = ImageOps::Clip(srcImage, + actualCropRect, + Nothing()); mImageContainer.swap(subImage); } } @@ -5368,7 +5389,8 @@ nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext, uint8_t aHFill, uint8_t aVFill, const nsSize& aUnitSize, - uint8_t aIndex) + uint8_t aIndex, + const Maybe& aSVGViewportSize) { if (!IsReady()) { NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me"); @@ -5381,11 +5403,19 @@ nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext, if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) { nsCOMPtr subImage; + // To draw one portion of an image into a border component, we stretch that + // portion to match the size of that border component and then draw onto. + // However, preserveAspectRatio attribute of a SVG image may break this rule. + // To get correct rendering result, we add + // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore + // preserveAspectRatio attribute, and always do non-uniform stretch. + uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) | + imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE; // Retrieve or create the subimage we'll draw. nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height); if (mType == eStyleImageType_Image) { if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) { - subImage = ImageOps::Clip(mImageContainer, srcRect); + subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize); mImage->SetSubImage(aIndex, subImage); } } else { @@ -5405,9 +5435,12 @@ nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext, } nsCOMPtr image(ImageOps::CreateFromDrawable(drawable)); - subImage = ImageOps::Clip(image, srcRect); + subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize); } + MOZ_ASSERT_IF(aSVGViewportSize, + subImage->GetType() == imgIContainer::TYPE_VECTOR); + Filter filter = nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame); if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) { @@ -5417,7 +5450,7 @@ nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext, filter, aFill, aDirtyRect, nullptr, - ConvertImageRendererToDrawFlags(mFlags)); + drawFlags); } nsRect tile = ComputeTile(aFill, aHFill, aVFill, aUnitSize); @@ -5426,7 +5459,7 @@ nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext, subImage, filter, tile, aFill, tile.TopLeft(), aDirtyRect, - ConvertImageRendererToDrawFlags(mFlags)); + drawFlags); } nsRect destTile = RequiresScaling(aFill, aHFill, aVFill, aUnitSize) diff --git a/layout/base/nsCSSRendering.h b/layout/base/nsCSSRendering.h index b546768930..2ddb05e5b5 100644 --- a/layout/base/nsCSSRendering.h +++ b/layout/base/nsCSSRendering.h @@ -232,6 +232,10 @@ public: * aIndex identifies the component: 0 1 2 * 3 4 5 * 6 7 8 + * aSVGViewportSize The image size evaluated by default sizing algorithm. + * Pass Nothing() if we can read a valid viewport size or aspect-ratio from + * the drawing image directly, otherwise, pass Some() with viewport size + * evaluated from default sizing algorithm. */ DrawResult DrawBorderImageComponent(nsPresContext* aPresContext, @@ -242,7 +246,8 @@ public: uint8_t aHFill, uint8_t aVFill, const nsSize& aUnitSize, - uint8_t aIndex); + uint8_t aIndex, + const mozilla::Maybe& aSVGViewportSize); bool IsRasterImage(); bool IsAnimatedImage(); @@ -282,7 +287,7 @@ private: const mozilla::CSSIntRect& aSrc); /** - * Helper method for creating a gfxDrawable from mPaintServerFrame or + * Helper method for creating a gfxDrawable from mPaintServerFrame or * mImageElementSurface. * Requires mType is eStyleImageType_Element. * Returns null if we cannot create the drawable. @@ -317,7 +322,6 @@ struct nsBackgroundLayerState { nsBackgroundLayerState(nsIFrame* aForFrame, const nsStyleImage* aImage, uint32_t aFlags) : mImageRenderer(aForFrame, aImage, aFlags) - , mCompositionOp(CompositionOp::OP_OVER) {} /** @@ -347,10 +351,6 @@ struct nsBackgroundLayerState { * 'space' keyword, which is the image size plus spacing. */ nsSize mRepeatSize; - /** - * The composition operation that the image should use. - */ - CompositionOp mCompositionOp; }; struct nsCSSRendering { @@ -550,7 +550,7 @@ struct nsCSSRendering { const nsRect& aBorderArea, const nsRect& aBGClipRect, const nsStyleImageLayers::Layer& aLayer, - bool aMask = false); + CompositionOp aCompositionOp = CompositionOp::OP_OVER); struct ImageLayerClipState { nsRect mBGClipArea; // Affected by mClippedRadii @@ -607,7 +607,8 @@ struct nsCSSRendering { const nsRect& aBorderArea, uint32_t aFlags, nsRect* aBGClipRect = nullptr, - int32_t aLayer = -1); + int32_t aLayer = -1, + CompositionOp aCompositionOp = CompositionOp::OP_OVER); /** @@ -618,6 +619,9 @@ struct nsCSSRendering { * The default value for aLayer, -1, means that all layers will be painted. * The background color will only be painted if the back-most layer is also * being painted. + * aCompositionOp is only respected if a single layer is specified (aLayer != -1). + * If all layers are painted, the image layer's blend mode (or the mask + * layer's composition mode) will be used. */ static DrawResult PaintBackgroundWithSC(nsPresContext* aPresContext, nsRenderingContext& aRenderingContext, @@ -628,7 +632,8 @@ struct nsCSSRendering { const nsStyleBorder& aBorder, uint32_t aFlags, nsRect* aBGClipRect = nullptr, - int32_t aLayer = -1); + int32_t aLayer = -1, + CompositionOp aCompositionOp = CompositionOp::OP_OVER); /** * Returns the rectangle covered by the given background layer image, taking diff --git a/layout/base/nsDisplayItemTypesList.h b/layout/base/nsDisplayItemTypesList.h index c0c883dc90..a53bed88ff 100644 --- a/layout/base/nsDisplayItemTypesList.h +++ b/layout/base/nsDisplayItemTypesList.h @@ -4,6 +4,7 @@ DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND) DECLARE_DISPLAY_ITEM_TYPE(THEMED_BACKGROUND) DECLARE_DISPLAY_ITEM_TYPE_FLAGS(BACKGROUND_COLOR,TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(BLEND_CONTAINER) +DECLARE_DISPLAY_ITEM_TYPE(BLEND_MODE) DECLARE_DISPLAY_ITEM_TYPE(BORDER) DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_OUTER) DECLARE_DISPLAY_ITEM_TYPE(BOX_SHADOW_INNER) @@ -32,7 +33,6 @@ DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BLANK) DECLARE_DISPLAY_ITEM_TYPE(HEADER_FOOTER) DECLARE_DISPLAY_ITEM_TYPE(IMAGE) DECLARE_DISPLAY_ITEM_TYPE(LIST_FOCUS) -DECLARE_DISPLAY_ITEM_TYPE(MIX_BLEND_MODE) DECLARE_DISPLAY_ITEM_TYPE(OPACITY) DECLARE_DISPLAY_ITEM_TYPE(OPTION_EVENT_GRABBER) DECLARE_DISPLAY_ITEM_TYPE(OUTLINE) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 26b4beed10..399a7a896f 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -52,6 +52,7 @@ #include "ImageContainer.h" #include "nsCanvasFrame.h" #include "StickyScrollContainer.h" +#include "mozilla/AnimationPerformanceWarning.h" #include "mozilla/AnimationUtils.h" #include "mozilla/EffectCompositor.h" #include "mozilla/EventStates.h" @@ -399,6 +400,7 @@ AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty, - timing.mDelay; animation->duration() = computedTiming.mDuration; animation->iterations() = computedTiming.mIterations; + animation->iterationStart() = computedTiming.mIterationStart; animation->direction() = static_cast(timing.mDirection); animation->property() = aProperty.mProperty; animation->playbackRate() = aAnimation->PlaybackRate(); @@ -605,15 +607,17 @@ nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame, mCurrentReferenceFrame(aReferenceFrame), mCurrentAGR(&mRootAGR), mRootAGR(aReferenceFrame, nullptr), + mUsedAGRBudget(0), mDirtyRect(-1,-1,-1,-1), mGlassDisplayItem(nullptr), - mPendingScrollInfoItems(nullptr), - mCommittedScrollInfoItems(nullptr), + mScrollInfoItemsForHoisting(nullptr), mMode(aMode), mCurrentScrollParentId(FrameMetrics::NULL_SCROLL_ID), mCurrentScrollbarTarget(FrameMetrics::NULL_SCROLL_ID), mCurrentScrollbarFlags(0), mPerspectiveItemIndex(0), + mSVGEffectsBuildingDepth(0), + mContainsBlendMode(false), mIsBuildingScrollbar(false), mCurrentScrollbarWillHaveLayer(false), mBuildCaret(aBuildCaret), @@ -672,12 +676,6 @@ static void MarkFrameForDisplay(nsIFrame* aFrame, nsIFrame* aStopAtFrame) { } } -void nsDisplayListBuilder::SetContainsBlendMode(uint8_t aBlendMode) -{ - MOZ_ASSERT(aBlendMode != NS_STYLE_BLEND_NORMAL); - mContainedBlendModes += nsCSSRendering::GetGFXBlendMode(aBlendMode); -} - bool nsDisplayListBuilder::NeedToForceTransparentSurfaceForItem(nsDisplayItem* aItem) { return aItem == mGlassDisplayItem || aItem->ClearsBackground(); @@ -1011,14 +1009,13 @@ nsDisplayListBuilder::AllocateDisplayItemClip(const DisplayItemClip& aOriginal) DisplayItemScrollClip* nsDisplayListBuilder::AllocateDisplayItemScrollClip(const DisplayItemScrollClip* aParent, - const DisplayItemScrollClip* aCrossStackingContextParent, nsIScrollableFrame* aScrollableFrame, const DisplayItemClip* aClip, bool aIsAsyncScrollable) { void* p = Allocate(sizeof(DisplayItemScrollClip)); DisplayItemScrollClip* c = - new (p) DisplayItemScrollClip(aParent, aCrossStackingContextParent, aScrollableFrame, aClip, aIsAsyncScrollable); + new (p) DisplayItemScrollClip(aParent, aScrollableFrame, aClip, aIsAsyncScrollable); mScrollClipsToDestroy.AppendElement(c); return c; } @@ -1086,8 +1083,12 @@ nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParen if (nsLayoutUtils::IsPopup(aFrame)) return true; - if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(aFrame)) - return true; + if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(aFrame)) { + const bool inBudget = AddToAGRBudget(aFrame); + if (inBudget) { + return true; + } + } if (!aFrame->GetParent() && nsLayoutUtils::ViewportHasDisplayPort(aFrame->PresContext())) { // Viewport frames in a display port need to be animated geometry roots @@ -1254,7 +1255,7 @@ nsDisplayListBuilder::GetWindowDraggingRegion() const } const uint32_t gWillChangeAreaMultiplier = 3; -static uint32_t GetWillChangeCost(const nsSize& aSize) { +static uint32_t GetLayerizationCost(const nsSize& aSize) { // There's significant overhead for each layer created from Gecko // (IPC+Shared Objects) and from the backend (like an OpenGL texture). // Therefore we set a minimum cost threshold of a 64x64 area. @@ -1271,7 +1272,7 @@ static uint32_t GetWillChangeCost(const nsSize& aSize) { bool nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize) { - if (mBudgetSet.Contains(aFrame)) { + if (mWillChangeBudgetSet.Contains(aFrame)) { return true; // Already accounted } @@ -1287,14 +1288,14 @@ nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame, uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) * nsPresContext::AppUnitsToIntCSSPixels(area.height); - uint32_t cost = GetWillChangeCost(aSize); + uint32_t cost = GetLayerizationCost(aSize); bool onBudget = (budget.mBudget + cost) / gWillChangeAreaMultiplier < budgetLimit; if (onBudget) { budget.mBudget += cost; mWillChangeBudget.Put(key, budget); - mBudgetSet.PutEntry(aFrame); + mWillChangeBudgetSet.PutEntry(aFrame); } return onBudget; @@ -1307,7 +1308,7 @@ nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame, if (!onBudget) { nsString usageStr; - usageStr.AppendInt(GetWillChangeCost(aSize)); + usageStr.AppendInt(GetLayerizationCost(aSize)); nsString multiplierStr; multiplierStr.AppendInt(gWillChangeAreaMultiplier); @@ -1326,27 +1327,69 @@ nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame, return onBudget; } -nsDisplayList* -nsDisplayListBuilder::EnterScrollInfoItemHoisting(nsDisplayList* aScrollInfoItemStorage) +#ifdef MOZ_GFX_OPTIMIZE_MOBILE +const float gAGRBudgetAreaMultiplier = 0.3; +#else +const float gAGRBudgetAreaMultiplier = 3.0; +#endif + +bool +nsDisplayListBuilder::AddToAGRBudget(nsIFrame* aFrame) { - MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting()); - nsDisplayList* old = mPendingScrollInfoItems; - mPendingScrollInfoItems = aScrollInfoItemStorage; - return old; + if (mAGRBudgetSet.Contains(aFrame)) { + return true; + } + + const nsPresContext* presContext = aFrame->PresContext()->GetRootPresContext(); + if (!presContext) { + return false; + } + + const nsRect area = presContext->GetVisibleArea(); + const uint32_t budgetLimit = gAGRBudgetAreaMultiplier * + nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + + const uint32_t cost = GetLayerizationCost(aFrame->GetSize()); + const bool onBudget = mUsedAGRBudget + cost < budgetLimit; + + if (onBudget) { + mUsedAGRBudget += cost; + mAGRBudgetSet.PutEntry(aFrame); + } + + return onBudget; } void -nsDisplayListBuilder::LeaveScrollInfoItemHoisting(nsDisplayList* aScrollInfoItemStorage) +nsDisplayListBuilder::EnterSVGEffectsContents(nsDisplayList* aHoistedItemsStorage) { - MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting()); - mPendingScrollInfoItems = aScrollInfoItemStorage; + MOZ_ASSERT(mSVGEffectsBuildingDepth >= 0); + MOZ_ASSERT(aHoistedItemsStorage); + if (mSVGEffectsBuildingDepth == 0) { + MOZ_ASSERT(!mScrollInfoItemsForHoisting); + mScrollInfoItemsForHoisting = aHoistedItemsStorage; + } + mSVGEffectsBuildingDepth++; +} + +void +nsDisplayListBuilder::ExitSVGEffectsContents() +{ + mSVGEffectsBuildingDepth--; + MOZ_ASSERT(mSVGEffectsBuildingDepth >= 0); + MOZ_ASSERT(mScrollInfoItemsForHoisting); + if (mSVGEffectsBuildingDepth == 0) { + mScrollInfoItemsForHoisting = nullptr; + } } void nsDisplayListBuilder::AppendNewScrollInfoItemForHoisting(nsDisplayScrollInfoLayer* aScrollInfoItem) { MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting()); - mPendingScrollInfoItems->AppendNewToTop(aScrollInfoItem); + MOZ_ASSERT(mScrollInfoItemsForHoisting); + mScrollInfoItemsForHoisting->AppendNewToTop(aScrollInfoItem); } void @@ -1371,14 +1414,25 @@ bool nsDisplayListBuilder::IsBuildingLayerEventRegions() { if (mMode == PAINTING) { - // Note: this is the only place that gets to query LayoutEventRegionsEnabled - // 'directly' - other code should call this function. + // Note: this function and LayerEventRegionsEnabled are the only places + // that get to query LayoutEventRegionsEnabled 'directly' - other code + // should call this function. return gfxPrefs::LayoutEventRegionsEnabledDoNotUseDirectly() || mAsyncPanZoomEnabled; } return false; } +/* static */ bool +nsDisplayListBuilder::LayerEventRegionsEnabled() +{ + // Note: this function and IsBuildingLayerEventRegions are the only places + // that get to query LayoutEventRegionsEnabled 'directly' - other code + // should call this function. + return gfxPrefs::LayoutEventRegionsEnabledDoNotUseDirectly() || + gfxPlatform::AsyncPanZoomEnabled(); +} + void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const { aDestination.BorderBackground()->AppendToTop(BorderBackground()); @@ -1411,7 +1465,21 @@ nsDisplayList::GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder, const DisplayItemScrollClip* aIncludeScrollClipsUpTo) const { nsRect bounds; for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) { - bounds.UnionRect(bounds, i->GetScrollClippedBoundsUpTo(aBuilder, aIncludeScrollClipsUpTo)); + nsRect r = i->GetClippedBounds(aBuilder); + if (r.IsEmpty()) { + continue; + } + for (auto* sc = i->ScrollClip(); sc && sc != aIncludeScrollClipsUpTo; sc = sc->mParent) { + if (sc->mClip && sc->mClip->HasClip()) { + if (sc->mIsAsyncScrollable) { + // Assume the item can move anywhere in the scroll clip's clip rect. + r = sc->mClip->GetClipRect(); + } else { + r = sc->mClip->ApplyNonRoundedIntersection(r); + } + } + } + bounds.UnionRect(bounds, r); } return bounds; } @@ -1934,7 +2002,8 @@ void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, bool same3DContext = (itemType == nsDisplayItem::TYPE_TRANSFORM && static_cast(item)->IsParticipating3DContext()) || - (itemType == nsDisplayItem::TYPE_PERSPECTIVE && + ((itemType == nsDisplayItem::TYPE_PERSPECTIVE || + itemType == nsDisplayItem::TYPE_OPACITY) && static_cast(item)->Frame()->Extend3DContext()); if (same3DContext && !static_cast(item)->IsLeafOf3DContext()) { @@ -2092,9 +2161,14 @@ void nsDisplayList::Sort(SortLEQ aCmp, void* aClosure) { } nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame, aBuilder->ClipState().GetCurrentInnermostScrollClip()) +{} + +nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const DisplayItemScrollClip* aScrollClip) : mFrame(aFrame) , mClip(aBuilder->ClipState().GetCurrentCombinedClip(aBuilder)) - , mScrollClip(aBuilder->ClipState().GetCurrentInnermostScrollClip()) + , mScrollClip(aScrollClip) , mAnimatedGeometryRoot(nullptr) #ifdef MOZ_DUMP_PAINTING , mPainted(false) @@ -2128,20 +2202,6 @@ nsDisplayItem::ForceActiveLayers() return sForce; } -/* static */ int32_t -nsDisplayItem::MaxActiveLayers() -{ - static int32_t sMaxLayers = false; - static bool sMaxLayersCached = false; - - if (!sMaxLayersCached) { - Preferences::AddIntVarCache(&sMaxLayers, "layers.max-active", -1); - sMaxLayersCached = true; - } - - return sMaxLayers; -} - static int32_t ZIndexForFrame(nsIFrame* aFrame) { if (!aFrame->IsAbsPosContaininingBlock() && !aFrame->IsFlexOrGridItem()) @@ -2199,21 +2259,6 @@ nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder) return GetClip().ApplyNonRoundedIntersection(r); } -nsRect -nsDisplayItem::GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder, - const DisplayItemScrollClip* aIncludeScrollClipsUpTo) -{ - nsRect r = GetClippedBounds(aBuilder); - for (const DisplayItemScrollClip* sc = mScrollClip; - sc && sc != aIncludeScrollClipsUpTo; - sc = sc->mParent) { - if (sc->mClip) { - r = sc->mClip->ApplyNonRoundedIntersection(r); - } - } - return r; -} - nsRect nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { @@ -2425,6 +2470,9 @@ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuil aList->AppendToTop(&bgItemList); return false; } + + const DisplayItemScrollClip* scrollClip = + aBuilder->ClipState().GetCurrentInnermostScrollClip(); bool needBlendContainer = false; @@ -2446,20 +2494,29 @@ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuil layer, willPaintBorder); } + nsDisplayList thisItemList; nsDisplayBackgroundImage* bgItem = new (aBuilder) nsDisplayBackgroundImage(aBuilder, aFrame, i, bg); if (bgItem->ShouldFixToViewport(aBuilder)) { - bgItemList.AppendNewToTop( + thisItemList.AppendNewToTop( nsDisplayFixedPosition::CreateForFixedBackground(aBuilder, aFrame, bgItem, i)); } else { - bgItemList.AppendNewToTop(bgItem); + thisItemList.AppendNewToTop(bgItem); } + + if (bg->mImage.mLayers[i].mBlendMode != NS_STYLE_BLEND_NORMAL) { + thisItemList.AppendNewToTop( + new (aBuilder) nsDisplayBlendMode(aBuilder, aFrame, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, + scrollClip, i + 1)); + } + bgItemList.AppendToTop(&thisItemList); } if (needBlendContainer) { bgItemList.AppendNewToTop( - new (aBuilder) nsDisplayBlendContainer(aBuilder, aFrame, &bgItemList)); + new (aBuilder) nsDisplayBlendContainer(aBuilder, aFrame, &bgItemList, scrollClip)); } aList->AppendToTop(&bgItemList); @@ -2752,10 +2809,20 @@ nsDisplayBackgroundImage::ConfigureLayer(ImageLayer* aLayer, // aParameters.Offset() is always zero. MOZ_ASSERT(aParameters.Offset() == LayerIntPoint(0,0)); + // It's possible (for example, due to downscale-during-decode) that the + // ImageContainer this ImageLayer is holding has a different size from the + // intrinsic size of the image. For this reason we compute the transform using + // the ImageContainer's size rather than the image's intrinsic size. + // XXX(seth): In reality, since the size of the ImageContainer may change + // asynchronously, this is not enough. Bug 1183378 will provide a more + // complete fix, but this solution is safe in more cases than simply relying + // on the intrinsic size. + IntSize containerSize = aLayer->GetContainer()->GetCurrentSize(); + const LayoutDevicePoint p = mImageLayerDestRect.TopLeft(); Matrix transform = Matrix::Translation(p.x, p.y); - transform.PreScale(mImageLayerDestRect.width / imageWidth, - mImageLayerDestRect.height / imageHeight); + transform.PreScale(mImageLayerDestRect.width / containerSize.width, + mImageLayerDestRect.height / containerSize.height); aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); } @@ -2923,7 +2990,8 @@ nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder, nsCSSRendering::PaintBackground(mFrame->PresContext(), *aCtx, mFrame, aBounds, nsRect(offset, mFrame->GetSize()), - flags, aClipRect, mLayer); + flags, aClipRect, mLayer, + CompositionOp::OP_OVER); nsDisplayBackgroundGeometry::UpdateDrawResult(this, result); } @@ -3020,13 +3088,6 @@ nsDisplayBackgroundImage::GetPerFrameKey() nsDisplayItem::GetPerFrameKey(); } -void -nsDisplayBackgroundImage::MarkBoundsAsVisible(nsDisplayListBuilder* aBuilder) -{ - bool snap; - mVisibleRect = GetBounds(aBuilder, &snap); -} - nsDisplayThemedBackground::nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) : nsDisplayItem(aBuilder, aFrame) @@ -3802,7 +3863,14 @@ nsDisplayBoxShadowInner::ComputeVisibility(nsDisplayListBuilder* aBuilder, nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList) - : nsDisplayItem(aBuilder, aFrame) + : nsDisplayWrapList(aBuilder, aFrame, aList, + aBuilder->ClipState().GetCurrentInnermostScrollClip()) +{} + +nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const DisplayItemScrollClip* aScrollClip) + : nsDisplayItem(aBuilder, aFrame, aScrollClip) , mOverrideZIndex(0) , mHasZIndexOverride(false) { @@ -3945,6 +4013,15 @@ RequiredLayerStateForChildren(nsDisplayListBuilder* aBuilder, } LayerState state = i->GetLayerState(aBuilder, aManager, aParameters); + if (state == LAYER_ACTIVE && i->GetType() == nsDisplayItem::TYPE_BLEND_MODE) { + // nsDisplayBlendMode always returns LAYER_ACTIVE to ensure that the + // blending operation happens in the intermediate surface of its parent + // display item (usually an nsDisplayBlendContainer). But this does not + // mean that it needs all its ancestor display items to become active. + // So we ignore its layer state and look at its children instead. + state = RequiredLayerStateForChildren(aBuilder, aManager, aParameters, + *i->GetSameCoordinateSystemChildren(), i->GetAnimatedGeometryRoot()); + } if ((state == LAYER_ACTIVE || state == LAYER_ACTIVE_FORCE) && state > result) { result = state; @@ -4062,12 +4139,12 @@ nsresult nsDisplayWrapper::WrapListsInPlace(nsDisplayListBuilder* aBuilder, nsDisplayOpacity::nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, - const DisplayItemScrollClip* aScrollClipForSameAGRChildren, + const DisplayItemScrollClip* aScrollClip, bool aForEventsOnly) - : nsDisplayWrapList(aBuilder, aFrame, aList) - , mScrollClipForSameAGRChildren(aScrollClipForSameAGRChildren) + : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip) , mOpacity(aFrame->StyleDisplay()->mOpacity) , mForEventsOnly(aForEventsOnly) + , mParticipatesInPreserve3D(false) { MOZ_COUNT_CTOR(nsDisplayOpacity); } @@ -4105,6 +4182,12 @@ nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder, nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(container, aBuilder, this, mFrame, eCSSProperty_opacity); + + if (mParticipatesInPreserve3D) { + container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_EXTEND_3D_CONTEXT); + } else { + container->SetContentFlags(container->GetContentFlags() & ~Layer::CONTENT_EXTEND_3D_CONTEXT); + } return container.forget(); } @@ -4262,19 +4345,25 @@ nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream) aStream << " (opacity " << mOpacity << ")"; } -nsDisplayMixBlendMode::nsDisplayMixBlendMode(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, nsDisplayList* aList) -: nsDisplayWrapList(aBuilder, aFrame, aList) { - MOZ_COUNT_CTOR(nsDisplayMixBlendMode); +nsDisplayBlendMode::nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + uint8_t aBlendMode, + const DisplayItemScrollClip* aScrollClip, + uint32_t aIndex) + : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip) + , mBlendMode(aBlendMode) + , mIndex(aIndex) +{ + MOZ_COUNT_CTOR(nsDisplayBlendMode); } #ifdef NS_BUILD_REFCNT_LOGGING -nsDisplayMixBlendMode::~nsDisplayMixBlendMode() { - MOZ_COUNT_DTOR(nsDisplayMixBlendMode); +nsDisplayBlendMode::~nsDisplayBlendMode() { + MOZ_COUNT_DTOR(nsDisplayBlendMode); } #endif -nsRegion nsDisplayMixBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, +nsRegion nsDisplayBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) { *aSnap = false; // We are never considered opaque @@ -4282,20 +4371,16 @@ nsRegion nsDisplayMixBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, } LayerState -nsDisplayMixBlendMode::GetLayerState(nsDisplayListBuilder* aBuilder, +nsDisplayBlendMode::GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) { - CompositionOp op = - nsCSSRendering::GetGFXBlendMode(mFrame->StyleDisplay()->mMixBlendMode); - return aManager->SupportsMixBlendMode(op) - ? LAYER_ACTIVE - : LAYER_INACTIVE; + return LAYER_ACTIVE; } -// nsDisplayMixBlendMode uses layers for rendering +// nsDisplayBlendMode uses layers for rendering already_AddRefed -nsDisplayMixBlendMode::BuildLayer(nsDisplayListBuilder* aBuilder, +nsDisplayBlendMode::BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { ContainerLayerParameters newContainerParameters = aContainerParameters; @@ -4308,12 +4393,12 @@ nsDisplayMixBlendMode::BuildLayer(nsDisplayListBuilder* aBuilder, return nullptr; } - container->SetMixBlendMode(nsCSSRendering::GetGFXBlendMode(mFrame->StyleDisplay()->mMixBlendMode)); + container->SetMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode)); return container.forget(); } -bool nsDisplayMixBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder, +bool nsDisplayBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder, nsRegion* aVisibleRegion) { // Our children are need their backdrop so we should not allow them to subtract // area from aVisibleRegion. We do need to find out what is visible under @@ -4326,38 +4411,29 @@ bool nsDisplayMixBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder, return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren); } -bool nsDisplayMixBlendMode::TryMerge(nsDisplayItem* aItem) { - if (aItem->GetType() != TYPE_MIX_BLEND_MODE) +bool nsDisplayBlendMode::TryMerge(nsDisplayItem* aItem) { + if (aItem->GetType() != TYPE_BLEND_MODE) return false; + nsDisplayBlendMode* item = static_cast(aItem); // items for the same content element should be merged into a single // compositing group - // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity - if (aItem->Frame()->GetContent() != mFrame->GetContent()) + if (item->Frame()->GetContent() != mFrame->GetContent()) return false; - if (aItem->GetClip() != GetClip()) + if (item->mIndex != 0 || mIndex != 0) + return false; // don't merge background-blend-mode items + if (item->GetClip() != GetClip()) return false; - if (aItem->ScrollClip() != ScrollClip()) + if (item->ScrollClip() != ScrollClip()) return false; - MergeFromTrackingMergedFrames(static_cast(aItem)); + MergeFromTrackingMergedFrames(item); return true; } nsDisplayBlendContainer::nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, - BlendModeSet& aContainedBlendModes) - : nsDisplayWrapList(aBuilder, aFrame, aList) + const DisplayItemScrollClip* aScrollClip) + : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip) , mIndex(0) - , mContainedBlendModes(aContainedBlendModes) - , mCanBeActive(true) -{ - MOZ_COUNT_CTOR(nsDisplayBlendContainer); -} - -nsDisplayBlendContainer::nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, nsDisplayList* aList) - : nsDisplayWrapList(aBuilder, aFrame, aList) - , mIndex(1) - , mCanBeActive(false) { MOZ_COUNT_CTOR(nsDisplayBlendContainer); } @@ -4394,10 +4470,7 @@ nsDisplayBlendContainer::GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) { - if (mCanBeActive && aManager->SupportsMixBlendModes(mContainedBlendModes)) { - return mozilla::LAYER_ACTIVE; - } - return mozilla::LAYER_INACTIVE; + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, GetAnimatedGeometryRoot()); } bool nsDisplayBlendContainer::TryMerge(nsDisplayItem* aItem) { @@ -4704,7 +4777,6 @@ nsDisplayFixedPosition::CreateForFixedBackground(nsDisplayListBuilder* aBuilder, // fixed position item as well. aImage->SetClip(aBuilder, DisplayItemClip()); aImage->SetScrollClip(nullptr); - aImage->MarkBoundsAsVisible(aBuilder); nsDisplayList temp; temp.AppendToTop(aImage); @@ -4865,7 +4937,6 @@ nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer( , mScrollFrame(aScrollFrame) , mScrolledFrame(aScrolledFrame) , mScrollParentId(aBuilder->GetCurrentScrollParentId()) - , mIgnoreIfCompositorSupportsBlending(false) { #ifdef NS_BUILD_REFCNT_LOGGING MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer); @@ -4889,16 +4960,6 @@ nsDisplayScrollInfoLayer::BuildLayer(nsDisplayListBuilder* aBuilder, // cannot be layerized, and so needs to scroll synchronously. To handle those // cases, we still want to generate scrollinfo layers. - if (mIgnoreIfCompositorSupportsBlending) { - // This item was created pessimistically because, during display list - // building, we encountered a mix blend mode. If our layer manager - // supports compositing this mix blend mode, we don't actually need to - // create a scroll info layer. - if (aManager->SupportsMixBlendModes(mContainedBlendModes)) { - return nullptr; - } - } - ContainerLayerParameters params = aContainerParameters; if (mScrolledFrame->GetContent() && nsLayoutUtils::HasCriticalDisplayPort(mScrolledFrame->GetContent())) { @@ -4942,24 +5003,7 @@ nsDisplayScrollInfoLayer::ComputeFrameMetrics(Layer* aLayer, return UniquePtr(new FrameMetrics(metrics)); } -void -nsDisplayScrollInfoLayer::IgnoreIfCompositorSupportsBlending(BlendModeSet aBlendModes) -{ - mContainedBlendModes += aBlendModes; - mIgnoreIfCompositorSupportsBlending = true; -} -void -nsDisplayScrollInfoLayer::UnsetIgnoreIfCompositorSupportsBlending() -{ - mIgnoreIfCompositorSupportsBlending = false; -} - -bool -nsDisplayScrollInfoLayer::ContainedInMixBlendMode() const -{ - return mIgnoreIfCompositorSupportsBlending; -} void nsDisplayScrollInfoLayer::WriteDebugInfo(std::stringstream& aStream) @@ -5590,11 +5634,11 @@ nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) return true; } - if (nsLayoutUtils::IsAnimationLoggingEnabled()) { - nsCString message; - message.AppendLiteral("Performance warning: Async animation disabled because frame was not marked active for opacity animation"); - AnimationUtils::LogAsyncAnimationFailure(message, Frame()->GetContent()); - } + EffectCompositor::SetPerformanceWarning( + mFrame, eCSSProperty_transform, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::OpacityFrameInactive)); + return false; } @@ -5625,15 +5669,14 @@ nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) // pre-render even if we're out of will change budget. return true; } - DebugOnly prerender = ShouldPrerenderTransformedContent(aBuilder, mFrame, true); + DebugOnly prerender = ShouldPrerenderTransformedContent(aBuilder, mFrame); NS_ASSERTION(!prerender, "Something changed under us!"); return false; } /* static */ bool nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, - bool aLogAnimations) + nsIFrame* aFrame) { // Elements whose transform has been modified recently, or which // have a compositor-animated transform, can be prerendered. An element @@ -5642,11 +5685,11 @@ nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBui if (!ActiveLayerTracker::IsStyleMaybeAnimated(aFrame, eCSSProperty_transform) && !EffectCompositor::HasAnimationsForCompositor(aFrame, eCSSProperty_transform)) { - if (aLogAnimations) { - nsCString message; - message.AppendLiteral("Performance warning: Async animation disabled because frame was not marked active for transform animation"); - AnimationUtils::LogAsyncAnimationFailure(message, aFrame->GetContent()); - } + EffectCompositor::SetPerformanceWarning( + aFrame, eCSSProperty_transform, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::TransformFrameInactive)); + return false; } @@ -5665,27 +5708,22 @@ nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBui } } - if (aLogAnimations) { - nsRect visual = aFrame->GetVisualOverflowRect(); + nsRect visual = aFrame->GetVisualOverflowRect(); - nsCString message; - message.AppendLiteral("Performance warning: Async animation disabled because frame size ("); - message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(frameSize.width)); - message.AppendLiteral(", "); - message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(frameSize.height)); - message.AppendLiteral(") is bigger than the viewport ("); - message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(refSize.width)); - message.AppendLiteral(", "); - message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(refSize.height)); - message.AppendLiteral(") or the visual rectangle ("); - message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(visual.width)); - message.AppendLiteral(", "); - message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(visual.height)); - message.AppendLiteral(") is larger than the max allowable value ("); - message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(maxInAppUnits)); - message.Append(')'); - AnimationUtils::LogAsyncAnimationFailure(message, aFrame->GetContent()); - } + + EffectCompositor::SetPerformanceWarning( + aFrame, eCSSProperty_transform, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::ContentTooLarge, + { + nsPresContext::AppUnitsToIntCSSPixels(frameSize.width), + nsPresContext::AppUnitsToIntCSSPixels(frameSize.height), + nsPresContext::AppUnitsToIntCSSPixels(refSize.width), + nsPresContext::AppUnitsToIntCSSPixels(refSize.height), + nsPresContext::AppUnitsToIntCSSPixels(visual.width), + nsPresContext::AppUnitsToIntCSSPixels(visual.height), + nsPresContext::AppUnitsToIntCSSPixels(maxInAppUnits) + })); return false; } diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index eb7dcff0dc..c17b52c760 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -435,6 +435,7 @@ public: mLayerEventRegions = aItem; } bool IsBuildingLayerEventRegions(); + static bool LayerEventRegionsEnabled(); bool IsInsidePointerEventsNoneDoc() { return CurrentPresShellState()->mInsidePointerEventsNoneDoc; @@ -640,7 +641,6 @@ public: * automatically when the arena goes away. */ DisplayItemScrollClip* AllocateDisplayItemScrollClip(const DisplayItemScrollClip* aParent, - const DisplayItemScrollClip* aCrossStackingContextParent, nsIScrollableFrame* aScrollableFrame, const DisplayItemClip* aClip, bool aIsAsyncScrollable); @@ -1028,14 +1028,8 @@ public: * has a blend mode attached. We do this so we can insert a * nsDisplayBlendContainer in the parent stacking context. */ - void SetContainsBlendMode(uint8_t aBlendMode); - void SetContainsBlendModes(const BlendModeSet& aModes) { - mContainedBlendModes = aModes; - } - bool ContainsBlendMode() const { return !mContainedBlendModes.isEmpty(); } - BlendModeSet& ContainedBlendModes() { - return mContainedBlendModes; - } + void SetContainsBlendMode(bool aContainsBlendMode) { mContainsBlendMode = aContainsBlendMode; } + bool ContainsBlendMode() const { return mContainsBlendMode; } uint32_t AllocatePerspectiveItemIndex() { return mPerspectiveItemIndex++; } @@ -1056,26 +1050,11 @@ public: */ bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize); - void SetCommittedScrollInfoItemList(nsDisplayList* aScrollInfoItemStorage) { - mCommittedScrollInfoItems = aScrollInfoItemStorage; - } - nsDisplayList* CommittedScrollInfoItems() const { - return mCommittedScrollInfoItems; - } - bool ShouldBuildScrollInfoItemsForHoisting() const { - return IsPaintingToWindow(); - } + void EnterSVGEffectsContents(nsDisplayList* aHoistedItemsStorage); + void ExitSVGEffectsContents(); - // When building display lists for stacking contexts, we append scroll info - // items to a temporary list. If the stacking context would create an - // inactive layer, we commit these items to the final hoisted scroll items - // list. Otherwise, we propagate these items to the parent stacking - // context's list of pending scroll info items. - // - // EnterScrollInfoItemHoisting returns the parent stacking context's pending - // item list. - nsDisplayList* EnterScrollInfoItemHoisting(nsDisplayList* aScrollInfoItemStorage); - void LeaveScrollInfoItemHoisting(nsDisplayList* aScrollInfoItemStorage); + bool ShouldBuildScrollInfoItemsForHoisting() const + { return mSVGEffectsBuildingDepth > 0; } void AppendNewScrollInfoItemForHoisting(nsDisplayScrollInfoLayer* aScrollInfoItem); @@ -1164,6 +1143,13 @@ private: nsDataHashtable, AnimatedGeometryRoot*> mFrameToAnimatedGeometryRootMap; + /** + * Add the current frame to the AGR budget if possible and remember + * the outcome. Subsequent calls will return the same value as + * returned here. + */ + bool AddToAGRBudget(nsIFrame* aFrame); + struct PresShellState { nsIPresShell* mPresShell; nsIFrame* mCaretFrame; @@ -1217,7 +1203,12 @@ private: // Any frame listed in this set is already counted in the budget // and thus is in-budget. - nsTHashtable > mBudgetSet; + nsTHashtable > mWillChangeBudgetSet; + + // Area of animated geometry root budget already allocated + uint32_t mUsedAGRBudget; + // Set of frames already counted in budget + nsTHashtable > mAGRBudgetSet; // rects are relative to the frame's reference frame nsDataHashtable, nsRect> mDirtyRectForScrolledContents; @@ -1230,23 +1221,22 @@ private: LayoutDeviceIntRegion mWindowNoDraggingRegion; // The display item for the Windows window glass background, if any nsDisplayItem* mGlassDisplayItem; - // When encountering inactive layers, we need to hoist scroll info items - // above these layers so APZ can deliver events to content. Such scroll - // info items are considered "committed" to the final hoisting list. If - // no hoisting is needed immediately, it may be needed later if a blend - // mode is introduced in a higher stacking context, so we keep all scroll - // info items until the end of display list building. - nsDisplayList* mPendingScrollInfoItems; - nsDisplayList* mCommittedScrollInfoItems; + // A temporary list that we append scroll info items to while building + // display items for the contents of frames with SVG effects. + // Only non-null when ShouldBuildScrollInfoItemsForHoisting() is true. + // This is a pointer and not a real nsDisplayList value because the + // nsDisplayList class is defined below this class, so we can't use it here. + nsDisplayList* mScrollInfoItemsForHoisting; nsTArray mScrollClipsToDestroy; nsTArray mDisplayItemClipsToDestroy; Mode mMode; ViewID mCurrentScrollParentId; ViewID mCurrentScrollbarTarget; uint32_t mCurrentScrollbarFlags; - BlendModeSet mContainedBlendModes; Preserves3DContext mPreserves3DCtx; uint32_t mPerspectiveItemIndex; + int32_t mSVGEffectsBuildingDepth; + bool mContainsBlendMode; bool mIsBuildingScrollbar; bool mCurrentScrollbarWillHaveLayer; bool mBuildCaret; @@ -1324,6 +1314,8 @@ public: // This is never instantiated directly (it has pure virtual methods), so no // need to count constructors and destructors. nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); + nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const DisplayItemScrollClip* aScrollClip); /** * This constructor is only used in rare cases when we need to construct * temporary items. @@ -1431,17 +1423,6 @@ public: * account. */ nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder); - /** - * Returns the result of GetClippedBounds, intersected with the item's - * scroll clips. The item walks up its chain of scroll clips, *not* crossing - * stacking contexts, applying each scroll clip until aIncludeScrollClipsUpTo - * is reached. aIncludeScrollClipsUpTo is *not* applied. - * The intersection is approximate since rounded corners are not taking into - * account. - */ - nsRect GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder, - const DisplayItemScrollClip* aIncludeScrollClipsUpTo); - nsRect GetBorderRect() { return nsRect(ToReferenceFrame(), Frame()->GetSize()); } @@ -1581,12 +1562,6 @@ public: */ static bool ForceActiveLayers(); - /** - * Returns the maximum number of layers that should be created - * or -1 for no limit. Requires setting the pref layers.max-acitve. - */ - static int32_t MaxActiveLayers(); - /** * @return LAYER_NONE if BuildLayer will return null. In this case * there is no layer for the item, and Paint should be called instead @@ -2158,7 +2133,14 @@ public: */ nsRect GetBounds(nsDisplayListBuilder* aBuilder) const; /** - * Return the union of the scroll clipped bounds of all children. + * Return the union of the scroll clipped bounds of all children. To get the + * scroll clipped bounds of a child item, we start with the item's clipped + * bounds and walk its scroll clip chain up to (but not including) + * aIncludeScrollClipsUpTo, and take each scroll clip into account. For + * scroll clips from async scrollable frames we assume that the item can move + * anywhere inside that scroll frame. + * In other words, the return value from this method includes all pixels that + * could potentially be covered by items in this list under async scrolling. */ nsRect GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder, const DisplayItemScrollClip* aIncludeScrollClipsUpTo) const; @@ -2748,8 +2730,6 @@ public: virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) override; - void MarkBoundsAsVisible(nsDisplayListBuilder* aBuilder); - protected: typedef class mozilla::layers::ImageContainer ImageContainer; typedef class mozilla::layers::ImageLayer ImageLayer; @@ -3200,6 +3180,9 @@ public: */ nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList); + nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, + const DisplayItemScrollClip* aScrollClip); nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayItem* aItem); nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) @@ -3358,7 +3341,7 @@ class nsDisplayOpacity : public nsDisplayWrapList { public: nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, - const DisplayItemScrollClip* aScrollClipForSameAGRChildren, + const DisplayItemScrollClip* aScrollClip, bool aForEventsOnly); #ifdef NS_BUILD_REFCNT_LOGGING virtual ~nsDisplayOpacity(); @@ -3392,21 +3375,24 @@ public: bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; - const DisplayItemScrollClip* ScrollClipForSameAGRChildren() const - { return mScrollClipForSameAGRChildren; } - + void SetParticipatesInPreserve3D(bool aParticipatesInPreserve3D) + { + mParticipatesInPreserve3D = aParticipatesInPreserve3D; + } private: - const DisplayItemScrollClip* mScrollClipForSameAGRChildren; float mOpacity; bool mForEventsOnly; + bool mParticipatesInPreserve3D; }; -class nsDisplayMixBlendMode : public nsDisplayWrapList { +class nsDisplayBlendMode : public nsDisplayWrapList { public: - nsDisplayMixBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, - nsDisplayList* aList); + nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, uint8_t aBlendMode, + const DisplayItemScrollClip* aScrollClip, + uint32_t aIndex = 0); #ifdef NS_BUILD_REFCNT_LOGGING - virtual ~nsDisplayMixBlendMode(); + virtual ~nsDisplayBlendMode(); #endif nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, @@ -3421,6 +3407,10 @@ public: { // We don't need to compute an invalidation region since we have LayerTreeInvalidation } + virtual uint32_t GetPerFrameKey() override { + return (mIndex << nsDisplayItem::TYPE_BITS) | + nsDisplayItem::GetPerFrameKey(); + } virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) override; @@ -3430,16 +3420,18 @@ public: virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { return false; } - NS_DISPLAY_DECL_NAME("MixBlendMode", TYPE_MIX_BLEND_MODE) + NS_DISPLAY_DECL_NAME("BlendMode", TYPE_BLEND_MODE) + +private: + uint8_t mBlendMode; + uint32_t mIndex; }; class nsDisplayBlendContainer : public nsDisplayWrapList { public: nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, - BlendModeSet& aContainedBlendModes); - nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, - nsDisplayList* aList); + const DisplayItemScrollClip* aScrollClip); #ifdef NS_BUILD_REFCNT_LOGGING virtual ~nsDisplayBlendContainer(); #endif @@ -3464,11 +3456,6 @@ private: // Used to distinguish containers created at building stacking // context or appending background. uint32_t mIndex; - // The set of all blend modes used by nsDisplayMixBlendMode descendents of this container. - BlendModeSet mContainedBlendModes; - // If this is true, then we should make the layer active if all contained blend - // modes can be supported by the current layer manager. - bool mCanBeActive; }; /** @@ -3705,20 +3692,10 @@ public: mozilla::UniquePtr ComputeFrameMetrics(Layer* aLayer, const ContainerLayerParameters& aContainerParameters); - void IgnoreIfCompositorSupportsBlending(BlendModeSet aBlendModes); - void UnsetIgnoreIfCompositorSupportsBlending(); - bool ContainedInMixBlendMode() const; - protected: nsIFrame* mScrollFrame; nsIFrame* mScrolledFrame; ViewID mScrollParentId; - - // If the only reason for the ScrollInfoLayer is a blend mode, the blend - // mode may be supported in the compositor. We track it here to determine - // if so during layer construction. - BlendModeSet mContainedBlendModes; - bool mIgnoreIfCompositorSupportsBlending; }; /** @@ -4103,8 +4080,7 @@ public: * transformed frame even when it's not completely visible (yet). */ static bool ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, - bool aLogAnimations = false); + nsIFrame* aFrame); bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; bool MayBeAnimated(nsDisplayListBuilder* aBuilder); diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index d3faa2f8c6..3799ce3ac7 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -1401,7 +1401,7 @@ public: * The resolution defaults to 1.0. */ virtual nsresult SetResolution(float aResolution) = 0; - float GetResolution() { return mResolution.valueOr(1.0); } + float GetResolution() const { return mResolution.valueOr(1.0); } virtual float GetCumulativeResolution() = 0; /** diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index dc91f72c33..b240c3a95b 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -3265,11 +3265,6 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram /* aAllowCreateDisplayPort = */ true); } - nsDisplayList hoistedScrollItemStorage; - if (builder.IsPaintingToWindow()) { - builder.SetCommittedScrollInfoItemList(&hoistedScrollItemStorage); - } - nsRegion visibleRegion; if (aFlags & PAINT_WIDGET_LAYERS) { // This layer tree will be reused, so we'll need to calculate it diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 86b41e1ec8..226a292e0a 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -2056,6 +2056,39 @@ nsPresContext::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint, RestyleManager()->PostRebuildAllStyleDataEvent(aExtraHint, aRestyleHint); } +struct MediaFeatureHints +{ + nsRestyleHint restyleHint; + nsChangeHint changeHint; +}; + +static bool +MediaFeatureValuesChangedAllDocumentsCallback(nsIDocument* aDocument, void* aHints) +{ + MediaFeatureHints* hints = static_cast(aHints); + if (nsIPresShell* shell = aDocument->GetShell()) { + if (nsPresContext* pc = shell->GetPresContext()) { + pc->MediaFeatureValuesChangedAllDocuments(hints->restyleHint, + hints->changeHint); + } + } + return true; +} + +void +nsPresContext::MediaFeatureValuesChangedAllDocuments(nsRestyleHint aRestyleHint, + nsChangeHint aChangeHint) +{ + MediaFeatureValuesChanged(aRestyleHint, aChangeHint); + MediaFeatureHints hints = { + aRestyleHint, + aChangeHint + }; + + mDocument->EnumerateSubDocuments(MediaFeatureValuesChangedAllDocumentsCallback, + &hints); +} + void nsPresContext::MediaFeatureValuesChanged(nsRestyleHint aRestyleHint, nsChangeHint aChangeHint) @@ -2162,6 +2195,25 @@ nsPresContext::HandleMediaFeatureValuesChangedEvent() } } +static void +NotifyTabSizeModeChanged(TabParent* aTab, void* aArg) +{ + nsSizeMode* sizeMode = static_cast(aArg); + aTab->SizeModeChanged(*sizeMode); +} + +void +nsPresContext::SizeModeChanged(nsSizeMode aSizeMode) +{ + if (HasCachedStyleData()) { + nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(), + NotifyTabSizeModeChanged, + &aSizeMode); + MediaFeatureValuesChangedAllDocuments(eRestyle_Subtree, + NS_STYLE_HINT_REFLOW); + } +} + nsCompatibility nsPresContext::CompatibilityMode() const { @@ -2894,7 +2946,7 @@ nsPresContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const } bool -nsPresContext::IsRootContentDocument() +nsPresContext::IsRootContentDocument() const { // We are a root content document if: we are not a resource doc, we are // not chrome, and we either have no parent or our parent is chrome. diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index 723179f860..f103f6b54c 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -21,6 +21,7 @@ #include "nsIObserver.h" #include "nsITimer.h" #include "nsCRT.h" +#include "nsIWidgetListener.h" #include "FramePropertyTable.h" #include "nsGkAtoms.h" #include "nsCycleCollectionParticipant.h" @@ -279,6 +280,14 @@ public: */ void MediaFeatureValuesChanged(nsRestyleHint aRestyleHint, nsChangeHint aChangeHint = nsChangeHint(0)); + /** + * Calls MediaFeatureValuesChanged for this pres context and all descendant + * subdocuments that have a pres context. This should be used for media + * features that must be updated in all subdocuments e.g. display-mode. + */ + void MediaFeatureValuesChangedAllDocuments(nsRestyleHint aRestyleHint, + nsChangeHint aChangeHint = nsChangeHint(0)); + void PostMediaFeatureValuesChangedEvent(); void HandleMediaFeatureValuesChangedEvent(); void FlushPendingMediaFeatureValuesChanged() { @@ -286,6 +295,13 @@ public: MediaFeatureValuesChanged(nsRestyleHint(0)); } + /** + * Updates the size mode on all remote children and recursively notifies this + * document and all subdocuments (including remote children) that a media + * feature value has changed. + */ + void SizeModeChanged(nsSizeMode aSizeMode); + /** * Access compatibility mode for this context. This is the same as * our document's compatibility mode. @@ -456,7 +472,7 @@ public: * presenting the document. The returned value is in the standard * nscoord units (as scaled by the device context). */ - nsRect GetVisibleArea() { return mVisibleArea; } + nsRect GetVisibleArea() const { return mVisibleArea; } /** * Set the currently visible area. The units for r are standard @@ -1013,7 +1029,7 @@ public: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } - bool IsRootContentDocument(); + bool IsRootContentDocument() const; bool IsCrossProcessRootContentDocument(); bool IsGlyph() const { @@ -1071,9 +1087,13 @@ public: static bool StyloEnabled() { + // Stylo (the Servo backend for Gecko's style system) is generally enabled + // or disabled at compile-time. However, we provide the additional capability + // to disable it dynamically in stylo-enabled builds via an environmental + // variable. #ifdef MOZ_STYLO - static bool enabled = PR_GetEnv("MOZ_STYLO"); - return enabled; + static bool disabled = PR_GetEnv("MOZ_DISABLE_STYLO"); + return !disabled; #else return false; #endif diff --git a/layout/generic/crashtests/1225005.html b/layout/generic/crashtests/1225005.html new file mode 100644 index 0000000000..8c2164df2a --- /dev/null +++ b/layout/generic/crashtests/1225005.html @@ -0,0 +1,4 @@ + + +
+ diff --git a/layout/generic/crashtests/crashtests.list b/layout/generic/crashtests/crashtests.list index 0fd98662a0..962e58748d 100644 --- a/layout/generic/crashtests/crashtests.list +++ b/layout/generic/crashtests/crashtests.list @@ -610,3 +610,4 @@ load details-display-none-summary-2.html load details-display-none-summary-3.html load 1304441.html pref(dom.details_element.enabled,true) load summary-position-out-of-flow.html +asserts(4) load 1225005.html # bug 682647 and bug 448083 diff --git a/layout/generic/nsCanvasFrame.cpp b/layout/generic/nsCanvasFrame.cpp index bba5f77e73..21d5a27782 100644 --- a/layout/generic/nsCanvasFrame.cpp +++ b/layout/generic/nsCanvasFrame.cpp @@ -407,6 +407,9 @@ nsCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, return; } + const DisplayItemScrollClip* scrollClip = + aBuilder->ClipState().GetCurrentInnermostScrollClip(); + bool needBlendContainer = false; // Create separate items for each background layer. @@ -418,19 +421,30 @@ nsCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, if (layers.mLayers[i].mBlendMode != NS_STYLE_BLEND_NORMAL) { needBlendContainer = true; } + + nsDisplayList thisItemList; nsDisplayCanvasBackgroundImage* bgItem = new (aBuilder) nsDisplayCanvasBackgroundImage(aBuilder, this, i, bg); if (bgItem->ShouldFixToViewport(aBuilder)) { - aLists.BorderBackground()->AppendNewToTop( + thisItemList.AppendNewToTop( nsDisplayFixedPosition::CreateForFixedBackground(aBuilder, this, bgItem, i)); } else { - aLists.BorderBackground()->AppendNewToTop(bgItem); + thisItemList.AppendNewToTop(bgItem); } + + if (layers.mLayers[i].mBlendMode != NS_STYLE_BLEND_NORMAL) { + thisItemList.AppendNewToTop( + new (aBuilder) nsDisplayBlendMode(aBuilder, this, &thisItemList, + layers.mLayers[i].mBlendMode, + scrollClip, i + 1)); + } + aLists.BorderBackground()->AppendToTop(&thisItemList); } if (needBlendContainer) { aLists.BorderBackground()->AppendNewToTop( - new (aBuilder) nsDisplayBlendContainer(aBuilder, this, aLists.BorderBackground())); + new (aBuilder) nsDisplayBlendContainer(aBuilder, this, aLists.BorderBackground(), + scrollClip)); } } diff --git a/layout/generic/nsFloatManager.cpp b/layout/generic/nsFloatManager.cpp index e101d27b13..93cc1bf4b1 100644 --- a/layout/generic/nsFloatManager.cpp +++ b/layout/generic/nsFloatManager.cpp @@ -115,7 +115,7 @@ void nsFloatManager::Shutdown() } #define CHECK_BLOCK_DIR(aWM) \ - NS_ASSERTION(aWM.GetBlockDir() == mWritingMode.value.GetBlockDir(), \ + NS_ASSERTION(aWM.GetBlockDir() == mWritingMode.GetBlockDir(), \ "incompatible writing modes") nsFlowAreaRect diff --git a/layout/generic/nsFloatManager.h b/layout/generic/nsFloatManager.h index ba326be604..ff99e982ab 100644 --- a/layout/generic/nsFloatManager.h +++ b/layout/generic/nsFloatManager.h @@ -340,7 +340,9 @@ private: nsRect mRect; }; - mozilla::DebugOnly mWritingMode; +#ifdef DEBUG + mozilla::WritingMode mWritingMode; +#endif // Translation from local to global coordinate space. nscoord mLineLeft, mBlockStart; diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index fdfe39a7a7..733deb363b 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -84,6 +84,7 @@ #include "gfxContext.h" #include "nsRenderingContext.h" #include "nsAbsoluteContainingBlock.h" +#include "DisplayItemScrollClip.h" #include "StickyScrollContainer.h" #include "nsFontInflationData.h" #include "gfxASurface.h" @@ -1166,12 +1167,6 @@ nsIFrame::Extend3DContext() const return false; } - // Opacity can only be only the root or leaves of a preserve-3d context - // as it requires flattening. - if (HasOpacity() && Combines3DTransformWithAncestors()) { - return false; - } - nsRect temp; return !nsFrame::ShouldApplyOverflowClipping(this, disp) && !GetClipPropClipRect(disp, &temp, GetSize()) && @@ -1837,99 +1832,18 @@ IsScrollFrameActive(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrolla return aScrollableFrame && aScrollableFrame->IsScrollingActive(aBuilder); } -class AutoSaveRestoreBlendMode +class AutoSaveRestoreContainsBlendMode { nsDisplayListBuilder& mBuilder; - EnumSet mSavedBlendModes; + bool mSavedContainsBlendMode; public: - explicit AutoSaveRestoreBlendMode(nsDisplayListBuilder& aBuilder) + explicit AutoSaveRestoreContainsBlendMode(nsDisplayListBuilder& aBuilder) : mBuilder(aBuilder) - , mSavedBlendModes(aBuilder.ContainedBlendModes()) + , mSavedContainsBlendMode(aBuilder.ContainsBlendMode()) { } - ~AutoSaveRestoreBlendMode() { - mBuilder.SetContainsBlendModes(mSavedBlendModes); - } -}; - -class AutoHoistScrollInfoItems -{ - nsDisplayListBuilder& mBuilder; - nsDisplayList* mParentPendingList; - nsDisplayList mPendingList; - -public: - explicit AutoHoistScrollInfoItems(nsDisplayListBuilder& aBuilder) - : mBuilder(aBuilder), - mParentPendingList(nullptr) - { - if (!mBuilder.ShouldBuildScrollInfoItemsForHoisting()) { - return; - } - mParentPendingList = mBuilder.EnterScrollInfoItemHoisting(&mPendingList); - } - ~AutoHoistScrollInfoItems() { - if (!mParentPendingList) { - // If we have no parent stacking context, we will throw out any scroll - // info items that are pending (meaning, we can safely ignore them since - // the scrollable layers they represent will not be flattened). - return; - } - mParentPendingList->AppendToTop(&mPendingList); - mBuilder.LeaveScrollInfoItemHoisting(mParentPendingList); - } - - // The current stacking context will definitely be flattened, so commit all - // pending scroll info items and make sure they will not be optimized away - // in the case they were also inside a compositor-supported mix-blend-mode. - void Commit() { - nsDisplayItem* iter = nullptr; - while ((iter = mPendingList.RemoveBottom()) != nullptr) { - MOZ_ASSERT(iter->GetType() == nsDisplayItem::TYPE_SCROLL_INFO_LAYER); - auto item = static_cast(iter); - - item->UnsetIgnoreIfCompositorSupportsBlending(); - mBuilder.CommittedScrollInfoItems()->AppendToTop(item); - } - } - - // The current stacking context will only be flattened if the given mix-blend - // mode is not supported in the compositor. Annotate the scroll info items - // and keep them in the pending list. - void AnnotateForBlendModes(BlendModeSet aBlendModes) { - for (nsDisplayItem* iter = mPendingList.GetBottom(); iter; iter = iter->GetAbove()) { - MOZ_ASSERT(iter->GetType() == nsDisplayItem::TYPE_SCROLL_INFO_LAYER); - auto item = static_cast(iter); - - item->IgnoreIfCompositorSupportsBlending(aBlendModes); - } - } - - bool IsRootStackingContext() { - // We're only finished building the hoisted list if we have no parent - // stacking context. - return !mParentPendingList; - } - - // Any scroll info items which contain a mix-blend mode are moved into the - // parent display list. - void Finish(nsDisplayList* aResultList) { - MOZ_ASSERT(IsRootStackingContext()); - - nsDisplayItem* iter = nullptr; - while ((iter = mPendingList.RemoveBottom()) != nullptr) { - MOZ_ASSERT(iter->GetType() == nsDisplayItem::TYPE_SCROLL_INFO_LAYER); - nsDisplayScrollInfoLayer *item = static_cast(iter); - - if (!item->ContainedInMixBlendMode()) { - // Discard the item, it was not committed for having an SVG effect nor - // was it contained with a mix-blend mode. - item->~nsDisplayScrollInfoLayer(); - continue; - } - - aResultList->AppendToTop(item); - } + ~AutoSaveRestoreContainsBlendMode() { + mBuilder.SetContainsBlendMode(mSavedContainsBlendMode); } }; @@ -1969,7 +1883,8 @@ static bool ItemParticipatesIn3DContext(nsIFrame* aAncestor, nsDisplayItem* aItem) { nsIFrame* transformFrame; - if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM) { + if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM || + aItem->GetType() == nsDisplayItem::TYPE_OPACITY) { transformFrame = aItem->Frame(); } else if (aItem->GetType() == nsDisplayItem::TYPE_PERSPECTIVE) { transformFrame = static_cast(aItem)->TransformFrame(); @@ -1998,24 +1913,22 @@ WrapSeparatorTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, static void CreateOpacityItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, - nsDisplayList& aList, bool aItemForEventsOnly) + nsDisplayList& aList, bool aItemForEventsOnly, + const DisplayItemScrollClip* aScrollClip, + bool aParticipatesInPreserve3D) { // Don't clip nsDisplayOpacity items. We clip their descendants instead. // The clip we would set on an element with opacity would clip // all descendant content, but some should not be clipped. - // We clear both regular clips and scroll clips. If this item's animated - // geometry root has async scrolling, then the async scroll transform will - // be applied on the opacity's descendants (because that's where the - // scroll clip will be). However, this won't work if the opacity item is - // inactive, which is why we record the pre-clear scroll clip here. - const DisplayItemScrollClip* scrollClipForSameAGRChildren = - aBuilder->ClipState().GetCurrentInnermostScrollClip(); DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder); - opacityClipState.ClearIncludingScrollClip(); - aList.AppendNewToTop( + opacityClipState.Clear(); + nsDisplayOpacity* opacity = new (aBuilder) nsDisplayOpacity(aBuilder, aFrame, &aList, - scrollClipForSameAGRChildren, - aItemForEventsOnly)); + aScrollClip, aItemForEventsOnly); + if (opacity) { + opacity->SetParticipatesInPreserve3D(aParticipatesInPreserve3D); + aList.AppendToTop(opacity); + } } void @@ -2074,8 +1987,8 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, // reset blend mode so we can keep track if this stacking context needs have // a nsDisplayBlendContainer. Set the blend mode back when the routine exits // so we keep track if the parent stacking context needs a container too. - AutoSaveRestoreBlendMode autoRestoreBlendMode(*aBuilder); - aBuilder->SetContainsBlendModes(BlendModeSet()); + AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder); + aBuilder->SetContainsBlendMode(false); nsRect dirtyRectOutsideTransform = dirtyRect; if (isTransformed) { @@ -2107,16 +2020,16 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, inTransform = true; } - AutoHoistScrollInfoItems hoistedScrollInfoItems(*aBuilder); - bool usingSVGEffects = nsSVGIntegrationUtils::UsingEffectsForFrame(this); nsRect dirtyRectOutsideSVGEffects = dirtyRect; + nsDisplayList hoistedScrollInfoItemsStorage; if (usingSVGEffects) { dirtyRect = nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, dirtyRect); + aBuilder->EnterSVGEffectsContents(&hoistedScrollInfoItemsStorage); } - bool useOpacity = HasVisualOpacity() && !nsSVGUtils::CanOptimizeOpacity(this); + bool useOpacity = HasVisualOpacity() && !nsSVGUtils::CanOptimizeOpacity(this) && !usingSVGEffects; bool useBlendMode = disp->mMixBlendMode != NS_STYLE_BLEND_NORMAL; bool useStickyPosition = disp->mPosition == NS_STYLE_POSITION_STICKY && IsScrollFrameActive(aBuilder, @@ -2135,19 +2048,18 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, DisplayListClipState::AutoSaveRestore clipState(aBuilder); - if (isTransformed || useBlendMode || usingSVGEffects || useFixedPosition || useStickyPosition) { + bool clearClip = false; + if (isTransformed || usingSVGEffects || useFixedPosition || useStickyPosition) { // We don't need to pass ancestor clipping down to our children; // everything goes inside a display item's child list, and the display // item itself will be clipped. // For transforms we also need to clear ancestor clipping because it's // relative to the wrong display item reference frame anyway. - // We clear both regular and scroll clips here. Our content needs to be - // able to walk up the complete cross stacking context scroll clip chain, - // so we call a special method on the clip state that keeps the ancestor - // scroll clip around. - clipState.ClearForStackingContextContents(); + clearClip = true; } + clipState.EnterStackingContextContents(clearClip); + nsDisplayListCollection set; { DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder); @@ -2239,13 +2151,34 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, // 8, 9: non-negative z-index children resultList.AppendToTop(set.PositionedDescendants()); - if (!isTransformed) { - // Restore saved clip state now so that any display items we create below - // are clipped properly. - clipState.Restore(); + // Get the scroll clip to use for the container items that we create here. + // If we cleared the clip, and we create multiple container items, then the + // items we create before we restore the clip will have a different scroll + // clip from the items we create after we restore the clip. + const DisplayItemScrollClip* containerItemScrollClip = + aBuilder->ClipState().CurrentAncestorScrollClipForStackingContextContents(); + + /* If adding both a nsDisplayBlendContainer and a nsDisplayBlendMode to the + * same list, the nsDisplayBlendContainer should be added first. This only + * happens when the element creating this stacking context has mix-blend-mode + * and also contains a child which has mix-blend-mode. + * The nsDisplayBlendContainer must be added to the list first, so it does not + * isolate the containing element blending as well. + */ + + if (aBuilder->ContainsBlendMode()) { + DisplayListClipState::AutoSaveRestore blendContainerClipState(aBuilder); + blendContainerClipState.Clear(); + resultList.AppendNewToTop( + new (aBuilder) nsDisplayBlendContainer(aBuilder, this, &resultList, + containerItemScrollClip)); } - bool is3DContextRoot = Extend3DContext() && !Combines3DTransformWithAncestors(); + if (!isTransformed && !useFixedPosition && !useStickyPosition) { + // Restore saved clip state now so that any display items we create below + // are clipped properly. + clipState.ExitStackingContextContents(&containerItemScrollClip); + } /* If there are any SVG effects, wrap the list up in an SVG effects item * (which also handles CSS group opacity). Note that we create an SVG effects @@ -2258,17 +2191,10 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, /* List now emptied, so add the new list to the top. */ resultList.AppendNewToTop( new (aBuilder) nsDisplaySVGEffects(aBuilder, this, &resultList)); - } - else if (useOpacity && !resultList.IsEmpty() && !is3DContextRoot) { - /* If this element is the root of a preserve-3d context, then we want - * to make sure any opacity items are on the outside of the transform - * so that they don't interfere with the chain of nsDisplayTransforms. - * Opacity on preserve-3d leaves need to be inside the transform for the - * same reason, and we do this in the general case as well to preserve - * existing behaviour. - */ - CreateOpacityItem(aBuilder, this, resultList, opacityItemForEventsOnly); - useOpacity = false; + // Also add the hoisted scroll info items. We need those for APZ scrolling + // because nsDisplaySVGEffects items can't build active layers. + aBuilder->ExitSVGEffectsContents(); + resultList.AppendToTop(&hoistedScrollInfoItemsStorage); } /* If we're going to apply a transformation and don't have preserve-3d set, wrap @@ -2282,39 +2208,52 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, * We also traverse into sublists created by nsDisplayWrapList or nsDisplayOpacity, so that * we find all the correct children. */ - if (isTransformed && !resultList.IsEmpty()) { - if (!resultList.IsEmpty() && Extend3DContext()) { - // Install dummy nsDisplayTransform as a leaf containing - // descendants not participating this 3D rendering context. - nsDisplayList nonparticipants; - nsDisplayList participants; - int index = 1; + bool hasPreserve3DChildren = false; + if (isTransformed && !resultList.IsEmpty() && Extend3DContext()) { + // Install dummy nsDisplayTransform as a leaf containing + // descendants not participating this 3D rendering context. + nsDisplayList nonparticipants; + nsDisplayList participants; + int index = 1; - while (nsDisplayItem* item = resultList.RemoveBottom()) { - if (ItemParticipatesIn3DContext(this, item) && !item->GetClip().HasClip()) { - // The frame of this item participates the same 3D context. - WrapSeparatorTransform(aBuilder, this, dirtyRect, - &nonparticipants, &participants, index++); - participants.AppendToTop(item); - } else { - // The frame of the item doesn't participate the current - // context, or has no transform. - // - // For items participating but not transformed, they are add - // to nonparticipants to get a separator layer for handling - // clips, if there is, on an intermediate surface. - // \see ContainerLayer::DefaultComputeEffectiveTransforms(). - nonparticipants.AppendToTop(item); - } + while (nsDisplayItem* item = resultList.RemoveBottom()) { + if (ItemParticipatesIn3DContext(this, item) && !item->GetClip().HasClip()) { + // The frame of this item participates the same 3D context. + WrapSeparatorTransform(aBuilder, this, dirtyRect, + &nonparticipants, &participants, index++); + participants.AppendToTop(item); + hasPreserve3DChildren = true; + } else { + // The frame of the item doesn't participate the current + // context, or has no transform. + // + // For items participating but not transformed, they are add + // to nonparticipants to get a separator layer for handling + // clips, if there is, on an intermediate surface. + // \see ContainerLayer::DefaultComputeEffectiveTransforms(). + nonparticipants.AppendToTop(item); } - WrapSeparatorTransform(aBuilder, this, dirtyRect, - &nonparticipants, &participants, index++); - resultList.AppendToTop(&participants); } + WrapSeparatorTransform(aBuilder, this, dirtyRect, + &nonparticipants, &participants, index++); + resultList.AppendToTop(&participants); + } + /* We create the opacity outside any transform separators we created, + * so that the opacity will be applied to them as groups. When we have + * opacity and preserve-3d we break the 'group' nature of opacity in order + * to maintain preserve-3d. This matches the behaviour of blink and WebKit, + * see bug 1250718. + */ + if (useOpacity && !resultList.IsEmpty()) { + CreateOpacityItem(aBuilder, this, resultList, + opacityItemForEventsOnly, containerItemScrollClip, hasPreserve3DChildren); + } + + if (isTransformed && !resultList.IsEmpty()) { // Restore clip state now so nsDisplayTransform is clipped properly. - if (!HasPerspective()) { - clipState.Restore(); + if (!HasPerspective() && !useFixedPosition && !useStickyPosition) { + clipState.ExitStackingContextContents(&containerItemScrollClip); } // Revert to the dirtyrect coming in from the parent, without our transform // taken into account. @@ -2335,19 +2274,18 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, resultList.AppendNewToTop(transformItem); if (HasPerspective()) { - clipState.Restore(); + if (!useFixedPosition && !useStickyPosition) { + clipState.ExitStackingContextContents(&containerItemScrollClip); + } resultList.AppendNewToTop( new (aBuilder) nsDisplayPerspective( aBuilder, this, GetContainingBlock()->GetContent()->GetPrimaryFrame(), &resultList)); } + } - /* If we need an opacity item, but didn't do it earlier, add it now on the - * outside of the transform. - */ - if (useOpacity && !usingSVGEffects) { - CreateOpacityItem(aBuilder, this, resultList, opacityItemForEventsOnly); - } + if (useFixedPosition || useStickyPosition) { + clipState.ExitStackingContextContents(&containerItemScrollClip); } /* If we have sticky positioning, wrap it in a sticky position item. @@ -2367,44 +2305,18 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, new (aBuilder) nsDisplayVR(aBuilder, this, &resultList, vrHMDInfo)); } - /* If adding both a nsDisplayBlendContainer and a nsDisplayMixBlendMode to the - * same list, the nsDisplayBlendContainer should be added first. This only - * happens when the element creating this stacking context has mix-blend-mode - * and also contains a child which has mix-blend-mode. - * The nsDisplayBlendContainer must be added to the list first, so it does not - * isolate the containing element blending as well. - */ - - if (aBuilder->ContainsBlendMode()) { - resultList.AppendNewToTop( - new (aBuilder) nsDisplayBlendContainer(aBuilder, this, &resultList, aBuilder->ContainedBlendModes())); - } - - if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) { - if (usingSVGEffects) { - // We know this stacking context will be flattened, so hoist any scroll - // info items we created. - hoistedScrollInfoItems.Commit(); - } else if (aBuilder->ContainsBlendMode()) { - hoistedScrollInfoItems.AnnotateForBlendModes(aBuilder->ContainedBlendModes()); - } - - if (hoistedScrollInfoItems.IsRootStackingContext()) { - // If we're the root stacking context, no more mix-blend modes can be - // introduced and it's safe to hoist scroll info items. - resultList.AppendToTop(aBuilder->CommittedScrollInfoItems()); - hoistedScrollInfoItems.Finish(&resultList); - } - } - /* If there's blending, wrap up the list in a blend-mode item. Note * that opacity can be applied before blending as the blend color is * not affected by foreground opacity (only background alpha). */ if (useBlendMode && !resultList.IsEmpty()) { + DisplayListClipState::AutoSaveRestore mixBlendClipState(aBuilder); + mixBlendClipState.Clear(); resultList.AppendNewToTop( - new (aBuilder) nsDisplayMixBlendMode(aBuilder, this, &resultList)); + new (aBuilder) nsDisplayBlendMode(aBuilder, this, &resultList, + disp->mMixBlendMode, + containerItemScrollClip)); } CreateOwnLayerIfNeeded(aBuilder, &resultList); @@ -2606,7 +2518,7 @@ nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder, nsDisplayList extraPositionedDescendants; if (isStackingContext) { if (disp->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { - aBuilder->SetContainsBlendMode(disp->mMixBlendMode); + aBuilder->SetContainsBlendMode(true); } // True stacking context. // For stacking contexts, BuildDisplayListForStackingContext handles @@ -4733,12 +4645,9 @@ nsFrame::ReflowAbsoluteFrames(nsPresContext* aPresContext, // The containing block for the abs pos kids is formed by our padding edge. nsMargin usedBorder = GetUsedBorder(); nscoord containingBlockWidth = - aDesiredSize.Width() - usedBorder.LeftRight(); - MOZ_ASSERT(containingBlockWidth >= 0); + std::max(0, aDesiredSize.Width() - usedBorder.LeftRight()); nscoord containingBlockHeight = - aDesiredSize.Height() - usedBorder.TopBottom(); - MOZ_ASSERT(containingBlockHeight >= 0); - + std::max(0, aDesiredSize.Height() - usedBorder.TopBottom()); nsContainerFrame* container = do_QueryFrame(this); NS_ASSERTION(container, "Abs-pos children only supported on container frames for now"); diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index faaa5012c4..f491d37531 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -2045,9 +2045,13 @@ ScrollFrameHelper::NotifyPluginFrames(AsyncScrollEventType aEvent) return; } if (XRE_IsContentProcess()) { + // Ignore 'inner' dom events triggered by apz transformations + if (mAsyncScrollEvent == BEGIN_APZ && aEvent != END_APZ) { + return; + } if (aEvent != mAsyncScrollEvent) { nsPresContext* presContext = mOuter->PresContext(); - PluginSearchCtx ctx = { mOuter, (aEvent == BEGIN_DOM) }; + PluginSearchCtx ctx = { mOuter, (aEvent == BEGIN_APZ || aEvent == BEGIN_DOM) }; presContext->Document()->EnumerateActivityObservers(NotifyPluginFramesCallback, (void*)&ctx); presContext->Document()->EnumerateSubDocuments(NotifyPluginSubframesCallback, @@ -2663,7 +2667,7 @@ ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOri ScrollVisual(); - if (LastScrollOrigin() == nsGkAtoms::apz) { + if (LastScrollOrigin() == nsGkAtoms::apz && gfxPrefs::APZPaintSkipping()) { // If this was an apz scroll and the displayport (relative to the // scrolled frame) hasn't changed, then this won't trigger // any painting, so no need to schedule one. diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h index 6426233e16..15bfbc1c3b 100644 --- a/layout/generic/nsGfxScrollFrame.h +++ b/layout/generic/nsGfxScrollFrame.h @@ -356,6 +356,7 @@ public: // because we have special behaviour for it when APZ scrolling is active. mOuter->SchedulePaint(); } + NotifyPluginFrames(aTransforming ? BEGIN_APZ : END_APZ); } bool IsTransformingByAPZ() const { return mTransformingByAPZ; @@ -563,7 +564,7 @@ protected: * Helper that notifies plugins about async smooth scroll operations managed * by nsGfxScrollFrame. */ - enum AsyncScrollEventType { BEGIN_DOM, END_DOM }; + enum AsyncScrollEventType { BEGIN_DOM, BEGIN_APZ, END_DOM, END_APZ }; void NotifyPluginFrames(AsyncScrollEventType aEvent); AsyncScrollEventType mAsyncScrollEvent; diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp index bedd38474b..35c53fdad9 100644 --- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -1702,10 +1702,20 @@ nsDisplayImage::ConfigureLayer(ImageLayer* aLayer, // aParameters.Offset() is always zero. MOZ_ASSERT(aParameters.Offset() == LayerIntPoint(0,0)); + // It's possible (for example, due to downscale-during-decode) that the + // ImageContainer this ImageLayer is holding has a different size from the + // intrinsic size of the image. For this reason we compute the transform using + // the ImageContainer's size rather than the image's intrinsic size. + // XXX(seth): In reality, since the size of the ImageContainer may change + // asynchronously, this is not enough. Bug 1183378 will provide a more + // complete fix, but this solution is safe in more cases than simply relying + // on the intrinsic size. + IntSize containerSize = aLayer->GetContainer()->GetCurrentSize(); + const LayoutDevicePoint p = destRect.TopLeft(); Matrix transform = Matrix::Translation(p.x, p.y); - transform.PreScale(destRect.Width() / imageWidth, - destRect.Height() / imageHeight); + transform.PreScale(destRect.Width() / containerSize.width, + destRect.Height() / containerSize.height); aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); } diff --git a/layout/generic/nsPluginFrame.cpp b/layout/generic/nsPluginFrame.cpp index ccaab85fd2..bc22c28a47 100644 --- a/layout/generic/nsPluginFrame.cpp +++ b/layout/generic/nsPluginFrame.cpp @@ -54,6 +54,7 @@ #include "gfxWindowsSurface.h" #endif +#include "DisplayItemScrollClip.h" #include "Layers.h" #include "ReadbackLayer.h" #include "ImageContainer.h" @@ -417,7 +418,8 @@ nsPluginFrame::GetWidgetConfiguration(nsTArray* aConfi #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) if (XRE_IsContentProcess()) { configuration->mWindowID = (uintptr_t)mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); - configuration->mVisible = mWidget->IsVisible(); + configuration->mVisible = !mIsHiddenDueToScroll && mWidget->IsVisible(); + } #endif // defined(XP_WIN) || defined(MOZ_WIDGET_GTK) } @@ -779,7 +781,8 @@ nsPluginFrame::SetScrollVisibility(bool aState) mIsHiddenDueToScroll = aState; // Force a paint so plugin window visibility gets flushed via // the compositor. - if (changed) { + if (changed && mInstanceOwner) { + mInstanceOwner->UpdateScrollState(mIsHiddenDueToScroll); SchedulePaint(); } } @@ -1002,6 +1005,19 @@ nsDisplayPlugin::Paint(nsDisplayListBuilder* aBuilder, f->PaintPlugin(aBuilder, *aCtx, mVisibleRect, GetBounds(aBuilder, &snap)); } +static nsRect +GetClippedBoundsIncludingAllScrollClips(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) +{ + nsRect r = aItem->GetClippedBounds(aBuilder); + for (auto* sc = aItem->ScrollClip(); sc; sc = sc->mParent) { + if (sc->mClip) { + r = sc->mClip->ApplyNonRoundedIntersection(r); + } + } + return r; +} + bool nsDisplayPlugin::ComputeVisibility(nsDisplayListBuilder* aBuilder, nsRegion* aVisibleRegion) @@ -1019,7 +1035,10 @@ nsDisplayPlugin::ComputeVisibility(nsDisplayListBuilder* aBuilder, rAncestor.ToNearestPixels(appUnitsPerDevPixel)); nsRegion visibleRegion; - visibleRegion.And(*aVisibleRegion, GetClippedBounds(aBuilder)); + // Apply all scroll clips when computing the clipped bounds of this item. + // We hide windowed plugins during APZ scrolling, so there never is an + // async transform that we need to take into account when clipping. + visibleRegion.And(*aVisibleRegion, GetClippedBoundsIncludingAllScrollClips(this, aBuilder)); // Make visibleRegion relative to f visibleRegion.MoveBy(-ToReferenceFrame()); @@ -1119,11 +1138,13 @@ nsPluginFrame::DidSetWidgetGeometry() bool nsPluginFrame::IsOpaque() const { +#if defined(MOZ_WIDGET_GTK) // Insure underlying content gets painted when we clip windowed plugins // during remote content scroll operations managed by nsGfxScrollFrame. if (mIsHiddenDueToScroll) { return false; } +#endif #if defined(XP_MACOSX) return false; #elif defined(MOZ_WIDGET_ANDROID) @@ -1169,11 +1190,13 @@ nsPluginFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { +#if defined(MOZ_WIDGET_GTK) // Clip windowed plugin frames from the list during remote content scroll // operations managed by nsGfxScrollFrame. if (mIsHiddenDueToScroll) { return; } +#endif // XXX why are we painting collapsed object frames? if (!IsVisibleOrCollapsedForPainting(aBuilder)) @@ -1197,8 +1220,9 @@ nsPluginFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, #endif if (aBuilder->IsForPainting() && mInstanceOwner) { -#ifdef XP_MACOSX + // Update plugin frame for both content scaling and full zoom changes. mInstanceOwner->ResolutionMayHaveChanged(); +#ifdef XP_MACOSX mInstanceOwner->WindowFocusMayHaveChanged(); #endif if (mInstanceOwner->UseAsyncRendering()) { @@ -1395,6 +1419,10 @@ nsPluginFrame::GetLayerState(nsDisplayListBuilder* aBuilder, return LAYER_ACTIVE; #endif + if (mInstanceOwner->NeedsScrollImageLayer()) { + return LAYER_ACTIVE; + } + if (!mInstanceOwner->UseAsyncRendering()) { return LAYER_NONE; } @@ -1459,10 +1487,13 @@ nsPluginFrame::BuildLayer(nsDisplayListBuilder* aBuilder, (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, aItem)); if (aItem->GetType() == nsDisplayItem::TYPE_PLUGIN) { - // Create image - RefPtr container = mInstanceOwner->GetImageContainer(); + RefPtr container; + // Image for Windowed plugins that support window capturing for scroll + // operations or async windowless rendering. + container = mInstanceOwner->GetImageContainer(); if (!container) { - // This can occur if our instance is gone. + // This can occur if our instance is gone or if the current plugin + // configuration does not require a backing image layer. return nullptr; } diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp index 062a6d7286..07d513edab 100644 --- a/layout/generic/nsSubDocumentFrame.cpp +++ b/layout/generic/nsSubDocumentFrame.cpp @@ -473,7 +473,7 @@ nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, // the layer we will construct will be clipped by the current clip. // In fact for nsDisplayZoom propagating it down would be incorrect since // nsDisplayZoom changes the meaning of appunits. - nestedClipState.ClearForStackingContextContents(); + nestedClipState.EnterStackingContextContents(true); } if (subdocRootFrame) { diff --git a/layout/reftests/async-scrolling/background-blend-mode-1-ref.html b/layout/reftests/async-scrolling/background-blend-mode-1-ref.html new file mode 100644 index 0000000000..b5a9e0c12d --- /dev/null +++ b/layout/reftests/async-scrolling/background-blend-mode-1-ref.html @@ -0,0 +1,17 @@ + + +Reference: Bug 1248913 - Keep background-attachment:fixed image fixed under APZ scrolling even when a background-blend-mode is applied. + + + +
diff --git a/layout/reftests/async-scrolling/background-blend-mode-1.html b/layout/reftests/async-scrolling/background-blend-mode-1.html new file mode 100644 index 0000000000..607e97e758 --- /dev/null +++ b/layout/reftests/async-scrolling/background-blend-mode-1.html @@ -0,0 +1,26 @@ + + + + +Bug 1248913 - Keep background-attachment:fixed image fixed under APZ scrolling even when a background-blend-mode is applied. + + + +
diff --git a/layout/reftests/async-scrolling/offscreen-clipped-blendmode-1.html b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-1.html new file mode 100644 index 0000000000..64795e5cfd --- /dev/null +++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-1.html @@ -0,0 +1,39 @@ + + + +Scrolled blend mode + + + +
+
+
+
+
diff --git a/layout/reftests/async-scrolling/offscreen-clipped-blendmode-2.html b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-2.html new file mode 100644 index 0000000000..d85dc23e69 --- /dev/null +++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-2.html @@ -0,0 +1,43 @@ + + + +Scrolled blend mode + + + +
+
+
+
+
diff --git a/layout/reftests/async-scrolling/offscreen-clipped-blendmode-3.html b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-3.html new file mode 100644 index 0000000000..e128e29872 --- /dev/null +++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-3.html @@ -0,0 +1,40 @@ + + + +Scrolled blend mode + + + +
+
+
+
+
diff --git a/layout/reftests/async-scrolling/offscreen-clipped-blendmode-4.html b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-4.html new file mode 100644 index 0000000000..8834747244 --- /dev/null +++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-4.html @@ -0,0 +1,44 @@ + + + +Scrolled blend mode + + + +
+
+
+
+
diff --git a/layout/reftests/async-scrolling/offscreen-clipped-blendmode-ref.html b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-ref.html new file mode 100644 index 0000000000..65660c6ca7 --- /dev/null +++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-ref.html @@ -0,0 +1,31 @@ + + + +Scrolled blend mode + + + +
+ + diff --git a/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity-ref.html b/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity-ref.html new file mode 100644 index 0000000000..da251e5aaf --- /dev/null +++ b/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity-ref.html @@ -0,0 +1,42 @@ + + + +Reference: Active opacity should be rendered if it's inside the display port, even if it's currently offscreen + + + +
+
+
+
+
+ + diff --git a/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity.html b/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity.html new file mode 100644 index 0000000000..d36c940dde --- /dev/null +++ b/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity.html @@ -0,0 +1,39 @@ + + + +Active opacity should be rendered if it's inside the display port, even if it's currently offscreen + + + +
+
+
+
+
diff --git a/layout/reftests/async-scrolling/reftest.list b/layout/reftests/async-scrolling/reftest.list index bcc063f796..1af9bff06a 100644 --- a/layout/reftests/async-scrolling/reftest.list +++ b/layout/reftests/async-scrolling/reftest.list @@ -29,6 +29,7 @@ skip-if(!asyncPan) == position-fixed-iframe-1.html position-fixed-iframe-1-ref.h skip-if(!asyncPan) == position-fixed-iframe-2.html position-fixed-iframe-2-ref.html skip-if(!asyncPan) == position-fixed-in-scroll-container.html position-fixed-in-scroll-container-ref.html fuzzy(1,60000) skip-if(!asyncPan) == group-opacity-surface-size-1.html group-opacity-surface-size-1-ref.html +skip-if(!asyncPan) == offscreen-prerendered-active-opacity.html offscreen-prerendered-active-opacity-ref.html # for the following tests, we want to disable the low-precision buffer # as it will expand the displayport beyond what the test specifies in diff --git a/layout/reftests/border-image/reftest.list b/layout/reftests/border-image/reftest.list index 67a3aedd4e..054402984d 100644 --- a/layout/reftests/border-image/reftest.list +++ b/layout/reftests/border-image/reftest.list @@ -82,3 +82,8 @@ fuzzy(1,1054) fails-if(OSX) == border-image-repeating-radial-gradient-repeat-rou # border-image-source (-moz-)element fuzzy(125,5808) fuzzy-if(B2G,151,5809) == border-image-element.html border-image-element-ref.html + +# svg-as-border-image +== svg-as-border-image-1a.html svg-as-border-image-1-ref.html +== svg-as-border-image-1b.html svg-as-border-image-1-ref.html +== svg-as-border-image-1c.html svg-as-border-image-1-ref.html diff --git a/layout/reftests/border-image/svg-as-border-image-1-ref.html b/layout/reftests/border-image/svg-as-border-image-1-ref.html new file mode 100644 index 0000000000..ea2a51381b --- /dev/null +++ b/layout/reftests/border-image/svg-as-border-image-1-ref.html @@ -0,0 +1,19 @@ + + + +reference of svg-as-border-image + + + +
+ + diff --git a/layout/reftests/border-image/svg-as-border-image-1a.html b/layout/reftests/border-image/svg-as-border-image-1a.html new file mode 100644 index 0000000000..6b5eaf405d --- /dev/null +++ b/layout/reftests/border-image/svg-as-border-image-1a.html @@ -0,0 +1,18 @@ + + + +test of svg-as-border-image + + + +
+ + diff --git a/layout/reftests/border-image/svg-as-border-image-1b.html b/layout/reftests/border-image/svg-as-border-image-1b.html new file mode 100644 index 0000000000..2c3e5d92bc --- /dev/null +++ b/layout/reftests/border-image/svg-as-border-image-1b.html @@ -0,0 +1,18 @@ + + + +test of svg-as-border-image + + + +
+ + diff --git a/layout/reftests/border-image/svg-as-border-image-1c.html b/layout/reftests/border-image/svg-as-border-image-1c.html new file mode 100644 index 0000000000..2ab2642e6b --- /dev/null +++ b/layout/reftests/border-image/svg-as-border-image-1c.html @@ -0,0 +1,18 @@ + + + +test of svg-as-border-image + + + +
+ + diff --git a/layout/reftests/css-blending/clipped-mixblendmode-containing-unclipped-stuff-ref.html b/layout/reftests/css-blending/clipped-mixblendmode-containing-unclipped-stuff-ref.html new file mode 100644 index 0000000000..71b215e1bc --- /dev/null +++ b/layout/reftests/css-blending/clipped-mixblendmode-containing-unclipped-stuff-ref.html @@ -0,0 +1,57 @@ + + + +Blend mode items shouldn't clip unclipped children to their own clip + + + +
+
+
+
+ +
diff --git a/layout/reftests/css-blending/clipped-mixblendmode-containing-unclipped-stuff.html b/layout/reftests/css-blending/clipped-mixblendmode-containing-unclipped-stuff.html new file mode 100644 index 0000000000..f82b50b617 --- /dev/null +++ b/layout/reftests/css-blending/clipped-mixblendmode-containing-unclipped-stuff.html @@ -0,0 +1,51 @@ + + + +Blend mode items shouldn't clip unclipped children to their own clip + + + +
+
+
+
+
+
diff --git a/layout/reftests/css-blending/clipped-opacity-containing-unclipped-mixblendmode-ref.html b/layout/reftests/css-blending/clipped-opacity-containing-unclipped-mixblendmode-ref.html new file mode 100644 index 0000000000..307483ac6b --- /dev/null +++ b/layout/reftests/css-blending/clipped-opacity-containing-unclipped-mixblendmode-ref.html @@ -0,0 +1,49 @@ + + + +Blend mode containers shouldn't clip unclipped children to their own clip + + + +
+
+
+
+
+
diff --git a/layout/reftests/css-blending/clipped-opacity-containing-unclipped-mixblendmode.html b/layout/reftests/css-blending/clipped-opacity-containing-unclipped-mixblendmode.html new file mode 100644 index 0000000000..444116feeb --- /dev/null +++ b/layout/reftests/css-blending/clipped-opacity-containing-unclipped-mixblendmode.html @@ -0,0 +1,50 @@ + + + +Blend mode containers shouldn't clip unclipped children to their own clip + + + +
+
+
+
+
+
diff --git a/layout/reftests/css-blending/reftest.list b/layout/reftests/css-blending/reftest.list index 868d23f5d9..20bd95fcee 100644 --- a/layout/reftests/css-blending/reftest.list +++ b/layout/reftests/css-blending/reftest.list @@ -48,6 +48,8 @@ fuzzy(1,14400) pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-ch pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-nested-976533.html mix-blend-mode-nested-976533-ref.html pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-culling-1207041.html mix-blend-mode-culling-1207041-ref.html pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-dest-alpha-1135271.html mix-blend-mode-dest-alpha-1135271-ref.html +== clipped-mixblendmode-containing-unclipped-stuff.html clipped-mixblendmode-containing-unclipped-stuff-ref.html +fuzzy(1,6800) == clipped-opacity-containing-unclipped-mixblendmode.html clipped-opacity-containing-unclipped-mixblendmode-ref.html # Test plan 5.3.1 Blending between the background layers and the background color for an element with background-blend-mode # Test 9 diff --git a/layout/reftests/transform-3d/opacity-preserve3d-1-ref.html b/layout/reftests/transform-3d/opacity-preserve3d-1-ref.html new file mode 100644 index 0000000000..b4763f88fc --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-1-ref.html @@ -0,0 +1,38 @@ + + + + + +
+
+
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-1.html b/layout/reftests/transform-3d/opacity-preserve3d-1.html new file mode 100644 index 0000000000..b73be49238 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-1.html @@ -0,0 +1,45 @@ + + + + + +
+
+
+
+
+
+
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-2-ref.html b/layout/reftests/transform-3d/opacity-preserve3d-2-ref.html new file mode 100644 index 0000000000..0cc05ac2a7 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-2-ref.html @@ -0,0 +1,25 @@ + + + + + +
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-2.html b/layout/reftests/transform-3d/opacity-preserve3d-2.html new file mode 100644 index 0000000000..a04b998291 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-2.html @@ -0,0 +1,31 @@ + + + + + +
+
+
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-3-ref.html b/layout/reftests/transform-3d/opacity-preserve3d-3-ref.html new file mode 100644 index 0000000000..d17b54b718 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-3-ref.html @@ -0,0 +1,39 @@ + + + + + +
+
+ +
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-3.html b/layout/reftests/transform-3d/opacity-preserve3d-3.html new file mode 100644 index 0000000000..f7bbf2da88 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-3.html @@ -0,0 +1,42 @@ + + + + + +
+
+
+
+ +
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-4-ref.html b/layout/reftests/transform-3d/opacity-preserve3d-4-ref.html new file mode 100644 index 0000000000..ba978f1f35 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-4-ref.html @@ -0,0 +1,30 @@ + + + + + + +
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-4.html b/layout/reftests/transform-3d/opacity-preserve3d-4.html new file mode 100644 index 0000000000..9fd6728644 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-4.html @@ -0,0 +1,38 @@ + + + + + +
+
+
+ +
+
+ + + + diff --git a/layout/reftests/transform-3d/reftest.list b/layout/reftests/transform-3d/reftest.list index 3e5beb7c54..3265235d47 100644 --- a/layout/reftests/transform-3d/reftest.list +++ b/layout/reftests/transform-3d/reftest.list @@ -71,3 +71,7 @@ fuzzy-if(cocoaWidget,128,9) == animate-preserve3d-parent.html animate-preserve3d fuzzy-if(cocoaWidget,128,9) == animate-preserve3d-child.html animate-preserve3d-ref.html # intermittently fuzzy on Mac == animate-backface-hidden.html about:blank == 1245450-1.html green-rect.html +fuzzy(1,2000) == opacity-preserve3d-1.html opacity-preserve3d-1-ref.html +fuzzy(1,15000) == opacity-preserve3d-2.html opacity-preserve3d-2-ref.html +fuzzy(1,10000) == opacity-preserve3d-3.html opacity-preserve3d-3-ref.html +fuzzy(1,10000) == opacity-preserve3d-4.html opacity-preserve3d-4-ref.html diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index 27044ee816..41b48a9bc2 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -169,6 +169,7 @@ 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) @@ -280,6 +281,7 @@ 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) @@ -535,6 +537,7 @@ 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) @@ -706,6 +709,7 @@ 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) diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp index 7678772443..0bd1b0cfef 100644 --- a/layout/style/nsMediaFeatures.cpp +++ b/layout/style/nsMediaFeatures.cpp @@ -17,6 +17,7 @@ #endif #include "nsCSSRuleProcessor.h" #include "nsDeviceContext.h" +#include "nsIBaseWindow.h" #include "nsIDocument.h" #include "nsContentUtils.h" #include "mozilla/StyleSheetHandle.h" @@ -36,6 +37,14 @@ static const nsCSSProps::KTableEntry kScanKeywords[] = { { 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; @@ -304,6 +313,34 @@ GetScan(nsPresContext* aPresContext, const nsMediaFeature*, return NS_OK; } +static nsresult +GetDisplayMode(nsPresContext* aPresContext, const nsMediaFeature*, + nsCSSValue& aResult) +{ + nsCOMPtr container = aPresContext->GetRootPresContext()-> + Document()->GetContainer(); + nsCOMPtr baseWindow = do_QueryInterface(container); + if (!baseWindow) { + aResult.SetIntValue(NS_STYLE_DISPLAY_MODE_BROWSER, eCSSUnit_Enumerated); + return NS_OK; + } + nsCOMPtr mainWidget; + baseWindow->GetMainWidget(getter_AddRefs(mainWidget)); + int32_t displayMode; + nsSizeMode mode = mainWidget ? mainWidget->SizeMode() : nsSizeMode_Normal; + 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) @@ -533,6 +570,14 @@ nsMediaFeatures::features[] = { { 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): diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index c7bdadea8a..606b613fff 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -1208,6 +1208,12 @@ enum class FillMode : uint32_t; #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/test/chrome/chrome.ini b/layout/style/test/chrome/chrome.ini index 11a3c96810..6c47faaff2 100644 --- a/layout/style/test/chrome/chrome.ini +++ b/layout/style/test/chrome/chrome.ini @@ -16,6 +16,7 @@ support-files = [test_bug1157097.html] [test_bug1160724.xul] [test_bug535806.xul] +[test_display_mode.html] [test_hover.html] skip-if = buildapp == 'mulet' [test_moz_document_rules.html] diff --git a/layout/style/test/chrome/test_display_mode.html b/layout/style/test/chrome/test_display_mode.html new file mode 100644 index 0000000000..244eefea27 --- /dev/null +++ b/layout/style/test/chrome/test_display_mode.html @@ -0,0 +1,94 @@ + + + + + + Test for Display Mode + + + + + + + + +Mozilla Bug 1104916 + +

+ +
+
+ + 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 0000000000..abd9ffd7d4 --- /dev/null +++ b/layout/style/test/file_animations_effect_timing_enddelay.html @@ -0,0 +1,146 @@ + + + + + + + + + +
+ + + diff --git a/layout/style/test/file_animations_iterationstart.html b/layout/style/test/file_animations_iterationstart.html new file mode 100644 index 0000000000..d1d8529ce9 --- /dev/null +++ b/layout/style/test/file_animations_iterationstart.html @@ -0,0 +1,60 @@ + + + + + + + + + +
+ + + diff --git a/layout/style/test/mochitest.ini b/layout/style/test/mochitest.ini index c14cc680aa..86adb66aba 100644 --- a/layout/style/test/mochitest.ini +++ b/layout/style/test/mochitest.ini @@ -44,7 +44,11 @@ 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_event_order.html] +[test_animations_iterationstart.html] +support-files = file_animations_iterationstart.html [test_animations_omta.html] [test_animations_omta_start.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1041017 diff --git a/layout/style/test/test_animations_effect_timing_enddelay.html b/layout/style/test/test_animations_effect_timing_enddelay.html new file mode 100644 index 0000000000..d4ad918dda --- /dev/null +++ b/layout/style/test/test_animations_effect_timing_enddelay.html @@ -0,0 +1,24 @@ + + + + Test for animation.effect.timing.endDelay on compositor + + + + +
+
+
+
+ + diff --git a/layout/style/test/test_animations_iterationstart.html b/layout/style/test/test_animations_iterationstart.html new file mode 100644 index 0000000000..d6a54f3b25 --- /dev/null +++ b/layout/style/test/test_animations_iterationstart.html @@ -0,0 +1,28 @@ + + + + + Test for iterationStart on compositor animations (Bug 1248338) + + + + +Mozilla Bug 1248338 +
+
+
+
+ + diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html index 49ffcb099d..231671188e 100644 --- a/layout/style/test/test_media_queries.html +++ b/layout/style/test/test_media_queries.html @@ -252,6 +252,14 @@ function run() { 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 = diff --git a/layout/svg/nsSVGClipPathFrame.h b/layout/svg/nsSVGClipPathFrame.h index 84ec21c6f8..14d189db5a 100644 --- a/layout/svg/nsSVGClipPathFrame.h +++ b/layout/svg/nsSVGClipPathFrame.h @@ -184,7 +184,9 @@ private: private: nsSVGClipPathFrame* mFrame; - DebugOnly mMarkAsInUseCalled; +#ifdef DEBUG + bool mMarkAsInUseCalled; +#endif }; // Set, during a GetClipMask() call, to the transform that still needs to be diff --git a/layout/svg/nsSVGIntegrationUtils.cpp b/layout/svg/nsSVGIntegrationUtils.cpp index 5ec3ecd50b..6b177062cd 100644 --- a/layout/svg/nsSVGIntegrationUtils.cpp +++ b/layout/svg/nsSVGIntegrationUtils.cpp @@ -573,9 +573,7 @@ nsSVGIntegrationUtils::PaintFramesWithEffects(gfxContext& aContext, aBorderArea, firstFrame->StyleContext(), *aFrame->StyleBorder(), - flags, - nullptr, - -1); + flags); maskSurface = targetDT->Snapshot(); // Compute mask transform. diff --git a/layout/xul/nsImageBoxFrame.cpp b/layout/xul/nsImageBoxFrame.cpp index b08d461516..9052bc249f 100644 --- a/layout/xul/nsImageBoxFrame.cpp +++ b/layout/xul/nsImageBoxFrame.cpp @@ -473,10 +473,20 @@ nsDisplayXULImage::ConfigureLayer(ImageLayer* aLayer, // aParameters.Offset() is always zero. MOZ_ASSERT(aParameters.Offset() == LayerIntPoint(0,0)); + // It's possible (for example, due to downscale-during-decode) that the + // ImageContainer this ImageLayer is holding has a different size from the + // intrinsic size of the image. For this reason we compute the transform using + // the ImageContainer's size rather than the image's intrinsic size. + // XXX(seth): In reality, since the size of the ImageContainer may change + // asynchronously, this is not enough. Bug 1183378 will provide a more + // complete fix, but this solution is safe in more cases than simply relying + // on the intrinsic size. + IntSize containerSize = aLayer->GetContainer()->GetCurrentSize(); + const LayoutDevicePoint p = destRect.TopLeft(); Matrix transform = Matrix::Translation(p.x, p.y); - transform.PreScale(destRect.Width() / imageWidth, - destRect.Height() / imageHeight); + transform.PreScale(destRect.Width() / containerSize.width, + destRect.Height() / containerSize.height); aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); } diff --git a/layout/xul/nsSliderFrame.cpp b/layout/xul/nsSliderFrame.cpp index b7090bd88d..d7a86d57f3 100644 --- a/layout/xul/nsSliderFrame.cpp +++ b/layout/xul/nsSliderFrame.cpp @@ -11,6 +11,8 @@ // #include "nsSliderFrame.h" + +#include "gfxPrefs.h" #include "nsStyleContext.h" #include "nsPresContext.h" #include "nsIContent.h" @@ -757,9 +759,12 @@ nsSliderFrame::CurrentPositionChanged() // set the rect thumbFrame->SetRect(newThumbRect); - // Request a repaint of the scrollbar + // Request a repaint of the scrollbar unless we have paint-skipping enabled + // and this is an APZ scroll. nsIScrollableFrame* scrollableFrame = do_QueryFrame(GetScrollbar()->GetParent()); - if (!scrollableFrame || scrollableFrame->LastScrollOrigin() != nsGkAtoms::apz) { + if (!gfxPrefs::APZPaintSkipping() || + !scrollableFrame || + scrollableFrame->LastScrollOrigin() != nsGkAtoms::apz) { SchedulePaint(); } diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 0742e5cc93..9445de0f0f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -611,6 +611,7 @@ pref("apz.overscroll.spring_stiffness", "0.0018"); pref("apz.overscroll.stop_distance_threshold", "5.0"); pref("apz.overscroll.stop_velocity_threshold", "0.01"); pref("apz.overscroll.stretch_factor", "0.35"); +pref("apz.paint_skipping.enabled", true); // Whether to print the APZC tree for debugging pref("apz.printtree", false); diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index eb56608a82..5c1a52ed9b 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -2705,7 +2705,8 @@ WebSocketChannel::SetupRequest() rv = mHttpChannel->SetLoadFlags(nsIRequest::LOAD_BACKGROUND | nsIRequest::INHIBIT_CACHING | - nsIRequest::LOAD_BYPASS_CACHE); + nsIRequest::LOAD_BYPASS_CACHE | + nsIChannel::LOAD_BYPASS_SERVICE_WORKER); NS_ENSURE_SUCCESS(rv, rv); // we never let websockets be blocked by head CSS/JS loads to avoid diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index b12837e0d1..5278fe8b2f 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -10901,6 +10901,10 @@ "path": "IndexedDB/idbindex_openKeyCursor3.htm", "url": "/IndexedDB/idbindex_openKeyCursor3.htm" }, + { + "path": "IndexedDB/idbkeyrange-includes.htm", + "url": "/IndexedDB/idbkeyrange-includes.htm" + }, { "path": "IndexedDB/idbkeyrange.htm", "url": "/IndexedDB/idbkeyrange.htm" @@ -18797,6 +18801,10 @@ "path": "web-animations/animation-effect-timing/duration.html", "url": "/web-animations/animation-effect-timing/duration.html" }, + { + "path": "web-animations/animation-effect-timing/endDelay.html", + "url": "/web-animations/animation-effect-timing/endDelay.html" + }, { "path": "web-animations/animation-effect-timing/getAnimations.html", "url": "/web-animations/animation-effect-timing/getAnimations.html" @@ -18844,6 +18852,30 @@ "path": "web-animations/animation-timeline/idlharness.html", "url": "/web-animations/animation-timeline/idlharness.html" }, + { + "path": "web-animations/animation/constructor.html", + "url": "/web-animations/animation/constructor.html" + }, + { + "path": "web-animations/keyframe-effect/constructor.html", + "url": "/web-animations/keyframe-effect/constructor.html" + }, + { + "path": "web-animations/keyframe-effect/effect-easing.html", + "url": "/web-animations/keyframe-effect/effect-easing.html" + }, + { + "path": "web-animations/keyframe-effect/getComputedTiming-currentIteration.html", + "url": "/web-animations/keyframe-effect/getComputedTiming-currentIteration.html" + }, + { + "path": "web-animations/keyframe-effect/getComputedTiming-progress.html", + "url": "/web-animations/keyframe-effect/getComputedTiming-progress.html" + }, + { + "path": "web-animations/keyframe-effect/getComputedTiming.html", + "url": "/web-animations/keyframe-effect/getComputedTiming.html" + }, { "path": "webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html", "url": "/webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html" @@ -21481,22 +21513,6 @@ { "path": "webdriver/timeouts/page_load_timeouts_tests.py" }, - { - "path": "web-animations/keyframe-effect/constructor.html", - "url": "/web-animations/keyframe-effect/constructor.html" - }, - { - "path": "web-animations/keyframe-effect/effect-easing.html", - "url": "/web-animations/keyframe-effect/effect-easing.html" - }, - { - "path": "web-animations/keyframe-effect/getComputedTiming.html", - "url": "/web-animations/keyframe-effect/getComputedTiming.html" - }, - { - "path": "web-animations/animation/constructor.html", - "url": "/web-animations/animation/constructor.html" - }, { "path": "webdriver/user_input/clear_test.py" } diff --git a/testing/web-platform/mozilla/meta/MANIFEST.json b/testing/web-platform/mozilla/meta/MANIFEST.json index 72557df142..3803bd9d08 100644 --- a/testing/web-platform/mozilla/meta/MANIFEST.json +++ b/testing/web-platform/mozilla/meta/MANIFEST.json @@ -34,6 +34,12 @@ "url": "/_mozilla/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html" } ], + "service-workers/service-worker/activate-event-after-install-state-change.https.html": [ + { + "path": "service-workers/service-worker/activate-event-after-install-state-change.https.html", + "url": "/_mozilla/service-workers/service-worker/activate-event-after-install-state-change.https.html" + } + ], "service-workers/service-worker/activation-after-registration.https.html": [ { "path": "service-workers/service-worker/activation-after-registration.https.html", @@ -64,18 +70,18 @@ "url": "/_mozilla/service-workers/service-worker/claim-using-registration.https.html" } ], - "service-workers/service-worker/clients-get.https.html": [ - { - "path": "service-workers/service-worker/clients-get.https.html", - "url": "/_mozilla/service-workers/service-worker/clients-get.https.html" - } - ], "service-workers/service-worker/clients-get-cross-origin.https.html": [ { "path": "service-workers/service-worker/clients-get-cross-origin.https.html", "url": "/_mozilla/service-workers/service-worker/clients-get-cross-origin.https.html" } ], + "service-workers/service-worker/clients-get.https.html": [ + { + "path": "service-workers/service-worker/clients-get.https.html", + "url": "/_mozilla/service-workers/service-worker/clients-get.https.html" + } + ], "service-workers/service-worker/clients-matchall-client-types.https.html": [ { "path": "service-workers/service-worker/clients-matchall-client-types.https.html", @@ -163,6 +169,7 @@ "service-workers/service-worker/fetch-event-redirect.https.html": [ { "path": "service-workers/service-worker/fetch-event-redirect.https.html", + "timeout": "long", "url": "/_mozilla/service-workers/service-worker/fetch-event-redirect.https.html" } ], @@ -181,6 +188,7 @@ "service-workers/service-worker/fetch-frame-resource.https.html": [ { "path": "service-workers/service-worker/fetch-frame-resource.https.html", + "timeout": "long", "url": "/_mozilla/service-workers/service-worker/fetch-frame-resource.https.html" } ], @@ -193,12 +201,14 @@ "service-workers/service-worker/fetch-mixed-content-to-inscope.https.html": [ { "path": "service-workers/service-worker/fetch-mixed-content-to-inscope.https.html", + "timeout": "long", "url": "/_mozilla/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html" } ], "service-workers/service-worker/fetch-mixed-content-to-outscope.https.html": [ { "path": "service-workers/service-worker/fetch-mixed-content-to-outscope.https.html", + "timeout": "long", "url": "/_mozilla/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html" } ], @@ -208,18 +218,18 @@ "url": "/_mozilla/service-workers/service-worker/fetch-request-css-base-url.https.html" } ], + "service-workers/service-worker/fetch-request-css-images.https.html": [ + { + "path": "service-workers/service-worker/fetch-request-css-images.https.html", + "url": "/_mozilla/service-workers/service-worker/fetch-request-css-images.https.html" + } + ], "service-workers/service-worker/fetch-request-fallback.https.html": [ { "path": "service-workers/service-worker/fetch-request-fallback.https.html", "url": "/_mozilla/service-workers/service-worker/fetch-request-fallback.https.html" } ], - "service-workers/service-worker/fetch-request-html-imports.https.html": [ - { - "path": "service-workers/service-worker/fetch-request-html-imports.https.html", - "url": "/_mozilla/service-workers/service-worker/fetch-request-html-imports.https.html" - } - ], "service-workers/service-worker/fetch-request-no-freshness-headers.https.html": [ { "path": "service-workers/service-worker/fetch-request-no-freshness-headers.https.html", @@ -264,10 +274,10 @@ "url": "/_mozilla/service-workers/service-worker/getregistration.https.html" } ], - "service-workers/service-worker/getregistrations.sub.html": [ + "service-workers/service-worker/getregistrations.https.html": [ { - "path": "service-workers/service-worker/getregistrations.sub.html", - "url": "/_mozilla/service-workers/service-worker/getregistrations.sub.html" + "path": "service-workers/service-worker/getregistrations.https.html", + "url": "/_mozilla/service-workers/service-worker/getregistrations.https.html" } ], "service-workers/service-worker/indexeddb.https.html": [ @@ -312,6 +322,18 @@ "url": "/_mozilla/service-workers/service-worker/multiple-register.https.html" } ], + "service-workers/service-worker/multiple-update.https.html": [ + { + "path": "service-workers/service-worker/multiple-update.https.html", + "url": "/_mozilla/service-workers/service-worker/multiple-update.https.html" + } + ], + "service-workers/service-worker/navigation-redirect.https.html": [ + { + "path": "service-workers/service-worker/navigation-redirect.https.html", + "url": "/_mozilla/service-workers/service-worker/navigation-redirect.https.html" + } + ], "service-workers/service-worker/onactivate-script-error.https.html": [ { "path": "service-workers/service-worker/onactivate-script-error.https.html", @@ -565,6 +587,12 @@ "url": "/_mozilla/service-workers/service-worker/waiting.https.html" } ], + "service-workers/service-worker/websocket.https.html": [ + { + "path": "service-workers/service-worker/websocket.https.html", + "url": "/_mozilla/service-workers/service-worker/websocket.https.html" + } + ], "service-workers/service-worker/worker-interception.https.html": [ { "path": "service-workers/service-worker/worker-interception.https.html", diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html.ini index 8800bea307..51a24ceda7 100644 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html.ini +++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html.ini @@ -1,6 +1,6 @@ [registration-attribute.https.html] type: testharness - expected: ERROR + expected: TIMEOUT [Verify registration attribute on ServiceWorkerGlobalScope] expected: TIMEOUT diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-cors-xhr.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-cors-xhr.https.html.ini deleted file mode 100644 index 908ece2a10..0000000000 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-cors-xhr.https.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[fetch-cors-xhr.https.html] - type: testharness - expected: TIMEOUT - [Verify CORS XHR of fetch() in a Service Worker] - expected: TIMEOUT - diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-html-imports.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-html-imports.https.html.ini deleted file mode 100644 index 46e03c95d4..0000000000 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-html-imports.https.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[fetch-request-html-imports.https.html] - type: testharness - [Verify the FetchEvent for HTMLImports] - expected: FAIL - diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-no-freshness-headers.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-no-freshness-headers.https.html.ini deleted file mode 100644 index 1f6c1e6c89..0000000000 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-no-freshness-headers.https.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[fetch-request-no-freshness-headers.https.html] - type: testharness - expected: ERROR diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/getregistrations.sub.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/getregistrations.sub.html.ini deleted file mode 100644 index 2b520d064c..0000000000 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/getregistrations.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[getregistrations.sub.html] - type: testharness - expected: ERROR diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/navigation-redirect.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/navigation-redirect.https.html.ini new file mode 100644 index 0000000000..54e71deef0 --- /dev/null +++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/navigation-redirect.https.html.ini @@ -0,0 +1,3 @@ +[navigation-redirect.https.html] + disabled: + if e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1219469 diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/onactivate-script-error.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/onactivate-script-error.https.html.ini index 7b74853467..1025023e71 100644 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/onactivate-script-error.https.html.ini +++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/onactivate-script-error.https.html.ini @@ -1,6 +1,3 @@ -[onactivate-script-error.https.html] - type: testharness - expected: ERROR [activate handler throws an error] expected: FAIL diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/oninstall-script-error.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/oninstall-script-error.https.html.ini index 8fc056dc95..102ede6a99 100644 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/oninstall-script-error.https.html.ini +++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/oninstall-script-error.https.html.ini @@ -1,6 +1,6 @@ [oninstall-script-error.https.html] type: testharness - expected: ERROR + expected: OK [install handler throws an error that is cancelled] expected: FAIL diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/request-end-to-end.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/request-end-to-end.https.html.ini index 41a9d1b9d7..2da46be217 100644 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/request-end-to-end.https.html.ini +++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/request-end-to-end.https.html.ini @@ -1,6 +1,6 @@ [request-end-to-end.https.html] type: testharness - expected: ERROR + expected: TIMEOUT [Request: end-to-end] expected: TIMEOUT diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html.ini new file mode 100644 index 0000000000..cc16272c0a --- /dev/null +++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html.ini @@ -0,0 +1,6 @@ +[sync-xhr-doesnt-deadlock.https.html] + type: testharness + expected: OK + [Verify SyncXHR does not deadlock] + expected: FAIL + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html new file mode 100644 index 0000000000..57fccf1371 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html @@ -0,0 +1,32 @@ + +Service Worker: registration events + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-cors-xhr.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-cors-xhr.https.html index 3562592469..3b1f1d6bc3 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-cors-xhr.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-cors-xhr.https.html @@ -11,7 +11,7 @@ async_test(function(t) { var SCRIPT = 'resources/fetch-rewrite-worker.js'; var host_info = get_host_info(); - login(t) + login_https(t) .then(function() { return service_worker_unregister_and_register(t, SCRIPT, SCOPE); }) @@ -28,7 +28,7 @@ async_test(function(t) { service_worker_unregister_and_done(t, SCOPE); }); frame.contentWindow.postMessage({}, - host_info['HTTP_ORIGIN'], + host_info['HTTPS_ORIGIN'], [channel.port2]); }); }) diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html index a1eb62b9e8..b53a4a41bc 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html @@ -1,5 +1,6 @@ Service Worker: Fetch Event Redirect Handling + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-frame-resource.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-frame-resource.https.html index 156880faa1..99df2463d9 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-frame-resource.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-frame-resource.https.html @@ -1,5 +1,6 @@ Service Worker: Fetch for the frame loading. + @@ -45,7 +46,7 @@ function getLoadedObject(win, contentFunc, closeFunc) { // use default empty string for cross-domain window } done(content); - }, 5000); + }, 10000); win.onload = function() { clearTimeout(timeout); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html index 16c0e61b1f..fb9fa9b7b7 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html @@ -1,5 +1,6 @@ Service Worker: Mixed content of fetch() + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html index 587dbe78c9..cee89ce167 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html @@ -1,5 +1,6 @@ Service Worker: Mixed content of fetch() + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-html-imports.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-html-imports.https.html deleted file mode 100644 index 45d730337a..0000000000 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-html-imports.https.html +++ /dev/null @@ -1,61 +0,0 @@ - -Service Worker: FetchEvent for HTMLImports - - - - - diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/getregistrations.sub.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/getregistrations.https.html similarity index 72% rename from testing/web-platform/mozilla/tests/service-workers/service-worker/getregistrations.sub.html rename to testing/web-platform/mozilla/tests/service-workers/service-worker/getregistrations.https.html index 908784ffdd..003cb0baa2 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/getregistrations.sub.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/getregistrations.https.html @@ -9,7 +9,7 @@ // Purge the existing registrations for the origin. // getRegistrations() is used in order to avoid adding additional complexity // e.g. adding an internal function. -sequential_promise_test(function(t) { +promise_test(function(t) { return navigator.serviceWorker.getRegistrations() .then(function(registrations) { return registrations.reduce(function(sequence, registration) { @@ -20,7 +20,7 @@ sequential_promise_test(function(t) { }); }, 'Purge the existing registrations.'); -sequential_promise_test(function(t) { +promise_test(function(t) { return navigator.serviceWorker.getRegistrations() .then(function(value) { assert_array_equals( @@ -30,41 +30,41 @@ sequential_promise_test(function(t) { }); }, 'getRegistrations'); -sequential_promise_test(function(t) { +promise_test(function(t) { var scope = 'resources/scope/getregistrations/normal'; var script = 'resources/empty-worker.js'; var registrations = []; return service_worker_unregister_and_register(t, script, scope) .then(function(r) { - registrations.push(r); + registrations.push(r.scope); return navigator.serviceWorker.getRegistrations(); }) .then(function(value) { assert_array_equals( - value, + value.map((r) => r.scope), registrations, 'getRegistrations should resolve with array of registrations.'); return service_worker_unregister(t, scope); }); }, 'Register then getRegistrations'); -sequential_promise_test(function(t) { +promise_test(function(t) { var scope1 = 'resources/scope/getregistrations/scope1'; var scope2 = 'resources/scope/getregistrations/scope2'; var script = 'resources/empty-worker.js'; var registrations = []; return service_worker_unregister_and_register(t, script, scope1) .then(function(r) { - registrations.push(r); + registrations.push(r.scope); return service_worker_unregister_and_register(t, script, scope2); }) .then(function(r) { - registrations.push(r); + registrations.push(r.scope); return navigator.serviceWorker.getRegistrations(); }) .then(function(value) { assert_array_equals( - value, + value.map((r) => r.scope), registrations, 'getRegistrations should resolve with array of registrations.'); return service_worker_unregister(t, scope1); @@ -74,7 +74,7 @@ sequential_promise_test(function(t) { }); }, 'Register multiple times then getRegistrations'); -sequential_promise_test(function(t) { +promise_test(function(t) { var scope = 'resources/scope/getregistrations/register-unregister'; var script = 'resources/empty-worker.js'; return service_worker_unregister_and_register(t, script, scope) @@ -92,33 +92,49 @@ sequential_promise_test(function(t) { }); }, 'Register then Unregister then getRegistrations'); -sequential_promise_test(function(t) { - // Top-level window's origin is http://{{domains[www]}}:{{ports[http][0]}} - // Set frame's origin to http://{{host}}:{{ports[http][0]}} +promise_test(function(t) { var host_info = get_host_info(); - var frame_url = host_info['HTTP_REMOTE_ORIGIN'] + - '/service-worker/resources/frame-for-getregistrations.sub.html'; + // Rewrite the url to point to remote origin. + var frame_same_origin_url = new URL("resources/frame-for-getregistrations.html", window.location); + var frame_url = host_info['HTTPS_REMOTE_ORIGIN'] + frame_same_origin_url.pathname; var scope = 'resources/scope-for-getregistrations'; var script = 'resources/empty-worker.js'; var frame; var registrations = []; - return with_iframe(frame_url) + // Loads an iframe and waits for 'ready' message from it to resolve promise. + // Caller is responsible for removing frame. + function with_iframe_ready(url) { + return new Promise(function(resolve) { + var frame = document.createElement('iframe'); + frame.src = url; + window.addEventListener('message', function onMessage(e) { + window.removeEventListener('message', onMessage); + if (e.data == 'ready') { + resolve(frame); + } + }); + document.body.appendChild(frame); + }); + } + + // We need this special frame loading function because the frame is going + // to register it's own service worker and there is the possibility that that + // register() finishes after the register() for the same domain later in the + // test. So we have to wait until the cross origin register() is done, and not + // just until the frame loads. + return with_iframe_ready(frame_url) .then(function(f) { - // frame registered its registration scoped - // http://{{host}}:{{ports[http][0]}}/service-worker/resources/scope-for-getregistrations frame = f; - // Top-level window registers its registration scoped - // http://{{domains[www]}}:{{ports[http][0]}}/service-worker/resources/scope-for-getregistrations return service_worker_unregister_and_register(t, script, scope); }) .then(function(r) { - registrations.push(r); + registrations.push(r.scope); return navigator.serviceWorker.getRegistrations(); }) .then(function(value) { assert_array_equals( - value, + value.map((r) => r.scope), registrations, 'getRegistrations should only return same origin registrations.'); @@ -139,6 +155,5 @@ sequential_promise_test(function(t) { }); }, 'getRegistrations promise resolves only with same origin registrations.'); -sequential_promise_test_done(); done(); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/multiple-update.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/multiple-update.https.html new file mode 100644 index 0000000000..84aac9583b --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/multiple-update.https.html @@ -0,0 +1,92 @@ + + +Service Worker: Trigger multiple updates + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html index 905c905534..48f618397c 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html @@ -54,8 +54,8 @@ function create_serial_promise(test_cases) { window.addEventListener('message', function(evt) { var port = evt.ports[0]; - var url = host_info['HTTP_ORIGIN'] + path; - var remote_url = host_info['HTTP_REMOTE_ORIGIN'] + path; + var url = host_info['HTTPS_ORIGIN'] + path; + var remote_url = host_info['HTTPS_REMOTE_ORIGIN'] + path; // If the 4th value of the item of TEST_CASES is true, the test case outputs // warning messages. So such tests must be executed in serial to match the // expected output text. @@ -81,15 +81,15 @@ window.addEventListener('message', function(evt) { [remote_url + '?ignore', false, FAIL, true], // Executed in serial. [remote_url + '?ignore', true, FAIL, true], // Executed in serial. [ - remote_url + '?ACAOrigin=' + host_info['HTTP_ORIGIN'] + '&ignore', + remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore', false, SUCCESS ], [ - remote_url + '?ACAOrigin=' + host_info['HTTP_ORIGIN'] + '&ignore', + remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore', true, FAIL, true // Executed in serial. ], [ - remote_url + '?ACAOrigin=' + host_info['HTTP_ORIGIN'] + + remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ACACredentials=true&ignore', true, SUCCESS ], @@ -99,15 +99,15 @@ window.addEventListener('message', function(evt) { [remote_url + '?Auth&ignore', false, FAIL, true], // Executed in serial. [remote_url + '?Auth&ignore', true, FAIL, true], // Executed in serial. [ - remote_url + '?Auth&ACAOrigin=' + host_info['HTTP_ORIGIN'] + '&ignore', + remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore', false, 'STATUS401' ], [ - remote_url + '?Auth&ACAOrigin=' + host_info['HTTP_ORIGIN'] + '&ignore', + remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore', true, FAIL, true // Executed in serial. ], [ - remote_url + '?Auth&ACAOrigin=' + host_info['HTTP_ORIGIN'] + + remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ACACredentials=true&ignore', true, SUCCESS ], @@ -149,26 +149,41 @@ window.addEventListener('message', function(evt) { [ url + '?mode=cors&url=' + encodeURIComponent(remote_url + '?ACAOrigin=' + - host_info['HTTP_ORIGIN']), + host_info['HTTPS_ORIGIN']), false, SUCCESS ], [ url + '?mode=cors&url=' + encodeURIComponent(remote_url + '?ACAOrigin=' + - host_info['HTTP_ORIGIN']), + host_info['HTTPS_ORIGIN']), + true, FAIL + ], + [ + url + '?mode=cors&url=' + + encodeURIComponent(remote_url + '?ACAOrigin=' + + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true'), true, SUCCESS ], [ remote_url + '?mode=cors&url=' + encodeURIComponent(remote_url + '?ACAOrigin=' + - host_info['HTTP_ORIGIN']), + host_info['HTTPS_ORIGIN']), false, SUCCESS ], [ remote_url + '?mode=cors&url=' + encodeURIComponent(remote_url + '?ACAOrigin=' + - host_info['HTTP_ORIGIN']), + host_info['HTTPS_ORIGIN']), + true, FAIL + ], + [ + remote_url + + '?mode=cors&url=' + + encodeURIComponent(remote_url + '?ACAOrigin=' + + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true'), true, SUCCESS ] ]; diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-iframe.sub.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-iframe.sub.html deleted file mode 100644 index a8bacc00f7..0000000000 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-iframe.sub.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-worker.sub.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-worker.sub.js deleted file mode 100644 index 6db5427b52..0000000000 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-worker.sub.js +++ /dev/null @@ -1,25 +0,0 @@ -self.addEventListener('fetch', function(event) { - var url = event.request.url; - if (url.indexOf('dummy-dir') == -1) { - return; - } - var result = 'mode=' + event.request.mode + - ' credentials=' + event.request.credentials; - if (url == 'http://{{domains[www]}}:{{ports[http][0]}}/dummy-dir/same.html') { - event.respondWith(new Response( - result + - '' + - '')); - } else if (url == 'http://{{host}}:{{ports[http][0]}}/dummy-dir/other.html') { - event.respondWith(new Response( - result + - '' + - '')); - } else { - event.respondWith(new Response(result)); - } - }); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/frame-for-getregistrations.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/frame-for-getregistrations.html index 511bd97209..7fc35f1891 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/frame-for-getregistrations.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/frame-for-getregistrations.html @@ -6,7 +6,7 @@ var script = 'empty-worker.js'; var registration; navigator.serviceWorker.register(script, { scope: scope }) - .then(function(r) { registration = r; }); + .then(function(r) { registration = r; window.parent.postMessage('ready', '*'); }) self.onmessage = function(e) { if (e.data == 'unregister') { diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html new file mode 100644 index 0000000000..c1441ba685 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html @@ -0,0 +1,66 @@ + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py new file mode 100644 index 0000000000..4b40762d89 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py @@ -0,0 +1,15 @@ +def main(request, response): + if "url" in request.GET: + headers = [("Location", request.GET["url"])] + return 302, headers, '' + + return [], ''' + + +''' diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py new file mode 100644 index 0000000000..4b40762d89 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py @@ -0,0 +1,15 @@ +def main(request, response): + if "url" in request.GET: + headers = [("Location", request.GET["url"])] + return 302, headers, '' + + return [], ''' + + +''' diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py new file mode 100644 index 0000000000..4b40762d89 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py @@ -0,0 +1,15 @@ +def main(request, response): + if "url" in request.GET: + headers = [("Location", request.GET["url"])] + return 302, headers, '' + + return [], ''' + + +''' diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-worker.js new file mode 100644 index 0000000000..cb15b3ff11 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/navigation-redirect-worker.js @@ -0,0 +1,75 @@ +// We store an empty response for each fetch event request we see +// in this Cache object so we can get the list of urls in the +// message event. +var cacheName = 'urls-' + self.registration.scope; + +var waitUntilPromiseList = []; + +self.addEventListener('message', function(event) { + var urls; + event.waitUntil(Promise.all(waitUntilPromiseList).then(function() { + waitUntilPromiseList = []; + return caches.open(cacheName); + }).then(function(cache) { + return cache.keys(); + }).then(function(requestList) { + urls = requestList.map(function(request) { return request.url; }); + return caches.delete(cacheName); + }).then(function() { + event.data.port.postMessage({urls: urls}); + })); + }); + +function get_query_params(url) { + var search = (new URL(url)).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split('&'); + params.forEach(function(param) { + var element = param.split('='); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} + +self.addEventListener('fetch', function(event) { + var waitUntilPromise = caches.open(cacheName).then(function(cache) { + return cache.put(event.request, new Response()); + }); + event.waitUntil(waitUntilPromise); + + var params = get_query_params(event.request.url); + if (!params['sw']) { + // To avoid races, add the waitUntil() promise to our global list. + // If we get a message event before we finish here, it will wait + // these promises to complete before proceeding to read from the + // cache. + waitUntilPromiseList.push(waitUntilPromise); + return; + } + + event.respondWith(waitUntilPromise.then(function() { + if (params['sw'] == 'gen') { + return Response.redirect(params['url']); + } else if (params['sw'] == 'fetch') { + return fetch(event.request); + } else if (params['sw'] == 'opaque') { + return fetch(new Request(event.request.url, {redirect: 'manual'})); + } else if (params['sw'] == 'opaqueThroughCache') { + var url = event.request.url; + var cache; + return caches.delete(url) + .then(function() { return self.caches.open(url); }) + .then(function(c) { + cache = c; + return fetch(new Request(url, {redirect: 'manual'})); + }) + .then(function(res) { return cache.put(event.request, res); }) + .then(function() { return cache.match(url); }); + } + + // unexpected... trigger an interception failure + })); + }); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js index 12fdb47ba7..bf582c7707 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js @@ -4,10 +4,6 @@ self.addEventListener('install', function() { self.state = 'installing'; }); -self.addEventListener('activate', function() { - self.state = 'activating'; - }); - self.addEventListener('message', function(event) { var port = event.data.port; if (self.state !== 'installing') { @@ -20,11 +16,6 @@ self.addEventListener('message', function(event) { port.postMessage('FAIL: Promise should be resolved with undefined'); return; } - if (self.state !== 'activating') { - port.postMessage( - 'FAIL: Promise should be resolved after worker activated'); - return; - } port.postMessage('PASS'); }) .catch(function(e) { diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/test-helpers.sub.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/test-helpers.sub.js index f11b94ee02..b0ffbd4062 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/test-helpers.sub.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/test-helpers.sub.js @@ -166,8 +166,8 @@ function base_path() { function test_login(test, origin, username, password, cookie) { return new Promise(function(resolve, reject) { with_iframe( - origin + - '/service-worker/resources/fetch-access-control-login.html') + origin + base_path() + + 'resources/fetch-access-control-login.html') .then(test.step_func(function(frame) { var channel = new MessageChannel(); channel.port1.onmessage = test.step_func(function() { @@ -181,8 +181,27 @@ function test_login(test, origin, username, password, cookie) { }); } +function test_websocket(test, frame, url) { + return new Promise(function(resolve, reject) { + var ws = new frame.contentWindow.WebSocket(url, ['echo', 'chat']); + var openCalled = false; + ws.addEventListener('open', test.step_func(function(e) { + assert_equals(ws.readyState, 1, "The WebSocket should be open"); + openCalled = true; + ws.close(); + }), true); + + ws.addEventListener('close', test.step_func(function(e) { + assert_true(openCalled, "The WebSocket should be closed after being opened"); + resolve(); + }), true); + + ws.addEventListener('error', reject); + }); +} + function login(test) { - return test_login(test, 'http://{{domains[www]}}:{{ports[http][0]}}', + return test_login(test, 'http://{{domains[www1]}}:{{ports[http][0]}}', 'username1', 'password1', 'cookie1') .then(function() { return test_login(test, 'http://{{host}}:{{ports[http][0]}}', @@ -191,10 +210,18 @@ function login(test) { } function login_https(test) { - return test_login(test, 'https://{{domains[www]}}:8443', + return test_login(test, 'https://{{domains[www1]}}:{{ports[https][0]}}', 'username1s', 'password1s', 'cookie1') .then(function() { - return test_login(test, 'https://{{host}}:8443', + return test_login(test, 'https://{{host}}:{{ports[https][0]}}', 'username2s', 'password2s', 'cookie2'); }); } + +function websocket(test, frame) { + return test_websocket(test, frame, get_websocket_url()); +} + +function get_websocket_url() { + return 'wss://{{host}}:{{ports[wss][0]}}/echo'; +} diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/websocket.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/websocket.js new file mode 100644 index 0000000000..fc6abd283a --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/websocket.js @@ -0,0 +1,7 @@ +self.urls = []; +self.addEventListener('fetch', function(event) { + self.urls.push(event.request.url); + }); +self.addEventListener('message', function(event) { + event.data.port.postMessage({urls: self.urls}); + }); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting-installed.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting-installed.https.html index f2ca7b5108..42e4000b1f 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting-installed.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting-installed.https.html @@ -10,13 +10,16 @@ promise_test(function(t) { var scope = 'resources/blank.html'; var url1 = 'resources/empty.js'; var url2 = 'resources/skip-waiting-installed-worker.js'; - var frame, frame_sw, service_worker, onmessage, oncontrollerchanged; + var frame, frame_sw, service_worker, registration, onmessage, oncontrollerchanged; var saw_message = new Promise(function(resolve) { onmessage = function(e) { var message = e.data; assert_equals( message, 'PASS', - 'skipWaiting promise should be resolved after activated'); + 'skipWaiting promise should be resolved with undefined'); + + assert_equals(registration.active.scriptURL, normalizeURL(url2), + "skipWaiting should make worker become active"); resolve(); }; }); @@ -29,8 +32,8 @@ promise_test(function(t) { }; }); return service_worker_unregister_and_register(t, url1, scope) - .then(function(registration) { - return wait_for_state(t, registration.installing, 'activated'); + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); }) .then(function() { return with_iframe(scope); @@ -44,8 +47,9 @@ promise_test(function(t) { frame_sw.oncontrollerchange = t.step_func(oncontrollerchanged); return navigator.serviceWorker.register(url2, {scope: scope}); }) - .then(function(registration) { - service_worker = registration.installing; + .then(function(r) { + registration = r; + service_worker = r.installing; return wait_for_state(t, service_worker, 'installed'); }) .then(function() { diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/websocket.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/websocket.https.html new file mode 100644 index 0000000000..3dfa6e5149 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/websocket.https.html @@ -0,0 +1,46 @@ + +Service Worker: WebSocket handshake channel is not intercepted + + + + + diff --git a/testing/web-platform/tests/IndexedDB/idbkeyrange-includes.htm b/testing/web-platform/tests/IndexedDB/idbkeyrange-includes.htm new file mode 100644 index 0000000000..bfe9d2c482 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/idbkeyrange-includes.htm @@ -0,0 +1,33 @@ + + + + + + diff --git a/testing/web-platform/tests/web-animations/animation-effect-timing/endDelay.html b/testing/web-platform/tests/web-animations/animation-effect-timing/endDelay.html new file mode 100644 index 0000000000..b4364bc612 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-effect-timing/endDelay.html @@ -0,0 +1,99 @@ + + +endDelay tests + + + + + + + +
+ + diff --git a/testing/web-platform/tests/web-animations/animation-effect-timing/getAnimations.html b/testing/web-platform/tests/web-animations/animation-effect-timing/getAnimations.html index 5a87a84a66..e7547a0734 100644 --- a/testing/web-platform/tests/web-animations/animation-effect-timing/getAnimations.html +++ b/testing/web-platform/tests/web-animations/animation-effect-timing/getAnimations.html @@ -25,5 +25,55 @@ test(function(t) { assert_equals(div.getAnimations().length, 0, 'set duration \'auto\''); }, 'when duration is changed'); +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + + anim.effect.timing.endDelay = -3000; + assert_equals(div.getAnimations().length, 0, + 'set negative endDelay so as endTime is less than currentTime'); + anim.effect.timing.endDelay = 1000; + assert_equals(div.getAnimations()[0], anim, + 'set positive endDelay so as endTime is more than currentTime'); + + anim.effect.timing.duration = 1000; + anim.currentTime = 1500; + assert_equals(div.getAnimations().length, 0, + 'set currentTime less than endTime'); + anim.effect.timing.endDelay = -500; + anim.currentTime = 400; + assert_equals(div.getAnimations()[0], anim, + 'set currentTime less than endTime when endDelay is negative value'); + anim.currentTime = 500; + assert_equals(div.getAnimations().length, 0, + 'set currentTime same as endTime when endDelay is negative value'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, + 'set currentTime same as duration when endDelay is negative value'); +}, 'when endDelay is changed'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 1000, delay: 500, endDelay: -500 }); + assert_equals(div.getAnimations()[0], anim, 'when currentTime 0'); + anim.currentTime = 500; + assert_equals(div.getAnimations()[0], anim, 'set currentTime 500'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, 'set currentTime 1000'); +}, 'when currentTime changed in duration:1000, delay: 500, endDelay: -500'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 1000, delay: -500, endDelay: -500 }); + assert_equals(div.getAnimations().length, 0, 'when currentTime 0'); + anim.currentTime = 500; + assert_equals(div.getAnimations().length, 0, 'set currentTime 500'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, 'set currentTime 1000'); +}, 'when currentTime changed in duration:1000, delay: -500, endDelay: -500'); + + diff --git a/testing/web-platform/tests/web-animations/animation-effect-timing/getComputedStyle.html b/testing/web-platform/tests/web-animations/animation-effect-timing/getComputedStyle.html index 0e773f7b0d..27670577e5 100644 --- a/testing/web-platform/tests/web-animations/animation-effect-timing/getComputedStyle.html +++ b/testing/web-platform/tests/web-animations/animation-effect-timing/getComputedStyle.html @@ -25,5 +25,84 @@ test(function(t) { assert_equals(getComputedStyle(div).opacity, '1', 'set duration \'auto\''); }, 'changed duration immediately updates its computed styles'); +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, endDelay: 1000, fill: 'none' }); + + anim.currentTime = 9000; + assert_equals(getComputedStyle(div).opacity, '0.1', + 'set currentTime during duration'); + + anim.currentTime = 10900; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime during endDelay'); + + anim.currentTime = 11100; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime after endDelay'); +}, 'change currentTime when fill is none and endDelay is positive'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, + endDelay: 1000, + fill: 'forwards' }); + anim.currentTime = 5000; + assert_equals(getComputedStyle(div).opacity, '0.5', + 'set currentTime during duration'); + + anim.currentTime = 9999; + assert_equals(getComputedStyle(div).opacity, '0.0001', + 'set currentTime just a little before duration'); + + anim.currentTime = 10900; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime during endDelay'); + + anim.currentTime = 11100; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime after endDelay'); +}, 'change currentTime when fill forwards and endDelay is positive'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, endDelay: -5000, fill: 'none' }); + + anim.currentTime = 1000; + assert_equals(getComputedStyle(div).opacity, '0.9', + 'set currentTime before endTime'); + + anim.currentTime = 10000; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime after endTime'); +}, 'change currentTime when fill none and endDelay is negative'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, + endDelay: -5000, + fill: 'forwards' }); + + anim.currentTime = 1000; + assert_equals(getComputedStyle(div).opacity, '0.9', + 'set currentTime before endTime'); + + anim.currentTime = 5000; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime same as endTime'); + + anim.currentTime = 9999; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime during duration'); + + anim.currentTime = 10000; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime after endTime'); +}, 'change currentTime when fill forwards and endDelay is negative'); + diff --git a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html index 1bcb4983b1..832b2471ee 100644 --- a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html +++ b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html @@ -473,143 +473,71 @@ test(function(t) { "KeyframeEffectOptions object"); var gKeyframeEffectOptionTests = [ - { desc: "an empty KeyframeEffectOption", - input: {}, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: "auto", - direction: "normal"} }, - { desc: "a normal KeyframeEffectOption", - input: {delay: 1000, - fill: "auto", - iterations: 5.5, - duration: "auto", - direction: "alternate"}, - expected: {delay: 1000, - fill: "auto", - iterations: 5.5, - duration: "auto", - direction: "alternate"} }, + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: { } }, + { desc: "a normal KeyframeEffectOptions object", + input: { delay: 1000, + fill: "auto", + iterations: 5.5, + duration: "auto", + direction: "alternate" }, + expected: { delay: 1000, + fill: "auto", + iterations: 5.5, + duration: "auto", + direction: "alternate" } }, { desc: "a double value", input: 3000, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: 3000, - direction: "normal"} }, + expected: { duration: 3000 } }, { desc: "+Infinity", input: Infinity, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: Infinity, - direction: "normal"} }, + expected: { duration: Infinity } }, { desc: "-Infinity", input: -Infinity, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: -Infinity, - direction: "normal"} }, + expected: { duration: -Infinity } }, { desc: "NaN", input: NaN, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: NaN, - direction: "normal"} }, + expected: { duration: NaN } }, { desc: "a negative value", input: -1, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: -1, - direction: "normal"} }, + expected: { duration: -1 } }, { desc: "an Infinity duration", - input: {duration: Infinity}, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: Infinity, - direction: "normal"} }, + input: { duration: Infinity }, + expected: { duration: Infinity } }, { desc: "a negative Infinity duration", - input: {duration: -Infinity}, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: -Infinity, - direction: "normal"} }, + input: { duration: -Infinity }, + expected: { duration: -Infinity } }, { desc: "a NaN duration", - input: {duration: NaN}, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: NaN, - direction: "normal"} }, + input: { duration: NaN }, + expected: { duration: NaN } }, { desc: "a negative duration", - input: {duration: -1}, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: -1, - direction: "normal"} }, + input: { duration: -1 }, + expected: { duration: -1 } }, { desc: "a string duration", - input: {duration: "merrychristmas"}, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: "merrychristmas", - direction: "normal"} }, + input: { duration: "merrychristmas" }, + expected: { duration: "merrychristmas" } }, { desc: "an auto duration", - input: {duration: "auto"}, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: "auto", - direction: "normal"} }, + input: { duration: "auto" }, + expected: { duration: "auto" } }, { desc: "an Infinity iterations", - input: {iterations: Infinity}, - expected: {delay: 0, - fill: "auto", - iterations: Infinity, - duration: "auto", - direction: "normal"} }, + input: { iterations: Infinity }, + expected: { iterations: Infinity } }, { desc: "a negative Infinity iterations", - input: {iterations: -Infinity}, - expected: {delay: 0, - fill: "auto", - iterations: -Infinity, - duration: "auto", - direction: "normal"} }, + input: { iterations: -Infinity }, + expected: { iterations: -Infinity } }, { desc: "a NaN iterations", - input: {iterations: NaN}, - expected: {delay: 0, - fill: "auto", - iterations: NaN, - duration: "auto", - direction: "normal"} }, + input: { iterations: NaN }, + expected: { iterations: NaN } }, { desc: "a negative iterations", - input: {iterations: -1}, - expected: {delay: 0, - fill: "auto", - iterations: -1, - duration: "auto", - direction: "normal"} }, + input: { iterations: -1 }, + expected: { iterations: -1 } }, { desc: "an auto fill", - input: {fill: "auto"}, - expected: {delay: 0, - fill: "auto", - iterations: 1, - duration: "auto", - direction: "normal"} }, + input: { fill: "auto" }, + expected: { fill: "auto" } }, { desc: "a forwards fill", - input: {fill: "forwards"}, - expected: {delay: 0, - fill: "forwards", - iterations: 1, - duration: "auto", - direction: "normal"} } + input: { fill: "forwards" }, + expected: { fill: "forwards" } } ]; gKeyframeEffectOptionTests.forEach(function(stest) { @@ -618,20 +546,30 @@ gKeyframeEffectOptionTests.forEach(function(stest) { {left: ["10px", "20px"]}, stest.input); + // Helper function to provide default expected values when the test does + // not supply them. + var expected = function(field, defaultValue) { + return field in stest.expected ? stest.expected[field] : defaultValue; + }; + var timing = effect.timing; - assert_equals(timing.delay, stest.expected.delay, "timing delay"); - assert_equals(timing.fill, stest.expected.fill, "timing fill"); - assert_equals(timing.iterations, stest.expected.iterations, + assert_equals(timing.delay, expected("delay", 0), + "timing delay"); + assert_equals(timing.fill, expected("fill", "auto"), + "timing fill"); + assert_equals(timing.iterations, expected("iterations", 1), "timing iterations"); - assert_equals(timing.duration, stest.expected.duration, "timing duration"); - assert_equals(timing.direction, stest.expected.direction, + assert_equals(timing.duration, expected("duration", "auto"), + "timing duration"); + assert_equals(timing.direction, expected("direction", "normal"), "timing direction"); + }, "a KeyframeEffectReadOnly constructed by " + stest.desc); }); test(function(t) { var effect = new KeyframeEffect(target, - {left: ["10px", "20px"]}); + { left: ["10px", "20px"] }); assert_class_string(effect, "KeyframeEffect"); assert_class_string(effect.timing, "AnimationEffectTiming"); diff --git a/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming-currentIteration.html b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming-currentIteration.html new file mode 100644 index 0000000000..f954e66b97 --- /dev/null +++ b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming-currentIteration.html @@ -0,0 +1,446 @@ + + +currentIteration of KeyframeEffectReadOnly getComputedTiming() tests + + + + + + + +
+ + diff --git a/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming-progress.html b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming-progress.html new file mode 100644 index 0000000000..09d8cf0048 --- /dev/null +++ b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming-progress.html @@ -0,0 +1,446 @@ + + +progress of KeyframeEffectReadOnly getComputedTiming() tests + + + + + + + +
+ + diff --git a/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html index 320cd78141..7412824e7e 100644 --- a/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html +++ b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html @@ -17,7 +17,7 @@ var target = document.getElementById("target"); test(function(t) { var effect = new KeyframeEffectReadOnly(target, - {left: ["10px", "20px"]}); + { left: ["10px", "20px"] }); var ct = effect.getComputedTiming(); assert_equals(ct.delay, 0, "computed delay"); @@ -29,167 +29,227 @@ test(function(t) { "constructed without any KeyframeEffectOptions object"); var gGetComputedTimingTests = [ - { desc: "an empty KeyframeEffectOption", - input: {}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, - { desc: "a normal KeyframeEffectOption", - input: {delay: 1000, - fill: "auto", - iterations: 5.5, - duration: "auto", - direction: "alternate"}, - expected: {delay: 1000, - fill: "none", - iterations: 5.5, - duration: 0, - direction: "alternate"} }, + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: { } }, + { desc: "a normal KeyframeEffectOptions object", + input: { delay: 1000, + fill: "auto", + iterations: 5.5, + duration: "auto", + direction: "alternate" }, + expected: { delay: 1000, + fill: "none", + iterations: 5.5, + duration: 0, + direction: "alternate" } }, { desc: "a double value", input: 3000, - timing: {delay: 0, - fill: "auto", - iterations: 1, - duration: 3000, - direction: "normal"}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 3000, - direction: "normal"} }, + timing: { duration: 3000 }, + expected: { delay: 0, + fill: "none", + iterations: 1, + duration: 3000, + direction: "normal" } }, { desc: "+Infinity", input: Infinity, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: Infinity, - direction: "normal"} }, + expected: { duration: Infinity } }, { desc: "-Infinity", input: -Infinity, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + expected: { duration: 0 } }, { desc: "NaN", input: NaN, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + expected: { duration: 0 } }, { desc: "a negative value", input: -1, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + expected: { duration: 0 } }, { desc: "an Infinity duration", - input: {duration: Infinity}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: Infinity, - direction: "normal"} }, + input: { duration: Infinity }, + expected: { duration: Infinity } }, { desc: "a negative Infinity duration", - input: {duration: -Infinity}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { duration: -Infinity }, + expected: { duration: 0 } }, { desc: "a NaN duration", - input: {duration: NaN}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { duration: NaN }, + expected: { duration: 0 } }, { desc: "a negative duration", - input: {duration: -1}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { duration: -1}, + expected: { duration: 0 } }, { desc: "a string duration", - input: {duration: "merrychristmas"}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { duration: "merrychristmas"}, + expected: { duration: 0 } }, { desc: "an auto duration", - input: {duration: "auto"}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { duration: "auto" }, + expected: { duration: 0 } }, { desc: "an Infinity iterations", - input: {iterations: Infinity}, - expected: {delay: 0, - fill: "none", - iterations: Infinity, - duration: 0, - direction: "normal"} }, + input: { iterations: Infinity }, + expected: { iterations: Infinity } }, { desc: "a negative Infinity iterations", - input: {iterations: -Infinity}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { iterations: -Infinity}, + expected: { iterations: 1 } }, { desc: "a NaN iterations", - input: {iterations: NaN}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { iterations: NaN }, + expected: { iterations: 1 } }, { desc: "a negative iterations", - input: {iterations: -1}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { iterations: -1 }, + expected: { iterations: 1 } }, { desc: "an auto fill", - input: {fill: "auto"}, - expected: {delay: 0, - fill: "none", - iterations: 1, - duration: 0, - direction: "normal"} }, + input: { fill: "auto" }, + expected: { fill: "none" } }, { desc: "a forwards fill", - input: {fill: "forwards"}, - expected: {delay: 0, - fill: "forwards", - iterations: 1, - duration: 0, - direction: "normal"} } + input: { fill: "forwards" }, + expected: { fill: "forwards" } } ]; gGetComputedTimingTests.forEach(function(stest) { test(function(t) { var effect = new KeyframeEffectReadOnly(target, - {left: ["10px", "20px"]}, + { left: ["10px", "20px"] }, stest.input); + // Helper function to provide default expected values when the test does + // not supply them. + var expected = function(field, defaultValue) { + return field in stest.expected ? stest.expected[field] : defaultValue; + }; + var ct = effect.getComputedTiming(); - assert_equals(ct.delay, stest.expected.delay, "computed delay"); - assert_equals(ct.fill, stest.expected.fill, "computed fill"); - assert_equals(ct.iterations, stest.expected.iterations, + assert_equals(ct.delay, expected("delay", 0), + "computed delay"); + assert_equals(ct.fill, expected("fill", "none"), + "computed fill"); + assert_equals(ct.iterations, expected("iterations", 1), "computed iterations"); - assert_equals(ct.duration, stest.expected.duration, "computed duration"); - assert_equals(ct.direction, stest.expected.direction, "computed direction"); + assert_equals(ct.duration, expected("duration", 0), + "computed duration"); + assert_equals(ct.direction, expected("direction", "normal"), + "computed direction"); + }, "values of getComputedTiming() when a KeyframeEffectReadOnly is " + "constructed by " + stest.desc); }); +var gActiveDurationTests = [ + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: 0 }, + { desc: "a non-zero duration and default iteration count", + input: { duration: 1000 }, + expected: 1000 }, + { desc: "a non-zero duration and integral iteration count", + input: { duration: 1000, iterations: 7 }, + expected: 7000 }, + { desc: "a non-zero duration and fractional iteration count", + input: { duration: 1000, iterations: 2.5 }, + expected: 2500 }, + { desc: "an non-zero duration and infinite iteration count", + input: { duration: 1000, iterations: Infinity }, + expected: Infinity }, + { desc: "an non-zero duration and zero iteration count", + input: { duration: 1000, iterations: 0 }, + expected: 0 }, + { desc: "a zero duration and default iteration count", + input: { duration: 0 }, + expected: 0 }, + { desc: "a zero duration and fractional iteration count", + input: { duration: 0, iterations: 2.5 }, + expected: 0 }, + { desc: "a zero duration and infinite iteration count", + input: { duration: 0, iterations: Infinity }, + expected: 0 }, + { desc: "a zero duration and zero iteration count", + input: { duration: 0, iterations: 0 }, + expected: 0 }, + { desc: "an infinite duration and default iteration count", + input: { duration: Infinity }, + expected: Infinity }, + { desc: "an infinite duration and zero iteration count", + input: { duration: Infinity, iterations: 0 }, + expected: 0 }, + { desc: "an infinite duration and fractional iteration count", + input: { duration: Infinity, iterations: 2.5 }, + expected: Infinity }, + { desc: "an infinite duration and infinite iteration count", + input: { duration: Infinity, iterations: Infinity }, + expected: Infinity }, + { desc: "an infinite duration and zero iteration count", + input: { duration: Infinity, iterations: 0 }, + expected: 0 }, + { desc: "an invalid duration and default iteration count", + input: { duration: "cabbage" }, + expected: 0 }, + { desc: "a non-zero duration and invalid iteration count", + input: { duration: 1000, iterations: "cabbage" }, + expected: 1000 }, + { desc: "an invalid duration and invalid iteration count", + input: { duration: "cabbage", iterations: "cabbage" }, + expected: 0 } +]; + +gActiveDurationTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + + assert_equals(effect.getComputedTiming().activeDuration, + stest.expected); + + }, "getComputedTiming().activeDuration for " + stest.desc); +}); + +var gEndTimeTests = [ + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: 0 }, + { desc: "a non-zero duration and default iteration count", + input: { duration: 1000 }, + expected: 1000 }, + { desc: "a non-zero duration and non-default iteration count", + input: { duration: 1000, iterations: 2.5 }, + expected: 2500 }, + { desc: "a non-zero duration and non-zero delay", + input: { duration: 1000, delay: 1500 }, + expected: 2500 }, + { desc: "a non-zero duration, non-zero delay and non-default iteration", + input: { duration: 1000, delay: 1500, iterations: 2 }, + expected: 3500 }, + { desc: "an infinite iteration count", + input: { duration: 1000, iterations: Infinity }, + expected: Infinity }, + { desc: "an infinite duration", + input: { duration: Infinity, iterations: 10 }, + expected: Infinity }, + { desc: "an infinite duration and delay", + input: { duration: Infinity, iterations: 10, delay: 1000 }, + expected: Infinity }, + { desc: "an infinite duration and negative delay", + input: { duration: Infinity, iterations: 10, delay: -1000 }, + expected: Infinity }, + { desc: "an non-zero duration and negative delay", + input: { duration: 1000, iterations: 2, delay: -1000 }, + expected: 1000 }, + { desc: "an non-zero duration and negative delay greater than active " + + "duration", + input: { duration: 1000, iterations: 2, delay: -3000 }, + expected: -1000 }, + { desc: "a zero duration and negative delay", + input: { duration: 0, iterations: 2, delay: -1000 }, + expected: -1000 } +]; + +gEndTimeTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + + assert_equals(effect.getComputedTiming().endTime, + stest.expected); + + }, "getComputedTiming().endTime for " + stest.desc); +}); + done(); diff --git a/testing/web-platform/tests/web-animations/testcommon.js b/testing/web-platform/tests/web-animations/testcommon.js index 00ac945597..a53519ee33 100644 --- a/testing/web-platform/tests/web-animations/testcommon.js +++ b/testing/web-platform/tests/web-animations/testcommon.js @@ -113,3 +113,15 @@ function stepStart(nsteps) { } } +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); + }); +} diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index e24be782e0..dd47d3f64b 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -5951,6 +5951,8 @@ void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) pl.length = sizeof(pl); ::GetWindowPlacement(mWnd, &pl); + nsSizeMode previousSizeMode = mSizeMode; + // Windows has just changed the size mode of this window. The call to // SizeModeChanged will trigger a call into SetSizeMode where we will // set the min/max window state again or for nsSizeMode_Normal, call @@ -5995,7 +5997,7 @@ void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) } #endif - if (mWidgetListener) + if (mWidgetListener && mSizeMode != previousSizeMode) mWidgetListener->SizeModeChanged(mSizeMode); // If window was restored, window activation was bypassed during the diff --git a/xpcom/base/nsWindowsHelpers.h b/xpcom/base/nsWindowsHelpers.h index 448654440a..552e74f474 100644 --- a/xpcom/base/nsWindowsHelpers.h +++ b/xpcom/base/nsWindowsHelpers.h @@ -49,6 +49,78 @@ public: } }; +template<> +class nsAutoRefTraits +{ +public: + typedef HDC RawRef; + static HDC Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + ::DeleteDC(aFD); + } + } +}; + +template<> +class nsAutoRefTraits +{ +public: + typedef HBRUSH RawRef; + static HBRUSH Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template<> +class nsAutoRefTraits +{ +public: + typedef HRGN RawRef; + static HRGN Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template<> +class nsAutoRefTraits +{ +public: + typedef HBITMAP RawRef; + static HBITMAP Void() + { + return nullptr; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + template<> class nsAutoRefTraits { @@ -140,6 +212,10 @@ public: }; typedef nsAutoRef nsAutoRegKey; +typedef nsAutoRef nsAutoHDC; +typedef nsAutoRef nsAutoBrush; +typedef nsAutoRef nsAutoRegion; +typedef nsAutoRef nsAutoBitmap; typedef nsAutoRef nsAutoServiceHandle; typedef nsAutoRef nsAutoHandle; typedef nsAutoRef nsModuleHandle; diff --git a/xpfe/appshell/nsWebShellWindow.cpp b/xpfe/appshell/nsWebShellWindow.cpp index 5e703e6a56..5e16086b19 100644 --- a/xpfe/appshell/nsWebShellWindow.cpp +++ b/xpfe/appshell/nsWebShellWindow.cpp @@ -356,6 +356,11 @@ nsWebShellWindow::SizeModeChanged(nsSizeMode sizeMode) ourWindow->DispatchCustomEvent(NS_LITERAL_STRING("sizemodechange")); } + nsIPresShell* presShell; + if ((presShell = GetPresShell())) { + presShell->GetPresContext()->SizeModeChanged(sizeMode); + } + // Note the current implementation of SetSizeMode just stores // the new state; it doesn't actually resize. So here we store // the state and pass the event on to the OS. The day is coming