From 604a6d61cecffff46a1c754d3f483afc19d97eb5 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Tue, 29 Jun 2021 09:54:02 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1123516 - Implement maplike/setlike in WebIDL parser; r=bz (5d62bcd93) - Bug 1140324 - Remove __noSuchMethod__ handling from WebIDL parser and throw an exception instead. r=peterv (f7ea99339) - Bug 1123516 - Implement maplike/setlike in WebIDL Codegen; r=b (0ca39b335) - Bug 1183604, add some more assertions to help implementing new cycle collectable classes, r=mccr8 (1e66d29fe) - Bug 1178665 - Part 1: Make Promise::DispatchToMicroTask public. r=khuey (b962e6006) - Bug 1178665 - Part 2 - Adapt to latest Animation.finish procedure changes. r=bbirtles (33219fc0d) - Bug 1178665 - Part 3: Make finish notifications asynchronously in most cases. r=bbirtles, r=smaug (144c0944a) - Bug 1180770 part 1. Remove the unused ThrowNotEnoughArgsError. r=peterv (8bc1690f5) - Bug 1180770 part 2. Remove the unused ifaceName/memberName arguments of ThrowMethodFailedWithDetails and rename it to ThrowMethodFailed. r=peterv (ee4900547) - Bug 1135961. Implement subclassing of DOM objects. r=peterv (8e7e67b88) - Bug 1170691 - part 1 - add the generating script's directory to sys.path in file_generate.py; r=glandium (dd1520952) - Bug 1168409 - part 1 - avoid importing buildconfig in histogram_tools.py; r=gfritzsche (6a46dce23) - Bug 1168409 - part 2 - avoiding importing usecounters in histogram_tools.py; r=gfritzsche (21a468303) - Bug 1144397. Disallow using fill when dedent would do. r=peterv (544d4978d) - Bug 1158806. Don't try to include stuff for a generated hasInstance hook if we have no interface object, since in that case we don't need the include. r=peterv (d280a1608) - missing bit of Bug 1161627 - part 2 - machine-convert TemporaryRef to already_AddRefed (c51384311) - Bug 1166910 followup: Add missing 'override' keyword to HTMLImageElement method GetImageReferrerPolicy. rs=ehsan (9e3dc8e6d) - Bug 1174913 - remove unnecessary attribute parsing. r=bz (fdb769eda) - Bug 1170680 - Do not add non-animated images to the visible list in response to UNLOCKED_DRAW. r=tn (a594883e8) - Bug 1174923 - Stop delaying the document load event until images are decoded. r=tn a=kwierso (caee1b25f) - Bug 968923 - part 3b - propagating use counters from SVG images into owning/parent documents; r=seth (234a41484) - Bug 968923 - part 3a - add core DOM use counter functionality; r=smaug (98bb77358) - Bug 968923 - part 3c - miscellaneous telemetry changes for use counters; r=gfritzsche (83adec291) - Bug 968923 - part 4 - hook up use counters to WebIDL bindings; r=bz (8545e9a9b) - Bug 771367 - Update test_animations_omta.html to support testing pseudo-elements. r=dbaron (4b2e5481b) - Bug 1177563 - Test that we share agent rule processors across different documents. r=dbaron (d64146359) - Bug 1181450 - Make GENERATED_FILES more visible during the build by printing their name when they are being generated. r=gps (b0c2166e8) - Bug 1215526 - part 1 - pass dependencies file to file_generate.py; r=glandium (a14ea304a) - Bug 1215526 - part 2 - write dependencies to file_generate.py's depfile; r=glandium (dc49ad380) --- b2g/installer/package-manifest.in | 1 + browser/installer/package-manifest.in | 1 + docshell/base/nsDocShell.cpp | 12 +- dom/animation/Animation.cpp | 102 ++- dom/animation/Animation.h | 26 +- .../css-animations/file_animation-finish.html | 21 + .../file_animation-finished.html | 136 +++ dom/base/Element.cpp | 14 + dom/base/Element.h | 2 + dom/base/UseCounter.h | 7 +- dom/base/nsDOMWindowUtils.cpp | 26 + dom/base/nsDocument.cpp | 206 +++++ dom/base/nsDocument.h | 17 +- dom/base/nsIDocument.h | 57 +- dom/base/nsImageLoadingContent.cpp | 93 ++- dom/base/nsImageLoadingContent.h | 1 - dom/base/nsWrapperCache.h | 2 + dom/base/usecounters.py | 4 +- dom/bindings/BindingUtils.cpp | 241 +++++- dom/bindings/BindingUtils.h | 167 +++- dom/bindings/Codegen.py | 782 ++++++++++++++++-- dom/bindings/ErrorResult.h | 9 +- dom/bindings/ToJSValue.cpp | 2 +- dom/bindings/ToJSValue.h | 9 + dom/bindings/moz.build | 19 + dom/bindings/parser/WebIDL.py | 454 +++++++++- .../tests/test_interface_maplikesetlike.py | 528 ++++++++++++ dom/bindings/parser/tests/test_method.py | 13 + dom/bindings/test/TestInterfaceJS.manifest | 2 + dom/bindings/test/TestInterfaceJSMaplike.js | 38 + dom/bindings/test/TestInterfaceMaplike.cpp | 84 ++ dom/bindings/test/TestInterfaceMaplike.h | 52 ++ .../test/TestInterfaceMaplikeObject.cpp | 88 ++ .../test/TestInterfaceMaplikeObject.h | 52 ++ dom/bindings/test/TestInterfaceSetlike.cpp | 58 ++ dom/bindings/test/TestInterfaceSetlike.h | 46 ++ .../test/TestInterfaceSetlikeNode.cpp | 58 ++ dom/bindings/test/TestInterfaceSetlikeNode.h | 46 ++ dom/bindings/test/chrome.ini | 2 + dom/bindings/test/mochitest.ini | 2 + dom/bindings/test/moz.build | 1 + .../test/test_bug1123516_maplikesetlike.html | 270 ++++++ .../test_bug1123516_maplikesetlikechrome.xul | 68 ++ dom/events/EventListenerManager.cpp | 2 +- dom/html/HTMLImageElement.h | 6 +- dom/html/HTMLOptionsCollection.h | 1 + dom/html/HTMLPropertiesCollection.h | 1 + dom/html/nsGenericHTMLElement.cpp | 2 - dom/html/nsGenericHTMLElement.h | 11 - dom/html/nsIHTMLCollection.h | 9 + dom/interfaces/base/nsIDOMWindowUtils.idl | 14 +- dom/promise/AbortablePromise.cpp | 7 +- dom/promise/AbortablePromise.h | 3 +- dom/promise/Promise.cpp | 15 +- dom/promise/Promise.h | 19 +- .../chrome/test_sandbox_bindings.xul | 3 + .../TestInterfaceJSMaplikeSetlike.webidl | 47 ++ dom/webidl/moz.build | 4 +- image/Decoder.cpp | 7 +- image/DynamicImage.cpp | 6 + image/ImageWrapper.cpp | 6 + image/RasterImage.cpp | 14 +- image/SVGDocumentWrapper.cpp | 1 + image/VectorImage.cpp | 9 + image/imgIContainer.idl | 11 +- image/test/browser/browser_bug666317.js | 38 +- image/test/mochitest/mochitest.ini | 1 - image/test/mochitest/test_bug512435.html | 49 -- js/xpconnect/src/Sandbox.cpp | 4 +- layout/base/nsIPresShell.h | 9 + layout/base/nsPresShell.cpp | 24 + layout/generic/nsBulletFrame.cpp | 10 + layout/reftests/css-break/reftest.list | 2 +- layout/reftests/image-element/reftest.list | 2 +- layout/style/ImageLoader.cpp | 8 + layout/style/nsCSSRuleProcessor.h | 1 + layout/style/nsStyleSet.cpp | 12 + layout/style/nsStyleSet.h | 2 + layout/style/test/animation_utils.js | 18 +- layout/style/test/browser.ini | 2 + .../browser_newtab_share_rule_processors.js | 38 + .../test/newtab_share_rule_processors.html | 22 + layout/style/test/test_animations_omta.html | 74 +- netwerk/base/ReferrerPolicy.h | 2 +- .../mozbuild/mozbuild/action/file_generate.py | 23 + .../mozbuild/backend/recursivemake.py | 6 +- python/mozbuild/mozbuild/frontend/context.py | 3 + .../test/backend/test_recursivemake.py | 9 +- toolkit/components/telemetry/Telemetry.cpp | 7 + toolkit/components/telemetry/Telemetry.h | 2 + .../telemetry/gen-histogram-enum.py | 2 + .../components/telemetry/histogram_tools.py | 29 +- 92 files changed, 4040 insertions(+), 376 deletions(-) create mode 100644 dom/bindings/parser/tests/test_interface_maplikesetlike.py create mode 100644 dom/bindings/test/TestInterfaceJSMaplike.js create mode 100644 dom/bindings/test/TestInterfaceMaplike.cpp create mode 100644 dom/bindings/test/TestInterfaceMaplike.h create mode 100644 dom/bindings/test/TestInterfaceMaplikeObject.cpp create mode 100644 dom/bindings/test/TestInterfaceMaplikeObject.h create mode 100644 dom/bindings/test/TestInterfaceSetlike.cpp create mode 100644 dom/bindings/test/TestInterfaceSetlike.h create mode 100644 dom/bindings/test/TestInterfaceSetlikeNode.cpp create mode 100644 dom/bindings/test/TestInterfaceSetlikeNode.h create mode 100644 dom/bindings/test/test_bug1123516_maplikesetlike.html create mode 100644 dom/bindings/test/test_bug1123516_maplikesetlikechrome.xul create mode 100644 dom/webidl/TestInterfaceJSMaplikeSetlike.webidl delete mode 100644 image/test/mochitest/test_bug512435.html create mode 100644 layout/style/test/browser_newtab_share_rule_processors.js create mode 100644 layout/style/test/newtab_share_rule_processors.html diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index e27f274cd3..184b4e1d49 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -677,6 +677,7 @@ #if defined(ENABLE_TESTS) && defined(MOZ_DEBUG) @RESPATH@/components/TestInterfaceJS.js @RESPATH@/components/TestInterfaceJS.manifest +@RESPATH@/components/TestInterfaceJSMaplike.js #endif @RESPATH@/components/PACGenerator.js diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index b770359c00..f99d70275e 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -636,6 +636,7 @@ #if defined(ENABLE_TESTS) && defined(MOZ_DEBUG) @RESPATH@/components/TestInterfaceJS.js @RESPATH@/components/TestInterfaceJS.manifest +@RESPATH@/components/TestInterfaceJSMaplike.js #endif @RESPATH@/components/PACGenerator.js diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 3805679ae8..a909e8bb81 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -13712,15 +13712,9 @@ nsDocShell::OnLinkClickSync(nsIContent* aContent, // if per element referrer is enabled, the element referrer overrules // the document wide referrer if (IsElementAnchor(aContent)) { - MOZ_ASSERT(aContent->IsHTMLElement()); - if (Preferences::GetBool("network.http.enablePerElementReferrer", false)) { - nsAutoString referrerPolicy; - if (aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::referrer, referrerPolicy)) { - uint32_t refPolEnum = mozilla::net::ReferrerPolicyFromString(referrerPolicy); - if (refPolEnum != mozilla::net::RP_Unset) { - refererPolicy = refPolEnum; - } - } + net::ReferrerPolicy refPolEnum = aContent->AsElement()->GetReferrerPolicy(); + if (refPolEnum != net::RP_Unset) { + refererPolicy = refPolEnum; } } diff --git a/dom/animation/Animation.cpp b/dom/animation/Animation.cpp index 4e88a496c5..1b1b686bf8 100644 --- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -13,6 +13,7 @@ #include "nsIDocument.h" // For nsIDocument #include "nsIPresShell.h" // For nsIPresShell #include "nsLayoutUtils.h" // For PostRestyleEvent (remove after bug 1073336) +#include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr #include "PendingAnimationTracker.h" // For PendingAnimationTracker namespace mozilla { @@ -70,7 +71,7 @@ Animation::SetTimeline(AnimationTimeline* aTimeline) // FIXME(spec): Once we implement the seeking defined in the spec // surely this should be SeekFlag::DidSeek but the spec says otherwise. - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); // FIXME: When we expose this method to script we'll need to call PostUpdate // (but *not* when this method gets called from style). @@ -107,7 +108,7 @@ Animation::SetStartTime(const Nullable& aNewStartTime) mReady->MaybeResolve(this); } - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); PostUpdate(); } @@ -148,7 +149,7 @@ Animation::SetCurrentTime(const TimeDuration& aSeekTime) CancelPendingTasks(); } - UpdateTiming(SeekFlag::DidSeek); + UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async); PostUpdate(); } @@ -210,8 +211,8 @@ Animation::GetFinished(ErrorResult& aRv) } if (!mFinished) { aRv.Throw(NS_ERROR_FAILURE); - } else if (PlayState() == AnimationPlayState::Finished) { - mFinished->MaybeResolve(this); + } else if (mFinishedIsResolved) { + MaybeResolveFinishedPromise(); } return mFinished; } @@ -236,7 +237,7 @@ Animation::Finish(ErrorResult& aRv) TimeDuration limit = mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0); - SetCurrentTime(limit); + SilentlySetCurrentTime(limit); // If we are paused or play-pending we need to fill in the start time in // order to transition to the finished state. @@ -252,18 +253,22 @@ Animation::Finish(ErrorResult& aRv) limit.MultDouble(1.0 / mPlaybackRate)); } - // If we just resolved the start time for a pause-pending animation, we need - // to clear the task. We don't do this as a branch of the above however since - // we can have a play-pending animation with a resolved start time if we - // aborted a pause operation. - if (mPendingState == PendingState::PlayPending && - !mStartTime.IsNull()) { + // If we just resolved the start time for a pause or play-pending + // animation, we need to clear the task. We don't do this as a branch of + // the above however since we can have a play-pending animation with a + // resolved start time if we aborted a pause operation. + if (!mStartTime.IsNull() && + (mPendingState == PendingState::PlayPending || + mPendingState == PendingState::PausePending)) { + if (mPendingState == PendingState::PausePending) { + mHoldTime.SetNull(); + } CancelPendingTasks(); if (mReady) { mReady->MaybeResolve(this); } } - UpdateTiming(SeekFlag::DidSeek); + UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync); PostUpdate(); } @@ -343,7 +348,7 @@ Animation::Tick() FinishPendingAt(mTimeline->GetCurrentTime().Value()); } - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); } void @@ -452,13 +457,12 @@ Animation::DoCancel() if (mFinished) { mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } - // Clear finished promise. We'll create a new one lazily. - mFinished = nullptr; + ResetFinishedPromise(); mHoldTime.SetNull(); mStartTime.SetNull(); - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); } void @@ -590,7 +594,7 @@ Animation::ComposeStyle(nsRefPtr& aStyleRule, mEffect->ComposeStyle(aStyleRule, aSetProperties); if (updatedHoldTime) { - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); } mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished); @@ -661,7 +665,7 @@ Animation::DoPlay(ErrorResult& aRv, LimitBehavior aLimitBehavior) TriggerOnNextTick(Nullable()); } - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); } // http://w3c.github.io/web-animations/#pause-an-animation @@ -712,7 +716,7 @@ Animation::DoPause(ErrorResult& aRv) TriggerOnNextTick(Nullable()); } - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); } void @@ -740,7 +744,7 @@ Animation::ResumeAt(const TimeDuration& aReadyTime) } mPendingState = PendingState::NotPending; - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); if (mReady) { mReady->MaybeResolve(this); @@ -760,7 +764,7 @@ Animation::PauseAt(const TimeDuration& aReadyTime) mStartTime.SetNull(); mPendingState = PendingState::NotPending; - UpdateTiming(SeekFlag::NoSeek); + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); if (mReady) { mReady->MaybeResolve(this); @@ -768,7 +772,7 @@ Animation::PauseAt(const TimeDuration& aReadyTime) } void -Animation::UpdateTiming(SeekFlag aSeekFlag) +Animation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag) { // Update the sequence number each time we transition in or out of the // idle state @@ -782,7 +786,7 @@ Animation::UpdateTiming(SeekFlag aSeekFlag) // We call UpdateFinishedState before UpdateEffect because the former // can change the current time, which is used by the latter. - UpdateFinishedState(aSeekFlag); + UpdateFinishedState(aSeekFlag, aSyncNotifyFlag); UpdateEffect(); // Unconditionally Add/Remove from the timeline. This is ok because if the @@ -822,7 +826,8 @@ Animation::UpdateTiming(SeekFlag aSeekFlag) } void -Animation::UpdateFinishedState(SeekFlag aSeekFlag) +Animation::UpdateFinishedState(SeekFlag aSeekFlag, + SyncNotifyFlag aSyncNotifyFlag) { Nullable currentTime = GetCurrentTime(); TimeDuration effectEnd = TimeDuration(EffectEnd()); @@ -860,18 +865,14 @@ Animation::UpdateFinishedState(SeekFlag aSeekFlag) } bool currentFinishedState = PlayState() == AnimationPlayState::Finished; - if (currentFinishedState && !mIsPreviousStateFinished) { - if (mFinished) { - mFinished->MaybeResolve(this); - } - } else if (!currentFinishedState && mIsPreviousStateFinished) { - // Clear finished promise. We'll create a new one lazily. - mFinished = nullptr; + if (currentFinishedState && !mFinishedIsResolved) { + DoFinishNotification(aSyncNotifyFlag); + } else if (!currentFinishedState && mFinishedIsResolved) { + ResetFinishedPromise(); if (mEffect->AsTransition()) { mEffect->SetIsFinishedTransition(false); } } - mIsPreviousStateFinished = currentFinishedState; // We must recalculate the current time to take account of any mHoldTime // changes the code above made. mPreviousCurrentTime = GetCurrentTime(); @@ -1042,5 +1043,40 @@ Animation::GetCollection() const return manager->GetAnimations(targetElement, targetPseudoType, false); } +void +Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag) +{ + if (aSyncNotifyFlag == SyncNotifyFlag::Sync) { + MaybeResolveFinishedPromise(); + } else if (!mFinishNotificationTask.IsPending()) { + nsRefPtr> runnable = + NS_NewRunnableMethod(this, &Animation::MaybeResolveFinishedPromise); + Promise::DispatchToMicroTask(runnable); + mFinishNotificationTask = runnable; + } +} + +void +Animation::ResetFinishedPromise() +{ + mFinishedIsResolved = false; + mFinished = nullptr; +} + +void +Animation::MaybeResolveFinishedPromise() +{ + mFinishNotificationTask.Revoke(); + + if (PlayState() != AnimationPlayState::Finished) { + return; + } + + if (mFinished) { + mFinished->MaybeResolve(this); + } + mFinishedIsResolved = true; +} + } // namespace dom } // namespace mozilla diff --git a/dom/animation/Animation.h b/dom/animation/Animation.h index daf6ed3113..e276d0f3db 100644 --- a/dom/animation/Animation.h +++ b/dom/animation/Animation.h @@ -60,9 +60,9 @@ public: , mPendingState(PendingState::NotPending) , mSequenceNum(kUnsequenced) , mIsRunningOnCompositor(false) - , mIsPreviousStateFinished(false) , mFinishedAtLastComposeStyle(false) , mIsRelevant(false) + , mFinishedIsResolved(false) { } @@ -321,11 +321,21 @@ protected: DidSeek }; - void UpdateTiming(SeekFlag aSeekFlag); - void UpdateFinishedState(SeekFlag aSeekFlag); + enum class SyncNotifyFlag { + Sync, + Async + }; + + void UpdateTiming(SeekFlag aSeekFlag, + SyncNotifyFlag aSyncNotifyFlag); + void UpdateFinishedState(SeekFlag aSeekFlag, + SyncNotifyFlag aSyncNotifyFlag); void UpdateEffect(); void FlushStyle() const; void PostUpdate(); + void ResetFinishedPromise(); + void MaybeResolveFinishedPromise(); + void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag); /** * Remove this animation from the pending animation tracker and reset @@ -383,13 +393,17 @@ protected: uint64_t mSequenceNum; bool mIsRunningOnCompositor; - // Indicates whether we were in the finished state during our - // most recent unthrottled sample (our last ComposeStyle call). - bool mIsPreviousStateFinished; // Spec calls this "previous finished state" bool mFinishedAtLastComposeStyle; // Indicates that the animation should be exposed in an element's // getAnimations() list. bool mIsRelevant; + + nsRevocableEventPtr> mFinishNotificationTask; + // 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; }; } // namespace dom diff --git a/dom/animation/test/css-animations/file_animation-finish.html b/dom/animation/test/css-animations/file_animation-finish.html index 13e2ed2590..0db0560bd3 100644 --- a/dom/animation/test/css-animations/file_animation-finish.html +++ b/dom/animation/test/css-animations/file_animation-finish.html @@ -239,6 +239,27 @@ async_test(function(t) { })); }, 'Test resetting of computed style'); +async_test(function(t) { + var div = addDiv(t, {'class': 'animated-div'}); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + animation.ready.then(function() { + animation.finish(); + }).then(t.step_func(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation.finish()'); + t.done(); + })); + +}, 'Test finish() resolves finished promise synchronously'); + done(); diff --git a/dom/animation/test/css-animations/file_animation-finished.html b/dom/animation/test/css-animations/file_animation-finished.html index ca8987f374..c3ae82eddf 100644 --- a/dom/animation/test/css-animations/file_animation-finished.html +++ b/dom/animation/test/css-animations/file_animation-finished.html @@ -405,6 +405,142 @@ async_test(function(t) { })); }, 'Test finished promise changes when animationPlayState set to running'); +async_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + + var previousFinishedPromise = animation.finished; + + animation.currentTime = ANIM_DURATION; + + animation.finished.then(t.step_func(function() { + animation.currentTime = 0; + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change once a prior ' + + 'finished promise resolved and the animation ' + + 'falls out finished state'); + t.done(); + })); +}, 'Test finished promise changes when a prior finished promise resolved ' + + 'and the animation falls out finished state'); + +async_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + + var previousFinishedPromise = animation.finished; + + animation.currentTime = ANIM_DURATION; + animation.currentTime = ANIM_DURATION / 2; + + assert_equals(animation.finished, previousFinishedPromise, + 'No new finished promise generated when finished state ' + + 'is checked asynchronously'); + t.done(); +}, 'Test no new finished promise generated when finished state ' + + 'is checked asynchronously'); + +async_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + + var previousFinishedPromise = animation.finished; + + animation.finish(); + animation.currentTime = ANIM_DURATION / 2; + + assert_not_equals(animation.finished, previousFinishedPromise, + 'New finished promise generated when finished state ' + + 'is checked synchronously'); + t.done(); +}, 'Test new finished promise generated when finished state ' + + 'is checked synchronously'); + +async_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + animation.ready.then(function() { + animation.finish(); + animation.currentTime = ANIM_DURATION / 2; + }).then(t.step_func(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved even if ' + + 'the finished state is changed soon'); + t.done(); + })); + +}, 'Test synchronous finished promise resolved even if finished state ' + + 'is changed soon'); + +async_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + animation.ready.then(t.step_func(function() { + animation.currentTime = ANIM_DURATION; + animation.finish(); + })).then(t.step_func(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after finish() is ' + + 'called even if there are other asynchronous promises just before it'); + t.done(); + })); +}, 'Test synchronous finished promise resolved even if asynchronous ' + + 'finished promise happens just before synchronous promise'); + +async_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + + animation.finished.then(t.step_func(function() { + assert_unreached('Animation.finished should not be resolved'); + })); + + animation.ready.then(function() { + animation.currentTime = ANIM_DURATION; + animation.currentTime = ANIM_DURATION / 2; + }).then(t.step_func(function() { + t.done(); + })); +}, 'Test finished promise is not resolved when the animation ' + + 'falls out finished state immediately'); + +async_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + + animation.ready.then(function() { + animation.currentTime = ANIM_DURATION; + animation.finished.then(t.step_func(function() { + assert_unreached('Animation.finished should not be resolved'); + })); + animation.currentTime = 0; + }).then(t.step_func(function() { + t.done(); + })); + +}, 'Test finished promise is not resolved once the animation ' + + 'falls out finished state even though the current finished ' + + 'promise is generated soon after animation state became finished'); + done(); diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 3a147605ae..37271435ae 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -140,6 +140,7 @@ #include "mozilla/dom/ElementBinding.h" #include "mozilla/dom/VRDevice.h" #include "nsComputedDOMStyle.h" +#include "mozilla/Preferences.h" using namespace mozilla; using namespace mozilla::dom; @@ -3681,3 +3682,16 @@ Element::FontSizeInflation() return 1.0; } + +net::ReferrerPolicy +Element::GetReferrerPolicy() +{ + if (Preferences::GetBool("network.http.enablePerElementReferrer", false) && + IsHTMLElement()) { + const nsAttrValue* referrerValue = GetParsedAttr(nsGkAtoms::referrer); + if (referrerValue && referrerValue->Type() == nsAttrValue::eEnum) { + return net::ReferrerPolicy(referrerValue->GetEnumValue()); + } + } + return net::RP_Unset; +} \ No newline at end of file diff --git a/dom/base/Element.h b/dom/base/Element.h index 110a3d2cca..ab01698735 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -1105,6 +1105,8 @@ public: */ float FontSizeInflation(); + net::ReferrerPolicy GetReferrerPolicy(); + protected: /* * Named-bools for use with SetAttrAndNotify to make call sites easier to diff --git a/dom/base/UseCounter.h b/dom/base/UseCounter.h index d3db541ecb..590ede2e26 100644 --- a/dom/base/UseCounter.h +++ b/dom/base/UseCounter.h @@ -5,14 +5,17 @@ #ifndef UseCounter_h_ #define UseCounter_h_ +#include + namespace mozilla { -enum UseCounter { +enum UseCounter : int16_t { eUseCounter_UNKNOWN = -1, #define USE_COUNTER_DOM_METHOD(interface_, name_) \ eUseCounter_##interface_##_##name_, #define USE_COUNTER_DOM_ATTRIBUTE(interface_, name_) \ - eUseCounter_##interface_##_##name_, + eUseCounter_##interface_##_##name_##_getter, \ + eUseCounter_##interface_##_##name_##_setter, #define USE_COUNTER_CSS_PROPERTY(name_, id_) \ eUseCounter_property_##id_, #include "mozilla/dom/UseCounterList.h" diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 912d265ffa..709974a3f9 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -101,6 +101,7 @@ #include "nsIContentIterator.h" #include "nsIDOMStyleSheet.h" #include "nsIStyleSheet.h" +#include "nsIStyleSheetService.h" #include "nsContentPermissionHelper.h" #include "nsNetUtil.h" @@ -3520,6 +3521,7 @@ nsDOMWindowUtils::RequestCompositorProperty(const nsAString& property, NS_IMETHODIMP nsDOMWindowUtils::GetOMTAStyle(nsIDOMElement* aElement, const nsAString& aProperty, + const nsAString& aPseudoElement, nsAString& aResult) { MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); @@ -3531,6 +3533,15 @@ nsDOMWindowUtils::GetOMTAStyle(nsIDOMElement* aElement, nsRefPtr cssValue = nullptr; nsIFrame* frame = element->GetPrimaryFrame(); + if (frame && !aPseudoElement.IsEmpty()) { + if (aPseudoElement.EqualsLiteral("::before")) { + frame = nsLayoutUtils::GetBeforeFrame(frame); + } else if (aPseudoElement.EqualsLiteral("::after")) { + frame = nsLayoutUtils::GetAfterFrame(frame); + } else { + return NS_ERROR_INVALID_ARG; + } + } if (frame && nsLayoutUtils::AreAsyncAnimationsEnabled()) { if (aProperty.EqualsLiteral("opacity")) { Layer* layer = @@ -3864,6 +3875,21 @@ nsDOMWindowUtils::LeaveChaosMode() return NS_OK; } +NS_IMETHODIMP +nsDOMWindowUtils::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType, + bool* aRetVal) +{ + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + + nsIPresShell* presShell = GetPresShell(); + if (!presShell) { + return NS_ERROR_FAILURE; + } + + return presShell->HasRuleProcessorUsedByMultipleStyleSets(aSheetType, + aRetVal); +} + NS_INTERFACE_MAP_BEGIN(nsTranslationNodeList) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsITranslationNodeList) diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 44c8699e61..18861a486c 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -1677,6 +1677,8 @@ nsDocument::~nsDocument() } } + ReportUseCounters(); + mInDestructor = true; mInUnlinkOrDeletion = true; @@ -4651,6 +4653,8 @@ nsIDocument::SetContainer(nsDocShell* aContainer) if (sameTypeRoot == aContainer) { static_cast(this)->SetIsTopLevelContentDocument(true); } + + static_cast(this)->SetIsContentDocument(true); } } @@ -4858,6 +4862,18 @@ nsDocument::SetIsTopLevelContentDocument(bool aIsTopLevelContentDocument) mIsTopLevelContentDocument = aIsTopLevelContentDocument; } +bool +nsDocument::IsContentDocument() const +{ + return mIsContentDocument; +} + +void +nsDocument::SetIsContentDocument(bool aIsContentDocument) +{ + mIsContentDocument = aIsContentDocument; +} + nsPIDOMWindow * nsDocument::GetWindowInternal() const { @@ -12973,6 +12989,196 @@ nsIDocument::InlineScriptAllowedByCSP() return allowsInlineScript; } +nsIDocument* +nsIDocument::GetTopLevelContentDocument() +{ + nsDocument* parent; + + if (!mLoadedAsData) { + parent = static_cast(this); + } else { + nsCOMPtr window = do_QueryInterface(GetScopeObject()); + if (!window) { + return nullptr; + } + + parent = static_cast(window->GetExtantDoc()); + if (!parent) { + return nullptr; + } + } + + do { + if (parent->IsTopLevelContentDocument()) { + break; + } + + // If we ever have a non-content parent before we hit a toplevel content + // parent, then we're never going to find one. Just bail. + if (!parent->IsContentDocument()) { + return nullptr; + } + + nsIDocument* candidate = parent->GetParentDocument(); + parent = static_cast(candidate); + } while (parent); + + return parent; +} + +void +nsIDocument::PropagateUseCounters(nsIDocument* aParentDocument) +{ + MOZ_ASSERT(this != aParentDocument); + + // What really matters here is that our use counters get propagated as + // high up in the content document hierarchy as possible. So, + // starting with aParentDocument, we need to find the toplevel content + // document, and propagate our use counters into its + // mChildDocumentUseCounters. + nsIDocument* contentParent = aParentDocument->GetTopLevelContentDocument(); + + if (!contentParent) { + return; + } + + contentParent->mChildDocumentUseCounters |= mUseCounters; + contentParent->mChildDocumentUseCounters |= mChildDocumentUseCounters; +} + +void +nsIDocument::SetPageUseCounter(UseCounter aUseCounter) +{ + // We want to set the use counter on the "page" that owns us; the definition + // of "page" depends on what kind of document we are. See the comments below + // for details. In any event, checking all the conditions below is + // reasonably expensive, so we cache whether we've notified our owning page. + if (mNotifiedPageForUseCounter[aUseCounter]) { + return; + } + mNotifiedPageForUseCounter[aUseCounter] = true; + + if (mDisplayDocument) { + // If we are a resource document, we won't have a docshell and so we won't + // record any page use counters on this document. Instead, we should + // forward it up to the document that loaded us. + MOZ_ASSERT(!mDocumentContainer); + mDisplayDocument->SetChildDocumentUseCounter(aUseCounter); + return; + } + + if (IsBeingUsedAsImage()) { + // If this is an SVG image document, we also won't have a docshell. + MOZ_ASSERT(!mDocumentContainer); + return; + } + + // We only care about use counters in content. If we're already a toplevel + // content document, then we should have already set the use counter on + // ourselves, and we are done. + nsIDocument* contentParent = GetTopLevelContentDocument(); + if (!contentParent) { + return; + } + + if (this == contentParent) { + MOZ_ASSERT(GetUseCounter(aUseCounter)); + return; + } + + contentParent->SetChildDocumentUseCounter(aUseCounter); +} + +static bool +MightBeAboutOrChromeScheme(nsIURI* aURI) +{ + MOZ_ASSERT(aURI); + bool isAbout = true; + bool isChrome = true; + aURI->SchemeIs("about", &isAbout); + aURI->SchemeIs("chrome", &isChrome); + return isAbout || isChrome; +} + +void +nsDocument::ReportUseCounters() +{ + static const bool sDebugUseCounters = false; + if (mReportedUseCounters) { + return; + } + + mReportedUseCounters = true; + + if (Telemetry::HistogramUseCounterCount > 0 && + (IsContentDocument() || IsResourceDoc())) { + nsCOMPtr uri; + NodePrincipal()->GetURI(getter_AddRefs(uri)); + if (!uri || MightBeAboutOrChromeScheme(uri)) { + return; + } + + if (sDebugUseCounters) { + nsCString spec; + uri->GetSpec(spec); + + // URIs can be rather long for data documents, so truncate them to + // some reasonable length. + spec.Truncate(std::min(128U, spec.Length())); + printf("-- Use counters for %s --\n", spec.get()); + } + + // We keep separate counts for individual documents and top-level + // pages to more accurately track how many web pages might break if + // certain features were removed. Consider the case of a single + // HTML document with several SVG images and/or iframes with + // sub-documents of their own. If we maintained a single set of use + // counters and all the sub-documents use a particular feature, then + // telemetry would indicate that we would be breaking N documents if + // that feature were removed. Whereas with a document/top-level + // page split, we can see that N documents would be affected, but + // only a single web page would be affected. + for (int32_t c = 0; + c < eUseCounter_Count; ++c) { + UseCounter uc = static_cast(c); + + Telemetry::ID id = + static_cast(Telemetry::HistogramFirstUseCounter + uc * 2); + bool value = GetUseCounter(uc); + + if (sDebugUseCounters && value) { + const char* name = Telemetry::GetHistogramName(id); + if (name) { + printf(" %s", name); + } else { + printf(" #%d", id); + } + printf(": %d\n", value); + } + + Telemetry::Accumulate(id, value); + + if (IsTopLevelContentDocument()) { + id = static_cast(Telemetry::HistogramFirstUseCounter + + uc * 2 + 1); + value = GetUseCounter(uc) || GetChildDocumentUseCounter(uc); + + if (sDebugUseCounters && value) { + const char* name = Telemetry::GetHistogramName(id); + if (name) { + printf(" %s", name); + } else { + printf(" #%d", id); + } + printf(": %d\n", value); + } + + Telemetry::Accumulate(id, value); + } + } + } +} + XPathEvaluator* nsIDocument::XPathEvaluator() { diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index bb6f88c8d6..0ec58e40c8 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -945,6 +945,8 @@ public: */ void OnAppThemeChanged(); + void ReportUseCounters(); + private: void AddOnDemandBuiltInUASheet(mozilla::CSSStyleSheet* aSheet); nsRadioGroupStruct* GetRadioGroupInternal(const nsAString& aName) const; @@ -1471,12 +1473,15 @@ public: static void XPCOMShutdown(); - bool mIsTopLevelContentDocument:1; + bool mIsTopLevelContentDocument: 1; + bool mIsContentDocument: 1; bool IsTopLevelContentDocument(); - void SetIsTopLevelContentDocument(bool aIsTopLevelContentDocument); + bool IsContentDocument() const; + void SetIsContentDocument(bool aIsContentDocument); + js::ExpandoAndGeneration mExpandoAndGeneration; bool ContainsMSEContent(); @@ -1682,6 +1687,14 @@ public: // The value is saturated to kPointerLockRequestLimit+1 = 3. uint8_t mCancelledPointerLockRequests:2; + // Whether we have reported use counters for this document with Telemetry yet. + // Normally this is only done at document destruction time, but for image + // documents (SVG documents) that are not guaranteed to be destroyed, we + // report use counters when the image cache no longer has any imgRequestProxys + // pointing to them. We track whether we ever reported use counters so + // that we only report them once for the document. + bool mReportedUseCounters:1; + uint8_t mXMLDeclarationBits; nsInterfaceHashtable, nsPIBoxObject> *mBoxObjectTable; diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index 0da4813172..ecc1673fea 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -27,6 +27,7 @@ #include "mozilla/net/ReferrerPolicy.h" // for member #include "nsWeakReference.h" #include "mozilla/dom/DocumentBinding.h" +#include "mozilla/UseCounter.h" #include "mozilla/WeakPtr.h" #include "Units.h" #include "nsExpirationTracker.h" @@ -1753,7 +1754,7 @@ public: bool IsResourceDoc() const { return IsBeingUsedAsImage() || // Are we a helper-doc for an SVG image? - !!mDisplayDocument; // Are we an external resource doc? + mHasDisplayDocument; // Are we an external resource doc? } /** @@ -1782,6 +1783,7 @@ public: NS_PRECONDITION(!aDisplayDocument->GetDisplayDocument(), "Display documents should not nest"); mDisplayDocument = aDisplayDocument; + mHasDisplayDocument = !!aDisplayDocument; } /** @@ -2357,6 +2359,8 @@ public: eAttributeChanged }; + nsIDocument* GetTopLevelContentDocument(); + /** * Registers an unresolved custom element that is a candidate for * upgrade when the definition is registered via registerElement. @@ -2624,7 +2628,24 @@ public: mozilla::dom::FontFaceSet* Fonts(); bool DidFireDOMContentLoaded() const { return mDidFireDOMContentLoaded; } - + + void SetDocumentUseCounter(mozilla::UseCounter aUseCounter) + { + if (!mUseCounters[aUseCounter]) { + mUseCounters[aUseCounter] = true; + } + } + + void SetPageUseCounter(mozilla::UseCounter aUseCounter); + + void SetDocumentAndPageUseCounter(mozilla::UseCounter aUseCounter) + { + SetDocumentUseCounter(aUseCounter); + SetPageUseCounter(aUseCounter); + } + + void PropagateUseCounters(nsIDocument* aParentDocument); + void SetUserHasInteracted(bool aUserHasInteracted) { mUserHasInteracted = aUserHasInteracted; @@ -2639,6 +2660,24 @@ public: bool InlineScriptAllowedByCSP(); +protected: + bool GetUseCounter(mozilla::UseCounter aUseCounter) + { + return mUseCounters[aUseCounter]; + } + + void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter) + { + if (!mChildDocumentUseCounters[aUseCounter]) { + mChildDocumentUseCounters[aUseCounter] = true; + } + } + + bool GetChildDocumentUseCounter(mozilla::UseCounter aUseCounter) + { + return mChildDocumentUseCounters[aUseCounter]; + } + private: mutable std::bitset mDeprecationWarnedAbout; mutable std::bitset mDocWarningWarnedAbout; @@ -2878,6 +2917,12 @@ protected: bool mIsLinkUpdateRegistrationsForbidden; #endif + // Whether this document has a display document and thus is considered to + // be a resource document. Normally this is the same as !!mDisplayDocument, + // but mDisplayDocument is cleared during Unlink. mHasDisplayDocument is + // valid in the document's destructor. + bool mHasDisplayDocument : 1; + // Is the current mFontFaceSet valid? bool mFontFaceSetDirty; @@ -3017,6 +3062,14 @@ protected: // Our live MediaQueryLists PRCList mDOMMediaQueryLists; + // Flags for use counters used directly by this document. + std::bitset mUseCounters; + // Flags for use counters used by any child documents of this document. + std::bitset mChildDocumentUseCounters; + // Flags for whether we've notified our top-level "page" of a use counter + // for this child document. + std::bitset mNotifiedPageForUseCounter; + // Whether the user has interacted with the document or not: bool mUserHasInteracted; }; diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp index 854f575f10..9f7cf5ec62 100644 --- a/dom/base/nsImageLoadingContent.cpp +++ b/dom/base/nsImageLoadingContent.cpp @@ -90,7 +90,6 @@ nsImageLoadingContent::nsImageLoadingContent() mBroken(true), mUserDisabled(false), mSuppressed(false), - mFireEventsOnDecode(false), mNewRequestsWillNeedAnimationReset(false), mStateChangerDepth(0), mCurrentRequestRegistered(false), @@ -200,16 +199,10 @@ nsImageLoadingContent::Notify(imgIRequest* aRequest, } if (aType == imgINotificationObserver::DECODE_COMPLETE) { - if (mFireEventsOnDecode) { - mFireEventsOnDecode = false; - - uint32_t reqStatus; - aRequest->GetImageStatus(&reqStatus); - if (reqStatus & imgIRequest::STATUS_ERROR) { - FireEvent(NS_LITERAL_STRING("error")); - } else { - FireEvent(NS_LITERAL_STRING("load")); - } + nsCOMPtr container; + aRequest->GetImage(getter_AddRefs(container)); + if (container) { + container->PropagateUseCounters(GetOurOwnerDoc()); } UpdateImageState(true); @@ -291,23 +284,11 @@ nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) } } - // We want to give the decoder a chance to find errors. If we haven't found - // an error yet and we've started decoding, either from the above - // StartDecoding or from some other place, we must only fire these events - // after we finish decoding. - uint32_t reqStatus; - aRequest->GetImageStatus(&reqStatus); - if (NS_SUCCEEDED(aStatus) && !(reqStatus & imgIRequest::STATUS_ERROR) && - (reqStatus & imgIRequest::STATUS_DECODE_STARTED) && - !(reqStatus & imgIRequest::STATUS_DECODE_COMPLETE)) { - mFireEventsOnDecode = true; + // Fire the appropriate DOM event. + if (NS_SUCCEEDED(aStatus)) { + FireEvent(NS_LITERAL_STRING("load")); } else { - // Fire the appropriate DOM event. - if (NS_SUCCEEDED(aStatus)) { - FireEvent(NS_LITERAL_STRING("load")); - } else { - FireEvent(NS_LITERAL_STRING("error")); - } + FireEvent(NS_LITERAL_STRING("error")); } nsCOMPtr thisNode = do_QueryInterface(static_cast(this)); @@ -316,6 +297,25 @@ nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) return NS_OK; } +static bool +ImageIsAnimated(imgIRequest* aRequest) +{ + if (!aRequest) { + return false; + } + + nsCOMPtr image; + if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) { + bool isAnimated = false; + nsresult rv = image->GetAnimated(&isAnimated); + if (NS_SUCCEEDED(rv) && isAnimated) { + return true; + } + } + + return false; +} + void nsImageLoadingContent::OnUnlockedDraw() { @@ -324,6 +324,15 @@ nsImageLoadingContent::OnUnlockedDraw() return; } + // It's OK for non-animated images to wait until the next image visibility + // update to become locked. (And that's preferable, since in the case of + // scrolling it keeps memory usage minimal.) For animated images, though, we + // want to mark them visible right away so we can call + // IncrementAnimationConsumers() on them and they'll start animating. + if (!ImageIsAnimated(mCurrentRequest) && !ImageIsAnimated(mPendingRequest)) { + return; + } + nsPresContext* presContext = GetFramePresContext(); if (!presContext) return; @@ -942,29 +951,25 @@ nsImageLoadingContent::LoadImage(nsIURI* aNewURI, } // get document wide referrer policy - mozilla::net::ReferrerPolicy referrerPolicy = aDocument->GetReferrerPolicy(); - bool referrerAttributeEnabled = Preferences::GetBool("network.http.enablePerElementReferrer", false); // if referrer attributes are enabled in preferences, load img referrer attribute - nsresult rv; - if (referrerAttributeEnabled) { - mozilla::net::ReferrerPolicy imgReferrerPolicy = GetImageReferrerPolicy(); - // if the image does not provide a referrer attribute, ignore this - if (imgReferrerPolicy != mozilla::net::RP_Unset) { - referrerPolicy = imgReferrerPolicy; - } + // if the image does not provide a referrer attribute, ignore this + net::ReferrerPolicy referrerPolicy = aDocument->GetReferrerPolicy(); + net::ReferrerPolicy imgReferrerPolicy = GetImageReferrerPolicy(); + if (imgReferrerPolicy != net::RP_Unset) { + referrerPolicy = imgReferrerPolicy; } // Not blocked. Do the load. nsRefPtr& req = PrepareNextRequest(aImageLoadType); nsIContent* content = AsContent(); - rv = nsContentUtils::LoadImage(aNewURI, aDocument, - aDocument->NodePrincipal(), - aDocument->GetDocumentURI(), - referrerPolicy, - this, loadFlags, - content->LocalName(), - getter_AddRefs(req), - policyType); + nsresult rv = nsContentUtils::LoadImage(aNewURI, aDocument, + aDocument->NodePrincipal(), + aDocument->GetDocumentURI(), + referrerPolicy, + this, loadFlags, + content->LocalName(), + getter_AddRefs(req), + policyType); // Tell the document to forget about the image preload, if any, for // this URI, now that we might have another imgRequestProxy for it. diff --git a/dom/base/nsImageLoadingContent.h b/dom/base/nsImageLoadingContent.h index e3c09f7947..24411b9bba 100644 --- a/dom/base/nsImageLoadingContent.h +++ b/dom/base/nsImageLoadingContent.h @@ -424,7 +424,6 @@ private: bool mBroken : 1; bool mUserDisabled : 1; bool mSuppressed : 1; - bool mFireEventsOnDecode : 1; protected: /** diff --git a/dom/base/nsWrapperCache.h b/dom/base/nsWrapperCache.h index 442dbc8029..612237ee6d 100644 --- a/dom/base/nsWrapperCache.h +++ b/dom/base/nsWrapperCache.h @@ -315,8 +315,10 @@ private: nsScriptObjectTracer* aTracer); #ifdef DEBUG +public: void CheckCCWrapperTraversal(void* aScriptObjectHolder, nsScriptObjectTracer* aTracer); +private: #endif // DEBUG /** diff --git a/dom/base/usecounters.py b/dom/base/usecounters.py index 653e02d9d4..bbdbf2761a 100644 --- a/dom/base/usecounters.py +++ b/dom/base/usecounters.py @@ -70,7 +70,9 @@ def generate_histograms(filename): append_counters(method.replace('.', '_').upper(), 'called %s' % method) elif counter['type'] == 'attribute': attr = '%s.%s' % (counter['interface_name'], counter['attribute_name']) - append_counters(attr.replace('.', '_').upper(), 'got or set %s' % attr) + counter_name = attr.replace('.', '_').upper() + append_counters('%s_getter' % counter_name, 'got %s' % attr) + append_counters('%s_setter' % counter_name, 'set %s' % attr) elif counter['type'] == 'property': prop = counter['property_name'] append_counters('PROPERTY_%s' % prop.replace('-', '_').upper(), "used the '%s' property" % prop) diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 252a318eaf..919166eb72 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -14,6 +14,7 @@ #include "mozilla/Assertions.h" #include "mozilla/Preferences.h" #include "mozilla/unused.h" +#include "mozilla/UseCounter.h" #include "AccessCheck.h" #include "jsfriendapi.h" @@ -30,6 +31,7 @@ #include "XrayWrapper.h" #include "nsPrintfCString.h" #include "prprf.h" +#include "nsGlobalWindow.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/DOMError.h" @@ -46,6 +48,7 @@ #include "WorkerPrivate.h" #include "nsDOMClassInfo.h" #include "ipc/ErrorIPCUtils.h" +#include "mozilla/UseCounter.h" namespace mozilla { namespace dom { @@ -289,17 +292,6 @@ ErrorResult::StealJSException(JSContext* cx, mResult = NS_OK; } -void -ErrorResult::ReportNotEnoughArgsError(JSContext* cx, - const char* ifaceName, - const char* memberName) -{ - MOZ_ASSERT(ErrorCode() == NS_ERROR_XPC_NOT_ENOUGH_ARGS); - - nsPrintfCString errorMessage("%s.%s", ifaceName, memberName); - ThrowErrorMessage(cx, dom::MSG_MISSING_ARGUMENTS, errorMessage.get()); -} - void ErrorResult::ReportGenericError(JSContext* cx) { @@ -978,6 +970,18 @@ ThrowConstructorWithoutNew(JSContext* cx, const char* name) return ThrowErrorMessage(cx, MSG_CONSTRUCTOR_WITHOUT_NEW, name); } +inline const NativePropertyHooks* +GetNativePropertyHooksFromConstructorFunction(JS::Handle obj) +{ + MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor)); + const JS::Value& v = + js::GetFunctionNativeReserved(obj, + CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT); + const JSNativeHolder* nativeHolder = + static_cast(v.toPrivate()); + return nativeHolder->mPropertyHooks; +} + inline const NativePropertyHooks* GetNativePropertyHooks(JSContext *cx, JS::Handle obj, DOMObjectType& type) @@ -992,14 +996,8 @@ GetNativePropertyHooks(JSContext *cx, JS::Handle obj, } if (JS_ObjectIsFunction(cx, obj)) { - MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor)); type = eInterface; - const JS::Value& v = - js::GetFunctionNativeReserved(obj, - CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT); - const JSNativeHolder* nativeHolder = - static_cast(v.toPrivate()); - return nativeHolder->mPropertyHooks; + return GetNativePropertyHooksFromConstructorFunction(obj); } MOZ_ASSERT(IsDOMIfaceAndProtoClass(js::GetObjectClass(obj))); @@ -2635,7 +2633,7 @@ ConvertExceptionToPromise(JSContext* cx, if (rv.Failed()) { // We just give up. Make sure to not leak memory on the // ErrorResult, but then just put the original exception back. - ThrowMethodFailedWithDetails(cx, rv, "", ""); + ThrowMethodFailed(cx, rv); JS_SetPendingException(cx, exn); return false; } @@ -2794,5 +2792,210 @@ SystemGlobalEnumerate(JSContext* cx, JS::Handle obj) ResolveSystemBinding(cx, obj, JSID_VOIDHANDLE, &ignored); } +template +bool +GetMaplikeSetlikeBackingObject(JSContext* aCx, JS::Handle aObj, + size_t aSlotIndex, + JS::MutableHandle aBackingObj, + bool* aBackingObjCreated) +{ + JS::Rooted reflector(aCx); + reflector = IsDOMObject(aObj) ? aObj : js::UncheckedUnwrap(aObj, + /* stopAtOuter = */ false); + + // Retrieve the backing object from the reserved slot on the maplike/setlike + // object. If it doesn't exist yet, create it. + JS::Rooted slotValue(aCx); + slotValue = js::GetReservedSlot(reflector, aSlotIndex); + if (slotValue.isUndefined()) { + // Since backing object access can happen in non-originating compartments, + // make sure to create the backing object in reflector compartment. + { + JSAutoCompartment ac(aCx, reflector); + JS::Rooted newBackingObj(aCx); + newBackingObj.set(Method(aCx)); + if (NS_WARN_IF(!newBackingObj)) { + return false; + } + js::SetReservedSlot(reflector, aSlotIndex, JS::ObjectValue(*newBackingObj)); + } + slotValue = js::GetReservedSlot(reflector, aSlotIndex); + *aBackingObjCreated = true; + } else { + *aBackingObjCreated = false; + } + if (!MaybeWrapNonDOMObjectValue(aCx, &slotValue)) { + return false; + } + aBackingObj.set(&slotValue.toObject()); + return true; +} + +bool +GetMaplikeBackingObject(JSContext* aCx, JS::Handle aObj, + size_t aSlotIndex, + JS::MutableHandle aBackingObj, + bool* aBackingObjCreated) +{ + return GetMaplikeSetlikeBackingObject(aCx, aObj, aSlotIndex, + aBackingObj, + aBackingObjCreated); +} + +bool +GetSetlikeBackingObject(JSContext* aCx, JS::Handle aObj, + size_t aSlotIndex, + JS::MutableHandle aBackingObj, + bool* aBackingObjCreated) +{ + return GetMaplikeSetlikeBackingObject(aCx, aObj, aSlotIndex, + aBackingObj, + aBackingObjCreated); +} + +bool +ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp) +{ + JS::CallArgs args = CallArgsFromVp(aArgc, aVp); + // Unpack callback and object from slots + JS::Rooted + callbackFn(aCx, js::GetFunctionNativeReserved(&args.callee(), + FOREACH_CALLBACK_SLOT)); + JS::Rooted + maplikeOrSetlikeObj(aCx, + js::GetFunctionNativeReserved(&args.callee(), + FOREACH_MAPLIKEORSETLIKEOBJ_SLOT)); + MOZ_ASSERT(aArgc == 3); + JS::AutoValueVector newArgs(aCx); + // Arguments are passed in as value, key, object. Keep value and key, replace + // object with the maplike/setlike object. + newArgs.append(args.get(0)); + newArgs.append(args.get(1)); + newArgs.append(maplikeOrSetlikeObj); + JS::Rooted rval(aCx, JS::UndefinedValue()); + // Now actually call the user specified callback + return JS::Call(aCx, args.thisv(), callbackFn, newArgs, &rval); +} + +static inline prototypes::ID +GetProtoIdForNewtarget(JS::Handle aNewTarget) +{ + const js::Class* newTargetClass = js::GetObjectClass(aNewTarget); + if (IsDOMIfaceAndProtoClass(newTargetClass)) { + const DOMIfaceAndProtoJSClass* newTargetIfaceClass = + DOMIfaceAndProtoJSClass::FromJSClass(newTargetClass); + if (newTargetIfaceClass->mType == eInterface) { + return newTargetIfaceClass->mPrototypeID; + } + } else if (JS_IsNativeFunction(aNewTarget, Constructor)) { + return GetNativePropertyHooksFromConstructorFunction(aNewTarget)->mPrototypeID; + } + + return prototypes::id::_ID_Count; +} + +bool +GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs, + JS::MutableHandle aDesiredProto) +{ + if (!aCallArgs.isConstructing()) { + aDesiredProto.set(nullptr); + return true; + } + + // The desired prototype depends on the actual constructor that was invoked, + // which is passed to us as the newTarget in the callargs. We want to do + // something akin to the ES6 specification's GetProtototypeFromConstructor (so + // get .prototype on the newTarget, with a fallback to some sort of default). + + // First, a fast path for the case when the the constructor is in fact one of + // our DOM constructors. This is safe because on those the "constructor" + // property is non-configurable and non-writable, so we don't have to do the + // slow JS_GetProperty call. + JS::Rooted newTarget(aCx, &aCallArgs.newTarget().toObject()); + JS::Rooted originalNewTarget(aCx, newTarget); + // See whether we have a known DOM constructor here, such that we can take a + // fast path. + prototypes::ID protoID = GetProtoIdForNewtarget(newTarget); + if (protoID == prototypes::id::_ID_Count) { + // We might still have a cross-compartment wrapper for a known DOM + // constructor. + newTarget = js::CheckedUnwrap(newTarget); + if (newTarget && newTarget != originalNewTarget) { + protoID = GetProtoIdForNewtarget(newTarget); + } + } + + if (protoID != prototypes::id::_ID_Count) { + ProtoAndIfaceCache& protoAndIfaceCache = + *GetProtoAndIfaceCache(js::GetGlobalForObjectCrossCompartment(newTarget)); + aDesiredProto.set(protoAndIfaceCache.EntrySlotMustExist(protoID)); + if (newTarget != originalNewTarget) { + return JS_WrapObject(aCx, aDesiredProto); + } + return true; + } + + // Slow path. This basically duplicates the ES6 spec's + // GetPrototypeFromConstructor except that instead of taking a string naming + // the fallback prototype we just fall back to using null and assume that our + // caller will then pick the right default. The actual defaulting behavior + // here still needs to be defined in the Web IDL specification. + // + // Note that it's very important to do this property get on originalNewTarget, + // not our unwrapped newTarget, since we want to get Xray behavior here as + // needed. + // XXXbz for speed purposes, using a preinterned id here sure would be nice. + JS::Rooted protoVal(aCx); + if (!JS_GetProperty(aCx, originalNewTarget, "prototype", &protoVal)) { + return false; + } + + if (!protoVal.isObject()) { + aDesiredProto.set(nullptr); + return true; + } + + aDesiredProto.set(&protoVal.toObject()); + return true; +} + +#ifdef DEBUG +namespace binding_detail { +void +AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector, + JS::Handle aGivenProto) +{ + if (!aGivenProto) { + // Nothing to assert here + return; + } + + JS::Rooted reflector(aCx, aReflector); + JSAutoCompartment ac(aCx, reflector); + JS::Rooted reflectorProto(aCx); + bool ok = JS_GetPrototype(aCx, reflector, &reflectorProto); + MOZ_ASSERT(ok); + // aGivenProto may not be in the right compartment here, so we + // have to wrap it to compare. + JS::Rooted givenProto(aCx, aGivenProto); + ok = JS_WrapObject(aCx, &givenProto); + MOZ_ASSERT(ok); + MOZ_ASSERT(givenProto == reflectorProto, + "How are we supposed to change the proto now?"); +} +} // namespace binding_detail +#endif // DEBUG + +void +SetDocumentAndPageUseCounter(JSContext* aCx, JSObject* aObject, + UseCounter aUseCounter) +{ + nsGlobalWindow* win = xpc::WindowGlobalOrNull(js::UncheckedUnwrap(aObject)); + if (win && win->GetDocument()) { + win->GetDocument()->SetDocumentAndPageUseCounter(aUseCounter); + } +} + } // namespace dom } // namespace mozilla diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 318914f670..d475677d00 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -43,6 +43,9 @@ class nsIJSID; class nsPIDOMWindow; namespace mozilla { + +enum UseCounter : int16_t; + namespace dom { template class MozMap; @@ -99,9 +102,7 @@ ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs, prototypes::ID aProtoId); inline bool -ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv, - const char* ifaceName, - const char* memberName) +ThrowMethodFailed(JSContext* cx, ErrorResult& rv) { if (rv.IsUncatchableException()) { // Nuke any existing exception on aCx, to make sure we're uncatchable. @@ -118,10 +119,6 @@ ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv, rv.ReportJSException(cx); return false; } - if (rv.IsNotEnoughArgsError()) { - rv.ReportNotEnoughArgsError(cx, ifaceName, memberName); - return false; - } rv.ReportGenericError(cx); return false; } @@ -908,30 +905,95 @@ struct TypeNeedsOuterization IsBaseOf::value || IsSame::value; }; +#ifdef DEBUG +template::value> +struct CheckWrapperCacheTracing +{ + static inline void Check(T* aObject) + { + } +}; + +template +struct CheckWrapperCacheTracing +{ + static void Check(T* aObject) + { + // Rooting analysis thinks QueryInterface may GC, but we're dealing with + // a subset of QueryInterface, C++ only types here. + JS::AutoSuppressGCAnalysis nogc; + + nsWrapperCache* wrapperCacheFromQI = nullptr; + aObject->QueryInterface(NS_GET_IID(nsWrapperCache), + reinterpret_cast(&wrapperCacheFromQI)); + + MOZ_ASSERT(wrapperCacheFromQI, + "Missing nsWrapperCache from QueryInterface implementation?"); + + if (!wrapperCacheFromQI->GetWrapperPreserveColor()) { + // Can't assert that we trace the wrapper, since we don't have any + // wrapper to trace. + return; + } + + nsISupports* ccISupports = nullptr; + aObject->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast(&ccISupports)); + MOZ_ASSERT(ccISupports, + "nsWrapperCache object which isn't cycle collectable?"); + + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(ccISupports, &participant); + MOZ_ASSERT(participant, "Can't QI to CycleCollectionParticipant?"); + + bool wasPreservingWrapper = wrapperCacheFromQI->PreservingWrapper(); + wrapperCacheFromQI->SetPreservingWrapper(true); + wrapperCacheFromQI->CheckCCWrapperTraversal(ccISupports, participant); + wrapperCacheFromQI->SetPreservingWrapper(wasPreservingWrapper); + } +}; + +void +AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector, + JS::Handle aGivenProto); +#endif // DEBUG + template MOZ_ALWAYS_INLINE bool DoGetOrCreateDOMReflector(JSContext* cx, T* value, + JS::Handle givenProto, JS::MutableHandle rval) { MOZ_ASSERT(value); - JSObject* obj = value->GetWrapperPreserveColor(); // We can get rid of this when we remove support for hasXPConnectImpls. bool couldBeDOMBinding = CouldBeDOMBinding(value); + JSObject* obj = value->GetWrapper(); if (obj) { - JS::ExposeObjectToActiveJS(obj); +#ifdef DEBUG + AssertReflectorHasGivenProto(cx, obj, givenProto); + // Have to reget obj because AssertReflectorHasGivenProto can + // trigger gc so the pointer may now be invalid. + obj = value->GetWrapper(); +#endif } else { // Inline this here while we have non-dom objects in wrapper caches. if (!couldBeDOMBinding) { return false; } - obj = value->WrapObject(cx, nullptr); + obj = value->WrapObject(cx, givenProto); if (!obj) { // At this point, obj is null, so just return false. // Callers seem to be testing JS_IsExceptionPending(cx) to // figure out whether WrapObject() threw. return false; } + +#ifdef DEBUG + if (IsBaseOf::value) { + CheckWrapperCacheTracing::Check(value); + } +#endif } #ifdef DEBUG @@ -984,10 +1046,12 @@ DoGetOrCreateDOMReflector(JSContext* cx, T* value, template MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflector(JSContext* cx, T* value, - JS::MutableHandle rval) + JS::MutableHandle rval, + JS::Handle givenProto = nullptr) { using namespace binding_detail; return DoGetOrCreateDOMReflector(cx, value, + givenProto, rval); } @@ -1001,6 +1065,7 @@ GetOrCreateDOMReflectorNoWrap(JSContext* cx, T* value, using namespace binding_detail; return DoGetOrCreateDOMReflector(cx, value, + nullptr, rval); } @@ -1012,7 +1077,8 @@ inline bool WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle scopeArg, T* value, - JS::MutableHandle rval) + JS::MutableHandle rval, + JS::Handle givenProto = nullptr) { static_assert(IsRefcounted::value, "Don't pass owned classes in here."); MOZ_ASSERT(value); @@ -1026,15 +1092,19 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx, // more Maybe (one for a Rooted and one for a Handle) adds more // code (and branches!) than just adding a single rooted. JS::Rooted scope(cx, scopeArg); + JS::Rooted proto(cx, givenProto); if (js::IsWrapper(scope)) { scope = js::CheckedUnwrap(scope, /* stopAtOuter = */ false); if (!scope) return false; ac.emplace(cx, scope); + if (!JS_WrapObject(cx, &proto)) { + return false; + } } MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); - if (!value->WrapObject(cx, nullptr, &obj)) { + if (!value->WrapObject(cx, proto, &obj)) { return false; } } @@ -1054,7 +1124,8 @@ inline bool WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle scopeArg, nsAutoPtr& value, - JS::MutableHandle rval) + JS::MutableHandle rval, + JS::Handle givenProto = nullptr) { static_assert(!IsRefcounted::value, "Only pass owned classes in here."); // We do a runtime check on value, because otherwise we might in @@ -1072,15 +1143,19 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx, // more Maybe (one for a Rooted and one for a Handle) adds more // code (and branches!) than just adding a single rooted. JS::Rooted scope(cx, scopeArg); + JS::Rooted proto(cx, givenProto); if (js::IsWrapper(scope)) { scope = js::CheckedUnwrap(scope, /* stopAtOuter = */ false); if (!scope) return false; ac.emplace(cx, scope); + if (!JS_WrapObject(cx, &proto)) { + return false; + } } MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); - if (!value->WrapObject(cx, nullptr, &obj)) { + if (!value->WrapObject(cx, proto, &obj)) { return false; } @@ -1099,9 +1174,11 @@ template