diff --git a/browser/components/sessionstore/ContentSessionStore.jsm b/browser/components/sessionstore/ContentSessionStore.jsm index 2ab4b6ad8057..938895383ed3 100644 --- a/browser/components/sessionstore/ContentSessionStore.jsm +++ b/browser/components/sessionstore/ContentSessionStore.jsm @@ -753,11 +753,6 @@ class ContentSessionStore { this.restoreHistory(data); break; case "SessionStore:restoreTabContent": - if (data.isRemotenessUpdate) { - let histogram = Services.telemetry.getKeyedHistogramById("FX_TAB_REMOTE_NAVIGATION_DELAY_MS"); - histogram.add("SessionStore:restoreTabContent", - Services.telemetry.msSystemNow() - data.requestTime); - } this.restoreTabContent(data); break; case "SessionStore:resetRestore": diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 08212e1899ec..671bee602a95 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -4310,8 +4310,7 @@ var SessionStoreInternal = { browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent", {loadArguments, isRemotenessUpdate, reason: aOptions.restoreContentReason || - RESTORE_TAB_CONTENT_REASON.SET_STATE, - requestTime: Services.telemetry.msSystemNow()}); + RESTORE_TAB_CONTENT_REASON.SET_STATE}); // Focus the tab's content area. if (aTab.selected && !window.isBlankPageURL(uri)) { diff --git a/devtools/client/inspector/inspector.js b/devtools/client/inspector/inspector.js index c1b04e9ce450..0054f99bbfbc 100644 --- a/devtools/client/inspector/inspector.js +++ b/devtools/client/inspector/inspector.js @@ -55,6 +55,7 @@ const THREE_PANE_ENABLED_PREF = "devtools.inspector.three-pane-enabled"; const THREE_PANE_ENABLED_SCALAR = "devtools.inspector.three_pane_enabled"; const THREE_PANE_CHROME_ENABLED_PREF = "devtools.inspector.chrome.three-pane-enabled"; const TELEMETRY_EYEDROPPER_OPENED = "devtools.toolbar.eyedropper.opened"; +const TELEMETRY_SCALAR_NODE_SELECTION_COUNT = "devtools.inspector.node_selection_count"; /** * Represents an open instance of the Inspector for a tab. @@ -1299,6 +1300,7 @@ Inspector.prototype = { executeSoon(() => { try { selfUpdate(this.selection.nodeFront); + this.telemetry.scalarAdd(TELEMETRY_SCALAR_NODE_SELECTION_COUNT, 1); } catch (ex) { console.error(ex); } diff --git a/devtools/client/responsive.html/browser/web-navigation.js b/devtools/client/responsive.html/browser/web-navigation.js index 3ac87735707b..e8a970d48d69 100644 --- a/devtools/client/responsive.html/browser/web-navigation.js +++ b/devtools/client/responsive.html/browser/web-navigation.js @@ -9,9 +9,6 @@ const ChromeUtils = require("ChromeUtils"); const Services = require("Services"); const { NetUtil } = require("resource://gre/modules/NetUtil.jsm"); const { E10SUtils } = require("resource://gre/modules/E10SUtils.jsm"); -const Telemetry = require("devtools/client/shared/telemetry"); - -const telemetry = new Telemetry(); function readInputStreamToString(stream) { return NetUtil.readInputStreamToString(stream, stream.available()); @@ -85,7 +82,6 @@ BrowserElementWebNavigation.prototype = { triggeringPrincipal: E10SUtils.serializePrincipal( triggeringPrincipal || Services.scriptSecurityManager.createNullPrincipal({})), - requestTime: telemetry.msSystemNow(), }); }, diff --git a/dom/animation/Animation.cpp b/dom/animation/Animation.cpp index 82cafd19dcf0..36f594279bea 100644 --- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -5,7 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Animation.h" + #include "AnimationUtils.h" +#include "mozAutoDocUpdate.h" #include "mozilla/dom/AnimationBinding.h" #include "mozilla/dom/AnimationPlaybackEvent.h" #include "mozilla/dom/Document.h" @@ -14,10 +16,13 @@ #include "mozilla/AnimationEventDispatcher.h" #include "mozilla/AnimationTarget.h" #include "mozilla/AutoRestore.h" -#include "mozilla/Maybe.h" // For Maybe -#include "mozilla/TypeTraits.h" // For std::forward<> -#include "nsAnimationManager.h" // For CSSAnimation -#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch +#include "mozilla/DeclarationBlock.h" +#include "mozilla/Maybe.h" // For Maybe +#include "mozilla/TypeTraits.h" // For std::forward<> +#include "nsAnimationManager.h" // For CSSAnimation +#include "nsComputedDOMStyle.h" +#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch +#include "nsDOMCSSAttrDeclaration.h" // For nsDOMCSSAttributeDeclaration #include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr #include "nsTransitionManager.h" // For CSSTransition #include "PendingAnimationTracker.h" // For PendingAnimationTracker @@ -168,6 +173,8 @@ void Animation::SetEffectNoUpdate(AnimationEffect* aEffect) { ReschedulePendingTasks(); } + MaybeScheduleReplacementCheck(); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); } @@ -594,6 +601,124 @@ void Animation::Reverse(ErrorResult& aRv) { // it here. } +void Animation::Persist() { + if (mReplaceState == AnimationReplaceState::Persisted) { + return; + } + + bool wasRemoved = mReplaceState == AnimationReplaceState::Removed; + + mReplaceState = AnimationReplaceState::Persisted; + + // If the animation is not (yet) removed, there should be no side effects of + // persisting it. + if (wasRemoved) { + UpdateEffect(PostRestyleMode::IfNeeded); + PostUpdate(); + } +} + +// https://drafts.csswg.org/web-animations/#dom-animation-commitstyles +void Animation::CommitStyles(ErrorResult& aRv) { + if (!mEffect) { + return; + } + + // Take an owning reference to the keyframe effect. This will ensure that + // this Animation and the target element remain alive after flushing style. + RefPtr keyframeEffect = mEffect->AsKeyframeEffect(); + if (!keyframeEffect) { + return; + } + + Maybe target = keyframeEffect->GetTarget(); + if (!target) { + return; + } + + if (target->mPseudoType != PseudoStyleType::NotPseudo) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + // Check it is an element with a style attribute + nsCOMPtr styledElement = do_QueryInterface(target->mElement); + if (!styledElement) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + // Flush style before checking if the target element is rendered since the + // result could depend on pending style changes. + if (Document* doc = target->mElement->GetComposedDoc()) { + doc->FlushPendingNotifications(FlushType::Style); + } + if (!target->mElement->IsRendered()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + nsPresContext* presContext = + nsContentUtils::GetContextForContent(target->mElement); + if (!presContext) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + // Get the computed animation values + UniquePtr animationValues = + Servo_AnimationValueMap_Create().Consume(); + if (!presContext->EffectCompositor()->ComposeServoAnimationRuleForEffect( + *keyframeEffect, CascadeLevel(), animationValues.get())) { + NS_WARNING("Failed to compose animation style to commit"); + return; + } + + // Calling SetCSSDeclaration will trigger attribute setting code. + // Start the update now so that the old rule doesn't get used + // between when we mutate the declaration and when we set the new + // rule. + mozAutoDocUpdate autoUpdate(target->mElement->OwnerDoc(), true); + + // Get the inline style to append to + RefPtr declarationBlock; + if (auto* existing = target->mElement->GetInlineStyleDeclaration()) { + declarationBlock = existing->EnsureMutable(); + } else { + declarationBlock = new DeclarationBlock(); + declarationBlock->SetDirty(); + } + + // Prepare the callback + MutationClosureData closureData; + closureData.mClosure = nsDOMCSSAttributeDeclaration::MutationClosureFunction; + closureData.mElement = target->mElement; + DeclarationBlockMutationClosure beforeChangeClosure = { + nsDOMCSSAttributeDeclaration::MutationClosureFunction, + &closureData, + }; + + // Set the animated styles + bool changed = false; + nsCSSPropertyIDSet properties = keyframeEffect->GetPropertySet(); + for (nsCSSPropertyID property : properties) { + RefPtr computedValue = + Servo_AnimationValueMap_GetValue(animationValues.get(), property) + .Consume(); + if (computedValue) { + changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue( + declarationBlock->Raw(), computedValue, beforeChangeClosure); + } + } + + if (!changed) { + return; + } + + // Update inline style declaration + target->mElement->SetInlineStyleDeclaration(*declarationBlock, closureData); +} + // --------------------------------------------------------------------------- // // JS wrappers for Animation interface: @@ -650,7 +775,14 @@ void Animation::Tick() { FinishPendingAt(mTimeline->GetCurrentTimeAsDuration().Value()); } - UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync); + + // Check for changes to whether or not this animation is replaceable. + bool isReplaceable = IsReplaceable(); + if (isReplaceable && !mWasReplaceableAtLastTick) { + ScheduleReplacementCheck(); + } + mWasReplaceableAtLastTick = isReplaceable; if (!mEffect) { return; @@ -837,7 +969,8 @@ bool Animation::ShouldBeSynchronizedWithMainThread( void Animation::UpdateRelevance() { bool wasRelevant = mIsRelevant; - mIsRelevant = HasCurrentEffect() || IsInEffect(); + mIsRelevant = mReplaceState != AnimationReplaceState::Removed && + (HasCurrentEffect() || IsInEffect()); // Notify animation observers. if (wasRelevant && !mIsRelevant) { @@ -847,6 +980,118 @@ void Animation::UpdateRelevance() { } } +template +bool IsMarkupAnimation(T* aAnimation) { + return aAnimation && aAnimation->IsTiedToMarkup(); +} + +// https://drafts.csswg.org/web-animations/#replaceable-animation +bool Animation::IsReplaceable() const { + // We never replace CSS animations or CSS transitions since they are managed + // by CSS. + if (IsMarkupAnimation(AsCSSAnimation()) || + IsMarkupAnimation(AsCSSTransition())) { + return false; + } + + // Only finished animations can be replaced. + if (PlayState() != AnimationPlayState::Finished) { + return false; + } + + // Already removed animations cannot be replaced. + if (ReplaceState() == AnimationReplaceState::Removed) { + return false; + } + + // We can only replace an animation if we know that, uninterfered, it would + // never start playing again. That excludes any animations on timelines that + // aren't monotonically increasing. + // + // If we don't have any timeline at all, then we can't be in the finished + // state (since we need both a resolved start time and current time for that) + // and will have already returned false above. + // + // (However, if it ever does become possible to be finished without a timeline + // then we will want to return false here since it probably suggests an + // animation being driven directly by script, in which case we can't assume + // anything about how they will behave.) + if (!GetTimeline() || !GetTimeline()->TracksWallclockTime()) { + return false; + } + + // If the animation doesn't have an effect then we can't determine if it is + // filling or not so just leave it alone. + if (!GetEffect()) { + return false; + } + + // At the time of writing we only know about KeyframeEffects. If we introduce + // other types of effects we will need to decide if they are replaceable or + // not. + MOZ_ASSERT(GetEffect()->AsKeyframeEffect(), + "Effect should be a keyframe effect"); + + // We only replace animations that are filling. + if (GetEffect()->GetComputedTiming().mProgress.IsNull()) { + return false; + } + + // We should only replace animations with a target element (since otherwise + // what other effects would we consider when determining if they are covered + // or not?). + if (!GetEffect()->AsKeyframeEffect()->GetTarget()) { + return false; + } + + return true; +} + +bool Animation::IsRemovable() const { + return ReplaceState() == AnimationReplaceState::Active && IsReplaceable(); +} + +void Animation::ScheduleReplacementCheck() { + MOZ_ASSERT( + IsReplaceable(), + "Should only schedule a replacement check for a replaceable animation"); + + // If IsReplaceable() is true, the following should also hold + MOZ_ASSERT(GetEffect()); + MOZ_ASSERT(GetEffect()->AsKeyframeEffect()); + MOZ_ASSERT(GetEffect()->AsKeyframeEffect()->GetTarget()); + + Maybe target = + GetEffect()->AsKeyframeEffect()->GetTarget(); + + nsPresContext* presContext = + nsContentUtils::GetContextForContent(target->mElement); + if (presContext) { + presContext->EffectCompositor()->NoteElementForReducing(*target); + } +} + +void Animation::MaybeScheduleReplacementCheck() { + if (!IsReplaceable()) { + return; + } + + ScheduleReplacementCheck(); +} + +void Animation::Remove() { + MOZ_ASSERT(IsRemovable(), + "Should not be trying to remove an effect that is not removable"); + + mReplaceState = AnimationReplaceState::Removed; + + UpdateEffect(PostRestyleMode::IfNeeded); + PostUpdate(); + + QueuePlaybackEvent(NS_LITERAL_STRING("remove"), + GetTimelineCurrentTimeAsTimeStamp()); +} + bool Animation::HasLowerCompositeOrderThan(const Animation& aOther) const { // 0. Object-equality case if (&aOther == this) { @@ -989,11 +1234,27 @@ void Animation::ComposeStyle(RawServoAnimationValueMap& aComposeResult, void Animation::NotifyEffectTimingUpdated() { MOZ_ASSERT(mEffect, - "We should only update timing effect when we have a target " + "We should only update effect timing when we have a target " "effect"); UpdateTiming(Animation::SeekFlag::NoSeek, Animation::SyncNotifyFlag::Async); } +void Animation::NotifyEffectPropertiesUpdated() { + MOZ_ASSERT(mEffect, + "We should only update effect properties when we have a target " + "effect"); + + MaybeScheduleReplacementCheck(); +} + +void Animation::NotifyEffectTargetUpdated() { + MOZ_ASSERT(mEffect, + "We should only update the effect target when we have a target " + "effect"); + + MaybeScheduleReplacementCheck(); +} + void Animation::NotifyGeometricAnimationsStartingThisFrame() { if (!IsNewlyStarted() || !mEffect) { return; @@ -1179,7 +1440,7 @@ void Animation::ResumeAt(const TimeDuration& aReadyTime) { mPendingState = PendingState::NotPending; - UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync); // If we had a pending playback rate, we will have now applied it so we need // to notify observers. @@ -1503,7 +1764,7 @@ void Animation::QueuePlaybackEvent(const nsAString& aName, AnimationPlaybackEventInit init; - if (aName.EqualsLiteral("finish")) { + if (aName.EqualsLiteral("finish") || aName.EqualsLiteral("remove")) { init.mCurrentTime = GetCurrentTimeAsDouble(); } if (mTimeline) { diff --git a/dom/animation/Animation.h b/dom/animation/Animation.h index 0729c401a083..40eb573462cc 100644 --- a/dom/animation/Animation.h +++ b/dom/animation/Animation.h @@ -51,15 +51,7 @@ class Animation : public DOMEventTargetHelper, public: explicit Animation(nsIGlobalObject* aGlobal) - : DOMEventTargetHelper(aGlobal), - mPlaybackRate(1.0), - mAnimationIndex(sNextAnimationIndex++), - mCachedChildIndex(-1), - mPendingState(PendingState::NotPending), - mFinishedAtLastComposeStyle(false), - mIsRelevant(false), - mFinishedIsResolved(false), - mSyncWithGeometricAnimations(false) {} + : DOMEventTargetHelper(aGlobal), mAnimationIndex(sNextAnimationIndex++) {} NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation, DOMEventTargetHelper) @@ -120,12 +112,14 @@ class Animation : public DOMEventTargetHelper, bool Pending() const { return mPendingState != PendingState::NotPending; } virtual bool PendingFromJS() const { return Pending(); } + AnimationReplaceState ReplaceState() const { return mReplaceState; } virtual Promise* GetReady(ErrorResult& aRv); Promise* GetFinished(ErrorResult& aRv); IMPL_EVENT_HANDLER(finish); IMPL_EVENT_HANDLER(cancel); + IMPL_EVENT_HANDLER(remove); void Cancel(PostRestyleMode aPostRestyle = PostRestyleMode::IfNeeded); @@ -147,6 +141,9 @@ class Animation : public DOMEventTargetHelper, void UpdatePlaybackRate(double aPlaybackRate); void Reverse(ErrorResult& aRv); + void Persist(); + void CommitStyles(ErrorResult& aRv); + bool IsRunningOnCompositor() const; virtual void Tick(); @@ -329,6 +326,25 @@ class Animation : public DOMEventTargetHelper, bool IsRelevant() const { return mIsRelevant; } void UpdateRelevance(); + // https://drafts.csswg.org/web-animations-1/#replaceable-animation + bool IsReplaceable() const; + + /** + * Returns true if this Animation satisfies the requirements for being + * removed when it is replaced. + * + * Returning true does not imply this animation _should_ be removed. + * Determining that depends on the other effects in the same EffectSet to + * which this animation's effect, if any, contributes. + */ + bool IsRemovable() const; + + /** + * Make this animation's target effect no-longer part of the effect stack + * while preserving its timing information. + */ + void Remove(); + /** * Returns true if this Animation has a lower composite order than aOther. */ @@ -366,6 +382,8 @@ class Animation : public DOMEventTargetHelper, const nsCSSPropertyIDSet& aPropertiesToSkip); void NotifyEffectTimingUpdated(); + void NotifyEffectPropertiesUpdated(); + void NotifyEffectTargetUpdated(); void NotifyGeometricAnimationsStartingThisFrame(); /** @@ -481,6 +499,9 @@ class Animation : public DOMEventTargetHelper, return GetCurrentTimeForHoldTime(Nullable()); } + void ScheduleReplacementCheck(); + void MaybeScheduleReplacementCheck(); + // Earlier side of the elapsed time range reported in CSS Animations and CSS // Transitions events. // @@ -527,7 +548,7 @@ class Animation : public DOMEventTargetHelper, Nullable mHoldTime; // Animation timescale Nullable mPendingReadyTime; // Timeline timescale Nullable mPreviousCurrentTime; // Animation timescale - double mPlaybackRate; + double mPlaybackRate = 1.0; Maybe mPendingPlaybackRate; // A Promise that is replaced on each call to Play() @@ -554,7 +575,7 @@ class Animation : public DOMEventTargetHelper, // While ordering Animation objects for event dispatch, the index of the // target node in its parent may be cached in mCachedChildIndex. - int32_t mCachedChildIndex; + int32_t mCachedChildIndex = -1; // Indicates if the animation is in the pending state (and what state it is // waiting to enter when it finished pending). We use this rather than @@ -563,23 +584,28 @@ class Animation : public DOMEventTargetHelper, // from the PendingAnimationTracker while it is waiting for the next tick // (see TriggerOnNextTick for details). enum class PendingState : uint8_t { NotPending, PlayPending, PausePending }; - PendingState mPendingState; + PendingState mPendingState = PendingState::NotPending; + + // Handling of this animation's target effect when filling while finished. + AnimationReplaceState mReplaceState = AnimationReplaceState::Active; + + bool mFinishedAtLastComposeStyle = false; + bool mWasReplaceableAtLastTick = false; - bool mFinishedAtLastComposeStyle; // Indicates that the animation should be exposed in an element's // getAnimations() list. - bool mIsRelevant; + bool mIsRelevant = false; // True if mFinished is resolved or would be resolved if mFinished has // yet to be created. This is not set when mFinished is rejected since // in that case mFinished is immediately reset to represent a new current // finished promise. - bool mFinishedIsResolved; + bool mFinishedIsResolved = false; // True if this animation was triggered at the same time as one or more // geometric animations and hence we should run any transform animations on // the main thread. - bool mSyncWithGeometricAnimations; + bool mSyncWithGeometricAnimations = false; RefPtr mFinishNotificationTask; diff --git a/dom/animation/AnimationTarget.h b/dom/animation/AnimationTarget.h index 4e99d63a4d31..e921f25c3a14 100644 --- a/dom/animation/AnimationTarget.h +++ b/dom/animation/AnimationTarget.h @@ -7,7 +7,9 @@ #ifndef mozilla_AnimationTarget_h #define mozilla_AnimationTarget_h -#include "mozilla/Attributes.h" // For MOZ_NON_OWNING_REF +#include "mozilla/Attributes.h" // For MOZ_NON_OWNING_REF +#include "mozilla/HashFunctions.h" // For HashNumber, AddToHash +#include "mozilla/HashTable.h" // For DefaultHasher, PointerHasher #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "nsCSSPseudoElements.h" @@ -69,6 +71,26 @@ inline void ImplCycleCollectionUnlink(Maybe& aTarget) { } } +// A DefaultHasher specialization for OwningAnimationTarget. +template <> +struct DefaultHasher { + using Key = OwningAnimationTarget; + using Lookup = OwningAnimationTarget; + using PtrHasher = PointerHasher; + + static HashNumber hash(const Lookup& aLookup) { + return AddToHash(PtrHasher::hash(aLookup.mElement.get()), + static_cast(aLookup.mPseudoType)); + } + + static bool match(const Key& aKey, const Lookup& aLookup) { + return PtrHasher::match(aKey.mElement.get(), aLookup.mElement.get()) && + aKey.mPseudoType == aLookup.mPseudoType; + } + + static void rekey(Key& aKey, Key&& aNewKey) { aKey = std::move(aNewKey); } +}; + } // namespace mozilla #endif // mozilla_AnimationTarget_h diff --git a/dom/animation/DocumentTimeline.cpp b/dom/animation/DocumentTimeline.cpp index c04ae997166b..3ac9771c1985 100644 --- a/dom/animation/DocumentTimeline.cpp +++ b/dom/animation/DocumentTimeline.cpp @@ -188,13 +188,6 @@ void DocumentTimeline::MostRecentRefreshTimeUpdated() { } void DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime) { - // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events, - // step2. - // Note that this should be done before nsAutoAnimationMutationBatch which is - // inside MostRecentRefreshTimeUpdated(). If PerformMicroTaskCheckpoint was - // called before nsAutoAnimationMutationBatch is destroyed, some mutation - // records might not be delivered in this checkpoint. - nsAutoMicroTask mt; MostRecentRefreshTimeUpdated(); } diff --git a/dom/animation/EffectCompositor.cpp b/dom/animation/EffectCompositor.cpp index 11c468d94dc8..4255d6cf4e56 100644 --- a/dom/animation/EffectCompositor.cpp +++ b/dom/animation/EffectCompositor.cpp @@ -394,6 +394,26 @@ class EffectCompositeOrderComparator { }; } // namespace +static void ComposeSortedEffects( + const nsTArray& aSortedEffects, + const EffectSet* aEffectSet, EffectCompositor::CascadeLevel aCascadeLevel, + RawServoAnimationValueMap* aAnimationValues) { + // If multiple animations affect the same property, animations with higher + // composite order (priority) override or add to animations with lower + // priority. + nsCSSPropertyIDSet propertiesToSkip; + if (aEffectSet) { + propertiesToSkip = + aCascadeLevel == EffectCompositor::CascadeLevel::Animations + ? aEffectSet->PropertiesForAnimationsLevel().Inverse() + : aEffectSet->PropertiesForAnimationsLevel(); + } + + for (KeyframeEffect* effect : aSortedEffects) { + effect->GetAnimation()->ComposeStyle(*aAnimationValues, propertiesToSkip); + } +} + bool EffectCompositor::GetServoAnimationRule( const dom::Element* aElement, PseudoStyleType aPseudoType, CascadeLevel aCascadeLevel, RawServoAnimationValueMap* aAnimationValues) { @@ -417,16 +437,8 @@ bool EffectCompositor::GetServoAnimationRule( } sortedEffectList.Sort(EffectCompositeOrderComparator()); - // If multiple animations affect the same property, animations with higher - // composite order (priority) override or add or animations with lower - // priority. - const nsCSSPropertyIDSet propertiesToSkip = - aCascadeLevel == CascadeLevel::Animations - ? effectSet->PropertiesForAnimationsLevel().Inverse() - : effectSet->PropertiesForAnimationsLevel(); - for (KeyframeEffect* effect : sortedEffectList) { - effect->GetAnimation()->ComposeStyle(*aAnimationValues, propertiesToSkip); - } + ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel, + aAnimationValues); MOZ_ASSERT(effectSet == EffectSet::GetEffectSet(aElement, aPseudoType), "EffectSet should not change while composing style"); @@ -434,6 +446,59 @@ bool EffectCompositor::GetServoAnimationRule( return true; } +bool EffectCompositor::ComposeServoAnimationRuleForEffect( + KeyframeEffect& aEffect, CascadeLevel aCascadeLevel, + RawServoAnimationValueMap* aAnimationValues) { + MOZ_ASSERT(aAnimationValues); + MOZ_ASSERT(mPresContext && mPresContext->IsDynamic(), + "Should not be in print preview"); + + Maybe target = aEffect.GetTarget(); + if (!target) { + return false; + } + + // Don't try to compose animations for elements in documents without a pres + // shell (e.g. XMLHttpRequest documents). + if (!nsContentUtils::GetPresShellForContent(target->mElement)) { + return false; + } + + // GetServoAnimationRule is called as part of the regular style resolution + // where the cascade results are updated in the pre-traversal as needed. + // This function, however, is only called when committing styles so we + // need to ensure the cascade results are up-to-date manually. + EffectCompositor::MaybeUpdateCascadeResults(target->mElement, + target->mPseudoType); + + EffectSet* effectSet = + EffectSet::GetEffectSet(target->mElement, target->mPseudoType); + + // Get a list of effects sorted by composite order up to and including + // |aEffect|, even if it is not in the EffectSet. + auto comparator = EffectCompositeOrderComparator(); + nsTArray sortedEffectList(effectSet ? effectSet->Count() + 1 + : 1); + if (effectSet) { + for (KeyframeEffect* effect : *effectSet) { + if (comparator.LessThan(effect, &aEffect)) { + sortedEffectList.AppendElement(effect); + } + } + sortedEffectList.Sort(comparator); + } + sortedEffectList.AppendElement(&aEffect); + + ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel, + aAnimationValues); + + MOZ_ASSERT(effectSet == + EffectSet::GetEffectSet(target->mElement, target->mPseudoType), + "EffectSet should not change while composing style"); + + return true; +} + /* static */ dom::Element* EffectCompositor::GetElementToRestyle( dom::Element* aElement, PseudoStyleType aPseudoType) { if (aPseudoType == PseudoStyleType::NotPseudo) { @@ -862,4 +927,53 @@ bool EffectCompositor::PreTraverseInSubtree(ServoTraversalFlags aFlags, return foundElementsNeedingRestyle; } +void EffectCompositor::NoteElementForReducing( + const NonOwningAnimationTarget& aTarget) { + if (!StaticPrefs::dom_animations_api_autoremove_enabled()) { + return; + } + + Unused << mElementsToReduce.put( + OwningAnimationTarget{aTarget.mElement, aTarget.mPseudoType}); +} + +static void ReduceEffectSet(EffectSet& aEffectSet) { + // Get a list of effects sorted by composite order. + nsTArray sortedEffectList(aEffectSet.Count()); + for (KeyframeEffect* effect : aEffectSet) { + sortedEffectList.AppendElement(effect); + } + sortedEffectList.Sort(EffectCompositeOrderComparator()); + + nsCSSPropertyIDSet setProperties; + + // Iterate in reverse + for (auto iter = sortedEffectList.rbegin(); iter != sortedEffectList.rend(); + ++iter) { + MOZ_ASSERT(*iter && (*iter)->GetAnimation(), + "Effect in an EffectSet should have an animation"); + KeyframeEffect& effect = **iter; + Animation& animation = *effect.GetAnimation(); + if (animation.IsRemovable() && + effect.GetPropertySet().IsSubsetOf(setProperties)) { + animation.Remove(); + } else if (animation.IsReplaceable()) { + setProperties |= effect.GetPropertySet(); + } + } +} + +void EffectCompositor::ReduceAnimations() { + for (auto iter = mElementsToReduce.iter(); !iter.done(); iter.next()) { + const OwningAnimationTarget& target = iter.get(); + EffectSet* effectSet = + EffectSet::GetEffectSet(target.mElement, target.mPseudoType); + if (effectSet) { + ReduceEffectSet(*effectSet); + } + } + + mElementsToReduce.clear(); +} + } // namespace mozilla diff --git a/dom/animation/EffectCompositor.h b/dom/animation/EffectCompositor.h index ae367f76fcaa..7367884ec414 100644 --- a/dom/animation/EffectCompositor.h +++ b/dom/animation/EffectCompositor.h @@ -8,7 +8,9 @@ #define mozilla_EffectCompositor_h #include "mozilla/AnimationPerformanceWarning.h" +#include "mozilla/AnimationTarget.h" #include "mozilla/EnumeratedArray.h" +#include "mozilla/HashTable.h" #include "mozilla/Maybe.h" #include "mozilla/OwningNonNull.h" #include "mozilla/PseudoElementHashEntry.h" @@ -38,6 +40,7 @@ struct NonOwningAnimationTarget; namespace dom { class Animation; class Element; +class KeyframeEffect; } // namespace dom class EffectCompositor { @@ -116,8 +119,9 @@ class EffectCompositor { dom::Element* aElement, PseudoStyleType aPseudoType); - // Get animation rule for stylo. This is an equivalent of GetAnimationRule - // and will be called from servo side. + // Get the animation rule for the appropriate level of the cascade for + // a (pseudo-)element. Called from the Servo side. + // // The animation rule is stored in |RawServoAnimationValueMap|. // We need to be careful while doing any modification because it may cause // some thread-safe issues. @@ -126,6 +130,15 @@ class EffectCompositor { CascadeLevel aCascadeLevel, RawServoAnimationValueMap* aAnimationValues); + // A variant on GetServoAnimationRule that composes all the effects for an + // element up to and including |aEffect|. + // + // Note that |aEffect| might not be in the EffectSet since we can use this for + // committing the computed style of a removed Animation. + bool ComposeServoAnimationRuleForEffect( + dom::KeyframeEffect& aEffect, CascadeLevel aCascadeLevel, + RawServoAnimationValueMap* aAnimationValues); + bool HasPendingStyleUpdates() const; static bool HasAnimationsForCompositor(const nsIFrame* aFrame, @@ -199,6 +212,12 @@ class EffectCompositor { // at aElement. bool PreTraverseInSubtree(ServoTraversalFlags aFlags, dom::Element* aRoot); + // Record a (pseudo-)element that may have animations that can be removed. + void NoteElementForReducing(const NonOwningAnimationTarget& aTarget); + + bool NeedsReducing() const { return !mElementsToReduce.empty(); } + void ReduceAnimations(); + // Returns the target element for restyling. // // If |aPseudoType| is ::after, ::before or ::marker, returns the generated @@ -239,6 +258,8 @@ class EffectCompositor { mElementsToRestyle; bool mIsInPreTraverse = false; + + HashSet mElementsToReduce; }; } // namespace mozilla diff --git a/dom/animation/KeyframeEffect.cpp b/dom/animation/KeyframeEffect.cpp index a3f1b167d047..c0259ac530ad 100644 --- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -317,7 +317,7 @@ nsCSSPropertyIDSet KeyframeEffect::GetPropertiesForCompositor( nsCSSPropertyIDSet properties; - if (!IsInEffect() && !IsCurrent()) { + if (!mAnimation || !mAnimation->IsRelevant()) { return properties; } @@ -342,14 +342,14 @@ nsCSSPropertyIDSet KeyframeEffect::GetPropertiesForCompositor( return properties; } -bool KeyframeEffect::HasAnimationOfPropertySet( - const nsCSSPropertyIDSet& aPropertySet) const { +nsCSSPropertyIDSet KeyframeEffect::GetPropertySet() const { + nsCSSPropertyIDSet result; + for (const AnimationProperty& property : mProperties) { - if (aPropertySet.HasProperty(property.mProperty)) { - return true; - } + result.AddProperty(property.mProperty); } - return false; + + return result; } #ifdef DEBUG @@ -418,6 +418,10 @@ void KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle) { MarkCascadeNeedsUpdate(); + if (mAnimation) { + mAnimation->NotifyEffectPropertiesUpdated(); + } + RequestRestyle(EffectCompositor::RestyleType::Layer); } @@ -575,13 +579,10 @@ void KeyframeEffect::ComposeStyle(RawServoAnimationValueMap& aComposeResult, if (HasPropertiesThatMightAffectOverflow()) { nsPresContext* presContext = nsContentUtils::GetContextForContent(mTarget->mElement); - if (presContext) { + EffectSet* effectSet = + EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); + if (presContext && effectSet) { TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh(); - EffectSet* effectSet = - EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); - MOZ_ASSERT(effectSet, - "ComposeStyle should only be called on an effect " - "that is part of an effect set"); effectSet->UpdateLastOverflowAnimationSyncTime(now); } } @@ -792,7 +793,9 @@ void KeyframeEffect::UpdateTargetRegistration() { // something calls Animation::UpdateRelevance. Whenever our timing changes, // we should be notifying our Animation before calling this, so // Animation::IsRelevant() should be up-to-date by the time we get here. - MOZ_ASSERT(isRelevant == IsCurrent() || IsInEffect(), + MOZ_ASSERT(isRelevant == + ((IsCurrent() || IsInEffect()) && mAnimation && + mAnimation->ReplaceState() != AnimationReplaceState::Removed), "Out of date Animation::IsRelevant value"); if (isRelevant && !mInEffectSet) { @@ -999,6 +1002,10 @@ void KeyframeEffect::SetTarget( mAnimation->ReschedulePendingTasks(); } } + + if (mAnimation) { + mAnimation->NotifyEffectTargetUpdated(); + } } static void CreatePropertyValue( @@ -1728,6 +1735,11 @@ bool KeyframeEffect::ContainsAnimatedScale(const nsIFrame* aFrame) const { return false; } + if (!mAnimation || + mAnimation->ReplaceState() == AnimationReplaceState::Removed) { + return false; + } + for (const AnimationProperty& prop : mProperties) { if (prop.mProperty != eCSSProperty_transform && prop.mProperty != eCSSProperty_scale && diff --git a/dom/animation/KeyframeEffect.h b/dom/animation/KeyframeEffect.h index 0f14d3e81fad..eb1774c9f2e3 100644 --- a/dom/animation/KeyframeEffect.h +++ b/dom/animation/KeyframeEffect.h @@ -183,9 +183,15 @@ class KeyframeEffect : public AnimationEffect { void SetKeyframes(nsTArray&& aKeyframes, const ComputedStyle* aStyle); + // Returns the set of properties affected by this effect regardless of + // whether any of these properties is overridden by an !important rule. + nsCSSPropertyIDSet GetPropertySet() const; + // Returns true if the effect includes a property in |aPropertySet| regardless - // of whether any property in the set is overridden by !important rule. - bool HasAnimationOfPropertySet(const nsCSSPropertyIDSet& aPropertySet) const; + // of whether any property in the set is overridden by an !important rule. + bool HasAnimationOfPropertySet(const nsCSSPropertyIDSet& aPropertySet) const { + return GetPropertySet().Intersects(aPropertySet); + } // GetEffectiveAnimationOfProperty returns AnimationProperty corresponding // to a given CSS property if the effect includes the property and the diff --git a/dom/animation/test/chrome.ini b/dom/animation/test/chrome.ini index ea5385db2662..9e7cf404a953 100644 --- a/dom/animation/test/chrome.ini +++ b/dom/animation/test/chrome.ini @@ -1,5 +1,6 @@ [DEFAULT] prefs = + dom.animations-api.autoremove.enabled=true dom.animations-api.compositing.enabled=true gfx.omta.background-color=true layout.css.individual-transform.enabled=true diff --git a/dom/animation/test/chrome/test_animation_observers_async.html b/dom/animation/test/chrome/test_animation_observers_async.html index bb1fe2833acd..b601dd838c18 100644 --- a/dom/animation/test/chrome/test_animation_observers_async.html +++ b/dom/animation/test/chrome/test_animation_observers_async.html @@ -578,5 +578,74 @@ promise_test(t => { }); }, "tree_ordering: subtree"); +// Test that animations removed by auto-removal trigger an event +promise_test(async t => { + setupAsynchronousObserver(t, { observe: div, subtree: false }); + + // Start two animations such that one will be auto-removed + const animA = div.animate( + { opacity: 1 }, + { duration: 100 * MS_PER_SEC, fill: 'forwards' } + ); + const animB = div.animate( + { opacity: 1 }, + { duration: 100 * MS_PER_SEC, fill: 'forwards' } + ); + + // Wait for the MutationRecords corresponding to each addition. + await waitForNextFrame(); + + assert_records( + [ + { added: [animA], changed: [], removed: [] }, + { added: [animB], changed: [], removed: [] }, + ], + 'records after animation start' + ); + + // Finish the animations -- this should cause animA to be replaced, and + // automatically removed. + animA.finish(); + animB.finish(); + + // Wait for the MutationRecords corresponding to the timing changes and the + // subsequent removal to be delivered. + await waitForNextFrame(); + + assert_records( + [ + { added: [], changed: [animA], removed: [] }, + { added: [], changed: [animB], removed: [] }, + { added: [], changed: [], removed: [animA] }, + ], + 'records after finishing' + ); + + // Restore animA. + animA.persist(); + + // Wait for the MutationRecord corresponding to the re-addition of animA. + await waitForNextFrame(); + + assert_records( + [{ added: [animA], changed: [], removed: [] }], + 'records after persisting' + ); + + // Tidy up + animA.cancel(); + animB.cancel(); + + await waitForNextFrame(); + + assert_records( + [ + { added: [], changed: [], removed: [animA] }, + { added: [], changed: [], removed: [animB] }, + ], + 'records after tidying up end' + ); +}, 'Animations automatically removed are reported'); + runTest(); diff --git a/dom/animation/test/mochitest.ini b/dom/animation/test/mochitest.ini index 3278955f19a8..5deb29fffa51 100644 --- a/dom/animation/test/mochitest.ini +++ b/dom/animation/test/mochitest.ini @@ -14,6 +14,7 @@ support-files = chrome/file_animate_xrays.html mozilla/xhr_doc.html mozilla/file_deferred_start.html + mozilla/file_disable_animations_api_autoremove.html mozilla/file_disable_animations_api_compositing.html mozilla/file_disable_animations_api_get_animations.html mozilla/file_disable_animations_api_implicit_keyframes.html @@ -32,6 +33,7 @@ skip-if = (verify && !debug && (os == 'mac')) [mozilla/test_cubic_bezier_limits.html] [mozilla/test_deferred_start.html] skip-if = (toolkit == 'android' && debug) || (os == 'win' && bits == 64) # Bug 1363957 +[mozilla/test_disable_animations_api_autoremove.html] [mozilla/test_disable_animations_api_compositing.html] [mozilla/test_disable_animations_api_get_animations.html] [mozilla/test_disable_animations_api_implicit_keyframes.html] diff --git a/dom/animation/test/mozilla/file_disable_animations_api_autoremove.html b/dom/animation/test/mozilla/file_disable_animations_api_autoremove.html new file mode 100644 index 000000000000..79cb50846722 --- /dev/null +++ b/dom/animation/test/mozilla/file_disable_animations_api_autoremove.html @@ -0,0 +1,69 @@ + + + + + + diff --git a/dom/animation/test/mozilla/test_disable_animations_api_autoremove.html b/dom/animation/test/mozilla/test_disable_animations_api_autoremove.html new file mode 100644 index 000000000000..56e63622735e --- /dev/null +++ b/dom/animation/test/mozilla/test_disable_animations_api_autoremove.html @@ -0,0 +1,15 @@ + + + + +
+ diff --git a/dom/base/Element.h b/dom/base/Element.h index 1b96faea7996..19709bb6513f 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -1407,6 +1407,14 @@ class Element : public FragmentOrElement { return HasServoData() && Servo_Element_IsDisplayContents(this); } + /* + * https://html.spec.whatwg.org/#being-rendered + * + * With a gotcha for display contents: + * https://github.com/whatwg/html/issues/1837 + */ + bool IsRendered() const { return GetPrimaryFrame() || IsDisplayContents(); } + const nsAttrValue* GetParsedAttr(const nsAtom* aAttr) const { return mAttrs.GetAttr(aAttr); } diff --git a/dom/base/nsCopySupport.cpp b/dom/base/nsCopySupport.cpp index 6ee3a5a79d9c..2a6451674328 100644 --- a/dom/base/nsCopySupport.cpp +++ b/dom/base/nsCopySupport.cpp @@ -346,9 +346,9 @@ static nsresult PutToClipboard( return rv; } -nsresult nsCopySupport::HTMLCopy(Selection* aSel, Document* aDoc, - int16_t aClipboardID, - bool aWithRubyAnnotation) { +nsresult nsCopySupport::EncodeDocumentWithContextAndPutToClipboard( + Selection* aSel, Document* aDoc, int16_t aClipboardID, + bool aWithRubyAnnotation) { NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); uint32_t additionalFlags = nsIDocumentEncoder::SkipInvisibleContent; @@ -915,8 +915,8 @@ bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage, // selection is inside a same ruby container. But we really should // expose the full functionality in browser. See bug 1130891. bool withRubyAnnotation = IsSelectionInsideRuby(sel); - // call the copy code - nsresult rv = HTMLCopy(sel, doc, aClipboardType, withRubyAnnotation); + nsresult rv = EncodeDocumentWithContextAndPutToClipboard( + sel, doc, aClipboardType, withRubyAnnotation); if (NS_FAILED(rv)) { return false; } diff --git a/dom/base/nsCopySupport.h b/dom/base/nsCopySupport.h index cff6f16f9120..1d6141a7b848 100644 --- a/dom/base/nsCopySupport.h +++ b/dom/base/nsCopySupport.h @@ -33,9 +33,9 @@ class nsCopySupport { /** * @param aDoc Needs to be not nullptr. */ - static nsresult HTMLCopy(mozilla::dom::Selection* aSel, - mozilla::dom::Document* aDoc, int16_t aClipboardID, - bool aWithRubyAnnotation); + static nsresult EncodeDocumentWithContextAndPutToClipboard( + mozilla::dom::Selection* aSel, mozilla::dom::Document* aDoc, + int16_t aClipboardID, bool aWithRubyAnnotation); // Get the selection, or entire document, in the format specified by the mime // type (text/html or text/plain). If aSel is non-null, use it, otherwise get @@ -47,8 +47,7 @@ class nsCopySupport { static nsresult ImageCopy(nsIImageLoadingContent* aImageElement, nsILoadContext* aLoadContext, int32_t aCopyFlags); - // Get the selection as a transferable. Similar to HTMLCopy except does - // not deal with the clipboard. + // Get the selection as a transferable. // @param aSelection Can be nullptr. // @param aDocument Needs to be not nullptr. // @param aTransferable Needs to be not nullptr. diff --git a/dom/base/nsDocumentEncoder.cpp b/dom/base/nsDocumentEncoder.cpp index b4c8ef3ec6a5..4bc325c90f34 100644 --- a/dom/base/nsDocumentEncoder.cpp +++ b/dom/base/nsDocumentEncoder.cpp @@ -85,9 +85,7 @@ class nsDocumentEncoder : public nsIDocumentEncoder { nsresult FlushText(nsAString& aString, bool aForce); - bool IsVisibleNode(nsINode* aNode) { - MOZ_ASSERT(aNode, "null node"); - + bool IsInvisibleNodeAndShouldBeSkipped(nsINode& aNode) const { if (mFlags & SkipInvisibleContent) { // Treat the visibility of the ShadowRoot as if it were // the host content. @@ -95,35 +93,38 @@ class nsDocumentEncoder : public nsIDocumentEncoder { // FIXME(emilio): I suspect instead of this a bunch of the GetParent() // calls here should be doing GetFlattenedTreeParent, then this condition // should be unreachable... - if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(aNode)) { - aNode = shadowRoot->GetHost(); + nsINode* node{&aNode}; + if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(node)) { + node = shadowRoot->GetHost(); } - if (aNode->IsContent()) { - nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); + if (node->IsContent()) { + nsIFrame* frame = node->AsContent()->GetPrimaryFrame(); if (!frame) { - if (aNode->IsElement() && aNode->AsElement()->IsDisplayContents()) { - return true; + if (node->IsElement() && node->AsElement()->IsDisplayContents()) { + return false; } - if (aNode->IsText()) { + if (node->IsText()) { // We have already checked that our parent is visible. // // FIXME(emilio): Text not assigned to a in Shadow DOM should // probably return false... - return true; + return false; } - if (aNode->IsHTMLElement(nsGkAtoms::rp)) { + if (node->IsHTMLElement(nsGkAtoms::rp)) { // Ruby parentheses are part of ruby structure, hence // shouldn't be stripped out even if it is not displayed. - return true; + return false; } - return false; + return true; } bool isVisible = frame->StyleVisibility()->IsVisible(); - if (!isVisible && aNode->IsText()) return false; + if (!isVisible && node->IsText()) { + return true; + } } } - return true; + return false; } virtual bool IncludeInContext(nsINode* aNode); @@ -349,7 +350,7 @@ nsresult nsDocumentEncoder::SerializeNodeStart(nsINode& aOriginalNode, } } - if (!IsVisibleNode(&aOriginalNode)) { + if (IsInvisibleNodeAndShouldBeSkipped(aOriginalNode)) { return NS_OK; } @@ -411,7 +412,7 @@ nsresult nsDocumentEncoder::SerializeNodeEnd(nsINode& aNode, nsAString& aStr) { } } - if (!IsVisibleNode(&aNode)) { + if (IsInvisibleNodeAndShouldBeSkipped(aNode)) { return NS_OK; } @@ -429,11 +430,12 @@ nsresult nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode, return NS_OK; } - if (!IsVisibleNode(aNode)) return NS_OK; + NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); - nsresult rv = NS_OK; + if (IsInvisibleNodeAndShouldBeSkipped(*aNode)) { + return NS_OK; + } - MOZ_ASSERT(aNode, "aNode shouldn't be nullptr."); FixupNodeDeterminer fixupNodeDeterminer{mNodeFixup, nullptr, *aNode}; nsINode* maybeFixedNode = &fixupNodeDeterminer.GetFixupNodeFallBackToOriginalNode(); @@ -449,6 +451,8 @@ nsresult nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode, } } + nsresult rv = NS_OK; + if (!aDontSerializeRoot) { int32_t endOffset = -1; if (aMaxLength > 0) { @@ -585,7 +589,9 @@ nsresult nsDocumentEncoder::SerializeRangeNodes(nsRange* const aRange, nsCOMPtr content = do_QueryInterface(aNode); NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); - if (!IsVisibleNode(aNode)) return NS_OK; + if (IsInvisibleNodeAndShouldBeSkipped(*aNode)) { + return NS_OK; + } nsresult rv = NS_OK; @@ -788,11 +794,13 @@ nsresult nsDocumentEncoder::SerializeRangeToString(nsRange* aRange, if (startContainer == endContainer && IsTextNode(startContainer)) { if (mFlags & SkipInvisibleContent) { // Check that the parent is visible if we don't a frame. - // IsVisibleNode() will do it when there's a frame. + // IsInvisibleNodeAndShouldBeSkipped() will do it when there's a frame. nsCOMPtr content = do_QueryInterface(startContainer); if (content && !content->GetPrimaryFrame()) { nsIContent* parent = content->GetParent(); - if (!parent || !IsVisibleNode(parent)) return NS_OK; + if (!parent || IsInvisibleNodeAndShouldBeSkipped(*parent)) { + return NS_OK; + } } } rv = SerializeNodeStart(*startContainer, startOffset, endOffset, @@ -847,9 +855,9 @@ nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength, aOutputString.Truncate(); nsString output; - static const size_t bufferSize = 2048; + static const size_t kStringBufferSizeInBytes = 2048; if (!mCachedBuffer) { - mCachedBuffer = nsStringBuffer::Alloc(bufferSize).take(); + mCachedBuffer = nsStringBuffer::Alloc(kStringBufferSizeInBytes).take(); if (NS_WARN_IF(!mCachedBuffer)) { return NS_ERROR_OUT_OF_MEMORY; } @@ -975,7 +983,7 @@ nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength, bool setOutput = false; // Try to cache the buffer. if (mCachedBuffer) { - if (mCachedBuffer->StorageSize() == bufferSize && + if ((mCachedBuffer->StorageSize() == kStringBufferSizeInBytes) && !mCachedBuffer->IsReadonly()) { mCachedBuffer->AddRef(); } else { diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index 82cc8ecc488c..664d5d92093f 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -466,7 +466,7 @@ class nsINode : public mozilla::dom::EventTarget { MOZ_CAN_RUN_SCRIPT mozilla::dom::Element* GetParentFlexElement(); /** - * Return whether the node is an Element node + * Return whether the node is an Element node. Faster than using `NodeType()`. */ bool IsElement() const { return GetBoolFlag(NodeIsElement); } diff --git a/dom/chrome-webidl/JSWindowActor.webidl b/dom/chrome-webidl/JSWindowActor.webidl index 749c11dd026f..f24480685f80 100644 --- a/dom/chrome-webidl/JSWindowActor.webidl +++ b/dom/chrome-webidl/JSWindowActor.webidl @@ -35,6 +35,9 @@ interface JSWindowActorChild { [Throws] readonly attribute BrowsingContext? browsingContext; + [Throws] + readonly attribute nsIDocShell? docShell; + // NOTE: As this returns a window proxy, it may not be currently referencing // the document associated with this JSWindowActor. Generally prefer using // `document`. diff --git a/dom/events/test/mochitest.ini b/dom/events/test/mochitest.ini index 1c48580efeed..21defc574183 100644 --- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -76,7 +76,7 @@ skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM [test_bug563329.html] skip-if = true # Disabled due to timeouts. [test_bug574663.html] -skip-if = (toolkit == 'android') || (os == 'win' && bits == 32 && !debug) || (os == 'linux' && !debug) #CRASH_DUMP, RANDOM, Bug 1523853 +skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM [test_bug591815.html] [test_bug593959.html] [test_bug603008.html] diff --git a/dom/events/test/test_bug574663.html b/dom/events/test/test_bug574663.html index b5b4fd85748c..76219dd90d88 100644 --- a/dom/events/test/test_bug574663.html +++ b/dom/events/test/test_bug574663.html @@ -63,7 +63,6 @@ function forceScrollAndWait(scrollbox, callback) { } } -var kExtraEvents = 5; var kDelta = 3; function sendTouchpadScrollMotion(scrollbox, direction, ctrl, momentum, callback) { @@ -91,11 +90,9 @@ function sendTouchpadScrollMotion(scrollbox, direction, ctrl, momentum, callback scrollbox.addEventListener("wheel", onwheel); synthesizeWheel(scrollbox, 10, 10, event, win); - // then 5 additional pixel scrolls + // then additional pixel scroll event.lineOrPageDeltaY = 0; - for (let i = 1; i <= kExtraEvents; ++i) { - synthesizeWheel(scrollbox, 10, 10, event, win); - } + synthesizeWheel(scrollbox, 10, 10, event, win); } function runTest() { @@ -147,7 +144,7 @@ function runTest() { let postfix = isMomentum ? ", even after releasing the touchpad" : ""; // Normal scroll: scroll is(winUtils.fullZoom, zoomFactorBefore, "Normal scrolling shouldn't change zoom" + postfix); - is(scrollbox.scrollTop, scrollTopBefore + kDelta * (kExtraEvents + 1), + is(scrollbox.scrollTop, scrollTopBefore + kDelta * 2, "Normal scrolling should scroll" + postfix); } else { if (!isMomentum) { @@ -155,7 +152,7 @@ function runTest() { is(scrollbox.scrollTop, scrollTopBefore, "Ctrl-scrolling shouldn't scroll while the user is touching the touchpad"); } else { is(winUtils.fullZoom, zoomFactorBefore, "Momentum scrolling shouldn't zoom, even when pressing Ctrl"); - is(scrollbox.scrollTop, scrollTopBefore + kDelta * (kExtraEvents + 1), + is(scrollbox.scrollTop, scrollTopBefore + kDelta * 2, "Momentum scrolling should scroll, even when pressing Ctrl"); } } diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index b207785e52e2..0fbb10472c33 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -2653,14 +2653,6 @@ nsresult nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec, return NS_OK; } -// https://html.spec.whatwg.org/#being-rendered -// -// With a gotcha for display contents: -// https://github.com/whatwg/html/issues/3947 -static bool IsRendered(const Element& aElement) { - return aElement.GetPrimaryFrame() || aElement.IsDisplayContents(); -} - void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue, mozilla::ErrorResult& aError) { // innerText depends on layout. For example, white space processing is @@ -2715,7 +2707,7 @@ void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue, doc->FlushPendingNotifications(FlushType::Layout); } - if (!IsRendered(*this)) { + if (!IsRendered()) { GetTextContentInternal(aValue, aError); } else { nsRange::GetInnerTextNoFlush(aValue, aError, this); diff --git a/dom/ipc/JSWindowActorChild.cpp b/dom/ipc/JSWindowActorChild.cpp index 81c4edbac23c..29e4257056c9 100644 --- a/dom/ipc/JSWindowActorChild.cpp +++ b/dom/ipc/JSWindowActorChild.cpp @@ -106,6 +106,14 @@ BrowsingContext* JSWindowActorChild::GetBrowsingContext(ErrorResult& aRv) { return mManager->BrowsingContext(); } +nsIDocShell* JSWindowActorChild::GetDocShell(ErrorResult& aRv) { + if (BrowsingContext* bc = GetBrowsingContext(aRv)) { + return bc->GetDocShell(); + } + + return nullptr; +} + Nullable JSWindowActorChild::GetContentWindow( ErrorResult& aRv) { if (BrowsingContext* bc = GetBrowsingContext(aRv)) { diff --git a/dom/ipc/JSWindowActorChild.h b/dom/ipc/JSWindowActorChild.h index c0399a7a3507..103f5bd18c4a 100644 --- a/dom/ipc/JSWindowActorChild.h +++ b/dom/ipc/JSWindowActorChild.h @@ -51,6 +51,7 @@ class JSWindowActorChild final : public JSWindowActor { void AfterDestroy(); Document* GetDocument(ErrorResult& aRv); BrowsingContext* GetBrowsingContext(ErrorResult& aRv); + nsIDocShell* GetDocShell(ErrorResult& aRv); Nullable GetContentWindow(ErrorResult& aRv); protected: diff --git a/dom/ipc/tests/browser.ini b/dom/ipc/tests/browser.ini index 8c51e579d0aa..4be5dc4a2be2 100644 --- a/dom/ipc/tests/browser.ini +++ b/dom/ipc/tests/browser.ini @@ -7,7 +7,5 @@ support-files = [browser_domainPolicy.js] [browser_memory_distribution_telemetry.js] skip-if = !e10 # This is an e10s only probe. -[browser_remote_navigation_delay_telemetry.js] -skip-if = !e10s # This is an e10s only probe. [browser_cancel_content_js.js] -skip-if = !e10s # This is an e10s only probe. \ No newline at end of file +skip-if = !e10s # This is an e10s only probe. diff --git a/dom/ipc/tests/browser_remote_navigation_delay_telemetry.js b/dom/ipc/tests/browser_remote_navigation_delay_telemetry.js deleted file mode 100644 index 1e4d90c1f4c9..000000000000 --- a/dom/ipc/tests/browser_remote_navigation_delay_telemetry.js +++ /dev/null @@ -1,48 +0,0 @@ -"use strict"; - -var session = ChromeUtils.import("resource://gre/modules/TelemetrySession.jsm", null); - -add_task(async function test_memory_distribution() { - if (Services.prefs.getIntPref("dom.ipc.processCount", 1) < 2) { - ok(true, "Skip this test if e10s-multi is disabled."); - return; - } - - let canRecordExtended = Services.telemetry.canRecordExtended; - Services.telemetry.canRecordExtended = true; - registerCleanupFunction(() => Services.telemetry.canRecordExtended = canRecordExtended); - - Services.telemetry.getSnapshotForKeyedHistograms("main", true /* clear */); - - // Open a remote page in a new tab to trigger the WebNavigation:LoadURI. - let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com"); - ok(tab1.linkedBrowser.isRemoteBrowser, "|tab1| should have a remote browser."); - - // Open a new tab with about:robots, so it ends up in the parent process with a non-remote browser. - let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots"); - ok(!tab2.linkedBrowser.isRemoteBrowser, "|tab2| should have a non-remote browser."); - // Navigate the tab, so it will change remotness and it triggers the SessionStore:restoreTabContent case. - await BrowserTestUtils.loadURI(tab2.linkedBrowser, "http://example.com"); - ok(tab2.linkedBrowser.isRemoteBrowser, "|tab2| should have a remote browser by now."); - - // There is no good way to make sure that the parent received the histogram entries from the child processes. - // Let's stick to the ugly, spinning the event loop until we have a good approach (Bug 1357509). - await BrowserTestUtils.waitForCondition(() => { - let s = Services.telemetry.getSnapshotForKeyedHistograms("main", false).content.FX_TAB_REMOTE_NAVIGATION_DELAY_MS; - return s && "WebNavigation:LoadURI" in s && "SessionStore:restoreTabContent" in s; - }); - - let s = Services.telemetry.getSnapshotForKeyedHistograms("main", false).content.FX_TAB_REMOTE_NAVIGATION_DELAY_MS; - let restoreTabSnapshot = s["SessionStore:restoreTabContent"]; - ok(restoreTabSnapshot.sum > 0, "Zero delay for the restoreTabContent case is unlikely."); - ok(restoreTabSnapshot.sum < 10000, "More than 10 seconds delay for the restoreTabContent case is unlikely."); - - let loadURISnapshot = s["WebNavigation:LoadURI"]; - ok(loadURISnapshot.sum > 0, "Zero delay for the LoadURI case is unlikely."); - ok(loadURISnapshot.sum < 10000, "More than 10 seconds delay for the LoadURI case is unlikely."); - - Services.telemetry.getSnapshotForKeyedHistograms("main", true /* clear */); - - BrowserTestUtils.removeTab(tab2); - BrowserTestUtils.removeTab(tab1); -}); diff --git a/dom/media/gtest/MockCubeb.h b/dom/media/gtest/MockCubeb.h index 19c49ac7da0e..2dce9879e579 100644 --- a/dom/media/gtest/MockCubeb.h +++ b/dom/media/gtest/MockCubeb.h @@ -424,14 +424,16 @@ void cubeb_mock_stream_destroy(cubeb_stream* stream) { } static char const* cubeb_mock_get_backend_id(cubeb* context) { -#if defined(XP_LINUX) - return "pulse"; -#elif defined(XP_MACOSX) +#if defined(XP_MACOSX) return "audiounit"; #elif defined(XP_WIN) return "wasapi"; #elif defined(ANDROID) return "opensl"; +#elif defined(__OpenBSD__) + return "sndio"; +#else + return "pulse"; #endif } diff --git a/dom/smil/SMILCSSValueType.cpp b/dom/smil/SMILCSSValueType.cpp index f50b86f84c4f..eaaa0f823e24 100644 --- a/dom/smil/SMILCSSValueType.cpp +++ b/dom/smil/SMILCSSValueType.cpp @@ -516,8 +516,8 @@ bool SMILCSSValueType::SetPropertyValues(const SMILValue& aValue, bool changed = false; for (const auto& value : wrapper->mServoValues) { - changed |= - Servo_DeclarationBlock_SetPropertyToAnimationValue(aDecl.Raw(), value); + changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue(aDecl.Raw(), + value, {}); } return changed; diff --git a/dom/webidl/Animation.webidl b/dom/webidl/Animation.webidl index 37fb62516eb6..cb5a66151dc0 100644 --- a/dom/webidl/Animation.webidl +++ b/dom/webidl/Animation.webidl @@ -12,8 +12,10 @@ enum AnimationPlayState { "idle", "running", "paused", "finished" }; -[Constructor (optional AnimationEffect? effect = null, - optional AnimationTimeline? timeline)] +enum AnimationReplaceState { "active", "removed", "persisted" }; + +[Constructor(optional AnimationEffect? effect = null, + optional AnimationTimeline? timeline)] interface Animation : EventTarget { attribute DOMString id; [Func="Document::IsWebAnimationsEnabled", Pure] @@ -30,22 +32,30 @@ interface Animation : EventTarget { readonly attribute AnimationPlayState playState; [BinaryName="pendingFromJS"] readonly attribute boolean pending; + [Pref="dom.animations-api.autoremove.enabled"] + readonly attribute AnimationReplaceState replaceState; [Func="Document::IsWebAnimationsEnabled", Throws] readonly attribute Promise ready; [Func="Document::IsWebAnimationsEnabled", Throws] readonly attribute Promise finished; attribute EventHandler onfinish; attribute EventHandler oncancel; - void cancel (); + [Pref="dom.animations-api.autoremove.enabled"] + attribute EventHandler onremove; + void cancel(); [Throws] - void finish (); + void finish(); [Throws, BinaryName="playFromJS"] - void play (); + void play(); [Throws, BinaryName="pauseFromJS"] - void pause (); + void pause(); void updatePlaybackRate (double playbackRate); [Throws] - void reverse (); + void reverse(); + [Pref="dom.animations-api.autoremove.enabled"] + void persist(); + [Pref="dom.animations-api.autoremove.enabled", Throws] + void commitStyles(); }; // Non-standard extensions diff --git a/dom/webidl/DocumentTimeline.webidl b/dom/webidl/DocumentTimeline.webidl index bd636b8a5105..ed4b3f6e7873 100644 --- a/dom/webidl/DocumentTimeline.webidl +++ b/dom/webidl/DocumentTimeline.webidl @@ -15,6 +15,6 @@ dictionary DocumentTimelineOptions { }; [Func="Document::AreWebAnimationsTimelinesEnabled", - Constructor (optional DocumentTimelineOptions options)] + Constructor(optional DocumentTimelineOptions options)] interface DocumentTimeline : AnimationTimeline { }; diff --git a/dom/webidl/KeyframeEffect.webidl b/dom/webidl/KeyframeEffect.webidl index ad7ce1429e74..d7b288a21734 100644 --- a/dom/webidl/KeyframeEffect.webidl +++ b/dom/webidl/KeyframeEffect.webidl @@ -24,18 +24,18 @@ dictionary KeyframeEffectOptions : EffectTiming { // processing on the `keyframes` object. [Func="Document::IsWebAnimationsEnabled", RunConstructorInCallerCompartment, - Constructor ((Element or CSSPseudoElement)? target, - object? keyframes, - optional (unrestricted double or KeyframeEffectOptions) options), - Constructor (KeyframeEffect source)] + Constructor((Element or CSSPseudoElement)? target, + object? keyframes, + optional (unrestricted double or KeyframeEffectOptions) options), + Constructor(KeyframeEffect source)] interface KeyframeEffect : AnimationEffect { attribute (Element or CSSPseudoElement)? target; [Pref="dom.animations-api.compositing.enabled"] attribute IterationCompositeOperation iterationComposite; [Pref="dom.animations-api.compositing.enabled"] attribute CompositeOperation composite; - [Throws] sequence getKeyframes (); - [Throws] void setKeyframes (object? keyframes); + [Throws] sequence getKeyframes(); + [Throws] void setKeyframes(object? keyframes); }; // Non-standard extensions diff --git a/js/public/Promise.h b/js/public/Promise.h index 47fcbc30d170..e7a396c5d478 100644 --- a/js/public/Promise.h +++ b/js/public/Promise.h @@ -260,7 +260,7 @@ class MOZ_RAII JS_PUBLIC_API AutoDebuggerJobQueueInterruption { enum class PromiseRejectionHandlingState { Unhandled, Handled }; typedef void (*PromiseRejectionTrackerCallback)( - JSContext* cx, JS::HandleObject promise, + JSContext* cx, bool mutedErrors, JS::HandleObject promise, JS::PromiseRejectionHandlingState state, void* data); /** diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 4070f5e38ea6..27e213f13647 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -1137,7 +1137,7 @@ static bool TrackUnhandledRejections(JSContext* cx, JS::HandleObject promise, } static void ForwardingPromiseRejectionTrackerCallback( - JSContext* cx, JS::HandleObject promise, + JSContext* cx, bool mutedErrors, JS::HandleObject promise, JS::PromiseRejectionHandlingState state, void* data) { AutoReportException are(cx); diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index afba1574260b..01b8ab7b6033 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -637,9 +637,15 @@ void JSRuntime::addUnhandledRejectedPromise(JSContext* cx, return; } + bool mutedErrors = false; + if (JSScript* script = cx->currentScript()) { + mutedErrors = script->mutedErrors(); + } + void* data = cx->promiseRejectionTrackerCallbackData; cx->promiseRejectionTrackerCallback( - cx, promise, JS::PromiseRejectionHandlingState::Unhandled, data); + cx, mutedErrors, promise, JS::PromiseRejectionHandlingState::Unhandled, + data); } void JSRuntime::removeUnhandledRejectedPromise(JSContext* cx, @@ -649,9 +655,15 @@ void JSRuntime::removeUnhandledRejectedPromise(JSContext* cx, return; } + bool mutedErrors = false; + if (JSScript* script = cx->currentScript()) { + mutedErrors = script->mutedErrors(); + } + void* data = cx->promiseRejectionTrackerCallbackData; cx->promiseRejectionTrackerCallback( - cx, promise, JS::PromiseRejectionHandlingState::Handled, data); + cx, mutedErrors, promise, JS::PromiseRejectionHandlingState::Handled, + data); } mozilla::non_crypto::XorShift128PlusRNG& JSRuntime::randomKeyGenerator() { diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 66fcc25e9518..ba6ab24856c3 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -225,6 +225,10 @@ static ContentMap& GetContentMap() { template static bool HasMatchingAnimations(EffectSet& aEffects, TestType&& aTest) { for (KeyframeEffect* effect : aEffects) { + if (!effect->GetAnimation() || !effect->GetAnimation()->IsRelevant()) { + continue; + } + if (aTest(*effect, aEffects)) { return true; } @@ -263,8 +267,7 @@ bool nsLayoutUtils::HasAnimationOfPropertySet( return HasMatchingAnimations( aFrame, aPropertySet, [&aPropertySet](KeyframeEffect& aEffect, const EffectSet&) { - return (aEffect.IsInEffect() || aEffect.IsCurrent()) && - aEffect.HasAnimationOfPropertySet(aPropertySet); + return aEffect.HasAnimationOfPropertySet(aPropertySet); }); } @@ -294,8 +297,7 @@ bool nsLayoutUtils::HasAnimationOfPropertySet( return HasMatchingAnimations( *aEffectSet, [&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) { - return (aEffect.IsInEffect() || aEffect.IsCurrent()) && - aEffect.HasAnimationOfPropertySet(aPropertySet); + return aEffect.HasAnimationOfPropertySet(aPropertySet); }); } @@ -305,8 +307,7 @@ bool nsLayoutUtils::HasEffectiveAnimation( return HasMatchingAnimations( aFrame, aPropertySet, [&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) { - return (aEffect.IsInEffect() || aEffect.IsCurrent()) && - aEffect.HasEffectiveAnimationOfPropertySet(aPropertySet, + return aEffect.HasEffectiveAnimationOfPropertySet(aPropertySet, aEffectSet); }); } diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index bf35c03c786c..a826b2538688 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -41,6 +41,7 @@ #include "nsComponentManagerUtils.h" #include "mozilla/Logging.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" #include "nsIXULRuntime.h" #include "jsapi.h" #include "nsContentUtils.h" @@ -1747,6 +1748,16 @@ void nsRefreshDriver::CancelIdleRunnable(nsIRunnable* aRunnable) { } } +static bool ReduceAnimations(Document* aDocument, void* aData) { + if (aDocument->GetPresContext() && + aDocument->GetPresContext()->EffectCompositor()->NeedsReducing()) { + aDocument->GetPresContext()->EffectCompositor()->ReduceAnimations(); + } + aDocument->EnumerateSubDocuments(ReduceAnimations, nullptr); + + return true; +} + void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) { MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(), "Shouldn't have a JSContext on the stack"); @@ -1897,6 +1908,28 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) { } } + // Any animation timelines updated above may cause animations to queue + // Promise resolution microtasks. We shouldn't run these, however, until we + // have fully updated the animation state. + // + // As per the "update animations and send events" procedure[1], we should + // remove replaced animations and then run these microtasks before + // dispatching the corresponding animation events. + // + // [1] + // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events + if (i == 1) { + nsAutoMicroTask mt; + ReduceAnimations(mPresContext->Document(), nullptr); + } + + // Check if running the microtask checkpoint caused the pres context to + // be destroyed. + if (i == 1 && (!mPresContext || !mPresContext->GetPresShell())) { + StopTimer(); + return; + } + if (i == 1) { // This is the FlushType::Style case. diff --git a/layout/generic/nsFrameSelection.cpp b/layout/generic/nsFrameSelection.cpp index 4c6e20bf1ac8..73bfaeabc660 100644 --- a/layout/generic/nsFrameSelection.cpp +++ b/layout/generic/nsFrameSelection.cpp @@ -2747,8 +2747,8 @@ nsresult nsFrameSelection::UpdateSelectionCacheOnRepaintSelection( nsCOMPtr aDoc = presShell->GetDocument(); if (aDoc && aSel && !aSel->IsCollapsed()) { - return nsCopySupport::HTMLCopy(aSel, aDoc, nsIClipboard::kSelectionCache, - false); + return nsCopySupport::EncodeDocumentWithContextAndPutToClipboard( + aSel, aDoc, nsIClipboard::kSelectionCache, false); } return NS_OK; @@ -2762,8 +2762,9 @@ int16_t AutoCopyListener::sClipboardID = -1; * What we do now: * On every selection change, we copy to the clipboard anew, creating a * HTML buffer, a transferable, an nsISupportsString and - * a huge mess every time. This is basically what nsCopySupport::HTMLCopy() - * does to move the selection into the clipboard for Edit->Copy. + * a huge mess every time. This is basically what + * nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() does to move the + * selection into the clipboard for Edit->Copy. * * What we should do, to make our end of the deal faster: * Create a singleton transferable with our own magic converter. When selection @@ -2830,8 +2831,10 @@ void AutoCopyListener::OnSelectionChange(Document* aDocument, return; } - // Call the copy code. DebugOnly rv = - nsCopySupport::HTMLCopy(&aSelection, aDocument, sClipboardID, false); - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsCopySupport::HTMLCopy() failed"); + nsCopySupport::EncodeDocumentWithContextAndPutToClipboard( + &aSelection, aDocument, sClipboardID, false); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed"); } diff --git a/layout/style/ServoBindingTypes.h b/layout/style/ServoBindingTypes.h index bfc04461aa64..fc34c627e41d 100644 --- a/layout/style/ServoBindingTypes.h +++ b/layout/style/ServoBindingTypes.h @@ -173,8 +173,6 @@ SERVO_ARC_TYPE(ComputedStyle, mozilla::ComputedStyle) // Other special cases. -// TODO(heycam): Handle these elsewhere. struct RawServoAnimationValueTable; -struct RawServoAnimationValueMap; #endif // mozilla_ServoBindingTypes_h diff --git a/layout/style/ServoBoxedTypeList.h b/layout/style/ServoBoxedTypeList.h index cbce1533af61..0269dd00c607 100644 --- a/layout/style/ServoBoxedTypeList.h +++ b/layout/style/ServoBoxedTypeList.h @@ -24,6 +24,7 @@ // to just generate the forward declaration. SERVO_BOXED_TYPE(StyleSet, RawServoStyleSet) +SERVO_BOXED_TYPE(AnimationValueMap, RawServoAnimationValueMap) SERVO_BOXED_TYPE(AuthorStyles, RawServoAuthorStyles) SERVO_BOXED_TYPE(SelectorList, RawServoSelectorList) SERVO_BOXED_TYPE(SharedMemoryBuilder, RawServoSharedMemoryBuilder) diff --git a/modules/libpref/init/StaticPrefList.h b/modules/libpref/init/StaticPrefList.h index c6e868aaf092..24be23bcea1f 100644 --- a/modules/libpref/init/StaticPrefList.h +++ b/modules/libpref/init/StaticPrefList.h @@ -127,6 +127,19 @@ VARCACHE_PREF( // DOM prefs //--------------------------------------------------------------------------- +// Is support for automatically removing replaced filling animations enabled? +#ifdef RELEASE_OR_BETA +# define PREF_VALUE false +#else +# define PREF_VALUE true +#endif +VARCACHE_PREF( + "dom.animations-api.autoremove.enabled", + dom_animations_api_autoremove_enabled, + bool, PREF_VALUE +) +#undef PREF_VALUE + // Is support for composite operations from the Web Animations API enabled? #ifdef RELEASE_OR_BETA # define PREF_VALUE false diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index e73d66f3b914..7ed7a46454f0 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1622,6 +1622,9 @@ pref("network.protocol-handler.external.hcp", false); pref("network.protocol-handler.external.vbscript", false); pref("network.protocol-handler.external.javascript", false); pref("network.protocol-handler.external.data", false); +pref("network.protocol-handler.external.ie.http", false); +pref("network.protocol-handler.external.iehistory", false); +pref("network.protocol-handler.external.ierss", false); pref("network.protocol-handler.external.ms-help", false); pref("network.protocol-handler.external.res", false); pref("network.protocol-handler.external.shell", false); diff --git a/security/manager/ssl/PKCS11ModuleDB.cpp b/security/manager/ssl/PKCS11ModuleDB.cpp index b30614eb455a..d048c0785079 100644 --- a/security/manager/ssl/PKCS11ModuleDB.cpp +++ b/security/manager/ssl/PKCS11ModuleDB.cpp @@ -7,8 +7,6 @@ #include "PKCS11ModuleDB.h" #include "ScopedNSSTypes.h" -#include "mozilla/Telemetry.h" -#include "nsCRTGlue.h" #include "nsIMutableArray.h" #include "nsNSSCertHelper.h" #include "nsNSSComponent.h" @@ -63,32 +61,6 @@ PKCS11ModuleDB::DeleteModule(const nsAString& aModuleName) { return NS_OK; } -// Given a PKCS#11 module, determines an appropriate name to identify it for the -// purposes of gathering telemetry. For 3rd party PKCS#11 modules, this should -// be the name of the dynamic library that implements the module. For built-in -// NSS modules, it will be the common name of the module. -// Because the result will be used as a telemetry scalar key (which must be less -// than 70 characters), this function will also truncate the result if it -// exceeds this limit. (Note that unfortunately telemetry doesn't expose a way -// to programmatically query the scalar key length limit, so we have to -// hard-code the value here.) -void GetModuleNameForTelemetry(/*in*/ const SECMODModule* module, - /*out*/ nsString& result) { - result.Truncate(); - if (module->dllName) { - result.AssignASCII(module->dllName); - int32_t separatorIndex = result.RFind(FILE_PATH_SEPARATOR); - if (separatorIndex != kNotFound) { - result = Substring(result, separatorIndex + 1); - } - } else { - result.AssignASCII(module->commonName); - } - if (result.Length() >= 70) { - result.Truncate(69); - } -} - // Add a new PKCS11 module to the user's profile. NS_IMETHODIMP PKCS11ModuleDB::AddModule(const nsAString& aModuleName, @@ -131,23 +103,6 @@ PKCS11ModuleDB::AddModule(const nsAString& aModuleName, if (srv != SECSuccess) { return NS_ERROR_FAILURE; } - - UniqueSECMODModule module(SECMOD_FindModule(moduleNameNormalized.get())); - if (!module) { - return NS_ERROR_FAILURE; - } - - nsAutoString scalarKey; - GetModuleNameForTelemetry(module.get(), scalarKey); - // Scalar keys must be between 0 and 70 characters (exclusive). - // GetModuleNameForTelemetry takes care of keys that are too long. - // If for some reason it couldn't come up with an appropriate name and - // returned an empty result, however, we need to not attempt to record this - // (it wouldn't give us anything useful anyway). - if (scalarKey.Length() > 0) { - Telemetry::ScalarSet(Telemetry::ScalarID::SECURITY_PKCS11_MODULES_LOADED, - scalarKey, true); - } return NS_OK; } diff --git a/security/manager/ssl/PKCS11ModuleDB.h b/security/manager/ssl/PKCS11ModuleDB.h index b65081bea57d..c79101409144 100644 --- a/security/manager/ssl/PKCS11ModuleDB.h +++ b/security/manager/ssl/PKCS11ModuleDB.h @@ -8,8 +8,6 @@ #include "nsIPKCS11ModuleDB.h" -#include "nsString.h" - namespace mozilla { namespace psm { @@ -31,9 +29,6 @@ class PKCS11ModuleDB : public nsIPKCS11ModuleDB { virtual ~PKCS11ModuleDB() {} }; -void GetModuleNameForTelemetry(/*in*/ const SECMODModule* module, - /*out*/ nsString& result); - } // namespace psm } // namespace mozilla diff --git a/security/manager/ssl/nsNSSComponent.cpp b/security/manager/ssl/nsNSSComponent.cpp index cffc48143e99..f34347794a5f 100644 --- a/security/manager/ssl/nsNSSComponent.cpp +++ b/security/manager/ssl/nsNSSComponent.cpp @@ -9,7 +9,6 @@ #include "EnterpriseRoots.h" #include "ExtendedValidation.h" #include "NSSCertDBTrustDomain.h" -#include "PKCS11ModuleDB.h" #include "ScopedNSSTypes.h" #include "SharedSSLState.h" #include "cert.h" @@ -1793,28 +1792,6 @@ nsresult nsNSSComponent::InitializeNSS() { Telemetry::Accumulate(Telemetry::FIPS_ENABLED, true); } - // Gather telemetry on any PKCS#11 modules we have loaded. Note that because - // we load the built-in root module asynchronously after this, the telemetry - // will not include it. - { // Introduce scope for the AutoSECMODListReadLock. - AutoSECMODListReadLock lock; - for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list; - list = list->next) { - nsAutoString scalarKey; - GetModuleNameForTelemetry(list->module, scalarKey); - // Scalar keys must be between 0 and 70 characters (exclusive). - // GetModuleNameForTelemetry takes care of keys that are too long. If for - // some reason it couldn't come up with an appropriate name and returned - // an empty result, however, we need to not attempt to record this (it - // wouldn't give us anything useful anyway). - if (scalarKey.Length() > 0) { - Telemetry::ScalarSet( - Telemetry::ScalarID::SECURITY_PKCS11_MODULES_LOADED, scalarKey, - true); - } - } - } - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n")); { diff --git a/security/manager/ssl/tests/unit/test_pkcs11_module.js b/security/manager/ssl/tests/unit/test_pkcs11_module.js index 8524dff01445..b06d21ea558c 100644 --- a/security/manager/ssl/tests/unit/test_pkcs11_module.js +++ b/security/manager/ssl/tests/unit/test_pkcs11_module.js @@ -50,40 +50,13 @@ function checkTestModuleExists() { return testModule; } -function checkModuleTelemetry(additionalExpectedModule = undefined) { - let expectedModules = [ - "NSS Internal PKCS #11 Module", - ]; - if (additionalExpectedModule) { - expectedModules.push(additionalExpectedModule); - } - expectedModules.sort(); - let telemetry = Services.telemetry.getSnapshotForKeyedScalars("main", false).parent; - let moduleTelemetry = telemetry["security.pkcs11_modules_loaded"]; - let actualModules = []; - Object.keys(moduleTelemetry).forEach((key) => { - ok(moduleTelemetry[key], "each keyed scalar should be true"); - actualModules.push(key); - }); - actualModules.sort(); - equal(actualModules.length, expectedModules.length, - "the number of actual and expected loaded modules should be the same"); - for (let i in actualModules) { - equal(actualModules[i], expectedModules[i], - "actual and expected module names should match"); - } -} - function run_test() { // Check that if we have never added the test module, that we don't find it // in the module list. checkTestModuleNotPresent(); - checkModuleTelemetry(); // Check that adding the test module makes it appear in the module list. loadPKCS11TestModule(true); - checkModuleTelemetry( - `${AppConstants.DLL_PREFIX}pkcs11testmodule${AppConstants.DLL_SUFFIX}`); let testModule = checkTestModuleExists(); // Check that listing the slots for the test module works. diff --git a/servo/components/style/gecko/boxed_types.rs b/servo/components/style/gecko/boxed_types.rs index 857f99e14891..c32e06c97987 100644 --- a/servo/components/style/gecko/boxed_types.rs +++ b/servo/components/style/gecko/boxed_types.rs @@ -5,12 +5,22 @@ //! FFI implementations for types listed in ServoBoxedTypeList.h. use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; +use crate::properties::animated_properties::AnimationValueMap; use to_shmem::SharedMemoryBuilder; // TODO(heycam): The FFI impls for most of the types in ServoBoxedTypeList.h are spread across // various files at the moment, but should probably all move here, and use macros to define // them more succinctly, like we do in arc_types.rs. +#[cfg(feature = "gecko")] +unsafe impl HasFFI for AnimationValueMap { + type FFIType = crate::gecko_bindings::bindings::RawServoAnimationValueMap; +} +#[cfg(feature = "gecko")] +unsafe impl HasSimpleFFI for AnimationValueMap {} +#[cfg(feature = "gecko")] +unsafe impl HasBoxFFI for AnimationValueMap {} + #[cfg(feature = "gecko")] unsafe impl HasFFI for SharedMemoryBuilder { type FFIType = crate::gecko_bindings::bindings::RawServoSharedMemoryBuilder; diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs index 1b536d24e8bf..d3b3ecd11e40 100644 --- a/servo/components/style/properties/helpers/animated_properties.mako.rs +++ b/servo/components/style/properties/helpers/animated_properties.mako.rs @@ -9,9 +9,7 @@ from itertools import groupby %> -#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::RawServoAnimationValueMap; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::nsCSSPropertyID; -#[cfg(feature = "gecko")] use crate::gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI}; use itertools::{EitherOrBoth, Itertools}; use crate::properties::{CSSWideKeyword, PropertyDeclaration}; use crate::properties::longhands; @@ -190,13 +188,6 @@ impl AnimatedProperty { /// composed for each TransitionProperty. pub type AnimationValueMap = FxHashMap; -#[cfg(feature = "gecko")] -unsafe impl HasFFI for AnimationValueMap { - type FFIType = RawServoAnimationValueMap; -} -#[cfg(feature = "gecko")] -unsafe impl HasSimpleFFI for AnimationValueMap {} - /// An enum to represent a single computed value belonging to an animated /// property in order to be interpolated with another one. When interpolating, /// both values need to belong to the same property. diff --git a/servo/components/style/properties/properties.mako.rs b/servo/components/style/properties/properties.mako.rs index 1e92066c7656..ed71019b390f 100644 --- a/servo/components/style/properties/properties.mako.rs +++ b/servo/components/style/properties/properties.mako.rs @@ -2337,6 +2337,14 @@ impl SourcePropertyDeclaration { } } + /// Create one with a single PropertyDeclaration. + #[inline] + pub fn with_one(decl: PropertyDeclaration) -> Self { + let mut result = Self::new(); + result.declarations.push(decl); + result + } + /// Similar to Vec::drain: leaves this empty when the return value is dropped. pub fn drain(&mut self) -> SourcePropertyDeclarationDrain { SourcePropertyDeclarationDrain { diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index b59c4b5cfa1f..b7a3daed95e8 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -101,7 +101,7 @@ use style::global_style_data::{GlobalStyleData, GLOBAL_STYLE_DATA, STYLE_THREAD_ use style::invalidation::element::restyle_hints::RestyleHint; use style::media_queries::MediaList; use style::parser::{self, Parse, ParserContext}; -use style::properties::animated_properties::AnimationValue; +use style::properties::animated_properties::{AnimationValue, AnimationValueMap}; use style::properties::{parse_one_declaration_into, parse_style_attribute}; use style::properties::{ComputedValues, Importance, NonCustomPropertyId}; use style::properties::{LonghandId, LonghandIdSet, PropertyDeclarationBlock, PropertyId}; @@ -913,6 +913,36 @@ fn resolve_rules_for_element_with_context<'a>( .0 } +#[no_mangle] +pub extern "C" fn Servo_AnimationValueMap_Create() -> Owned { + Box::::default().into_ffi() +} + +#[no_mangle] +pub unsafe extern "C" fn Servo_AnimationValueMap_Drop(value_map: *mut structs::RawServoAnimationValueMap) { + AnimationValueMap::drop_ffi(value_map) +} + +#[no_mangle] +pub extern "C" fn Servo_AnimationValueMap_GetValue( + raw_value_map: &mut structs::RawServoAnimationValueMap, + property_id: nsCSSPropertyID, +) -> Strong { + use style::properties::animated_properties::AnimationValueMap; + + let property = match LonghandId::from_nscsspropertyid(property_id) { + Ok(longhand) => longhand, + Err(()) => return Strong::null(), + }; + let value_map = AnimationValueMap::from_ffi_mut(raw_value_map); + + value_map + .get(&property) + .map_or(Strong::null(), |value| { + Arc::new(value.clone()).into_strong() + }) +} + #[no_mangle] pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement( raw_style_set: &RawServoStyleSet, @@ -4091,6 +4121,28 @@ pub unsafe extern "C" fn Servo_DeclarationBlock_GetPropertyIsImportant( }) } +#[inline(always)] +fn set_property_to_declarations( + block: &RawServoDeclarationBlock, + parsed_declarations: &mut SourcePropertyDeclaration, + before_change_closure: DeclarationBlockMutationClosure, + importance: Importance, +) -> bool { + let mut updates = Default::default(); + let will_change = read_locked_arc(block, |decls: &PropertyDeclarationBlock| { + decls.prepare_for_update(&parsed_declarations, importance, &mut updates) + }); + if !will_change { + return false; + } + + before_change_closure.invoke(); + write_locked_arc(block, |decls: &mut PropertyDeclarationBlock| { + decls.update(parsed_declarations.drain(), importance, &mut updates) + }); + true +} + fn set_property( declarations: &RawServoDeclarationBlock, property_id: PropertyId, @@ -4123,19 +4175,13 @@ fn set_property( } else { Importance::Normal }; - let mut updates = Default::default(); - let will_change = read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| { - decls.prepare_for_update(&source_declarations, importance, &mut updates) - }); - if !will_change { - return false; - } - before_change_closure.invoke(); - write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| { - decls.update(source_declarations.drain(), importance, &mut updates) - }); - true + set_property_to_declarations( + declarations, + &mut source_declarations, + before_change_closure, + importance, + ) } #[no_mangle] @@ -4167,13 +4213,17 @@ pub unsafe extern "C" fn Servo_DeclarationBlock_SetProperty( pub unsafe extern "C" fn Servo_DeclarationBlock_SetPropertyToAnimationValue( declarations: &RawServoDeclarationBlock, animation_value: &RawServoAnimationValue, + before_change_closure: DeclarationBlockMutationClosure, ) -> bool { - write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| { - decls.push( - AnimationValue::as_arc(&animation_value).uncompute(), - Importance::Normal, - ) - }) + let mut source_declarations = + SourcePropertyDeclaration::with_one(AnimationValue::as_arc(&animation_value).uncompute()); + + set_property_to_declarations( + declarations, + &mut source_declarations, + before_change_closure, + Importance::Normal, + ) } #[no_mangle] diff --git a/taskcluster/ci/beetmover-checksums/kind.yml b/taskcluster/ci/beetmover-checksums/kind.yml index f3923c4d272a..004d59d61e92 100644 --- a/taskcluster/ci/beetmover-checksums/kind.yml +++ b/taskcluster/ci/beetmover-checksums/kind.yml @@ -21,16 +21,12 @@ job-template: attributes: artifact_prefix: public artifact_map: - by-project: + by-release-type: + beta|release.*|esr.*: + by-platform: + android.*: taskcluster/taskgraph/manifests/fennec_candidates_checksums.yml + default: taskcluster/taskgraph/manifests/firefox_candidates_checksums.yml default: by-platform: android.*: taskcluster/taskgraph/manifests/fennec_nightly_checksums.yml default: taskcluster/taskgraph/manifests/firefox_nightly_checksums.yml - mozilla-beta: - by-platform: - android.*: taskcluster/taskgraph/manifests/fennec_candidates_checksums.yml - default: taskcluster/taskgraph/manifests/firefox_candidates_checksums.yml - mozilla-release: - by-platform: - android.*: taskcluster/taskgraph/manifests/fennec_candidates_checksums.yml - default: taskcluster/taskgraph/manifests/firefox_candidates_checksums.yml diff --git a/taskcluster/ci/beetmover-repackage/kind.yml b/taskcluster/ci/beetmover-repackage/kind.yml index 7cf0c54e6229..2228b0d42d37 100644 --- a/taskcluster/ci/beetmover-repackage/kind.yml +++ b/taskcluster/ci/beetmover-repackage/kind.yml @@ -57,7 +57,6 @@ job-template: shipping-phase: promote attributes: artifact_map: - by-project: + by-release-type: + beta|release.*|esr.*: taskcluster/taskgraph/manifests/firefox_candidates.yml default: taskcluster/taskgraph/manifests/firefox_nightly.yml - mozilla-beta: taskcluster/taskgraph/manifests/firefox_candidates.yml - mozilla-release: taskcluster/taskgraph/manifests/firefox_candidates.yml diff --git a/taskcluster/ci/beetmover/kind.yml b/taskcluster/ci/beetmover/kind.yml index 2dd1230f32b5..006334af8020 100644 --- a/taskcluster/ci/beetmover/kind.yml +++ b/taskcluster/ci/beetmover/kind.yml @@ -41,7 +41,6 @@ job-template: shipping-phase: promote attributes: artifact_map: - by-project: - mozilla-release: taskcluster/taskgraph/manifests/fennec_candidates.yml - mozilla-beta: taskcluster/taskgraph/manifests/fennec_candidates.yml + by-release-type: + beta|release.*|esr.*: taskcluster/taskgraph/manifests/fennec_candidates.yml default: taskcluster/taskgraph/manifests/fennec_nightly.yml diff --git a/taskcluster/ci/build/windows.yml b/taskcluster/ci/build/windows.yml index 5cf791674fc6..4e78849308cd 100755 --- a/taskcluster/ci/build/windows.yml +++ b/taskcluster/ci/build/windows.yml @@ -269,7 +269,7 @@ win32-shippable/opt: by-release-type: nightly: true beta: true - release: true + release.*: true esr.*: false default: by-project: @@ -832,7 +832,7 @@ win32-devedition-nightly/opt: by-release-type: nightly: true beta: true - release: true + release.*: true default: by-project: # browser/confvars.sh looks for nightly-try diff --git a/taskcluster/ci/cron-bouncer-check/kind.yml b/taskcluster/ci/cron-bouncer-check/kind.yml index 59fac3a745c7..77401fffb1b6 100644 --- a/taskcluster/ci/cron-bouncer-check/kind.yml +++ b/taskcluster/ci/cron-bouncer-check/kind.yml @@ -42,7 +42,7 @@ jobs: - releases/bouncer_firefox_beta.py release: - releases/bouncer_firefox_release.py - esr60: + esr.*: - releases/bouncer_firefox_esr.py default: - releases/bouncer_firefox_beta.py @@ -51,6 +51,7 @@ jobs: mozilla-beta: LATEST_FIREFOX_RELEASED_DEVEL_VERSION mozilla-release: LATEST_FIREFOX_VERSION mozilla-esr60: FIREFOX_ESR + mozilla-esr68: FIREFOX_ESR_NEXT default: LATEST_FIREFOX_DEVEL_VERSION products-url: https://product-details.mozilla.org/1.0/firefox_versions.json treeherder: diff --git a/taskcluster/ci/release-balrog-scheduling/kind.yml b/taskcluster/ci/release-balrog-scheduling/kind.yml index 4437cc00ef7c..e0fe8a52cc52 100644 --- a/taskcluster/ci/release-balrog-scheduling/kind.yml +++ b/taskcluster/ci/release-balrog-scheduling/kind.yml @@ -34,12 +34,14 @@ jobs: beta: [32] release: [145] esr60: [806] + esr68: [882] default: [] staging: by-release-type: beta: [32] release: [145] esr60: [806] + esr68: [875] default: [] treeherder: platform: firefox-release/opt @@ -50,10 +52,13 @@ jobs: description: Schedule Firefox publishing in balrog (bz2) name: release-firefox_schedule_publishing_in_balrog-bz2 shipping-product: firefox - run-on-releases: [esr60] + run-on-releases: [esr60, esr68] worker: product: firefox - publish-rules: [521] + publish-rules: + by-release-type: + esr60: [521] + default: [] blob-suffix: -bz2 treeherder: platform: firefox-release/opt diff --git a/taskcluster/ci/release-balrog-submit-toplevel/kind.yml b/taskcluster/ci/release-balrog-submit-toplevel/kind.yml index d4538ea0eba4..bda14864611c 100644 --- a/taskcluster/ci/release-balrog-submit-toplevel/kind.yml +++ b/taskcluster/ci/release-balrog-submit-toplevel/kind.yml @@ -39,12 +39,13 @@ jobs: by-release-type: beta: ["beta", "beta-localtest", "beta-cdntest"] release(-rc)?: ["release", "release-localtest", "release-cdntest"] - esr60: ["esr", "esr-localtest", "esr-cdntest"] + esr.*: ["esr", "esr-localtest", "esr-cdntest", "esr-localtest-next", "esr-cdntest-next"] default: [] rules-to-update: by-release-type: beta: ["firefox-beta-cdntest", "firefox-beta-localtest"] release(-rc)?: ["firefox-release-cdntest", "firefox-release-localtest"] + esr68: ["firefox-esr68-cdntest", "firefox-esr68-localtest"] esr60: ["firefox-esr60-cdntest", "firefox-esr60-localtest"] default: [] platforms: ["linux", "linux64", "macosx64", "win32", "win64", "win64-aarch64"] @@ -58,11 +59,14 @@ jobs: name: submit-toplevel-firefox-release-to-balrog-bz2 description: Submit toplevel Firefox release to balrog shipping-product: firefox - run-on-releases: [esr60] + run-on-releases: [esr60, esr68] worker: product: firefox - channel-names: ["esr", "esr-localtest", "esr-cdntest"] - rules-to-update: ["esr52-cdntest", "esr52-localtest"] + channel-names: ["esr", "esr-localtest", "esr-cdntest", "esr-localtest-next", "esr-cdntest-next"] + rules-to-update: + by-release-type: + esr68: ["esr52-cdntest-next", "esr52-localtest-next"] + esr60: ["esr52-cdntest", "esr52-localtest"] platforms: ["linux", "linux64", "macosx64", "win32", "win64"] blob-suffix: -bz2 complete-mar-filename-pattern: '%s-%s.bz2.complete.mar' diff --git a/taskcluster/ci/release-bouncer-aliases/kind.yml b/taskcluster/ci/release-bouncer-aliases/kind.yml index a8b6a74e1bae..46ba3712c511 100644 --- a/taskcluster/ci/release-bouncer-aliases/kind.yml +++ b/taskcluster/ci/release-bouncer-aliases/kind.yml @@ -79,6 +79,10 @@ jobs: firefox-esr-latest-ssl: installer-ssl firefox-esr-latest: installer firefox-esr-msi-latest-ssl: msi + mozilla-esr68: + firefox-esr-next-latest-ssl: installer-ssl + firefox-esr-next-latest: installer + firefox-esr-next-msi-latest-ssl: msi birch: firefox-latest-ssl: installer-ssl firefox-latest: installer diff --git a/taskcluster/ci/release-bouncer-check/kind.yml b/taskcluster/ci/release-bouncer-check/kind.yml index 18bbd8335c6c..1c5e929f2680 100644 --- a/taskcluster/ci/release-bouncer-check/kind.yml +++ b/taskcluster/ci/release-bouncer-check/kind.yml @@ -57,7 +57,7 @@ jobs: - releases/bouncer_firefox_beta.py release: - releases/bouncer_firefox_release.py - esr60: + esr.*: - releases/bouncer_firefox_esr.py default: - releases/bouncer_firefox_beta.py diff --git a/taskcluster/ci/release-bouncer-sub/kind.yml b/taskcluster/ci/release-bouncer-sub/kind.yml index 5e58c33d7f2c..059dc5999a2a 100644 --- a/taskcluster/ci/release-bouncer-sub/kind.yml +++ b/taskcluster/ci/release-bouncer-sub/kind.yml @@ -52,11 +52,11 @@ jobs: firefox: bouncer-platforms: ['linux', 'linux64', 'osx', 'win', 'win64', 'win64-aarch64'] bouncer-products: - by-project: + by-release-type: default: ['complete-mar', 'installer', 'installer-ssl', 'partial-mar', 'stub-installer', 'msi'] + esr68: ['complete-mar', 'complete-mar-bz2', 'installer', 'installer-ssl', 'partial-mar', 'msi'] # No stub installer in esr60 - mozilla-esr60: ['complete-mar', 'complete-mar-bz2', 'installer', 'installer-ssl', 'partial-mar'] - jamun: ['complete-mar', 'complete-mar-bz2', 'installer', 'installer-ssl', 'partial-mar'] + esr60: ['complete-mar', 'complete-mar-bz2', 'installer', 'installer-ssl', 'partial-mar'] shipping-product: firefox treeherder: platform: firefox-release/opt diff --git a/taskcluster/ci/release-snap-repackage/kind.yml b/taskcluster/ci/release-snap-repackage/kind.yml index 7a9bd3e661e7..810adbcb72da 100644 --- a/taskcluster/ci/release-snap-repackage/kind.yml +++ b/taskcluster/ci/release-snap-repackage/kind.yml @@ -59,7 +59,7 @@ jobs: firefox: shipping-product: firefox attributes: - build_platform: linux64-snap-shippable + build_platform: linux64-shippable build_type: opt treeherder: symbol: Snap(r) diff --git a/taskcluster/ci/release-update-verify-config-next/kind.yml b/taskcluster/ci/release-update-verify-config-next/kind.yml new file mode 100644 index 000000000000..eaf8eb90cfa8 --- /dev/null +++ b/taskcluster/ci/release-update-verify-config-next/kind.yml @@ -0,0 +1,98 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--- +loader: taskgraph.loader.transform:loader + +transforms: + - taskgraph.transforms.release:run_on_releases + - taskgraph.transforms.update_verify_config:transforms + - taskgraph.transforms.job:transforms + - taskgraph.transforms.task:transforms + +job-defaults: + name: update-verify-config-next + run-on-projects: [] # to make sure this never runs as part of CI + run-on-releases: [esr68] + shipping-phase: promote + worker-type: b-linux + worker: + docker-image: + in-tree: "update-verify" + max-run-time: 3600 + artifacts: + - name: public/build/update-verify.cfg + path: /builds/worker/checkouts/gecko/update-verify.cfg + type: file + run: + sparse-profile: mozharness + treeherder: + symbol: UVCnext + kind: test + tier: 1 + extra: + app-name: browser + branch-prefix: mozilla + archive-prefix: + by-release-level: + staging: "http://ftp.stage.mozaws.net/pub" + production: "https://archive.mozilla.org/pub" + previous-archive-prefix: + by-release-level: + staging: "https://archive.mozilla.org/pub" + production: null + aus-server: + by-release-level: + staging: "https://stage.balrog.nonprod.cloudops.mozgcp.net" + production: "https://aus5.mozilla.org" + override-certs: + by-release-level: + staging: dep + production: null + updater-platform: linux-x86_64 + product: firefox + channel: "esr-localtest-next" + include-version: esr68-next + last-watershed: "52.0esr" + +jobs: + firefox-next-linux: + shipping-product: firefox + treeherder: + platform: linux32-shippable/opt + attributes: + build_platform: linux-shippable + extra: + platform: linux-i686 + firefox-next-linux64: + shipping-product: firefox + treeherder: + platform: linux64-shippable/opt + attributes: + build_platform: linux64-shippable + extra: + platform: linux-x86_64 + firefox-next-macosx64: + shipping-product: firefox + treeherder: + platform: osx-shippable/opt + attributes: + build_platform: macosx64-shippable + extra: + platform: mac + firefox-next-win32: + shipping-product: firefox + treeherder: + platform: windows2012-32-shippable/opt + attributes: + build_platform: win32-shippable + extra: + platform: win32 + firefox-next-win64: + shipping-product: firefox + treeherder: + platform: windows2012-64-shippable/opt + attributes: + build_platform: win64-shippable + extra: + platform: win64 diff --git a/taskcluster/ci/release-update-verify-config/kind.yml b/taskcluster/ci/release-update-verify-config/kind.yml index be734f98b921..44fdfd5430c5 100644 --- a/taskcluster/ci/release-update-verify-config/kind.yml +++ b/taskcluster/ci/release-update-verify-config/kind.yml @@ -70,6 +70,7 @@ job-defaults: win64-aarch64.*: "67.0" default: null esr60: "52.0esr" + esr68: "68.0esr" default: "default" jobs: diff --git a/taskcluster/ci/release-update-verify-next/kind.yml b/taskcluster/ci/release-update-verify-next/kind.yml new file mode 100644 index 000000000000..b34cf1fe197d --- /dev/null +++ b/taskcluster/ci/release-update-verify-next/kind.yml @@ -0,0 +1,73 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--- +loader: taskgraph.loader.transform:loader + +kind-dependencies: + - post-balrog-dummy + - post-beetmover-dummy + - release-balrog-submit-toplevel + - release-update-verify-config-next + +transforms: + - taskgraph.transforms.release:run_on_releases + - taskgraph.transforms.release_deps:transforms + - taskgraph.transforms.update_verify:transforms + - taskgraph.transforms.job:transforms + - taskgraph.transforms.task:transforms + +job-defaults: + name: update-verify-next + run-on-projects: [] # to make sure this never runs as part of CI + run-on-releases: [esr68] + shipping-phase: promote + worker-type: b-linux + worker: + artifacts: + - name: 'public/build/diff-summary.log' + path: '/builds/worker/tools/release/updates/diff-summary.log' + type: file + docker-image: + in-tree: "update-verify" + max-run-time: 7200 + retry-exit-status: + - 255 + env: + CHANNEL: "esr-localtest-next" + treeherder: + symbol: UV(UVnext) + kind: test + extra: + chunks: 12 + +jobs: + firefox-next-linux64: + description: linux64 esr-next update verify + shipping-product: firefox + attributes: + build_platform: linux64-shippable + + firefox-next-linux: + description: linux esr-next update verify + shipping-product: firefox + attributes: + build_platform: linux-shippable + + firefox-next-win64: + description: win64 esr-next update verify + shipping-product: firefox + attributes: + build_platform: win64-shippable + + firefox-next-win32: + description: win32 esr-next update verify + shipping-product: firefox + attributes: + build_platform: win32-shippable + + firefox-next-macosx64: + description: macosx64 esr-next update verify + shipping-product: firefox + attributes: + build_platform: macosx64-shippable diff --git a/taskcluster/ci/repackage-l10n/kind.yml b/taskcluster/ci/repackage-l10n/kind.yml index deabf6bf3f15..7bdb3dfcce5b 100644 --- a/taskcluster/ci/repackage-l10n/kind.yml +++ b/taskcluster/ci/repackage-l10n/kind.yml @@ -63,7 +63,7 @@ job-template: - repackage/win64_signed.py package-formats: by-release-type: - esr60: + esr(60|68): by-build-platform: linux.*: [mar, mar-bz2] linux4\b.*: [mar, mar-bz2] diff --git a/taskcluster/ci/repackage/kind.yml b/taskcluster/ci/repackage/kind.yml index 6e25cefe07ab..3d257e869029 100644 --- a/taskcluster/ci/repackage/kind.yml +++ b/taskcluster/ci/repackage/kind.yml @@ -73,7 +73,7 @@ job-template: - repackage/win64_signed.py package-formats: by-release-type: - esr60: + esr(60|68): by-build-platform: linux.*: [mar, mar-bz2] linux4\b.*: [mar, mar-bz2] diff --git a/taskcluster/docker/funsize-update-generator/Pipfile.lock b/taskcluster/docker/funsize-update-generator/Pipfile.lock index 8d0ba25047d3..b4d585a42366 100644 --- a/taskcluster/docker/funsize-update-generator/Pipfile.lock +++ b/taskcluster/docker/funsize-update-generator/Pipfile.lock @@ -46,10 +46,10 @@ }, "arrow": { "hashes": [ - "sha256:3397e5448952e18e1295bf047014659effa5ae8da6a5371d37ff0ddc46fa6872", - "sha256:6f54d9f016c0b7811fac9fb8c2c7fa7421d80c54dbdd75ffb12913c55db60b8a" + "sha256:002f2315cf4c8404de737c42860441732d339bbc57fee584e2027520e055ecc1", + "sha256:82dd5e13b733787d4eb0fef42d1ee1a99136dc1d65178f70373b3678b3181bfc" ], - "version": "==0.13.1" + "version": "==0.13.2" }, "asn1crypto": { "hashes": [ @@ -74,11 +74,11 @@ }, "awscli": { "hashes": [ - "sha256:b7a6e758a7d2e7230e4e21acab9f80db2fd31248333ca8575b4538a5c43ebd2c", - "sha256:fae8839c4ddf6e7fb49543beec8d9659afd60e2fa23481ee723390f0a3a7d0f7" + "sha256:34e7ee2bd912e6613ac064099c13e2114722d508fc35e01fd0dfc3be41ddd92c", + "sha256:f73c11e6726a5ca25df3399762fae7f6882c71e097dc622d0e4743c9f8e84526" ], "index": "pypi", - "version": "==1.16.156" + "version": "==1.16.161" }, "backports.lzma": { "hashes": [ @@ -88,10 +88,10 @@ }, "botocore": { "hashes": [ - "sha256:1517c52eaa3056d0e81f9a81b580d7f28440e7e1523d10a8acc8160c56be7113", - "sha256:19d9d56fcf4f16ffea8a929bbf3c72db3458b6c1f306c04031f3166759cd62ac" + "sha256:5e4774c106bb02f8e4639818c2f8157b8ec114a76e481e17cd3fe6955206e088", + "sha256:cfc667e7888aad09ead8f7e32129ea90aa5c7f602531094954bf6305db74aac4" ], - "version": "==1.12.146" + "version": "==1.12.151" }, "certifi": { "hashes": [ @@ -370,11 +370,11 @@ }, "requests": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" ], "index": "pypi", - "version": "==2.21.0" + "version": "==2.22.0" }, "rsa": { "hashes": [ @@ -392,11 +392,11 @@ }, "scriptworker": { "hashes": [ - "sha256:44b19ef0ddfe14309ddb035e4f6e82da8d9eb3d7c3de8ed82ee74a75beefb767", - "sha256:4b7bd567c8b511f1a87c68ac541c94b730d6f307ad86bb0af279ac30ef5867e9" + "sha256:836181e36befcd74bb6b9457fd9336d8efa1350e77c285f0dc32bdb0ef6e4270", + "sha256:d858c4e0dae3305dec3683458d2e879752b0a3645e9f95278b717d53d45c2809" ], "index": "pypi", - "version": "==23.0.4" + "version": "==23.0.5" }, "sh": { "hashes": [ diff --git a/taskcluster/docs/kinds.rst b/taskcluster/docs/kinds.rst index f1c3555cdc0e..740433511be8 100644 --- a/taskcluster/docs/kinds.rst +++ b/taskcluster/docs/kinds.rst @@ -367,11 +367,14 @@ Publishes signed langpacks to archive.mozilla.org release-update-verify --------------------- Verifies the contents and package of release update MARs. - release-secondary-update-verify ------------------------------- Verifies the contents and package of release update MARs. +release-update-verify-next +-------------------------- +Verifies the contents and package of release and updare MARs from the previous ESR release. + release-update-verify-config ---------------------------- Creates configs for release-update-verify tasks @@ -380,6 +383,10 @@ release-secondary-update-verify-config -------------------------------------- Creates configs for release-secondary-update-verify tasks +release-update-verify-config-next +--------------------------------- +Creates configs for release-update-verify-next tasks + release-updates-builder ----------------------- Top level Balrog blob submission & patcher/update verify config updates. diff --git a/taskcluster/taskgraph/manifests/firefox_candidates.yml b/taskcluster/taskgraph/manifests/firefox_candidates.yml index c972b55fdc0b..602a9227ee35 100644 --- a/taskcluster/taskgraph/manifests/firefox_candidates.yml +++ b/taskcluster/taskgraph/manifests/firefox_candidates.yml @@ -332,7 +332,7 @@ mapping: checksums_path: ${path_platform}/${locale}/Firefox Setup ${version}.msi target.complete.mar: <<: *default - description: "The main installer we ship our mobile products baked within" + description: "Complete MAR to serve as updates" all_locales: true from: - mar-signing @@ -341,6 +341,18 @@ mapping: update_balrog_manifest: true destinations: - ${version}-candidates/build${build_number}/update/${path_platform} + target.bz2.complete.mar: + <<: *default + description: "Complete MAR with bz2 compression and SHA1 signing to serve as updates" + all_locales: true + from: + - mar-signing + pretty_name: firefox-${version}.bz2.complete.mar + checksums_path: update/${path_platform}/${locale}/firefox-${version}.bz2.complete.mar + update_balrog_manifest: true + balrog_format: bz2 + destinations: + - ${version}-candidates/build${build_number}/update/${path_platform} ${partial}: <<: *default description: "Partials MAR files to serve as updates" diff --git a/taskcluster/taskgraph/transforms/beetmover.py b/taskcluster/taskgraph/transforms/beetmover.py index 1d04bbe13324..0e5f9675bb8e 100644 --- a/taskcluster/taskgraph/transforms/beetmover.py +++ b/taskcluster/taskgraph/transforms/beetmover.py @@ -315,7 +315,7 @@ def make_task_worker(config, jobs): signing_task_ref = "<" + str(signing_task) + ">" build_task_ref = "<" + str(build_task) + ">" - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): upstream_artifacts = generate_beetmover_upstream_artifacts( config, job, platform, locale ) @@ -329,7 +329,7 @@ def make_task_worker(config, jobs): 'upstream-artifacts': upstream_artifacts, } - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): worker['artifact-map'] = generate_beetmover_artifact_map( config, job, platform=platform, locale=locale) diff --git a/taskcluster/taskgraph/transforms/beetmover_checksums.py b/taskcluster/taskgraph/transforms/beetmover_checksums.py index f85eb40a0149..ac6eb8f187a3 100644 --- a/taskcluster/taskgraph/transforms/beetmover_checksums.py +++ b/taskcluster/taskgraph/transforms/beetmover_checksums.py @@ -145,7 +145,7 @@ def make_beetmover_checksums_worker(config, jobs): 'release-properties': craft_release_properties(config, job), } - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): upstream_artifacts = generate_beetmover_upstream_artifacts( config, job, platform, locale ) diff --git a/taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py b/taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py index d5f4b7f7c709..8cb1a769d1fb 100644 --- a/taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py +++ b/taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py @@ -138,7 +138,7 @@ def make_beetmover_checksums_worker(config, jobs): 'release-properties': craft_release_properties(config, job), } - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): upstream_artifacts = generate_beetmover_upstream_artifacts( config, job, platform, locales ) diff --git a/taskcluster/taskgraph/transforms/beetmover_repackage.py b/taskcluster/taskgraph/transforms/beetmover_repackage.py index 5f348e0468d8..5551807db238 100644 --- a/taskcluster/taskgraph/transforms/beetmover_repackage.py +++ b/taskcluster/taskgraph/transforms/beetmover_repackage.py @@ -358,7 +358,7 @@ def make_task_worker(config, jobs): locale = job["attributes"].get("locale") platform = job["attributes"]["build_platform"] - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): upstream_artifacts = generate_beetmover_upstream_artifacts( config, job, platform, locale) else: @@ -373,7 +373,7 @@ def make_task_worker(config, jobs): 'upstream-artifacts': upstream_artifacts, } - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): worker['artifact-map'] = generate_beetmover_artifact_map( config, job, platform=platform, locale=locale) @@ -412,7 +412,7 @@ def make_partials_artifacts(config, jobs): partials_info = get_partials_info_from_params( config.params.get('release_history'), balrog_platform, locale) - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): job['worker']['artifact-map'].extend( generate_beetmover_partials_artifact_map( config, job, partials_info, platform=platform, locale=locale)) diff --git a/taskcluster/taskgraph/transforms/beetmover_source_checksums.py b/taskcluster/taskgraph/transforms/beetmover_source_checksums.py index 1210e856abd0..680860c811ef 100644 --- a/taskcluster/taskgraph/transforms/beetmover_source_checksums.py +++ b/taskcluster/taskgraph/transforms/beetmover_source_checksums.py @@ -137,7 +137,7 @@ def make_beetmover_checksums_worker(config, jobs): raise NotImplementedError( "Beetmover checksums must have a beetmover and signing dependency!") - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): upstream_artifacts = generate_beetmover_upstream_artifacts(config, job, platform, locale) else: @@ -149,7 +149,7 @@ def make_beetmover_checksums_worker(config, jobs): 'upstream-artifacts': upstream_artifacts, } - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): worker['artifact-map'] = generate_beetmover_artifact_map( config, job, platform=platform) diff --git a/taskcluster/taskgraph/transforms/bouncer_submission.py b/taskcluster/taskgraph/transforms/bouncer_submission.py index 01c96fef5f82..6e5775f460ef 100644 --- a/taskcluster/taskgraph/transforms/bouncer_submission.py +++ b/taskcluster/taskgraph/transforms/bouncer_submission.py @@ -131,7 +131,8 @@ def make_task_worker(config, jobs): **{'release-level': config.params.release_level()} ) resolve_keyed_by( - job, 'bouncer-products', item_name=job['name'], project=config.params['project'] + job, 'bouncer-products', item_name=job['name'], + **{'release-type': config.params['release_type']} ) # No need to filter out ja-JP-mac, we need to upload both; but we do diff --git a/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py b/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py index 7b50aaa48cd9..78826f1e7d00 100644 --- a/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py +++ b/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py @@ -125,7 +125,7 @@ def make_task_worker(config, jobs): platform = job["attributes"]["build_platform"] locale = job["attributes"]["chunk_locales"] - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): upstream_artifacts = generate_beetmover_upstream_artifacts( config, job, platform, locale, ) @@ -139,7 +139,7 @@ def make_task_worker(config, jobs): 'upstream-artifacts': upstream_artifacts, } - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): job['worker']['artifact-map'] = generate_beetmover_artifact_map( config, job, platform=platform, locale=locale) @@ -232,7 +232,7 @@ def _change_platform_data(config, platform_job, platform): platform_job['worker']['release-properties']['platform'] = platform # amend artifactMap entries as well - if should_use_artifact_map(backup_platform, config.params['project']): + if should_use_artifact_map(backup_platform): platform_mapping = { 'linux64': 'linux-x86_64', 'linux': 'linux-i686', diff --git a/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py b/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py index da4db99e831b..4c313288b864 100644 --- a/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py +++ b/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py @@ -156,7 +156,7 @@ def make_task_worker(config, jobs): platform = job["attributes"]["build_platform"] # Works with Firefox/Devedition. Commented for migration. - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): upstream_artifacts = generate_beetmover_upstream_artifacts( config, job, platform=None, locale=None ) @@ -168,7 +168,7 @@ def make_task_worker(config, jobs): worker['upstream-artifacts'] = upstream_artifacts # Works with Firefox/Devedition. Commented for migration. - if should_use_artifact_map(platform, config.params['project']): + if should_use_artifact_map(platform): worker['artifact-map'] = generate_beetmover_artifact_map( config, job, platform=platform) diff --git a/taskcluster/taskgraph/transforms/update_verify.py b/taskcluster/taskgraph/transforms/update_verify.py index 5080f543719a..ec91a77ae628 100644 --- a/taskcluster/taskgraph/transforms/update_verify.py +++ b/taskcluster/taskgraph/transforms/update_verify.py @@ -19,7 +19,7 @@ transforms = TransformSequence() def add_command(config, tasks): config_tasks = {} for dep in config.kind_dependencies_tasks: - if 'update-verify-config' in dep.kind: + if 'update-verify-config' in dep.kind or 'update-verify-next-config' in dep.kind: config_tasks[dep.name] = dep for task in tasks: diff --git a/taskcluster/taskgraph/transforms/update_verify_config.py b/taskcluster/taskgraph/transforms/update_verify_config.py index 7bb6fc9eb309..9a6ea2ef2c07 100644 --- a/taskcluster/taskgraph/transforms/update_verify_config.py +++ b/taskcluster/taskgraph/transforms/update_verify_config.py @@ -33,6 +33,8 @@ INCLUDE_VERSION_REGEXES = { "devedition_hack": r"'^((?!58\.0b1$)\d+\.\d+(b\d+)?)$'", # Same as nonbeta, except for the esr suffix "esr": r"'^\d+\.\d+(\.\d+)?esr$'", + # Previous esr versions, for update testing before we update users to esr68 + "esr68-next": r"'^(52|60)+\.\d+(\.\d+)?esr$'", } MAR_CHANNEL_ID_OVERRIDE_REGEXES = { diff --git a/taskcluster/taskgraph/util/scriptworker.py b/taskcluster/taskgraph/util/scriptworker.py index 4236bcc76fc1..eae5be197ac6 100644 --- a/taskcluster/taskgraph/util/scriptworker.py +++ b/taskcluster/taskgraph/util/scriptworker.py @@ -427,8 +427,10 @@ def generate_beetmover_upstream_artifacts(config, job, platform, locale=None, de resolve_keyed_by( job, 'attributes.artifact_map', 'artifact map', - project=config.params['project'], - platform=platform + **{ + 'release-type': config.params['release_type'], + 'platform': platform, + } ) map_config = deepcopy(cached_load_yaml(job['attributes']['artifact_map'])) upstream_artifacts = list() @@ -469,6 +471,11 @@ def generate_beetmover_upstream_artifacts(config, job, platform, locale=None, de filename, )) + if getattr(job['dependencies'][dep], 'release_artifacts', None): + paths = [ + path for path in paths + if path in job['dependencies'][dep].release_artifacts] + if not paths: continue @@ -556,8 +563,10 @@ def generate_beetmover_artifact_map(config, job, **kwargs): resolve_keyed_by( job, 'attributes.artifact_map', 'artifact map', - project=config.params['project'], - platform=platform + **{ + 'release-type': config.params['release_type'], + 'platform': platform, + } ) map_config = deepcopy(cached_load_yaml(job['attributes']['artifact_map'])) base_artifact_prefix = map_config.get('base_artifact_prefix', get_artifact_prefix(job)) @@ -697,8 +706,10 @@ def generate_beetmover_partials_artifact_map(config, job, partials_info, **kwarg resolve_keyed_by( job, 'attributes.artifact_map', 'artifact map', - project=config.params['project'], - platform=platform + **{ + 'release-type': config.params['release_type'], + 'platform': platform, + } ) map_config = deepcopy(cached_load_yaml(job['attributes']['artifact_map'])) base_artifact_prefix = map_config.get('base_artifact_prefix', get_artifact_prefix(job)) @@ -811,48 +822,10 @@ def generate_beetmover_partials_artifact_map(config, job, partials_info, **kwarg return artifacts -# should_use_artifact_map {{{ -def should_use_artifact_map(platform, project): +def should_use_artifact_map(platform): """Return True if this task uses the beetmover artifact map. This function exists solely for the beetmover artifact map migration. """ - if 'linux64-snap-shippable' in platform: - # Snap has never been implemented outside of declarative artifacts. We need to use - # declarative artifacts no matter the branch we're on - return True - - # FIXME: once we're ready to switch fully to declarative artifacts on other - # branches, we can expand this; for now, Fennec is rolled-out to all - # release branches, while Firefox only to mozilla-central - platforms = [ - 'android', - 'fennec' - ] - projects = ['mozilla-central', 'mozilla-beta', 'mozilla-release'] - if any([pl in platform for pl in platforms]) and any([pj == project for pj in projects]): - return True - - platforms = [ - 'linux', # needed for beetmover-langpacks-checksums - 'linux64', # which inherit amended platform from their beetmover counterpart - 'win32', - 'win64', - 'macosx64', - 'linux-shippable', - 'linux64-shippable', - 'macosx64-shippable', - 'win32-shippable', - 'win64-shippable', - 'win64-aarch64-shippable', - 'win64-asan-reporter-nightly', - 'linux64-asan-reporter-nightly', - 'firefox-source', - 'firefox-release', - ] - projects = ['mozilla-central', 'mozilla-beta', 'mozilla-release'] - if any([pl == platform for pl in platforms]) and any([pj == project for pj in projects]): - return True - - return False + return 'devedition' not in platform diff --git a/testing/marionette/harness/marionette_harness/runtests.py b/testing/marionette/harness/marionette_harness/runtests.py index dd493a3bb319..c8cf1f3cc182 100644 --- a/testing/marionette/harness/marionette_harness/runtests.py +++ b/testing/marionette/harness/marionette_harness/runtests.py @@ -91,8 +91,8 @@ def cli(runner_class=MarionetteTestRunner, parser_class=MarionetteArguments, failed = harness_instance.run() if failed > 0: sys.exit(10) - except Exception: - logger.error('Failure during harness execution', exc_info=True) + except Exception as e: + logger.error(e.message, exc_info=True) sys.exit(1) sys.exit(0) diff --git a/testing/mozharness/configs/releases/bouncer_firefox_esr.py b/testing/mozharness/configs/releases/bouncer_firefox_esr.py index 3d4493ea2b49..0041519f263c 100644 --- a/testing/mozharness/configs/releases/bouncer_firefox_esr.py +++ b/testing/mozharness/configs/releases/bouncer_firefox_esr.py @@ -44,6 +44,18 @@ config = { "win64", ], }, + "complete-mar-bz2": { + "product-name": "Firefox-%(version)s-Complete-bz2", + "check_uptake": True, + "platforms": [ + "linux", + "linux64", + "osx", + "win", + "win64", + "win64-aarch64", + ], + }, }, "partials": { "releases-dir": { diff --git a/testing/web-platform/meta/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html.ini b/testing/web-platform/meta/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html.ini deleted file mode 100644 index c7df378428ed..000000000000 --- a/testing/web-platform/meta/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[disallow-crossorigin.html] - expected: ERROR - [Promise rejection event should be muted for cross-origin non-CORS script] - expected: FAIL - diff --git a/testing/web-platform/meta/web-animations/__dir__.ini b/testing/web-platform/meta/web-animations/__dir__.ini index c6ea259f0f3d..7e0e6bed671f 100644 --- a/testing/web-platform/meta/web-animations/__dir__.ini +++ b/testing/web-platform/meta/web-animations/__dir__.ini @@ -1 +1 @@ -prefs: [dom.animations-api.compositing.enabled:true, dom.animations-api.core.enabled:true, dom.animations-api.getAnimations.enabled:true, dom.animations-api.implicit-keyframes.enabled:true, dom.animations-api.timelines.enabled:true, layout.css.step-position-jump.enabled:true] +prefs: [dom.animations-api.autoremove.enabled:true, dom.animations-api.compositing.enabled:true, dom.animations-api.core.enabled:true, dom.animations-api.getAnimations.enabled:true, dom.animations-api.implicit-keyframes.enabled:true, dom.animations-api.timelines.enabled:true, layout.css.step-position-jump.enabled:true] diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js index 0de39f695ab6..e9ba3a22a2f9 100644 --- a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js @@ -162,6 +162,24 @@ async_test(function(t) { p = Promise.all([Promise.reject(e)]); }, 'unhandledrejection: from Promise.reject, indirected through Promise.all'); +async_test(function(t) { + var p; + + var unhandled = function(ev) { + if (ev.promise === p) { + t.step(function() { + assert_equals(ev.reason.name, 'InvalidStateError'); + assert_equals(ev.promise, p); + }); + t.done(); + } + }; + addEventListener('unhandledrejection', unhandled); + ensureCleanup(t, unhandled); + + p = createImageBitmap(new Blob()); +}, 'unhandledrejection: from createImageBitmap which is UA triggered'); + // // Negative unhandledrejection/rejectionhandled tests with immediate attachment // @@ -270,6 +288,16 @@ async_test(function(t) { }, 'no unhandledrejection/rejectionhandled: all inside a queued task, a rejection handler attached synchronously to ' + 'a promise created from returning a Promise.reject-created promise in a fulfillment handler'); +async_test(function(t) { + var p; + + onUnhandledFail(t, function() { return p; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + p = createImageBitmap(new Blob()).then(unreached, function() {}); +}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' + + 'createImageBitmap'); + // // Negative unhandledrejection/rejectionhandled tests with microtask-delayed attachment // @@ -659,6 +687,43 @@ async_test(function(t) { }, 10); }, 'delayed handling: delaying handling by setTimeout(,10) will cause both events to fire'); +async_test(function(t) { + var unhandledPromises = []; + var unhandledReasons = []; + var p; + + var unhandled = function(ev) { + if (ev.promise === p) { + t.step(function() { + unhandledPromises.push(ev.promise); + unhandledReasons.push(ev.reason.name); + }); + } + }; + var handled = function(ev) { + if (ev.promise === p) { + t.step(function() { + assert_array_equals(unhandledPromises, [p]); + assert_array_equals(unhandledReasons, ['InvalidStateError']); + assert_equals(ev.promise, p); + assert_equals(ev.reason.name, 'InvalidStateError'); + }); + } + }; + addEventListener('unhandledrejection', unhandled); + addEventListener('rejectionhandled', handled); + ensureCleanup(t, unhandled, handled); + + p = createImageBitmap(new Blob()); + setTimeout(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + p.then(unreached, function(reason) { + assert_equals(reason.name, 'InvalidStateError'); + setTimeout(function() { t.done(); }, 10); + }); + }, 10); +}, 'delayed handling: delaying handling rejected promise created from createImageBitmap will cause both events to fire'); + // // Miscellaneous tests about integration with the rest of the platform // diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations.html new file mode 100644 index 000000000000..d40a01fdd2a8 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations.html @@ -0,0 +1,161 @@ + + +The effect value of a keyframe effect: Overlapping keyframes + + + + + +
+ + diff --git a/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html b/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html index 8b563848561b..01b4f112a0cf 100644 --- a/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html +++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html @@ -209,6 +209,28 @@ test(t => { }, 'Returns animations based on dynamic changes to individual' + ' animations\' current time'); +promise_test(async t => { + const div = createDiv(t); + + const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); + const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); + await animA.finished; + + assert_array_equals(div.getAnimations(), [animB]); +}, 'Does not return an animation that has been removed'); + +promise_test(async t => { + const div = createDiv(t); + + const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); + const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); + await animA.finished; + + animA.persist(); + + assert_array_equals(div.getAnimations(), [animA, animB]); +}, 'Returns an animation that has been persisted'); + promise_test(async t => { const div = createDiv(t); const watcher = EventWatcher(t, div, 'transitionrun'); diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html b/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html new file mode 100644 index 000000000000..9005db9e9f2a --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html @@ -0,0 +1,389 @@ + + +Animation.commitStyles + + + + + +
+ + diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/persist.html b/testing/web-platform/tests/web-animations/interfaces/Animation/persist.html new file mode 100644 index 000000000000..c18993cbc444 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/persist.html @@ -0,0 +1,40 @@ + + +Animation.persist + + + + + +
+ + diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html b/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html index 080458660eba..b41f748720ad 100644 --- a/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html @@ -11,8 +11,8 @@ diff --git a/testing/web-platform/tests/web-animations/timing-model/timelines/update-and-send-events-replacement.html b/testing/web-platform/tests/web-animations/timing-model/timelines/update-and-send-events-replacement.html new file mode 100644 index 000000000000..1391b97a8ae7 --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/timelines/update-and-send-events-replacement.html @@ -0,0 +1,1019 @@ + + +Update animations and send events (replacement) + + + + + +
+ diff --git a/testing/web-platform/tests/web-animations/timing-model/timelines/update-and-send-events.html b/testing/web-platform/tests/web-animations/timing-model/timelines/update-and-send-events.html index 1e4a0c255a93..255e013f27a7 100644 --- a/testing/web-platform/tests/web-animations/timing-model/timelines/update-and-send-events.html +++ b/testing/web-platform/tests/web-animations/timing-model/timelines/update-and-send-events.html @@ -223,4 +223,35 @@ promise_test(async t => { }, 'Playback events with the same timeline retain the order in which they are' + 'queued'); +promise_test(async t => { + const div = createDiv(t); + + // Create two animations with separate timelines + + const timelineA = document.timeline; + const animA = div.animate(null, 100 * MS_PER_SEC); + + const timelineB = new DocumentTimeline(); + const animB = new Animation( + new KeyframeEffect(div, null, 100 * MS_PER_SEC), + timelineB + ); + animB.play(); + + animA.currentTime = 99.9 * MS_PER_SEC; + animB.currentTime = 99.9 * MS_PER_SEC; + + // When the next tick happens both animations should be updated, and we will + // notice that they are now finished. As a result their finished promise + // callbacks should be queued. All of that should happen before we run the + // next microtask checkpoint and actually run the promise callbacks and + // hence the calls to cancel should not stop the existing callbacks from + // being run. + + animA.finished.then(() => { animB.cancel() }); + animB.finished.then(() => { animA.cancel() }); + + await Promise.all([animA.finished, animB.finished]); +}, 'All timelines are updated before running microtasks'); + diff --git a/toolkit/actors/WebNavigationChild.jsm b/toolkit/actors/WebNavigationChild.jsm index b024850732ef..a422b376b86f 100644 --- a/toolkit/actors/WebNavigationChild.jsm +++ b/toolkit/actors/WebNavigationChild.jsm @@ -36,12 +36,7 @@ class WebNavigationChild extends ActorChild { this.gotoIndex(message.data); break; case "WebNavigation:LoadURI": - let histogram = Services.telemetry.getKeyedHistogramById("FX_TAB_REMOTE_NAVIGATION_DELAY_MS"); - histogram.add("WebNavigation:LoadURI", - Services.telemetry.msSystemNow() - message.data.requestTime); - this.loadURI(message.data); - break; case "WebNavigation:SetOriginAttributes": this.setOriginAttributes(message.data.originAttributes); diff --git a/toolkit/components/prompts/src/SharedPromptUtils.jsm b/toolkit/components/prompts/src/SharedPromptUtils.jsm index 02413d9b21fc..90834e9d6d8d 100644 --- a/toolkit/components/prompts/src/SharedPromptUtils.jsm +++ b/toolkit/components/prompts/src/SharedPromptUtils.jsm @@ -64,6 +64,9 @@ var EnableDelayHelper = function({enableDialog, disableDialog, focusTarget}) { this.focusTarget.addEventListener("blur", this); this.focusTarget.addEventListener("focus", this); + // While the user key-repeats, we want to renew the timer until keyup: + this.focusTarget.addEventListener("keyup", this, true); + this.focusTarget.addEventListener("keydown", this, true); this.focusTarget.document.addEventListener("unload", this); this.startOnFocusDelay(); @@ -75,11 +78,29 @@ this.EnableDelayHelper.prototype = { }, handleEvent(event) { - if (event.target != this.focusTarget && + if (!event.type.startsWith("key") && + event.target != this.focusTarget && event.target != this.focusTarget.document) return; switch (event.type) { + case "keyup": + // As soon as any key goes up, we can stop treating keypresses + // as indicative of key-repeating that should prolong the timer. + this.focusTarget.removeEventListener("keyup", this, true); + this.focusTarget.removeEventListener("keydown", this, true); + break; + + case "keydown": + // Renew timer for repeating keydowns: + if (this._focusTimer) { + this._focusTimer.cancel(); + this._focusTimer = null; + this.startOnFocusDelay(); + event.preventDefault(); + } + break; + case "blur": this.onBlur(); break; @@ -111,6 +132,8 @@ this.EnableDelayHelper.prototype = { onUnload() { this.focusTarget.removeEventListener("blur", this); this.focusTarget.removeEventListener("focus", this); + this.focusTarget.removeEventListener("keyup", this, true); + this.focusTarget.removeEventListener("keydown", this, true); this.focusTarget.document.removeEventListener("unload", this); if (this._focusTimer) { diff --git a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm index d8991d5c83f1..598be1b4a246 100644 --- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm +++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm @@ -116,7 +116,6 @@ RemoteWebNavigation.prototype = { triggeringPrincipal: E10SUtils.serializePrincipal( aLoadURIOptions.triggeringPrincipal || Services.scriptSecurityManager.createNullPrincipal({})), csp: aLoadURIOptions.csp ? E10SUtils.serializeCSP(aLoadURIOptions.csp) : null, - requestTime: Services.telemetry.msSystemNow(), cancelContentJSEpoch, }); }, diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 0710ed58164d..22a5d46c3505 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -6282,17 +6282,6 @@ "n_buckets": 20, "description": "Firefox: Time in ms spent on switching tabs in response to a tab click" }, - "FX_TAB_REMOTE_NAVIGATION_DELAY_MS": { - "record_in_processes": ["content"], - "alert_emails": ["mconley@mozilla.com"], - "bug_numbers": [1352961, 1501295], - "expires_in_version": "69", - "kind": "exponential", - "high": 4000, - "n_buckets": 100, - "keyed": true, - "description": "Time taken (in ms) from the point of the parent sending the naviagion triggering message to the content and the content receiving it. This message can be either SessionStore:restoreTabContent or WebNavigation:LoadURI and these names are used as keys for this histogram. This is e10s only and recorded in the content process." - }, "FX_BOOKMARKS_TOOLBAR_INIT_MS": { "record_in_processes": ["main", "content"], "expires_in_version": "never", diff --git a/toolkit/components/telemetry/Scalars.yaml b/toolkit/components/telemetry/Scalars.yaml index 478ea35949f3..41063d28f3a3 100644 --- a/toolkit/components/telemetry/Scalars.yaml +++ b/toolkit/components/telemetry/Scalars.yaml @@ -449,22 +449,6 @@ identity.fxaccounts: - main security: - pkcs11_modules_loaded: - bug_numbers: - - 1369911 - - 1445961 - description: > - A keyed boolean indicating the library names of the PKCS#11 modules that - have been loaded by the browser. - expires: "69" - kind: boolean - keyed: true - notification_emails: - - seceng-telemetry@mozilla.com - - dkeeler@mozilla.com - release_channel_collection: opt-out - record_in_processes: - - main webauthn_used: bug_numbers: - 1265472 @@ -1449,6 +1433,21 @@ devtools.inspector: record_in_processes: - 'main' + node_selection_count: + bug_numbers: + - 1550794 + description: > + Number of times a different node is marked as selected in the Inspector regardless + of the cause: context menu, manual selection in markup view, etc. + expires: "never" + kind: uint + notification_emails: + - dev-developer-tools@lists.mozilla.org + - mbalfanz@mozilla.com + release_channel_collection: opt-out + record_in_processes: + - 'main' + devtools.shadowdom: shadow_root_displayed: bug_numbers: diff --git a/toolkit/mozapps/handling/content/dialog.js b/toolkit/mozapps/handling/content/dialog.js index 903c7bd9f1f6..bb6eb4020234 100644 --- a/toolkit/mozapps/handling/content/dialog.js +++ b/toolkit/mozapps/handling/content/dialog.js @@ -104,7 +104,7 @@ var dialog = { (window.opener && PrivateBrowsingUtils.isWindowPrivate(window.opener)); this._itemChoose = document.getElementById("item-choose"); - this._okButton = document.documentElement.getButton("accept"); + this._okButton = document.documentElement.getButton("extra1"); var description = { image: document.getElementById("description-image"), @@ -131,7 +131,9 @@ var dialog = { // UI is ready, lets populate our list this.populateList(); - document.addEventListener("dialogaccept", () => { this.onAccept(); }); + // Explicitly not an 'accept' button to avoid having `enter` accept the dialog. + document.addEventListener("dialogextra1", () => { this.onOK(); }); + document.addEventListener("dialogaccept", e => { e.preventDefault(); }); this._delayHelper = new EnableDelayHelper({ disableDialog: () => { @@ -289,7 +291,10 @@ var dialog = { /** * Function called when the OK button is pressed. */ - onAccept: function onAccept() { + onOK: function onOK() { + if (this._buttonDisabled) { + return; + } var checkbox = document.getElementById("remember"); if (!checkbox.hidden) { // We need to make sure that the default is properly set now @@ -308,6 +313,7 @@ var dialog = { hs.store(this._handlerInfo); this._handlerInfo.launchWithURI(this._URI, this._windowCtxt); + window.close(); }, /** @@ -335,7 +341,7 @@ var dialog = { if (this.selectedItem == this._itemChoose) this.chooseApplication(); else - document.documentElement.acceptDialog(); + this.onOK(); }, // Getters / Setters diff --git a/toolkit/mozapps/handling/content/dialog.xul b/toolkit/mozapps/handling/content/dialog.xul index 32534f094f9a..c153cfbf2de7 100644 --- a/toolkit/mozapps/handling/content/dialog.xul +++ b/toolkit/mozapps/handling/content/dialog.xul @@ -13,6 +13,7 @@ style="min-width: &window.emWidth;; min-height: &window.emHeight;;" persist="width height screenX screenY" aria-describedby="description-text" + buttons="cancel,extra1" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">