diff --git a/b2g/components/test/mochitest/test_permission_deny.html b/b2g/components/test/mochitest/test_permission_deny.html index a91667f291..29c35bdecd 100644 --- a/b2g/components/test/mochitest/test_permission_deny.html +++ b/b2g/components/test/mochitest/test_permission_deny.html @@ -43,7 +43,7 @@ function runNext() { ok(false, 'unexpected success, permission request should be denied'); runNext(); }, function failure(err) { - is(err.name, 'PermissionDeniedError', 'expected permission denied'); + is(err.name, 'SecurityError', 'expected permission denied'); runNext(); }); } else { diff --git a/config/check_spidermonkey_style.py b/config/check_spidermonkey_style.py index b35543e10c..74c38bfe7d 100644 --- a/config/check_spidermonkey_style.py +++ b/config/check_spidermonkey_style.py @@ -76,6 +76,7 @@ included_inclnames_to_ignore = set([ 'prthread.h', # NSPR 'prtypes.h', # NSPR 'selfhosted.out.h', # generated in $OBJDIR + 'shellmoduleloader.out.h', # generated in $OBJDIR 'unicode/locid.h', # ICU 'unicode/numsys.h', # ICU 'unicode/timezone.h', # ICU diff --git a/dom/animation/Animation.cpp b/dom/animation/Animation.cpp index cdee486e43..a8ef7efd29 100644 --- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -457,6 +457,44 @@ Animation::GetCurrentOrPendingStartTime() const return result; } +TimeStamp +Animation::AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const +{ + // Initializes to null. Return the same object every time to benefit from + // return-value-optimization. + TimeStamp result; + + // We *don't* check for mTimeline->TracksWallclockTime() here because that + // method only tells us if the timeline times can be converted to + // TimeStamps that can be compared to TimeStamp::Now() or not, *not* + // whether the timelines can be converted to TimeStamp values at all. + // + // Furthermore, we want to be able to use this method when the refresh driver + // is under test control (in which case TracksWallclockTime() will return + // false). + // + // Once we introduce timelines that are not time-based we will need to + // differentiate between them here and determine how to sort their events. + if (!mTimeline) { + return result; + } + + // Check the time is convertible to a timestamp + if (aTime == TimeDuration::Forever() || + mPlaybackRate == 0.0 || + mStartTime.IsNull()) { + return result; + } + + // Invert the standard relation: + // animation time = (timeline time - start time) * playback rate + TimeDuration timelineTime = + TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value(); + + result = mTimeline->ToTimeStamp(timelineTime); + return result; +} + // https://w3c.github.io/web-animations/#silently-set-the-current-time void Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime) @@ -574,7 +612,7 @@ Animation::CanThrottle() const return true; } - return IsRunningOnCompositor(); + return mEffect->CanThrottle(); } void @@ -1017,46 +1055,6 @@ Animation::EffectEnd() const + mEffect->GetComputedTiming().mActiveDuration; } -TimeStamp -Animation::AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const -{ - // Initializes to null. Return the same object every time to benefit from - // return-value-optimization. - TimeStamp result; - - // We *don't* check for mTimeline->TracksWallclockTime() here because that - // method only tells us if the timeline times can be converted to - // TimeStamps that can be compared to TimeStamp::Now() or not, *not* - // whether the timelines can be converted to TimeStamp values at all. - // - // Since we never compare the result of this method with TimeStamp::Now() - // it is ok to return values even if mTimeline->TracksWallclockTime() is - // false. Furthermore, we want to be able to use this method when the - // refresh driver is under test control (in which case TracksWallclockTime() - // will return false). - // - // Once we introduce timelines that are not time-based we will need to - // differentiate between them here and determine how to sort their events. - if (!mTimeline) { - return result; - } - - // Check the time is convertible to a timestamp - if (aTime == TimeDuration::Forever() || - mPlaybackRate == 0.0 || - mStartTime.IsNull()) { - return result; - } - - // Invert the standard relation: - // animation time = (timeline time - start time) * playback rate - TimeDuration timelineTime = - TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value(); - - result = mTimeline->ToTimeStamp(timelineTime); - return result; -} - nsIDocument* Animation::GetRenderedDocument() const { @@ -1064,28 +1062,17 @@ Animation::GetRenderedDocument() const return nullptr; } - Element* targetElement; - nsCSSPseudoElements::Type pseudoType; - mEffect->GetTarget(targetElement, pseudoType); - if (!targetElement) { - return nullptr; - } - - return targetElement->GetComposedDoc(); + return mEffect->GetRenderedDocument(); } nsPresContext* Animation::GetPresContext() const { - nsIDocument* doc = GetRenderedDocument(); - if (!doc) { + if (!mEffect) { return nullptr; } - nsIPresShell* shell = doc->GetShell(); - if (!shell) { - return nullptr; - } - return shell->GetPresContext(); + + return mEffect->GetPresContext(); } AnimationCollection* diff --git a/dom/animation/Animation.h b/dom/animation/Animation.h index 4bbdfa9258..21543d4993 100644 --- a/dom/animation/Animation.h +++ b/dom/animation/Animation.h @@ -230,6 +230,15 @@ public: */ Nullable GetCurrentOrPendingStartTime() const; + /** + * Converts a time in the timescale of this Animation's currentTime, to a + * TimeStamp. Returns a null TimeStamp if the conversion cannot be performed + * because of the current state of this Animation (e.g. it has no timeline, a + * zero playbackRate, an unresolved start time etc.) or the value of the time + * passed-in (e.g. an infinite time). + */ + TimeStamp AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const; + bool IsPausedOrPausing() const { return PlayState() == AnimationPlayState::Paused || @@ -295,6 +304,8 @@ public: void NotifyEffectTimingUpdated(); + AnimationCollection* GetCollection() const; + protected: void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime); void SilentlySetPlaybackRate(double aPlaybackRate); @@ -350,12 +361,10 @@ protected: bool IsPossiblyOrphanedPendingAnimation() const; StickyTimeDuration EffectEnd() const; - TimeStamp AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const; nsIDocument* GetRenderedDocument() const; nsPresContext* GetPresContext() const; virtual CommonAnimationManager* GetAnimationManager() const = 0; - AnimationCollection* GetCollection() const; RefPtr mTimeline; RefPtr mEffect; diff --git a/dom/animation/KeyframeEffect.cpp b/dom/animation/KeyframeEffect.cpp index 8ab75a8687..0a655018b3 100644 --- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -9,8 +9,10 @@ #include "mozilla/dom/KeyframeEffectBinding.h" #include "mozilla/dom/PropertyIndexedKeyframesBinding.h" #include "mozilla/FloatingPoint.h" +#include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt #include "mozilla/StyleAnimationValue.h" #include "AnimationCommon.h" +#include "Layers.h" // For Layer #include "nsCSSParser.h" #include "nsCSSPropertySet.h" #include "nsCSSProps.h" // For nsCSSProps::PropHasFlags @@ -466,6 +468,19 @@ KeyframeEffectReadOnly::ComposeStyle(RefPtr& aStyleRule, } } +bool +KeyframeEffectReadOnly::IsPropertyRunningOnCompositor( + nsCSSProperty aProperty) const +{ + const auto& info = LayerAnimationInfo::sRecords; + for (size_t i = 0; i < ArrayLength(mIsPropertyRunningOnCompositor); i++) { + if (info[i].mProperty == aProperty) { + return mIsPropertyRunningOnCompositor[i]; + } + } + return false; +} + bool KeyframeEffectReadOnly::IsRunningOnCompositor() const { @@ -1709,5 +1724,272 @@ KeyframeEffectReadOnly::GetFrames(JSContext*& aCx, } } +/* static */ const TimeDuration +KeyframeEffectReadOnly::OverflowRegionRefreshInterval() +{ + // The amount of time we can wait between updating throttled animations + // on the main thread that influence the overflow region. + static const TimeDuration kOverflowRegionRefreshInterval = + TimeDuration::FromMilliseconds(200); + + return kOverflowRegionRefreshInterval; +} + +bool +KeyframeEffectReadOnly::CanThrottle() const +{ + // Animation::CanThrottle checks for not in effect animations + // before calling this. + MOZ_ASSERT(IsInEffect(), "Effect should be in effect"); + + // Unthrottle if this animation is not current (i.e. it has passed the end). + // In future we may be able to throttle this case too, but we should only get + // occasional ticks while the animation is in this state so it doesn't matter + // too much. + if (!IsCurrent()) { + return false; + } + + nsIFrame* frame = GetAnimationFrame(); + if (!frame) { + // There are two possible cases here. + // a) No target element + // b) The target element has no frame, e.g. because it is in a display:none + // subtree. + // In either case we can throttle the animation because there is no + // need to update on the main thread. + return true; + } + + // First we need to check layer generation and transform overflow + // prior to the IsPropertyRunningOnCompositor check because we should + // occasionally unthrottle these animations even if the animations are + // already running on compositor. + for (const LayerAnimationInfo::Record& record : + LayerAnimationInfo::sRecords) { + // Skip properties that are overridden in the cascade. + // (GetAnimationOfProperty, as called by HasAnimationOfProperty, + // only returns an animation if it currently wins in the cascade.) + if (!HasAnimationOfProperty(record.mProperty)) { + continue; + } + + AnimationCollection* collection = GetCollection(); + MOZ_ASSERT(collection, + "CanThrottle should be called on an effect associated with an animation"); + layers::Layer* layer = + FrameLayerBuilder::GetDedicatedLayer(frame, record.mLayerType); + // Unthrottle if the layer needs to be brought up to date with the animation. + if (!layer || + collection->mAnimationGeneration > layer->GetAnimationGeneration()) { + return false; + } + + // If this is a transform animation that affects the overflow region, + // we should unthrottle the animation periodically. + if (record.mProperty == eCSSProperty_transform && + !CanThrottleTransformChanges(*frame)) { + return false; + } + } + + for (const AnimationProperty& property : mProperties) { + if (!IsPropertyRunningOnCompositor(property.mProperty)) { + return false; + } + } + + return true; +} + +bool +KeyframeEffectReadOnly::CanThrottleTransformChanges(nsIFrame& aFrame) const +{ + // If we know that the animation cannot cause overflow, + // we can just disable flushes for this animation. + + // If we don't show scrollbars, we don't care about overflow. + if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) { + return true; + } + + nsPresContext* presContext = GetPresContext(); + // CanThrottleTransformChanges is only called as part of a refresh driver tick + // in which case we expect to has a pres context. + MOZ_ASSERT(presContext); + + TimeStamp now = + presContext->RefreshDriver()->MostRecentRefresh(); + + AnimationCollection* collection = GetCollection(); + MOZ_ASSERT(collection, + "CanThrottleTransformChanges should be involved with animation collection"); + TimeStamp styleRuleRefreshTime = collection->mStyleRuleRefreshTime; + // If this animation can cause overflow, we can throttle some of the ticks. + if (!styleRuleRefreshTime.IsNull() && + (now - styleRuleRefreshTime) < OverflowRegionRefreshInterval()) { + return true; + } + + // If the nearest scrollable ancestor has overflow:hidden, + // we don't care about overflow. + nsIScrollableFrame* scrollable = + nsLayoutUtils::GetNearestScrollableFrame(&aFrame); + if (!scrollable) { + return true; + } + + ScrollbarStyles ss = scrollable->GetScrollbarStyles(); + if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN && + ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && + scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) { + return true; + } + + return false; +} + +nsIFrame* +KeyframeEffectReadOnly::GetAnimationFrame() const +{ + if (!mTarget) { + return nullptr; + } + + nsIFrame* frame = mTarget->GetPrimaryFrame(); + if (!frame) { + return nullptr; + } + + if (mPseudoType == nsCSSPseudoElements::ePseudo_before) { + frame = nsLayoutUtils::GetBeforeFrame(frame); + } else if (mPseudoType == nsCSSPseudoElements::ePseudo_after) { + frame = nsLayoutUtils::GetAfterFrame(frame); + } else { + MOZ_ASSERT(mPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement, + "unknown mPseudoType"); + } + if (!frame) { + return nullptr; + } + + return nsLayoutUtils::GetStyleFrame(frame); +} + +nsIDocument* +KeyframeEffectReadOnly::GetRenderedDocument() const +{ + if (!mTarget) { + return nullptr; + } + return mTarget->GetComposedDoc(); +} + +nsPresContext* +KeyframeEffectReadOnly::GetPresContext() const +{ + nsIDocument* doc = GetRenderedDocument(); + if (!doc) { + return nullptr; + } + nsIPresShell* shell = doc->GetShell(); + if (!shell) { + return nullptr; + } + return shell->GetPresContext(); +} + +AnimationCollection * +KeyframeEffectReadOnly::GetCollection() const +{ + return mAnimation ? mAnimation->GetCollection() : nullptr; +} + +/* static */ bool +KeyframeEffectReadOnly::IsGeometricProperty( + const nsCSSProperty aProperty) +{ + switch (aProperty) { + case eCSSProperty_bottom: + case eCSSProperty_height: + case eCSSProperty_left: + case eCSSProperty_right: + case eCSSProperty_top: + case eCSSProperty_width: + return true; + default: + return false; + } +} + +/* static */ bool +KeyframeEffectReadOnly::CanAnimateTransformOnCompositor( + const nsIFrame* aFrame, + const nsIContent* aContent) +{ + if (aFrame->Combines3DTransformWithAncestors() || + aFrame->Extend3DContext()) { + if (aContent) { + nsCString message; + message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' " + "transforms is not supported. See bug 779598"); + AnimationCollection::LogAsyncAnimationFailure(message, aContent); + } + return false; + } + // Note that testing BackfaceIsHidden() is not a sufficient test for + // what we need for animating backface-visibility correctly if we + // remove the above test for Extend3DContext(); that would require + // looking at backface-visibility on descendants as well. + if (aFrame->StyleDisplay()->BackfaceIsHidden()) { + if (aContent) { + nsCString message; + message.AppendLiteral("Gecko bug: Async animation of " + "'backface-visibility: hidden' transforms is not supported." + " See bug 1186204."); + AnimationCollection::LogAsyncAnimationFailure(message, aContent); + } + return false; + } + if (aFrame->IsSVGTransformed()) { + if (aContent) { + nsCString message; + message.AppendLiteral("Gecko bug: Async 'transform' animations of " + "aFrames with SVG transforms is not supported. See bug 779599"); + AnimationCollection::LogAsyncAnimationFailure(message, aContent); + } + return false; + } + + return true; +} + +/* static */ bool +KeyframeEffectReadOnly::CanAnimatePropertyOnCompositor( + const nsIFrame* aFrame, + nsCSSProperty aProperty) +{ + bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled(); + + if (IsGeometricProperty(aProperty)) { + if (shouldLog) { + nsCString message; + message.AppendLiteral("Performance warning: Async animation of " + "'transform' or 'opacity' not possible due to animation of geometric" + "properties on the same element"); + AnimationCollection::LogAsyncAnimationFailure(message, + aFrame->GetContent()); + } + return false; + } + if (aProperty == eCSSProperty_transform) { + if (!CanAnimateTransformOnCompositor(aFrame, + shouldLog ? aFrame->GetContent() : nullptr)) { + return false; + } + } + return true; +} + } // namespace dom } // namespace mozilla diff --git a/dom/animation/KeyframeEffect.h b/dom/animation/KeyframeEffect.h index 7b4d629d7d..10632dcfb4 100644 --- a/dom/animation/KeyframeEffect.h +++ b/dom/animation/KeyframeEffect.h @@ -25,9 +25,14 @@ struct JSContext; class nsCSSPropertySet; +class nsIContent; +class nsIDocument; +class nsIFrame; +class nsPresContext; namespace mozilla { +struct AnimationCollection; class AnimValuesStyleRule; namespace dom { @@ -266,9 +271,22 @@ public: // Any updated properties are added to |aSetProperties|. void ComposeStyle(RefPtr& aStyleRule, nsCSSPropertySet& aSetProperties); + // Returns true if |aProperty| is currently being animated on compositor. + bool IsPropertyRunningOnCompositor(nsCSSProperty aProperty) const; + // Returns true if at least one property is being animated on compositor. bool IsRunningOnCompositor() const; void SetIsRunningOnCompositor(nsCSSProperty aProperty, bool aIsRunning); + bool CanThrottle() const; + + // Returns true |aProperty| can be run on compositor for |aFrame|. + static bool CanAnimatePropertyOnCompositor(const nsIFrame* aFrame, + nsCSSProperty aProperty); + nsIDocument* GetRenderedDocument() const; + nsPresContext* GetPresContext() const; + + inline AnimationCollection* GetCollection() const; + protected: virtual ~KeyframeEffectReadOnly(); void ResetIsRunningOnCompositor(); @@ -300,6 +318,21 @@ protected: // restyle is performed, this member may temporarily become false even if // the animation remains on the layer after the restyle. bool mIsPropertyRunningOnCompositor[LayerAnimationInfo::kRecords]; + +private: + nsIFrame* GetAnimationFrame() const; + + bool CanThrottleTransformChanges(nsIFrame& aFrame) const; + + // Returns true unless Gecko limitations prevent performing transform + // animations for |aFrame|. Any limitations that are encountered are + // logged using |aContent| to describe the affected content. + // If |aContent| is nullptr, no logging is performed + static bool CanAnimateTransformOnCompositor(const nsIFrame* aFrame, + const nsIContent* aContent); + static bool IsGeometricProperty(const nsCSSProperty aProperty); + + static const TimeDuration OverflowRegionRefreshInterval(); }; } // namespace dom diff --git a/dom/base/nsDocumentWarningList.h b/dom/base/nsDocumentWarningList.h index 8a059ce5f9..26cb33d215 100644 --- a/dom/base/nsDocumentWarningList.h +++ b/dom/base/nsDocumentWarningList.h @@ -10,4 +10,4 @@ * designed to be used as input to the C preprocessor *only*. */ -DOCUMENT_WARNING(WillChangeBudget) +DOCUMENT_WARNING(IgnoringWillChangeOverBudget) diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 66f089ebda..e5c03af8a0 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -3288,6 +3288,13 @@ GetErrorPrototype(JSContext* aCx, JS::Handle aForObj) return JS_GetErrorPrototype(aCx); } +inline +JSObject* +GetIteratorPrototype(JSContext* aCx, JS::Handle aForObj) +{ + return JS_GetIteratorPrototype(aCx); +} + // Resolve an id on the given global object that wants to be included in // Exposed=System webidl annotations. False return value means exception // thrown. diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 5febcf4aad..dac6557a22 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -580,6 +580,8 @@ def InterfacePrototypeObjectProtoGetter(descriptor): protoGetter = "JS_GetArrayPrototype" elif descriptor.interface.getExtendedAttribute("ExceptionClass"): protoGetter = "GetErrorPrototype" + elif descriptor.interface.isIteratorInterface(): + protoGetter = "GetIteratorPrototype" else: protoGetter = "JS_GetObjectPrototype" protoHandleGetter = None @@ -2259,6 +2261,22 @@ class MethodDefiner(PropertyDefiner): "condition": MemberCondition(None, None) }) + # Output an @@iterator for generated iterator interfaces. This should + # not be necessary, but + # https://bugzilla.mozilla.org/show_bug.cgi?id=1091945 means that + # %IteratorPrototype%[@@iterator] is a broken puppy. + if (not static and + not unforgeable and + descriptor.interface.isIteratorInterface()): + self.regular.append({ + "name": "@@iterator", + "methodInfo": False, + "selfHostedName": "IteratorIdentity", + "length": 0, + "flags": "0", + "condition": MemberCondition(None, None) + }) + # Generate the maplike/setlike iterator, if one wasn't already # generated by a method. If we already have an @@iterator symbol, fail. if descriptor.interface.maplikeOrSetlikeOrIterable: diff --git a/dom/bindings/test/test_iterable.html b/dom/bindings/test/test_iterable.html index d5c71d6d6e..fb37c22691 100644 --- a/dom/bindings/test/test_iterable.html +++ b/dom/bindings/test/test_iterable.html @@ -50,6 +50,9 @@ info("IterableSingle: Testing simple iterable creation and functionality"); itr = new TestInterfaceIterableSingle(); testExistence("IterableSingle: ", itr, base_properties); + var keys = [...itr.keys()]; + var values = [...itr.values()]; + var entries = [...itr.entries()]; var key_itr = itr.keys(); var value_itr = itr.values(); var entries_itr = itr.entries(); @@ -58,9 +61,17 @@ var value = value_itr.next(); var entry = entries_itr.next(); is(key.value, i, "IterableSingle: Key iterator value should be " + i); + is(key.value, keys[i], + "IterableSingle: Key iterator value should match destructuring " + i); is(value.value, key.value, "IterableSingle: Value iterator value should be " + key.value); + is(value.value, values[i], + "IterableSingle: Value iterator value should match destructuring " + i); is(entry.value[0], i, "IterableSingle: Entry iterator value 0 should be " + i); is(entry.value[1], i, "IterableSingle: Entry iterator value 1 should be " + i); + is(entry.value[0], entries[i][0], + "IterableSingle: Entry iterator value 0 should match destructuring " + i); + is(entry.value[1], entries[i][1], + "IterableSingle: Entry iterator value 1 should match destructuring " + i); } var key = key_itr.next(); var value = value_itr.next(); @@ -80,6 +91,9 @@ itr = new TestInterfaceIterableDouble(); testExistence("IterableDouble: ", itr, base_properties); var elements = [["a", "b"], ["c", "d"], ["e", "f"]] + var keys = [...itr.keys()]; + var values = [...itr.values()]; + var entries = [...itr.entries()]; var key_itr = itr.keys(); var value_itr = itr.values(); var entries_itr = itr.entries(); @@ -88,9 +102,17 @@ var value = value_itr.next(); var entry = entries_itr.next(); is(key.value, elements[i][0], "IterableDouble: Key iterator value should be " + elements[i][0]); + is(key.value, keys[i], + "IterableDouble: Key iterator value should match destructuring " + i); is(value.value, elements[i][1], "IterableDouble: Value iterator value should be " + elements[i][1]); + is(value.value, values[i], + "IterableDouble: Value iterator value should match destructuring " + i); is(entry.value[0], elements[i][0], "IterableDouble: Entry iterator value 0 should be " + elements[i][0]); is(entry.value[1], elements[i][1], "IterableDouble: Entry iterator value 1 should be " + elements[i][1]); + is(entry.value[0], entries[i][0], + "IterableDouble: Entry iterator value 0 should match destructuring " + i); + is(entry.value[1], entries[i][1], + "IterableDouble: Entry iterator value 1 should match destructuring " + i); } var key = key_itr.next(); var value = value_itr.next(); diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 51c4e1f708..e82bee5bc8 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -160,8 +160,8 @@ ImportXULIntoContentWarning=Importing XUL nodes into a content document is depre XMLDocumentLoadPrincipalMismatch=Use of document.load forbidden on Documents that come from other Windows. Only the Window in which a Document was created is allowed to call .load on that Document. Preferably, use XMLHttpRequest instead. # LOCALIZATION NOTE: Do not translate "IndexedDB". IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet complete has been aborted due to page navigation. -# LOCALIZATION NOTE (WillChangeBudgetWarning): Do not translate Will-change, %1$S,%2$S,%3$S are numbers. -WillChangeBudgetWarning=Will-change memory consumption is too high. Surface area covers %1$S pixels, budget is the document surface area multiplied by %2$S (%3$S pixels). Occurences of will-change over the budget will be ignored. +# LOCALIZATION NOTE: Do not translate Will-change, %1$S,%2$S are numbers. +IgnoringWillChangeOverBudgetWarning=Will-change memory consumption is too high. Budget limit is the document surface area multiplied by %1$S (%2$S px). Occurrences of will-change over the budget will be ignored. # LOCALIZATION NOTE: Do not translate "ServiceWorker". HittingMaxWorkersPerDomain=A ServiceWorker could not be started immediately because other documents in the same origin are already using the maximum number of workers. The ServiceWorker is now queued and will be started after some of the other workers have completed. # LOCALIZATION NOTE: Do not translate "setVelocity", "PannerNode", "AudioListener", "speedOfSound" and "dopplerFactor" diff --git a/dom/locales/en-US/chrome/layout/css.properties b/dom/locales/en-US/chrome/layout/css.properties index 102e456727..cdcd50aa7f 100644 --- a/dom/locales/en-US/chrome/layout/css.properties +++ b/dom/locales/en-US/chrome/layout/css.properties @@ -169,3 +169,4 @@ PEExpectedVariableNameEOF=identifier for variable name PEExpectedVariableName=Expected identifier for variable name but found '%1$S'. PEExpectedVariableFallback=Expected variable reference fallback after ','. PEExpectedVariableCommaOrCloseParen=Expected ',' or ')' after variable name in variable reference but found '%1$S'. +PESubgridNotSupported=Support for the 'subgrid' keyword of CSS Grid is not enabled. diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index 4d97d49169..f19c4b5fd2 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -617,7 +617,7 @@ void MediaFormatReader::NotifyError(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); - LOGV("Decoder has requested more %s data", TrackTypeToStr(aTrack)); + LOGV("%s Decoding error", TrackTypeToStr(aTrack)); auto& decoder = GetDecoderData(aTrack); decoder.mError = true; ScheduleUpdate(aTrack); @@ -636,7 +636,6 @@ void MediaFormatReader::NotifyEndOfStream(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); - LOGV("%s Decoding error", TrackTypeToStr(aTrack)); auto& decoder = GetDecoderData(aTrack); decoder.mDemuxEOS = true; decoder.mNeedDraining = true; @@ -792,6 +791,8 @@ MediaFormatReader::HandleDemuxedSamples(TrackType aTrack, return; } + LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack)); + // Decode all our demuxed frames. bool samplesPending = false; while (decoder.mQueuedSamples.Length()) { @@ -1269,6 +1270,7 @@ MediaFormatReader::Seek(int64_t aTime, int64_t aUnused) MOZ_ASSERT(OnTaskQueue()); LOG("aTime=(%lld)", aTime); + MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty()); MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise()); MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise()); @@ -1321,7 +1323,7 @@ void MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult) { MOZ_ASSERT(OnTaskQueue()); - LOGV("%s failure = %d", TrackTypeToStr(aTrack), aResult); + LOGV("%s failure:%d", TrackTypeToStr(aTrack), aResult); if (aTrack == TrackType::kVideoTrack) { mVideo.mSeekRequest.Complete(); } else { @@ -1381,6 +1383,7 @@ void MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime) { MOZ_ASSERT(OnTaskQueue()); + LOGV("Video seeked to %lld", aTime.ToMicroseconds()); mVideo.mSeekRequest.Complete(); if (HasAudio()) { @@ -1409,6 +1412,7 @@ void MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime) { MOZ_ASSERT(OnTaskQueue()); + LOGV("Audio seeked to %lld", aTime.ToMicroseconds()); mAudio.mSeekRequest.Complete(); mPendingSeekTime.reset(); mSeekPromise.Resolve(aTime.ToMicroseconds(), __func__); diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index a2b85a1812..7a5280affd 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -798,17 +798,6 @@ public: } } - // let us intervene for direct listeners when someone does track.enabled = false - virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled) override - { - // We encapsulate the SourceMediaStream and TrackUnion into one entity, so - // we can handle the disabling at the SourceMediaStream - - // We need to find the input track ID for output ID aTrackID, so we let the TrackUnion - // forward the request to the source and translate the ID - GetInputStream()->AsProcessedStream()->ForwardTrackEnabled(aTrackID, aEnabled); - } - virtual DOMLocalMediaStream* AsDOMLocalMediaStream() override { return this; @@ -1495,7 +1484,6 @@ MediaManager::EnumerateRawDevices(uint64_t aWindowId, MediaManager::MediaManager() : mMediaThread(nullptr) - , mMutex("mozilla::MediaManager") , mBackend(nullptr) { mPrefs.mFreq = 1000; // 1KHz test tone mPrefs.mWidth = 0; // adaptive default @@ -1898,7 +1886,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, (!privileged && !HostHasPermission(*docURI))) { RefPtr error = new MediaStreamError(aWindow, - NS_LITERAL_STRING("PermissionDeniedError")); + NS_LITERAL_STRING("SecurityError")); onFailure->OnError(error); return NS_OK; } @@ -1930,12 +1918,12 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, if (!privileged) { // only allow privileged content to set the window id if (vc.mBrowserWindow.WasPassed()) { - vc.mBrowserWindow.Construct(-1); + vc.mBrowserWindow.Value() = -1; } if (vc.mAdvanced.WasPassed()) { for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) { if (cs.mBrowserWindow.WasPassed()) { - cs.mBrowserWindow.Construct(-1); + cs.mBrowserWindow.Value() = -1; } } } @@ -1973,7 +1961,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, if (!Preferences::GetBool("media.getusermedia.audiocapture.enabled")) { RefPtr error = new MediaStreamError(aWindow, - NS_LITERAL_STRING("PermissionDeniedError")); + NS_LITERAL_STRING("SecurityError")); onFailure->OnError(error); return NS_OK; } @@ -2033,7 +2021,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) && (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) { RefPtr error = - new MediaStreamError(aWindow, NS_LITERAL_STRING("PermissionDeniedError")); + new MediaStreamError(aWindow, NS_LITERAL_STRING("SecurityError")); onFailure->OnError(error); RemoveFromWindowList(windowID, listener); return NS_OK; @@ -2385,10 +2373,10 @@ MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow, MediaEngine* MediaManager::GetBackend(uint64_t aWindowId) { + MOZ_ASSERT(MediaManager::IsInMediaThread()); // Plugin backends as appropriate. The default engine also currently // includes picture support for Android. // This IS called off main-thread. - MutexAutoLock lock(mMutex); if (!mBackend) { MOZ_RELEASE_ASSERT(!sInShutdown); // we should never create a new backend in shutdown #if defined(MOZ_WEBRTC) @@ -2585,12 +2573,6 @@ MediaManager::Shutdown() GetActiveWindows()->Clear(); mActiveCallbacks.Clear(); mCallIds.Clear(); - { - MutexAutoLock lock(mMutex); - if (mBackend) { - mBackend->Shutdown(); // ok to invoke multiple times - } - } // Because mMediaThread is not an nsThread, we must dispatch to it so it can // clean up BackgroundChild. Continue stopping thread once this is done. @@ -2598,26 +2580,32 @@ MediaManager::Shutdown() class ShutdownTask : public Task { public: - ShutdownTask(already_AddRefed aBackend, + ShutdownTask(MediaManager* aManager, nsRunnable* aReply) - : mReply(aReply) - , mBackend(aBackend) {} + : mManager(aManager) + , mReply(aReply) {} private: virtual void Run() { LOG(("MediaManager Thread Shutdown")); MOZ_ASSERT(MediaManager::IsInMediaThread()); + // Must shutdown backend on MediaManager thread, since that's where we started it from! + { + if (mManager->mBackend) { + mManager->mBackend->Shutdown(); // ok to invoke multiple times + } + } mozilla::ipc::BackgroundChild::CloseForCurrentThread(); // must explicitly do this before dispatching the reply, since the reply may kill us with Stop() - mBackend = nullptr; // last reference, will invoke Shutdown() again + mManager->mBackend = nullptr; // last reference, will invoke Shutdown() again if (NS_FAILED(NS_DispatchToMainThread(mReply.forget()))) { LOG(("Will leak thread: DispatchToMainthread of reply runnable failed in MediaManager shutdown")); } } + RefPtr mManager; RefPtr mReply; - RefPtr mBackend; }; // Post ShutdownTask to execute on mMediaThread and pass in a lambda @@ -2630,14 +2618,8 @@ MediaManager::Shutdown() // note that this == sSingleton RefPtr that(sSingleton); // Release the backend (and call Shutdown()) from within the MediaManager thread - RefPtr temp; - { - MutexAutoLock lock(mMutex); - temp = mBackend.forget(); - } // Don't use MediaManager::PostTask() because we're sInShutdown=true here! - mMediaThread->message_loop()->PostTask(FROM_HERE, new ShutdownTask( - temp.forget(), + mMediaThread->message_loop()->PostTask(FROM_HERE, new ShutdownTask(this, media::NewRunnableFrom([this, that]() mutable { LOG(("MediaManager shutdown lambda running, releasing MediaManager singleton and thread")); if (mMediaThread) { @@ -2700,7 +2682,7 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, array->Count(&len); if (!len) { // neither audio nor video were selected - task->Denied(NS_LITERAL_STRING("PermissionDeniedError")); + task->Denied(NS_LITERAL_STRING("SecurityError")); return NS_OK; } bool videoFound = false, audioFound = false; @@ -2737,7 +2719,7 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, return NS_OK; } else if (!strcmp(aTopic, "getUserMedia:response:deny")) { - nsString errorMessage(NS_LITERAL_STRING("PermissionDeniedError")); + nsString errorMessage(NS_LITERAL_STRING("SecurityError")); if (aSubject) { nsCOMPtr msg(do_QueryInterface(aSubject)); diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h index 0454f7a769..9168a9766f 100644 --- a/dom/media/MediaManager.h +++ b/dom/media/MediaManager.h @@ -533,8 +533,7 @@ private: nsAutoPtr mMediaThread; nsCOMPtr mShutdownBlocker; - Mutex mMutex; - // protected with mMutex: + // ONLY accessed from MediaManagerThread RefPtr mBackend; static StaticRefPtr sSingleton; diff --git a/dom/media/MediaPermissionGonk.cpp b/dom/media/MediaPermissionGonk.cpp index 2bddd5acf0..0f12eb9e75 100644 --- a/dom/media/MediaPermissionGonk.cpp +++ b/dom/media/MediaPermissionGonk.cpp @@ -230,7 +230,7 @@ MediaPermissionRequest::Cancel() { nsString callID; mRequest->GetCallID(callID); - NotifyPermissionDeny(callID, NS_LITERAL_STRING("PermissionDeniedError")); + NotifyPermissionDeny(callID, NS_LITERAL_STRING("SecurityError")); return NS_OK; } diff --git a/dom/media/MediaStreamError.cpp b/dom/media/MediaStreamError.cpp index 44d45b2502..659d1231cd 100644 --- a/dom/media/MediaStreamError.cpp +++ b/dom/media/MediaStreamError.cpp @@ -20,8 +20,8 @@ BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName, if (mMessage.IsEmpty()) { if (mName.EqualsLiteral("NotFoundError")) { mMessage.AssignLiteral("The object can not be found here."); - } else if (mName.EqualsLiteral("PermissionDeniedError")) { - mMessage.AssignLiteral("The user did not grant permission for the operation."); + } else if (mName.EqualsLiteral("SecurityError")) { + mMessage.AssignLiteral("The operation is insecure."); } else if (mName.EqualsLiteral("SourceUnavailableError")) { mMessage.AssignLiteral("The source of the MediaStream could not be " "accessed due to a hardware error (e.g. lock from another process)."); diff --git a/dom/media/MediaStreamGraph.h b/dom/media/MediaStreamGraph.h index 2236f2dff2..05eb8b46c6 100644 --- a/dom/media/MediaStreamGraph.h +++ b/dom/media/MediaStreamGraph.h @@ -1112,11 +1112,6 @@ public: virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) = 0; void SetAutofinishImpl(bool aAutofinish) { mAutofinish = aAutofinish; } - /** - * Forward SetTrackEnabled() to the input MediaStream(s) and translate the ID - */ - virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {}; - // Only valid after MediaStreamGraphImpl::UpdateStreamOrder() has run. // A DelayNode is considered to break a cycle and so this will not return // true for echo loops, only for muted cycles. diff --git a/dom/media/TrackUnionStream.cpp b/dom/media/TrackUnionStream.cpp index 8310d22ac3..e4e844a438 100644 --- a/dom/media/TrackUnionStream.cpp +++ b/dom/media/TrackUnionStream.cpp @@ -149,18 +149,6 @@ TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) : } } - // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream, - // translating the output track ID into the correct ID in the source. - void TrackUnionStream::ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) - { - for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) { - if (mTrackMap[i].mOutputTrackID == aOutputID) { - mTrackMap[i].mInputPort->GetSource()-> - SetTrackEnabled(mTrackMap[i].mInputTrackID, aEnabled); - } - } - } - uint32_t TrackUnionStream::AddTrack(MediaInputPort* aPort, StreamBuffer::Track* aTrack, GraphTime aFrom) { diff --git a/dom/media/TrackUnionStream.h b/dom/media/TrackUnionStream.h index e9dd979225..009a3fac1e 100644 --- a/dom/media/TrackUnionStream.h +++ b/dom/media/TrackUnionStream.h @@ -21,10 +21,6 @@ public: virtual void RemoveInput(MediaInputPort* aPort) override; virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override; - // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream, - // translating the output track ID into the correct ID in the source. - virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) override; - protected: // Only non-ended tracks are allowed to persist in this map. struct TrackMapEntry { diff --git a/dom/media/fmp4/MP4Decoder.cpp b/dom/media/fmp4/MP4Decoder.cpp index ad631b6a67..955d5ae153 100644 --- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -13,6 +13,7 @@ #include "nsContentTypeParser.h" #include "VideoUtils.h" #include "mozilla/Logging.h" +#include "nsMimeTypes.h" #ifdef XP_WIN #include "mozilla/WindowsVersion.h" @@ -112,7 +113,7 @@ MP4Decoder::CanHandleMediaType(const nsACString& aMIMETypeExcludingCodecs, return false; } - #ifdef MOZ_GONK_MEDIACODEC +#ifdef MOZ_GONK_MEDIACODEC if (aMIMETypeExcludingCodecs.EqualsASCII(VIDEO_3GPP)) { return Preferences::GetBool("media.gonk.enabled", false); } diff --git a/dom/media/tests/mochitest/test_getUserMedia_constraints.html b/dom/media/tests/mochitest/test_getUserMedia_constraints.html index bbda436215..88d0d8993c 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html +++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html @@ -29,16 +29,16 @@ var tests = [ error: null }, { message: "full screensharing requires permission", constraints: { video: { mediaSource: 'screen' } }, - error: "PermissionDeniedError" }, + error: "SecurityError" }, { message: "application screensharing requires permission", constraints: { video: { mediaSource: 'application' } }, - error: "PermissionDeniedError" }, + error: "SecurityError" }, { message: "window screensharing requires permission", constraints: { video: { mediaSource: 'window' } }, - error: "PermissionDeniedError" }, + error: "SecurityError" }, { message: "browser screensharing requires permission", constraints: { video: { mediaSource: 'browser' } }, - error: "PermissionDeniedError" }, + error: "SecurityError" }, { message: "unknown mediaSource in video fails", constraints: { video: { mediaSource: 'uncle' } }, error: "OverconstrainedError", diff --git a/dom/tests/mochitest/general/mochitest.ini b/dom/tests/mochitest/general/mochitest.ini index df48f92c12..54ac94428a 100644 --- a/dom/tests/mochitest/general/mochitest.ini +++ b/dom/tests/mochitest/general/mochitest.ini @@ -72,6 +72,7 @@ skip-if = buildapp == 'mulet' skip-if = e10s || buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT [test_for_of.html] [test_frameElementWrapping.html] +[test_pointerPreserves3D.html] [test_framedhistoryframes.html] [test_idleapi_permissions.html] skip-if = e10s || buildapp == 'b2g' || buildapp == 'mulet' diff --git a/dom/tests/mochitest/general/test_pointerPreserves3D.html b/dom/tests/mochitest/general/test_pointerPreserves3D.html new file mode 100644 index 0000000000..b9826a1857 --- /dev/null +++ b/dom/tests/mochitest/general/test_pointerPreserves3D.html @@ -0,0 +1,25 @@ + + + + Test for pointer events with preserve-3d + + + + +
+
+
+
+
+
+
+ + + diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h index 3518efdb62..5cebf063ac 100644 --- a/gfx/2d/2D.h +++ b/gfx/2d/2D.h @@ -1147,6 +1147,7 @@ public: static void ShutDown(); static bool HasSSE2(); + static bool HasVMX(); /** Make sure that the given dimensions don't overflow a 32-bit signed int * using 4 bytes per pixel; optionally, make sure that either dimension diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp index c3b1912b40..2618e2b1ec 100644 --- a/gfx/2d/Blur.cpp +++ b/gfx/2d/Blur.cpp @@ -568,14 +568,33 @@ AlphaBoxBlur::Blur(uint8_t* aData) BoxBlur_NEON(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], verticalLobes[2][1], integralImage, integralImageStride); } else +#endif +#ifdef USE_VMX + if (Factory::HasVMX()) { + BoxBlur_VMX(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_VMX(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_VMX(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); + } else #endif { +#ifdef _MIPS_ARCH_LOONGSON3A + BoxBlur_LS3(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_LS3(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_LS3(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); +#else BoxBlur_C(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], verticalLobes[0][1], integralImage, integralImageStride); BoxBlur_C(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], verticalLobes[1][1], integralImage, integralImageStride); BoxBlur_C(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], verticalLobes[2][1], integralImage, integralImageStride); +#endif } } } diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h index 9e5e4a2260..18a99d4392 100644 --- a/gfx/2d/Blur.h +++ b/gfx/2d/Blur.h @@ -129,6 +129,16 @@ private: int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); #endif +#ifdef _MIPS_ARCH_LOONGSON3A + void BoxBlur_LS3(uint8_t* aData, + int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); +#endif +#ifdef USE_VMX + void BoxBlur_VMX(uint8_t* aData, + int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); +#endif static CheckedInt RoundUpToMultipleOf4(int32_t aVal); diff --git a/gfx/2d/BlurLS3.cpp b/gfx/2d/BlurLS3.cpp new file mode 100644 index 0000000000..20c28b37ee --- /dev/null +++ b/gfx/2d/BlurLS3.cpp @@ -0,0 +1,588 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" + +#include + +#ifdef _MIPS_ARCH_LOONGSON3A + +#include "MMIHelpers.h" + +namespace mozilla { +namespace gfx { + +typedef struct { double l; double h; } __m128i; + +MOZ_ALWAYS_INLINE +__m128i loadUnaligned128(__m128i *p) +{ + __m128i v; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gsldlc1 %[vh], 0xf(%[p]) \n\t" + "gsldrc1 %[vh], 0x8(%[p]) \n\t" + "gsldlc1 %[vl], 0x7(%[p]) \n\t" + "gsldrc1 %[vl], 0x0(%[p]) \n\t" + ".set pop \n\t" + :[vh]"=f"(v.h), [vl]"=f"(v.l) + :[p]"r"(p) + :"memory" + ); + + return v; +} + +MOZ_ALWAYS_INLINE +__m128i Divide(__m128i aValues, __m128i aDivisor) +{ + uint64_t tmp; + double srl32; + __m128i mask, ra, p4321, t1, t2; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 0x80000000 \n\t" + "mtc1 %[tmp], %[ral] \n\t" + "xor %[maskl], %[maskl], %[maskl] \n\t" + "mov.d %[rah], %[ral] \n\t" + "li %[tmp], 0xffffffff \n\t" + "mthc1 %[tmp], %[maskl] \n\t" + "mov.d %[maskh], %[maskl] \n\t" + ".set pop \n\t" + :[rah]"=f"(ra.h), [ral]"=f"(ra.l), + [maskh]"=f"(mask.h), [maskl]"=f"(mask.l), + [tmp]"=&r"(tmp) + ); + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "ori %[tmp], $0, 32 \n\t" + "mtc1 %[tmp], %[srl32] \n\t" + _mm_pmuluw(t1, av, ad) + _mm_psrld(t2, av, srl32) + _mm_pmuluw(t2, t2, ad) + // Add 1 << 31 before shifting or masking the lower 32 bits away, so that the + // result is rounded. + _mm_paddd(t1, t1, ra) + _mm_psrld(t1, t1, srl32) + _mm_paddd(t2, t2, ra) + _mm_and(t2, t2, mask) + _mm_or(p4321, t1, t2) + ".set pop \n\t" + :[p4321h]"=&f"(p4321.h), [p4321l]"=&f"(p4321.l), + [t1h]"=&f"(t1.h), [t1l]"=&f"(t1.l), + [t2h]"=&f"(t2.h), [t2l]"=&f"(t2.l), + [srl32]"=&f"(srl32), [tmp]"=&r"(tmp) + :[rah]"f"(ra.h), [ral]"f"(ra.l), + [maskh]"f"(mask.h), [maskl]"f"(mask.l), + [avh]"f"(aValues.h), [avl]"f"(aValues.l), + [adh]"f"(aDivisor.h), [adl]"f"(aDivisor.l) + ); + + return p4321; +} + +MOZ_ALWAYS_INLINE +__m128i BlurFourPixels(const __m128i& aTopLeft, const __m128i& aTopRight, + const __m128i& aBottomRight, const __m128i& aBottomLeft, + const __m128i& aDivisor) +{ + __m128i values; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_psubw(val, abr, atr) + _mm_psubw(val, val, abl) + _mm_paddw(val, val, atl) + ".set pop \n\t" + :[valh]"=&f"(values.h), [vall]"=&f"(values.l) + :[abrh]"f"(aBottomRight.h), [abrl]"f"(aBottomRight.l), + [atrh]"f"(aTopRight.h), [atrl]"f"(aTopRight.l), + [ablh]"f"(aBottomLeft.h), [abll]"f"(aBottomLeft.l), + [atlh]"f"(aTopLeft.h), [atll]"f"(aTopLeft.l) + ); + + return Divide(values, aDivisor); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) +{ + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +// This function calculates an integral of four pixels stored in the 4 +// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns +// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after +// much testing. +MOZ_ALWAYS_INLINE +__m128i AccumulatePixelSums(__m128i aPixels) +{ + uint64_t tr; + double tmp, s4, s64; + __m128i sumPixels, currentPixels, zero; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(z, z, z) + "li %[tr], 64 \n\t" + "mtc1 %[tr], %[s64] \n\t" + "li %[tr], 32 \n\t" + "mtc1 %[tr], %[s4] \n\t" + _mm_psllq(cp, ap, s4, s64, t) + _mm_paddw(sp, ap, cp) + _mm_punpckldq(cp, z, sp) + _mm_paddw(sp, sp, cp) + ".set pop \n\t" + :[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l), + [cph]"=&f"(currentPixels.h), [cpl]"=&f"(currentPixels.l), + [zh]"=&f"(zero.h), [zl]"=&f"(zero.l), + [s4]"=&f"(s4), [s64]"=&f"(s64), [t]"=&f"(tmp), [tr]"=&r"(tr) + :[aph]"f"(aPixels.h), [apl]"f"(aPixels.l) + ); + + return sumPixels; +} + +MOZ_ALWAYS_INLINE +void GenerateIntegralImage_LS3(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i firstRow, previousRow; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gslqc1 %[frh], %[frl], (%[fr]) \n\t" + "gslqc1 %[prh], %[prl], (%[pr]) \n\t" + _mm_paddw(fr, fr, pr) + "gssqc1 %[frh], %[frl], (%[r]) \n\t" + ".set pop \n\t" + :[frh]"=&f"(firstRow.h), [frl]"=&f"(firstRow.l), + [prh]"=&f"(previousRow.h), [prl]"=&f"(previousRow.l) + :[fr]"r"(intFirstRow + x), [pr]"r"(intPrevRow + x), + [r]"r"(intRow + x) + :"memory" + ); + } + } + + uint64_t tmp; + double s44, see; + __m128i zero; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 0xee \n\t" + "mtc1 %[tmp], %[see] \n\t" + "li %[tmp], 0x44 \n\t" + "mtc1 %[tmp], %[s44] \n\t" + _mm_xor(zero, zero, zero) + ".set pop \n\t" + :[tmp]"=&r"(tmp), [s44]"=f"(s44), [see]"=f"(see), + [zeroh]"=f"(zero.h), [zerol]"=f"(zero.l) + ); + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + __m128i currentRowSum; + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation); + uint32_t pixel = sourceRow[0]; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(cr, cr, cr) + ".set pop \n\t" + :[crh]"=f"(currentRowSum.h), [crl]"=f"(currentRowSum.l) + ); + for (int x = 0; x < aLeftInflation; x += 4) { + __m128i sumPixels, t; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[pix], %[spl] \n\t" + "punpcklwd %[spl], %[spl], %[spl] \n\t" + "mov.d %[sph], %[spl] \n\t" + "pshufh %[sph], %[spl], %[s44] \n\t" + "pshufh %[spl], %[spl], %[s44] \n\t" + ".set pop \n\t" + :[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l) + :[pix]"r"(pixel), [s44]"f"(s44) + ); + sumPixels = AccumulatePixelSums(sumPixels); + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_paddw(sp, sp, cr) + "pshufh %[crh], %[sph], %[see] \n\t" + "pshufh %[crl], %[sph], %[see] \n\t" + "gslqc1 %[th], %[tl], (%[pr]) \n\t" + _mm_paddw(t, sp, t) + "gssqc1 %[th], %[tl], (%[r]) \n\t" + ".set pop \n\t" + :[th]"=&f"(t.h), [tl]"=&f"(t.l), + [sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[r]"r"(intRow + x), [pr]"r"(intPrevRow + x), [see]"f"(see) + :"memory" + ); + } + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); + __m128i sumPixels, t; + + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "pshufh %[crl], %[crh], %[see] \n\t" + "pshufh %[crh], %[crh], %[see] \n\t" + "mtc1 %[pix], %[spl] \n\t" + "punpcklwd %[spl], %[spl], %[spl] \n\t" + "mov.d %[sph], %[spl] \n\t" + _mm_punpcklbh(sp, sp, zero) + _mm_punpcklhw(sp, sp, zero) + ".set pop \n\t" + :[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[pix]"r"(pixels), [see]"f"(see), + [zeroh]"f"(zero.h), [zerol]"f"(zero.l) + ); + sumPixels = AccumulatePixelSums(sumPixels); + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_paddw(sp, sp, cr) + "mov.d %[crh], %[sph] \n\t" + "mov.d %[crl], %[spl] \n\t" + "gslqc1 %[th], %[tl], (%[pr]) \n\t" + _mm_paddw(t, sp, t) + "gssqc1 %[th], %[tl], (%[r]) \n\t" + ".set pop \n\t" + :[th]"=&f"(t.h), [tl]"=&f"(t.l), + [sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[r]"r"(intRow + x), [pr]"r"(intPrevRow + x) + :"memory" + ); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = ((uint32_t*)¤tRowSum)[(aSize.width % 4) - 1]; + for (; x < integralImageSize.width; x++) { + // We could be unaligned here! + if (!(x & 3)) { + // aligned! + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[cr], %[crl] \n\t" + "punpcklwd %[crl], %[crl], %[crl] \n\t" + "mov.d %[crh], %[crl] \n\t" + ".set pop \n\t" + :[crh]"=f"(currentRowSum.h), [crl]"=f"(currentRowSum.l) + :[cr]"r"(intCurrentRowSum) + ); + break; + } + intCurrentRowSum += pixel; + intRow[x] = intPrevRow[x] + intCurrentRowSum; + } + } else { + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "pshufh %[crl], %[crh], %[see] \n\t" + "pshufh %[crh], %[crh], %[see] \n\t" + ".set pop \n\t" + :[crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[see]"f"(see) + ); + } + for (; x < integralImageSize.width; x += 4) { + __m128i sumPixels, t; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[pix], %[spl] \n\t" + "punpcklwd %[spl], %[spl], %[spl] \n\t" + "mov.d %[sph], %[spl] \n\t" + ".set pop \n\t" + :[sph]"=f"(sumPixels.h), [spl]"=f"(sumPixels.l) + :[pix]"r"(pixel) + ); + sumPixels = AccumulatePixelSums(sumPixels); + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_paddw(sp, sp, cr) + "pshufh %[crh], %[sph], %[see] \n\t" + "pshufh %[crl], %[sph], %[see] \n\t" + "gslqc1 %[th], %[tl], (%[pr]) \n\t" + _mm_paddw(t, sp, t) + "gssqc1 %[th], %[tl], (%[r]) \n\t" + ".set pop \n\t" + :[th]"=&f"(t.h), [tl]"=&f"(t.l), + [sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[r]"r"(intRow + x), [pr]"r"(intPrevRow + x), [see]"f"(see) + :"memory" + ); + } + } + + if (aBottomInflation) { + // Store the last valid row of our source image in the last row of + // our integral image. This will be overwritten with the correct values + // in the upcoming loop. + LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit, + aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation); + + + for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) { + __m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit)); + __m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit); + __m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit); + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i t1, t2; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gslqc1 %[t1h], %[t1l], (%[lr]) \n\t" + "gslqc1 %[t2h], %[t2l], (%[pr]) \n\t" + _mm_paddw(t1, t1, t2) + "gssqc1 %[t1h], %[t1l], (%[r]) \n\t" + ".set pop \n\t" + :[t1h]"=&f"(t1.h), [t1l]"=&f"(t1.l), + [t2h]"=&f"(t2.h), [t2l]"=&f"(t2.l) + :[r]"r"(intRow + (x / 4)), + [lr]"r"(intLastRow + (x / 4)), + [pr]"r"(intPrevRow + (x / 4)) + :"memory" + ); + } + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_LS3(uint8_t* aData, + int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_LS3(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, + mStride, size); + + __m128i divisor, zero; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[rec], %[divl] \n\t" + "punpcklwd %[divl], %[divl], %[divl] \n\t" + "mov.d %[divh], %[divl] \n\t" + _mm_xor(zero, zero, zero) + ".set pop \n\t" + :[divh]"=f"(divisor.h), [divl]"=f"(divisor.l), + [zeroh]"=f"(zero.h), [zerol]"=f"(zero.l) + :[rec]"r"(reciprocal) + ); + + // This points to the start of the rectangle within the IntegralImage that overlaps + // the surface being blurred. + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t *data = aData; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + int32_t x = 0; + // Process 16 pixels at a time for as long as possible. + for (; x <= size.width - 16; x += 16) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 16; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + __m128i topLeft; + __m128i topRight; + __m128i bottomRight; + __m128i bottomLeft; + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + __m128i result1 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 4)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 4)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 4)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 4)); + __m128i result2 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 8)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 8)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 8)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 8)); + __m128i result3 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 12)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 12)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 12)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 12)); + __m128i result4 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + double t; + __m128i final; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_packsswh(r3, r3, r4, t) + _mm_packsswh(f, r1, r2, t) + _mm_packushb(f, f, r3, t) + "gssdlc1 %[fh], 0xf(%[d]) \n\t" + "gssdrc1 %[fh], 0x8(%[d]) \n\t" + "gssdlc1 %[fl], 0x7(%[d]) \n\t" + "gssdrc1 %[fl], 0x0(%[d]) \n\t" + ".set pop \n\t" + :[fh]"=&f"(final.h), [fl]"=&f"(final.l), + [r3h]"+f"(result3.h), [r3l]"+f"(result3.l), + [t]"=&f"(t) + :[r1h]"f"(result1.h), [r1l]"f"(result1.l), + [r2h]"f"(result2.h), [r2l]"f"(result2.l), + [r4h]"f"(result4.h), [r4l]"f"(result4.l), + [d]"r"(data + stride * y + x) + :"memory" + ); + } + + // Process the remaining pixels 4 bytes at a time. + for (; x < size.width; x += 4) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + + __m128i result = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + double t; + __m128i final; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_packsswh(f, r, zero, t) + _mm_packushb(f, f, zero, t) + "swc1 %[fl], (%[d]) \n\t" + ".set pop \n\t" + :[fh]"=&f"(final.h), [fl]"=&f"(final.l), + [t]"=&f"(t) + :[d]"r"(data + stride * y + x), + [rh]"f"(result.h), [rl]"f"(result.l), + [zeroh]"f"(zero.h), [zerol]"f"(zero.l) + :"memory" + ); + } + } + +} + +} +} + +#endif /* _MIPS_ARCH_LOONGSON3A */ diff --git a/gfx/2d/DrawTargetCG.cpp b/gfx/2d/DrawTargetCG.cpp index a45d63f5f4..6e23509d3e 100644 --- a/gfx/2d/DrawTargetCG.cpp +++ b/gfx/2d/DrawTargetCG.cpp @@ -1940,6 +1940,7 @@ DrawTargetCG::Mask(const Pattern &aSource, const Pattern &aMask, const DrawOptions &aDrawOptions) { + MOZ_CRASH("not completely implemented"); MarkChanged(); CGContextSaveGState(mCg); diff --git a/gfx/2d/DrawTargetD2D1.cpp b/gfx/2d/DrawTargetD2D1.cpp index 6ed9464f5c..beeda69406 100644 --- a/gfx/2d/DrawTargetD2D1.cpp +++ b/gfx/2d/DrawTargetD2D1.cpp @@ -254,8 +254,9 @@ DrawTargetD2D1::ClearRect(const Rect &aRect) return; } - mDC->SetTarget(mTempBitmap); - mDC->Clear(); + RefPtr list; + mDC->CreateCommandList(getter_AddRefs(list)); + mDC->SetTarget(list); IntRect addClipRect; RefPtr geom = GetClippedGeometry(&addClipRect); @@ -267,7 +268,9 @@ DrawTargetD2D1::ClearRect(const Rect &aRect) mDC->PopAxisAlignedClip(); mDC->SetTarget(mBitmap); - mDC->DrawImage(mTempBitmap, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_DESTINATION_OUT); + list->Close(); + + mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_DESTINATION_OUT); PopClip(); @@ -284,7 +287,12 @@ DrawTargetD2D1::MaskSurface(const Pattern &aSource, RefPtr bitmap; - RefPtr image = GetImageForSurface(aMask, ExtendMode::CLAMP); + Matrix mat = Matrix::Translation(aOffset); + RefPtr image = GetImageForSurface(aMask, mat, ExtendMode::CLAMP, nullptr); + + MOZ_ASSERT(!mat.HasNonTranslation()); + aOffset.x = mat._31; + aOffset.y = mat._32; if (!image) { gfxWarning() << "Failed to get image for surface."; @@ -296,14 +304,16 @@ DrawTargetD2D1::MaskSurface(const Pattern &aSource, // FillOpacityMask only works if the antialias mode is MODE_ALIASED mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); - IntSize size = aMask->GetSize(); - Rect maskRect = Rect(0.f, 0.f, Float(size.width), Float(size.height)); - image->QueryInterface((ID2D1Bitmap**)&bitmap); + image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); if (!bitmap) { gfxWarning() << "FillOpacityMask only works with Bitmap source surfaces."; return; } + IntSize size = IntSize(bitmap->GetSize().width, bitmap->GetSize().height); + + Rect maskRect = Rect(0.f, 0.f, Float(size.width), Float(size.height)); + Rect dest = Rect(aOffset.x, aOffset.y, Float(size.width), Float(size.height)); RefPtr brush = CreateBrushForPattern(aSource, aOptions.mAlpha); mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS, D2DRect(dest), D2DRect(maskRect)); @@ -854,18 +864,8 @@ DrawTargetD2D1::Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat) mFormat = aFormat; D3D11_TEXTURE2D_DESC desc; aTexture->GetDesc(&desc); - desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; mSize.width = desc.Width; mSize.height = desc.Height; - props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; - - hr = mDC->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props, (ID2D1Bitmap1**)getter_AddRefs(mTempBitmap)); - - if (FAILED(hr)) { - gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(mSize))) << "[D2D1.1] 2CreateBitmap failure " << mSize << " Code: " << hexa(hr); - return false; - } // This single solid color brush system is not very 'threadsafe', however, // issueing multiple drawing commands simultaneously to a single drawtarget @@ -922,16 +922,6 @@ DrawTargetD2D1::Init(const IntSize &aSize, SurfaceFormat aFormat) return false; } - props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; - - hr = mDC->CreateBitmap(D2DIntSize(aSize), nullptr, 0, props, (ID2D1Bitmap1**)getter_AddRefs(mTempBitmap)); - - if (FAILED(hr)) { - gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) << "[D2D1.1] failed to create new TempBitmap " << aSize << " Code: " << hexa(hr); - return false; - } - mDC->SetTarget(mBitmap); hr = mDC->CreateSolidColorBrush(D2D1::ColorF(0, 0), getter_AddRefs(mSolidColorBrush)); @@ -1038,8 +1028,8 @@ DrawTargetD2D1::PrepareForDrawing(CompositionOp aOp, const Pattern &aPattern) PopAllClips(); - mDC->SetTarget(mTempBitmap); - mDC->Clear(D2D1::ColorF(0, 0)); + mDC->CreateCommandList(getter_AddRefs(mCommandList)); + mDC->SetTarget(mCommandList); PushAllClips(); FlushTransformToDC(); @@ -1058,10 +1048,11 @@ DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern) PopAllClips(); - RefPtr image; - mDC->GetTarget(getter_AddRefs(image)); - mDC->SetTarget(mBitmap); + mCommandList->Close(); + + RefPtr source = mCommandList; + mCommandList = nullptr; mDC->SetTransform(D2D1::IdentityMatrix()); mTransformDirty = true; @@ -1089,7 +1080,7 @@ DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern) } else { PushAllClips(); } - mDC->DrawImage(image, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2DCompositionMode(aOp)); + mDC->DrawImage(source, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2DCompositionMode(aOp)); if (tmpBitmap) { RefPtr brush; @@ -1128,7 +1119,7 @@ DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern) tmpBitmap->CopyFromBitmap(nullptr, mBitmap, nullptr); mBlendEffect->SetInput(0, tmpBitmap); - mBlendEffect->SetInput(1, mTempBitmap); + mBlendEffect->SetInput(1, source); mBlendEffect->SetValue(D2D1_BLEND_PROP_MODE, D2DBlendMode(aOp)); PushAllClips(); @@ -1161,7 +1152,7 @@ DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern) radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_2, pat->mRadius2); radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_2, pat->mRadius2); radialGradientEffect->SetValue(RADIAL_PROP_TRANSFORM, D2DMatrix(pat->mMatrix * mTransform)); - radialGradientEffect->SetInput(0, image); + radialGradientEffect->SetInput(0, source); mDC->DrawImage(radialGradientEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2DCompositionMode(aOp)); } @@ -1473,7 +1464,6 @@ DrawTargetD2D1::CreateBrushForPattern(const Pattern &aPattern, Float aAlpha) return CreateTransparentBlackBrush(); } - bool useSamplingRect = false; if (pat->mSamplingRect.IsEmpty()) { RefPtr bitmap; image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); diff --git a/gfx/2d/DrawTargetD2D1.h b/gfx/2d/DrawTargetD2D1.h index f0793c8e02..c015b48585 100644 --- a/gfx/2d/DrawTargetD2D1.h +++ b/gfx/2d/DrawTargetD2D1.h @@ -200,7 +200,7 @@ private: IntRect mCurrentClipBounds; mutable RefPtr mDC; RefPtr mBitmap; - RefPtr mTempBitmap; + RefPtr mCommandList; RefPtr mBlendEffect; RefPtr mSolidColorBrush; diff --git a/gfx/2d/Factory.cpp b/gfx/2d/Factory.cpp index 535ffff5e3..e01d78cc04 100644 --- a/gfx/2d/Factory.cpp +++ b/gfx/2d/Factory.cpp @@ -241,6 +241,17 @@ Factory::HasSSE2() #endif } +bool +Factory::HasVMX() +{ +#if (defined(__powerpc__) || defined(__POWERPC__)) \ + && (defined(__ALTIVEC__) || defined(__APPLE_ALTIVEC__)) + return true; +#else + return false; +#endif +} + // If the size is "reasonable", we want gfxCriticalError to assert, so // this is the option set up for it. inline int LoggerOptionsBasedOnSize(const IntSize& aSize) diff --git a/gfx/2d/Matrix.cpp b/gfx/2d/Matrix.cpp index d7059e4d0b..0bc67fdb66 100644 --- a/gfx/2d/Matrix.cpp +++ b/gfx/2d/Matrix.cpp @@ -186,8 +186,10 @@ Matrix4x4::TransformBounds(const RectTyped& aRect) const return RectTyped(min_x, min_y, max_x - min_x, max_y - min_y); } -Point4D ComputePerspectivePlaneIntercept(const Point4D& aFirst, - const Point4D& aSecond) +template +Point4DTyped +ComputePerspectivePlaneIntercept(const Point4DTyped& aFirst, + const Point4DTyped& aSecond) { // This function will always return a point with a w value of 0. // The X, Y, and Z components will point towards an infinite vanishing @@ -204,7 +206,9 @@ Point4D ComputePerspectivePlaneIntercept(const Point4D& aFirst, return aFirst + (aSecond - aFirst) * t; } -Rect Matrix4x4::ProjectRectBounds(const Rect& aRect, const Rect &aClip) const +template +RectTyped +Matrix4x4::ProjectRectBounds(const RectTyped& aRect, const RectTyped& aClip) const { // This function must never return std::numeric_limits::max() or any // other arbitrary large value in place of inifinity. This often occurs when @@ -230,26 +234,26 @@ Rect Matrix4x4::ProjectRectBounds(const Rect& aRect, const Rect &aClip) const // Callers should pass an aClip value that represents the extents to clip // the result to, in the same coordinate system as aRect. - Point4D points[4]; + Point4DTyped points[4]; points[0] = ProjectPoint(aRect.TopLeft()); points[1] = ProjectPoint(aRect.TopRight()); points[2] = ProjectPoint(aRect.BottomRight()); points[3] = ProjectPoint(aRect.BottomLeft()); - Float min_x = std::numeric_limits::max(); - Float min_y = std::numeric_limits::max(); - Float max_x = -std::numeric_limits::max(); - Float max_y = -std::numeric_limits::max(); + F min_x = std::numeric_limits::max(); + F min_y = std::numeric_limits::max(); + F max_x = -std::numeric_limits::max(); + F max_y = -std::numeric_limits::max(); for (int i=0; i<4; i++) { // Only use points that exist above the w=0 plane if (points[i].HasPositiveWCoord()) { - Point point2d = aClip.ClampPoint(points[i].As2DPoint()); - min_x = std::min(point2d.x, min_x); - max_x = std::max(point2d.x, max_x); - min_y = std::min(point2d.y, min_y); - max_y = std::max(point2d.y, max_y); + PointTyped point2d = aClip.ClampPoint(points[i].As2DPoint()); + min_x = std::min(point2d.x, min_x); + max_x = std::max(point2d.x, max_x); + min_y = std::min(point2d.y, min_y); + max_y = std::max(point2d.y, max_y); } int next = (i == 3) ? 0 : i + 1; @@ -257,7 +261,8 @@ Rect Matrix4x4::ProjectRectBounds(const Rect& aRect, const Rect &aClip) const // If the line between two points crosses the w=0 plane, then interpolate // to find the point of intersection with the w=0 plane and use that // instead. - Point4D intercept = ComputePerspectivePlaneIntercept(points[i], points[next]); + Point4DTyped intercept = + ComputePerspectivePlaneIntercept(points[i], points[next]); // Since intercept.w will always be 0 here, we interpret x,y,z as a // direction towards an infinite vanishing point. if (intercept.x < 0.0f) { @@ -273,11 +278,11 @@ Rect Matrix4x4::ProjectRectBounds(const Rect& aRect, const Rect &aClip) const } } - if (max_x <= min_x || max_y <= min_y) { - return Rect(0, 0, 0, 0); + if (max_x < min_x || max_y < min_y) { + return RectTyped(0, 0, 0, 0); } - return Rect(min_x, min_y, max_x - min_x, max_y - min_y); + return RectTyped(min_x, min_y, max_x - min_x, max_y - min_y); } template @@ -638,5 +643,14 @@ template RectDouble Matrix4x4::TransformBounds(const RectDouble& aRect) const; +template +Rect +Matrix4x4::ProjectRectBounds(const Rect& aRect, const Rect& aClip) const; + +template +RectDouble +Matrix4x4::ProjectRectBounds(const RectDouble& aRect, const RectDouble& aClip) const; + + } // namespace gfx } // namespace mozilla diff --git a/gfx/2d/Matrix.h b/gfx/2d/Matrix.h index b57fc97234..cb2134647f 100644 --- a/gfx/2d/Matrix.h +++ b/gfx/2d/Matrix.h @@ -507,20 +507,24 @@ public: return *this; } - Point4D ProjectPoint(const Point& aPoint) const { + template + Point4DTyped + ProjectPoint(const PointTyped& aPoint) const { // Find a value for z that will transform to 0. // The transformed value of z is computed as: // z' = aPoint.x * _13 + aPoint.y * _23 + z * _33 + _43; // Solving for z when z' = 0 gives us: - float z = -(aPoint.x * _13 + aPoint.y * _23 + _43) / _33; + F z = -(aPoint.x * _13 + aPoint.y * _23 + _43) / _33; // Compute the transformed point - return *this * Point4D(aPoint.x, aPoint.y, z, 1); + return *this * Point4DTyped(aPoint.x, aPoint.y, z, 1); } - Rect ProjectRectBounds(const Rect& aRect, const Rect &aClip) const; + template + RectTyped + ProjectRectBounds(const RectTyped& aRect, const RectTyped& aClip) const; /** * TransformAndClipBounds transforms aRect as a bounding box, while clipping diff --git a/gfx/2d/ScaleFactors2D.h b/gfx/2d/ScaleFactors2D.h index 3327b4ce9f..b8f34e6419 100644 --- a/gfx/2d/ScaleFactors2D.h +++ b/gfx/2d/ScaleFactors2D.h @@ -7,7 +7,6 @@ #define MOZILLA_GFX_SCALEFACTORS2D_H_ #include -#include #include "mozilla/Attributes.h" #include "mozilla/FloatingPoint.h" diff --git a/gfx/2d/ScaledFontDWrite.h b/gfx/2d/ScaledFontDWrite.h index a517ab55db..5e1a564a48 100644 --- a/gfx/2d/ScaledFontDWrite.h +++ b/gfx/2d/ScaledFontDWrite.h @@ -19,8 +19,8 @@ class ScaledFontDWrite final : public ScaledFontBase public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontDwrite) ScaledFontDWrite(IDWriteFontFace *aFont, Float aSize) - : mFontFace(aFont) - , ScaledFontBase(aSize) + : ScaledFontBase(aSize) + , mFontFace(aFont) {} ScaledFontDWrite(uint8_t *aData, uint32_t aSize, uint32_t aIndex, Float aGlyphSize); diff --git a/gfx/2d/SourceSurfaceD2D1.cpp b/gfx/2d/SourceSurfaceD2D1.cpp index 5dcbe968f6..bab1fc8dad 100644 --- a/gfx/2d/SourceSurfaceD2D1.cpp +++ b/gfx/2d/SourceSurfaceD2D1.cpp @@ -15,8 +15,8 @@ SourceSurfaceD2D1::SourceSurfaceD2D1(ID2D1Image *aImage, ID2D1DeviceContext *aDC DrawTargetD2D1 *aDT) : mImage(aImage) , mDC(aDC) - , mDrawTarget(aDT) , mDevice(Factory::GetD2D1Device()) + , mDrawTarget(aDT) { aImage->QueryInterface((ID2D1Bitmap1**)getter_AddRefs(mRealizedBitmap)); diff --git a/gfx/2d/SourceSurfaceDual.h b/gfx/2d/SourceSurfaceDual.h index d392459e87..df81b87f26 100644 --- a/gfx/2d/SourceSurfaceDual.h +++ b/gfx/2d/SourceSurfaceDual.h @@ -27,8 +27,12 @@ public: virtual IntSize GetSize() const { return mA->GetSize(); } virtual SurfaceFormat GetFormat() const { return mA->GetFormat(); } - /* Readback from this surface type is not supported! */ - virtual already_AddRefed GetDataSurface() { return nullptr; } + // This is implemented for debugging purposes only (used by dumping + // client-side textures for paint dumps), for which we don't care about + // component alpha, so we just use the first of the two surfaces. + virtual already_AddRefed GetDataSurface() { + return mA->GetDataSurface(); + } private: friend class DualSurface; friend class DualPattern; diff --git a/gfx/2d/SourceSurfaceRawData.h b/gfx/2d/SourceSurfaceRawData.h index e5773b6cc5..1d43e04cee 100644 --- a/gfx/2d/SourceSurfaceRawData.h +++ b/gfx/2d/SourceSurfaceRawData.h @@ -16,7 +16,7 @@ namespace gfx { class SourceSurfaceRawData : public DataSourceSurface { public: - MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceRawData) + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceRawData, override) SourceSurfaceRawData() : mMapCount(0) {} @@ -26,12 +26,12 @@ public: MOZ_ASSERT(mMapCount == 0); } - virtual uint8_t *GetData() { return mRawData; } - virtual int32_t Stride() { return mStride; } + virtual uint8_t *GetData() override { return mRawData; } + virtual int32_t Stride() override { return mStride; } - virtual SurfaceType GetType() const { return SurfaceType::DATA; } - virtual IntSize GetSize() const { return mSize; } - virtual SurfaceFormat GetFormat() const { return mFormat; } + virtual SurfaceType GetType() const override { return SurfaceType::DATA; } + virtual IntSize GetSize() const override { return mSize; } + virtual SurfaceFormat GetFormat() const override { return mFormat; } bool InitWrappingData(unsigned char *aData, const IntSize &aSize, @@ -39,7 +39,7 @@ public: SurfaceFormat aFormat, bool aOwnData); - virtual void GuaranteePersistance(); + virtual void GuaranteePersistance() override; // Althought Map (and Moz2D in general) isn't normally threadsafe, // we want to allow it for SourceSurfaceRawData since it should @@ -79,7 +79,7 @@ private: class SourceSurfaceAlignedRawData : public DataSourceSurface { public: - MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceAlignedRawData) + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceAlignedRawData, override) SourceSurfaceAlignedRawData() : mMapCount(0) {} @@ -88,12 +88,12 @@ public: MOZ_ASSERT(mMapCount == 0); } - virtual uint8_t *GetData() { return mArray; } - virtual int32_t Stride() { return mStride; } + virtual uint8_t *GetData() override { return mArray; } + virtual int32_t Stride() override { return mStride; } - virtual SurfaceType GetType() const { return SurfaceType::DATA; } - virtual IntSize GetSize() const { return mSize; } - virtual SurfaceFormat GetFormat() const { return mFormat; } + virtual SurfaceType GetType() const override { return SurfaceType::DATA; } + virtual IntSize GetSize() const override { return mSize; } + virtual SurfaceFormat GetFormat() const override { return mFormat; } bool Init(const IntSize &aSize, SurfaceFormat aFormat, diff --git a/gfx/2d/convolverLS3.cpp b/gfx/2d/convolverLS3.cpp index 83006cf402..8c6b26cafd 100644 --- a/gfx/2d/convolverLS3.cpp +++ b/gfx/2d/convolverLS3.cpp @@ -37,13 +37,13 @@ namespace skia { // Convolves horizontally along a single row. The row data is given in -// |src_data| and continues for the [begin, end) of the filter. +// |src_data| and continues for the num_values() of the filter. void ConvolveHorizontally_LS3(const unsigned char* src_data, - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row) { + int num_values = filter.num_values(); int tmp, filter_offset, filter_length; - double zero, mask[3], shuf_50, shuf_fa; + double zero, mask[4], shuf_50, shuf_fa; asm volatile ( ".set push \n\t" @@ -51,27 +51,28 @@ void ConvolveHorizontally_LS3(const unsigned char* src_data, "xor %[zero], %[zero], %[zero] \n\t" // |mask| will be used to decimate all extra filter coefficients that are // loaded by SIMD when |filter_length| is not divisible by 4. + // mask[0] is not used in following algorithm. "li %[tmp], 1 \n\t" "dsll32 %[tmp], 0x10 \n\t" "daddiu %[tmp], -1 \n\t" - "dmtc1 %[tmp], %[mask2] \n\t" + "dmtc1 %[tmp], %[mask3] \n\t" + "dsrl %[tmp], 0x10 \n\t" + "mtc1 %[tmp], %[mask2] \n\t" "dsrl %[tmp], 0x10 \n\t" "mtc1 %[tmp], %[mask1] \n\t" - "dsrl %[tmp], 0x10 \n\t" - "mtc1 %[tmp], %[mask0] \n\t" "ori %[tmp], $0, 0x50 \n\t" "mtc1 %[tmp], %[shuf_50] \n\t" "ori %[tmp], $0, 0xfa \n\t" "mtc1 %[tmp], %[shuf_fa] \n\t" ".set pop \n\t" - :[zero]"=f"(zero), [mask0]"=f"(mask[0]), - [mask1]"=f"(mask[1]), [mask2]"=f"(mask[2]), + :[zero]"=f"(zero), [mask1]"=f"(mask[1]), + [mask2]"=f"(mask[2]), [mask3]"=f"(mask[3]), [shuf_50]"=f"(shuf_50), [shuf_fa]"=f"(shuf_fa), [tmp]"=&r"(tmp) ); // Output one pixel each iteration, calculating all channels (RGBA) together. - for (int out_x = begin; out_x < end; out_x++) { + for (int out_x = 0; out_x < num_values; out_x++) { const ConvolutionFilter1D::Fixed* filter_values = filter.FilterForValue(out_x, &filter_offset, &filter_length); double accumh, accuml; @@ -203,7 +204,7 @@ void ConvolveHorizontally_LS3(const unsigned char* src_data, [mul_hih]"=&f"(mul_hih), [mul_hil]"=&f"(mul_hil), [mul_loh]"=&f"(mul_loh), [mul_lol]"=&f"(mul_lol) :[fval]"r"(filter_values), [rtf]"r"(row_to_filter), - [zeroh]"f"(zero), [zerol]"f"(zero), [mask]"f"(mask[r-1]), + [zeroh]"f"(zero), [zerol]"f"(zero), [mask]"f"(mask[r]), [shuf_50]"f"(shuf_50), [shuf_fa]"f"(shuf_fa) ); } @@ -235,15 +236,15 @@ void ConvolveHorizontally_LS3(const unsigned char* src_data, } // Convolves horizontally along four rows. The row data is given in -// |src_data| and continues for the [begin, end) of the filter. +// |src_data| and continues for the num_values() of the filter. // The algorithm is almost same as |ConvolveHorizontally_LS3|. Please // refer to that function for detailed comments. void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row[4]) { + int num_values = filter.num_values(); int tmp, filter_offset, filter_length; - double zero, mask[3], shuf_50, shuf_fa; + double zero, mask[4], shuf_50, shuf_fa; asm volatile ( ".set push \n\t" @@ -251,27 +252,28 @@ void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], "xor %[zero], %[zero], %[zero] \n\t" // |mask| will be used to decimate all extra filter coefficients that are // loaded by SIMD when |filter_length| is not divisible by 4. + // mask[0] is not used in following algorithm. "li %[tmp], 1 \n\t" "dsll32 %[tmp], 0x10 \n\t" "daddiu %[tmp], -1 \n\t" - "dmtc1 %[tmp], %[mask2] \n\t" + "dmtc1 %[tmp], %[mask3] \n\t" + "dsrl %[tmp], 0x10 \n\t" + "mtc1 %[tmp], %[mask2] \n\t" "dsrl %[tmp], 0x10 \n\t" "mtc1 %[tmp], %[mask1] \n\t" - "dsrl %[tmp], 0x10 \n\t" - "mtc1 %[tmp], %[mask0] \n\t" "ori %[tmp], $0, 0x50 \n\t" "mtc1 %[tmp], %[shuf_50] \n\t" "ori %[tmp], $0, 0xfa \n\t" "mtc1 %[tmp], %[shuf_fa] \n\t" ".set pop \n\t" - :[zero]"=f"(zero), [mask0]"=f"(mask[0]), - [mask1]"=f"(mask[1]), [mask2]"=f"(mask[2]), + :[zero]"=f"(zero), [mask1]"=f"(mask[1]), + [mask2]"=f"(mask[2]), [mask3]"=f"(mask[3]), [shuf_50]"=f"(shuf_50), [shuf_fa]"=f"(shuf_fa), [tmp]"=&r"(tmp) ); // Output one pixel each iteration, calculating all channels (RGBA) together. - for (int out_x = begin; out_x < end; out_x++) { + for (int out_x = 0; out_x < num_values; out_x++) { const ConvolutionFilter1D::Fixed* filter_values = filter.FilterForValue(out_x, &filter_offset, &filter_length); double accum0h, accum0l, accum1h, accum1l; @@ -385,7 +387,7 @@ void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], :[coeffh]"=&f"(coeffh), [coeffl]"=&f"(coeffl), [coeff16loh]"=&f"(coeff16loh), [coeff16lol]"=&f"(coeff16lol), [coeff16hih]"=&f"(coeff16hih), [coeff16hil]"=&f"(coeff16hil) - :[fval]"r"(filter_values), [mask]"f"(mask[r-1]), + :[fval]"r"(filter_values), [mask]"f"(mask[r]), [shuf_50]"f"(shuf_50), [shuf_fa]"f"(shuf_fa) ); @@ -440,16 +442,17 @@ void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], // Does vertical convolution to produce one output row. The filter values and // length are given in the first two parameters. These are applied to each // of the rows pointed to in the |source_data_rows| array, with each row -// being |end - begin| wide. +// being |pixel_width| wide. // -// The output must have room for |(end - begin) * 4| bytes. +// The output must have room for |pixel_width * 4| bytes. template void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values, int filter_length, unsigned char* const* source_data_rows, - int begin, int end, + int pixel_width, unsigned char* out_row) { uint64_t tmp; + int width = pixel_width & ~3; double zero, sra, coeff16h, coeff16l; double accum0h, accum0l, accum1h, accum1l; double accum2h, accum2l, accum3h, accum3l; @@ -468,7 +471,7 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values ); // Output four pixels per iteration (16 bytes). - for (out_x = begin; out_x + 3 < end; out_x += 4) { + for (out_x = 0; out_x < width; out_x += 4) { // Accumulated result for each pixel. 32 bits per RGBA channel. asm volatile ( ".set push \n\t" @@ -651,8 +654,7 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values // When the width of the output is not divisible by 4, We need to save one // pixel (4 bytes) each time. And also the fourth pixel is always absent. - int r = end - out_x; - if (r > 0) { + if (pixel_width & 3) { asm volatile ( ".set push \n\t" ".set arch=loongson3a \n\t" @@ -793,7 +795,7 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values :[s4]"=f"(s4), [s64]"=f"(s64), [tmp]"=&r"(tmp) ); - for (; out_x < end; out_x++) { + for (int out_x = width; out_x < pixel_width; out_x++) { double t; asm volatile ( @@ -815,14 +817,14 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values void ConvolveVertically_LS3(const ConvolutionFilter1D::Fixed* filter_values, int filter_length, unsigned char* const* source_data_rows, - int begin, int end, + int pixel_width, unsigned char* out_row, bool has_alpha) { if (has_alpha) { ConvolveVertically_LS3_impl(filter_values, filter_length, - source_data_rows, begin, end, out_row); + source_data_rows, pixel_width, out_row); } else { ConvolveVertically_LS3_impl(filter_values, filter_length, - source_data_rows, begin, end, out_row); + source_data_rows, pixel_width, out_row); } } diff --git a/gfx/2d/convolverLS3.h b/gfx/2d/convolverLS3.h index 7ee26bdc4f..c97fe69104 100644 --- a/gfx/2d/convolverLS3.h +++ b/gfx/2d/convolverLS3.h @@ -40,7 +40,6 @@ namespace skia { // Convolves horizontally along a single row. The row data is given in // |src_data| and continues for the [begin, end) of the filter. void ConvolveHorizontally_LS3(const unsigned char* src_data, - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row); @@ -49,7 +48,6 @@ void ConvolveHorizontally_LS3(const unsigned char* src_data, // The algorithm is almost same as |ConvolveHorizontally_LS3|. Please // refer to that function for detailed comments. void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row[4]); @@ -62,7 +60,7 @@ void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], void ConvolveVertically_LS3(const ConvolutionFilter1D::Fixed* filter_values, int filter_length, unsigned char* const* source_data_rows, - int begin, int end, + int pixel_width, unsigned char* out_row, bool has_alpha); } // namespace skia diff --git a/gfx/2d/moz.build b/gfx/2d/moz.build index 3fc089af39..16f23a8d1e 100644 --- a/gfx/2d/moz.build +++ b/gfx/2d/moz.build @@ -115,6 +115,9 @@ if CONFIG['INTEL_ARCHITECTURE']: if CONFIG['MOZ_ENABLE_SKIA']: SOURCES['convolverSSE2.cpp'].flags += CONFIG['SSE2_FLAGS'] elif CONFIG['CPU_ARCH'].startswith('mips'): + SOURCES += [ + 'BlurLS3.cpp', + ] if CONFIG['MOZ_ENABLE_SKIA']: SOURCES += [ 'convolverLS3.cpp', diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 4c8e1c74ea..9192ce834f 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -77,6 +77,7 @@ static const char *sExtensionNames[] = { "GL_ANGLE_timer_query", "GL_APPLE_client_storage", "GL_APPLE_framebuffer_multisample", + "GL_APPLE_sync", "GL_APPLE_texture_range", "GL_APPLE_vertex_array_object", "GL_ARB_ES2_compatibility", @@ -780,7 +781,6 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl) } if (!IsSupported(GLFeature::framebuffer_object)) { - // Check for aux symbols based on extensions if (IsSupported(GLFeature::framebuffer_object_EXT_OES)) { @@ -808,11 +808,7 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl) } } - if (IsExtensionSupported(GLContext::ANGLE_framebuffer_blit) || - IsExtensionSupported(GLContext::EXT_framebuffer_blit) || - IsExtensionSupported(GLContext::NV_framebuffer_blit)) - - { + if (IsSupported(GLFeature::framebuffer_blit)) { SymLoadStruct extSymbols[] = { EXT_SYMBOL3(BlitFramebuffer, ANGLE, EXT, NV), END_SYMBOLS @@ -823,11 +819,7 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl) } } - if (IsExtensionSupported(GLContext::ANGLE_framebuffer_multisample) || - IsExtensionSupported(GLContext::APPLE_framebuffer_multisample) || - IsExtensionSupported(GLContext::EXT_framebuffer_multisample) || - IsExtensionSupported(GLContext::EXT_multisampled_render_to_texture)) - { + if (IsSupported(GLFeature::framebuffer_multisample)) { SymLoadStruct extSymbols[] = { EXT_SYMBOL3(RenderbufferStorageMultisample, ANGLE, APPLE, EXT), END_SYMBOLS @@ -1428,10 +1420,11 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl) } if (IsSupported(GLFeature::uniform_buffer_object)) { + // Note: Don't query for glGetActiveUniformName because it is not + // supported by GL ES 3. SymLoadStruct uboSymbols[] = { { (PRFuncPtr*) &mSymbols.fGetUniformIndices, { "GetUniformIndices", nullptr } }, { (PRFuncPtr*) &mSymbols.fGetActiveUniformsiv, { "GetActiveUniformsiv", nullptr } }, - { (PRFuncPtr*) &mSymbols.fGetActiveUniformName, { "GetActiveUniformName", nullptr } }, { (PRFuncPtr*) &mSymbols.fGetUniformBlockIndex, { "GetUniformBlockIndex", nullptr } }, { (PRFuncPtr*) &mSymbols.fGetActiveUniformBlockiv, { "GetActiveUniformBlockiv", nullptr } }, { (PRFuncPtr*) &mSymbols.fGetActiveUniformBlockName, { "GetActiveUniformBlockName", nullptr } }, @@ -1542,7 +1535,7 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl) if (IsSupported(GLFeature::read_buffer)) { SymLoadStruct extSymbols[] = { - { (PRFuncPtr*) &mSymbols.fReadBuffer, { "ReadBuffer", nullptr } }, + { (PRFuncPtr*) &mSymbols.fReadBuffer, { "ReadBuffer", nullptr } }, END_SYMBOLS }; @@ -1554,6 +1547,20 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl) } } + if (IsExtensionSupported(APPLE_framebuffer_multisample)) { + SymLoadStruct extSymbols[] = { + { (PRFuncPtr*) &mSymbols.fResolveMultisampleFramebufferAPPLE, { "ResolveMultisampleFramebufferAPPLE", nullptr } }, + END_SYMBOLS + }; + + if (!LoadSymbols(&extSymbols[0], trygl, prefix)) { + NS_ERROR("GL supports APPLE_framebuffer_multisample without supplying its functions."); + + MarkExtensionUnsupported(APPLE_framebuffer_multisample); + ClearSymbols(extSymbols); + } + } + // Load developer symbols, don't fail if we can't find them. SymLoadStruct auxSymbols[] = { { (PRFuncPtr*) &mSymbols.fGetTexImage, { "GetTexImage", nullptr } }, @@ -2906,7 +2913,7 @@ GLContext::GetReadFB() if (mScreen) return mScreen->GetReadFB(); - GLenum bindEnum = IsSupported(GLFeature::framebuffer_blit) + GLenum bindEnum = IsSupported(GLFeature::split_framebuffer) ? LOCAL_GL_READ_FRAMEBUFFER_BINDING_EXT : LOCAL_GL_FRAMEBUFFER_BINDING; diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index 58501d63c0..b9391eea13 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -124,6 +124,7 @@ enum class GLFeature { sRGB_framebuffer, sRGB_texture, sampler_objects, + split_framebuffer, standard_derivatives, sync, texture_3D, @@ -390,6 +391,7 @@ public: ANGLE_timer_query, APPLE_client_storage, APPLE_framebuffer_multisample, + APPLE_sync, APPLE_texture_range, APPLE_vertex_array_object, ARB_ES2_compatibility, @@ -3011,15 +3013,6 @@ public: AFTER_GL_CALL; } - void fGetActiveUniformName(GLuint program, GLuint uniformIndex, GLsizei bufSize, - GLsizei* length, GLchar* uniformName) - { - ASSERT_SYMBOL_PRESENT(fGetActiveUniformName); - BEFORE_GL_CALL; - mSymbols.fGetActiveUniformName(program, uniformIndex, bufSize, length, uniformName); - AFTER_GL_CALL; - } - GLuint fGetUniformBlockIndex(GLuint program, const GLchar* uniformBlockName) { ASSERT_SYMBOL_PRESENT(fGetUniformBlockIndex); BEFORE_GL_CALL; @@ -3148,6 +3141,16 @@ public: return ret; } +// ----------------------------------------------------------------------------- +// APPLE_framebuffer_multisample + + void fResolveMultisampleFramebufferAPPLE() { + BEFORE_GL_CALL; + ASSERT_SYMBOL_PRESENT(fResolveMultisampleFramebufferAPPLE); + mSymbols.fResolveMultisampleFramebufferAPPLE(); + AFTER_GL_CALL; + } + // ----------------------------------------------------------------------------- // Constructor protected: diff --git a/gfx/gl/GLContextFeatures.cpp b/gfx/gl/GLContextFeatures.cpp index 780a995016..8234374f56 100644 --- a/gfx/gl/GLContextFeatures.cpp +++ b/gfx/gl/GLContextFeatures.cpp @@ -547,6 +547,20 @@ static const FeatureInfo sFeatureInfoArr[] = { GLContext::Extensions_End } }, + { + // Do we have separate DRAW and READ framebuffer bind points? + "split_framebuffer", + GLVersion::GL3, + GLESVersion::ES3, + GLContext::ARB_framebuffer_object, + { + GLContext::ANGLE_framebuffer_blit, + GLContext::APPLE_framebuffer_multisample, + GLContext::EXT_framebuffer_blit, + GLContext::NV_framebuffer_blit, + GLContext::Extensions_End + } + }, { "standard_derivatives", GLVersion::GL2, @@ -561,8 +575,10 @@ static const FeatureInfo sFeatureInfoArr[] = { "sync", GLVersion::GL3_2, GLESVersion::ES3, - GLContext::ARB_sync, + GLContext::Extension_None, { + GLContext::ARB_sync, + GLContext::APPLE_sync, GLContext::Extensions_End } }, diff --git a/gfx/gl/GLContextSymbols.h b/gfx/gl/GLContextSymbols.h index 3c42c56f06..efed07959d 100644 --- a/gfx/gl/GLContextSymbols.h +++ b/gfx/gl/GLContextSymbols.h @@ -614,8 +614,6 @@ struct GLContextSymbols typedef void (GLAPIENTRY * PFNGLGETACTIVEUNIFORMSIVPROC) (GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params); PFNGLGETACTIVEUNIFORMSIVPROC fGetActiveUniformsiv; - typedef void (GLAPIENTRY * PFNGLGETACTIVEUNIFORMNAMEPROC) (GLuint program, GLuint uniformIdex, GLsizei bufSize, GLsizei* length, GLchar* uniformName); - PFNGLGETACTIVEUNIFORMNAMEPROC fGetActiveUniformName; typedef GLuint (GLAPIENTRY * PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar* uniformBlockName); PFNGLGETUNIFORMBLOCKINDEXPROC fGetUniformBlockIndex; typedef void (GLAPIENTRY * PFNGLGETACTIVEUNIFORMBLOCKIVPROC) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params); @@ -675,19 +673,23 @@ struct GLContextSymbols GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); PFNGLCOPYTEXSUBIMAGE3DPROC fCopyTexSubImage3D; - typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE3D) (GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth, - GLint border, GLsizei imageSize, const GLvoid* data); - PFNGLCOMPRESSEDTEXIMAGE3D fCompressedTexImage3D; - typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE3D) (GLenum target, GLint level, - GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLsizei imageSize, const GLvoid* data); - PFNGLCOMPRESSEDTEXSUBIMAGE3D fCompressedTexSubImage3D; + typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, + GLsizei width, GLsizei height, GLsizei depth, + GLint border, GLsizei imageSize, const GLvoid* data); + PFNGLCOMPRESSEDTEXIMAGE3DPROC fCompressedTexImage3D; + typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, + GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, + GLenum format, GLsizei imageSize, const GLvoid* data); + PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC fCompressedTexSubImage3D; // get_string_indexed - typedef const GLubyte* (GLAPIENTRY * pfnGLGetStringiT)(GLenum name, GLuint index); - pfnGLGetStringiT fGetStringi; + typedef const GLubyte* (GLAPIENTRY * PFNGLGETSTRINGIPROC)(GLenum name, GLuint index); + PFNGLGETSTRINGIPROC fGetStringi; + + // APPLE_framebuffer_multisample + typedef void (GLAPIENTRY * PFNRESOLVEMULTISAMPLEFRAMEBUFFERAPPLE) (void); + PFNRESOLVEMULTISAMPLEFRAMEBUFFERAPPLE fResolveMultisampleFramebufferAPPLE; }; } // namespace gl diff --git a/gfx/gl/GLScreenBuffer.cpp b/gfx/gl/GLScreenBuffer.cpp index 27ecc644b9..f6dbad7c91 100755 --- a/gfx/gl/GLScreenBuffer.cpp +++ b/gfx/gl/GLScreenBuffer.cpp @@ -151,7 +151,7 @@ GLScreenBuffer::BindAsFramebuffer(GLContext* const gl, GLenum target) const GLuint drawFB = DrawFB(); GLuint readFB = ReadFB(); - if (!gl->IsSupported(GLFeature::framebuffer_blit)) { + if (!gl->IsSupported(GLFeature::split_framebuffer)) { MOZ_ASSERT(drawFB == readFB); gl->raw_fBindFramebuffer(target, readFB); return; @@ -164,16 +164,10 @@ GLScreenBuffer::BindAsFramebuffer(GLContext* const gl, GLenum target) const break; case LOCAL_GL_DRAW_FRAMEBUFFER_EXT: - if (!gl->IsSupported(GLFeature::framebuffer_blit)) - NS_WARNING("DRAW_FRAMEBUFFER requested but unavailable."); - gl->raw_fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER_EXT, drawFB); break; case LOCAL_GL_READ_FRAMEBUFFER_EXT: - if (!gl->IsSupported(GLFeature::framebuffer_blit)) - NS_WARNING("READ_FRAMEBUFFER requested but unavailable."); - gl->raw_fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER_EXT, readFB); break; @@ -196,7 +190,7 @@ GLScreenBuffer::BindFB(GLuint fb) if (mInternalDrawFB == mInternalReadFB) { mGL->raw_fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mInternalDrawFB); } else { - MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit)); + MOZ_ASSERT(mGL->IsSupported(GLFeature::split_framebuffer)); mGL->raw_fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER_EXT, mInternalDrawFB); mGL->raw_fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER_EXT, mInternalReadFB); } @@ -210,7 +204,7 @@ GLScreenBuffer::BindFB(GLuint fb) void GLScreenBuffer::BindDrawFB(GLuint fb) { - MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit)); + MOZ_ASSERT(mGL->IsSupported(GLFeature::split_framebuffer)); GLuint drawFB = DrawFB(); mUserDrawFB = fb; @@ -226,7 +220,7 @@ GLScreenBuffer::BindDrawFB(GLuint fb) void GLScreenBuffer::BindReadFB(GLuint fb) { - MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit)); + MOZ_ASSERT(mGL->IsSupported(GLFeature::split_framebuffer)); GLuint readFB = ReadFB(); mUserReadFB = fb; @@ -255,7 +249,7 @@ GLScreenBuffer::BindFB_Internal(GLuint fb) void GLScreenBuffer::BindDrawFB_Internal(GLuint fb) { - MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit)); + MOZ_ASSERT(mGL->IsSupported(GLFeature::split_framebuffer)); mInternalDrawFB = mUserDrawFB = fb; mGL->raw_fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER_EXT, mInternalDrawFB); @@ -268,7 +262,7 @@ GLScreenBuffer::BindDrawFB_Internal(GLuint fb) void GLScreenBuffer::BindReadFB_Internal(GLuint fb) { - MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit)); + MOZ_ASSERT(mGL->IsSupported(GLFeature::split_framebuffer)); mInternalReadFB = mUserReadFB = fb; mGL->raw_fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER_EXT, mInternalReadFB); @@ -314,7 +308,7 @@ GLScreenBuffer::GetReadFB() const // We use raw_ here because this is debug code and we need to see what // the driver thinks. GLuint actual = 0; - if (mGL->IsSupported(GLFeature::framebuffer_blit)) + if (mGL->IsSupported(GLFeature::split_framebuffer)) mGL->raw_fGetIntegerv(LOCAL_GL_READ_FRAMEBUFFER_BINDING_EXT, (GLint*)&actual); else mGL->raw_fGetIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, (GLint*)&actual); @@ -429,7 +423,7 @@ GLScreenBuffer::AssureBlitted() MOZ_ASSERT(drawFB != 0); MOZ_ASSERT(drawFB != readFB); - MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit)); + MOZ_ASSERT(mGL->IsSupported(GLFeature::split_framebuffer)); MOZ_ASSERT(mDraw->mSize == mRead->Size()); ScopedBindFramebuffer boundFB(mGL); @@ -438,13 +432,19 @@ GLScreenBuffer::AssureBlitted() BindReadFB_Internal(drawFB); BindDrawFB_Internal(readFB); - const gfx::IntSize& srcSize = mDraw->mSize; - const gfx::IntSize& destSize = mRead->Size(); + if (mGL->IsSupported(GLFeature::framebuffer_blit)) { + const gfx::IntSize& srcSize = mDraw->mSize; + const gfx::IntSize& destSize = mRead->Size(); - mGL->raw_fBlitFramebuffer(0, 0, srcSize.width, srcSize.height, - 0, 0, destSize.width, destSize.height, - LOCAL_GL_COLOR_BUFFER_BIT, - LOCAL_GL_NEAREST); + mGL->raw_fBlitFramebuffer(0, 0, srcSize.width, srcSize.height, + 0, 0, destSize.width, destSize.height, + LOCAL_GL_COLOR_BUFFER_BIT, + LOCAL_GL_NEAREST); + } else if (mGL->IsExtensionSupported(GLContext::APPLE_framebuffer_multisample)) { + mGL->fResolveMultisampleFramebufferAPPLE(); + } else { + MOZ_CRASH("No available blit methods."); + } // Done! } diff --git a/gfx/gl/ScopedGLHelpers.cpp b/gfx/gl/ScopedGLHelpers.cpp index 46227afc4d..a70c53137a 100644 --- a/gfx/gl/ScopedGLHelpers.cpp +++ b/gfx/gl/ScopedGLHelpers.cpp @@ -62,7 +62,7 @@ ScopedGLState::UnwrapImpl() void ScopedBindFramebuffer::Init() { - if (mGL->IsSupported(GLFeature::framebuffer_blit)) { + if (mGL->IsSupported(GLFeature::split_framebuffer)) { mOldReadFB = mGL->GetReadFB(); mOldDrawFB = mGL->GetDrawFB(); } else { diff --git a/gfx/gl/SharedSurfaceGL.cpp b/gfx/gl/SharedSurfaceGL.cpp index 7969686dfe..f050ec9246 100644 --- a/gfx/gl/SharedSurfaceGL.cpp +++ b/gfx/gl/SharedSurfaceGL.cpp @@ -153,7 +153,7 @@ SharedSurface_GLTexture::ProducerReleaseImpl() { mGL->MakeCurrent(); - if (mGL->IsExtensionSupported(GLContext::ARB_sync)) { + if (mGL->IsSupported(GLFeature::sync)) { if (mSync) { mGL->fDeleteSync(mSync); mSync = 0; diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index 89e9ba0641..096ca7293e 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -469,17 +469,6 @@ LinkModuleToHeap(JSContext* cx, AsmJSModule& module, HandlebyteLength(); - if (IsDeprecatedAsmJSHeapLength(heapLength)) { - LinkFail(cx, "ArrayBuffer byteLengths smaller than 64KB are deprecated and " - "will cause a link-time failure in the future"); - - // The goal of deprecation is to give apps some time before linking - // fails. However, if warnings-as-errors is turned on (which happens as - // part of asm.js testing) an exception may be raised. - if (cx->isExceptionPending()) - return false; - } - if (!IsValidAsmJSHeapLength(heapLength)) { ScopedJSFreePtr msg( JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next " @@ -631,7 +620,6 @@ ChangeHeap(JSContext* cx, AsmJSModule& module, const CallArgs& args) } MOZ_ASSERT(IsValidAsmJSHeapLength(heapLength)); - MOZ_ASSERT(!IsDeprecatedAsmJSHeapLength(heapLength)); if (!ArrayBufferObject::prepareForAsmJS(cx, newBuffer, module.usesSignalHandlersForOOB())) return false; @@ -749,12 +737,8 @@ CallAsmJS(JSContext* cx, unsigned argc, Value* vp) // that the optimized asm.js-to-Ion FFI call path (which we want to be // very fast) can avoid doing so. The JitActivation is marked as // inactive so stack iteration will skip over it. - // - // We needn't provide an entry script pointer; that's only used for - // reporting entry points to performance-monitoring tools, and asm.js -> - // Ion calls will never be entry points. AsmJSActivation activation(cx, module); - JitActivation jitActivation(cx, /* entryScript */ nullptr, /* active */ false); + JitActivation jitActivation(cx, /* active */ false); // Call the per-exported-function trampoline created by GenerateEntry. AsmJSModule::CodePtr enter = module.entryTrampoline(func); diff --git a/js/src/asmjs/AsmJSModule.cpp b/js/src/asmjs/AsmJSModule.cpp index 4a207953c3..ba4825763b 100644 --- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -326,8 +326,6 @@ AsmJSModule::finish(ExclusiveContext* cx, TokenStream& tokenStream, MacroAssembl if (!callSites_.appendAll(callSites)) return false; - MOZ_ASSERT(pod.functionBytes_ % AsmJSPageSize == 0); - // Absolute link metadata: absolute addresses that refer to some fixed // address in the address space. AbsoluteLinkArray& absoluteLinks = staticLinkData_.absoluteLinks; diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index 894704e8ee..c48e8885a4 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -69,6 +69,7 @@ using mozilla::Maybe; using mozilla::Move; using mozilla::PositiveInfinity; using mozilla::UniquePtr; +using JS::AsmJSOption; using JS::GenericNaN; /*****************************************************************************/ @@ -172,18 +173,22 @@ VarListHead(ParseNode* pn) return ListHead(pn); } +static inline bool +IsDefaultCase(ParseNode* pn) +{ + return pn->as().isDefault(); +} + static inline ParseNode* CaseExpr(ParseNode* pn) { - MOZ_ASSERT(pn->isKind(PNK_CASE) || pn->isKind(PNK_DEFAULT)); - return BinaryLeft(pn); + return pn->as().caseExpression(); } static inline ParseNode* CaseBody(ParseNode* pn) { - MOZ_ASSERT(pn->isKind(PNK_CASE) || pn->isKind(PNK_DEFAULT)); - return BinaryRight(pn); + return pn->as().statementList(); } static inline ParseNode* @@ -1596,17 +1601,9 @@ class MOZ_STACK_CLASS ModuleValidator masm().patchCall(callerOffset, calleeOffset); } - // When an interrupt is triggered, all function code is mprotected and, - // for sanity, stub code (particularly the interrupt stub) is not. - // Protection works at page granularity, so we need to ensure that no - // stub code gets into the function code pages. - // TODO; this is no longer true and could be removed, see also - // bug 1200609. MOZ_ASSERT(!finishedFunctionBodies_); - masm().haltingAlign(AsmJSPageSize); module_->finishFunctionBodies(masm().currentOffset()); finishedFunctionBodies_ = true; - return true; } @@ -5915,8 +5912,7 @@ static bool CheckDefaultAtEnd(FunctionValidator& f, ParseNode* stmt) { for (; stmt; stmt = NextNode(stmt)) { - MOZ_ASSERT(stmt->isKind(PNK_CASE) || stmt->isKind(PNK_DEFAULT)); - if (stmt->isKind(PNK_DEFAULT) && NextNode(stmt) != nullptr) + if (IsDefaultCase(stmt) && NextNode(stmt) != nullptr) return f.fail(stmt, "default label must be at the end"); } @@ -5927,7 +5923,7 @@ static bool CheckSwitchRange(FunctionValidator& f, ParseNode* stmt, int32_t* low, int32_t* high, int32_t* tableLength) { - if (stmt->isKind(PNK_DEFAULT)) { + if (IsDefaultCase(stmt)) { *low = 0; *high = -1; *tableLength = 0; @@ -5941,7 +5937,7 @@ CheckSwitchRange(FunctionValidator& f, ParseNode* stmt, int32_t* low, int32_t* h *low = *high = i; ParseNode* initialStmt = stmt; - for (stmt = NextNode(stmt); stmt && stmt->isKind(PNK_CASE); stmt = NextNode(stmt)) { + for (stmt = NextNode(stmt); stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) { int32_t i = 0; if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) return false; @@ -6016,7 +6012,7 @@ CheckSwitch(FunctionValidator& f, ParseNode* switchStmt) return false; uint32_t numCases = 0; - for (; stmt && stmt->isKind(PNK_CASE); stmt = NextNode(stmt)) { + for (; stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) { int32_t caseValue = ExtractNumericLiteral(f.m(), CaseExpr(stmt)).toInt32(); unsigned caseIndex = caseValue - low; @@ -6033,7 +6029,7 @@ CheckSwitch(FunctionValidator& f, ParseNode* switchStmt) } bool hasDefault = false; - if (stmt && stmt->isKind(PNK_DEFAULT)) { + if (stmt && IsDefaultCase(stmt)) { hasDefault = true; if (!CheckStatement(f, CaseBody(stmt))) return false; @@ -8275,11 +8271,14 @@ EstablishPreconditions(ExclusiveContext* cx, AsmJSParser& parser) if (cx->gcSystemPageSize() != AsmJSPageSize) return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by non 4KiB system page size"); - if (!parser.options().asmJSOption) + switch (parser.options().asmJSOption) { + case AsmJSOption::Disabled: return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by javascript.options.asmjs in about:config"); - - if (cx->compartment()->debuggerObservesAsmJS()) + case AsmJSOption::DisabledByDebugger: return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by debugger"); + case AsmJSOption::Enabled: + break; + } if (parser.pc->isGenerator()) return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by generator context"); diff --git a/js/src/asmjs/AsmJSValidate.h b/js/src/asmjs/AsmJSValidate.h index c6aff6449f..4defadc2b2 100644 --- a/js/src/asmjs/AsmJSValidate.h +++ b/js/src/asmjs/AsmJSValidate.h @@ -51,8 +51,17 @@ extern bool ValidateAsmJS(ExclusiveContext* cx, AsmJSParser& parser, frontend::ParseNode* stmtList, bool* validated); +// The minimum heap length for asm.js. +const size_t AsmJSMinHeapLength = 64 * 1024; + // The assumed page size; dynamically checked in ValidateAsmJS. +#ifdef _MIPS_ARCH_LOONGSON3A +const size_t AsmJSPageSize = 16384; +#else const size_t AsmJSPageSize = 4096; +#endif + +static_assert(AsmJSMinHeapLength % AsmJSPageSize == 0, "Invalid page size"); #if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB) @@ -86,8 +95,8 @@ static const size_t AsmJSMappedSize = 4 * 1024ULL * 1024ULL * 1024ULL + inline uint32_t RoundUpToNextValidAsmJSHeapLength(uint32_t length) { - if (length <= 4 * 1024) - return 4 * 1024; + if (length <= AsmJSMinHeapLength) + return AsmJSMinHeapLength; if (length <= 16 * 1024 * 1024) return mozilla::RoundUpPow2(length); @@ -99,7 +108,7 @@ RoundUpToNextValidAsmJSHeapLength(uint32_t length) inline bool IsValidAsmJSHeapLength(uint32_t length) { - bool valid = length >= 4 * 1024 && + bool valid = length >= AsmJSMinHeapLength && (IsPowerOfTwo(length) || (length & 0x00ffffff) == 0); @@ -109,14 +118,6 @@ IsValidAsmJSHeapLength(uint32_t length) return valid; } -// For now, power-of-2 lengths in this range are accepted, but in the future -// we'll change this to cause link-time failure. -inline bool -IsDeprecatedAsmJSHeapLength(uint32_t length) -{ - return length >= 4 * 1024 && length < 64 * 1024 && IsPowerOfTwo(length); -} - // Return whether asm.js optimization is inhibited by the platform or // dynamically disabled: extern bool diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 79646e2b22..30bc1a5dbe 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -771,7 +771,7 @@ function ArrayFrom(items, mapfn=undefined, thisArg=undefined) { // See . while (true) { // Steps 6.g.i-iii. - var next = iterator.next(); + var next = callFunction(iterator.next, iterator); if (!IsObject(next)) ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js index b2dbbc3933..892b9e905e 100644 --- a/js/src/builtin/Intl.js +++ b/js/src/builtin/Intl.js @@ -443,9 +443,9 @@ function CanonicalizeLanguageTag(locale) { while (i < subtags.length && subtags[i].length > 1) i++; var extension = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, extensionStart, i), "-"); - extensions.push(extension); + callFunction(std_Array_push, extensions, extension); } - extensions.sort(); + callFunction(std_Array_sort, extensions); // Private use sequences are left as is. "x-private" var privateUse = ""; @@ -455,7 +455,7 @@ function CanonicalizeLanguageTag(locale) { // Put everything back together. var canonical = normal; if (extensions.length > 0) - canonical += "-" + extensions.join("-"); + canonical += "-" + callFunction(std_Array_join, extensions, "-"); if (privateUse.length > 0) { // Be careful of a Language-Tag that is entirely privateuse. if (canonical.length > 0) @@ -578,11 +578,14 @@ function DefaultLocale() { // (perhaps via fallback). Otherwise use the last-ditch locale. var candidate = DefaultLocaleIgnoringAvailableLocales(); var locale; - if (BestAvailableLocaleIgnoringDefault(collatorInternalProperties.availableLocales(), + if (BestAvailableLocaleIgnoringDefault(callFunction(collatorInternalProperties.availableLocales, + collatorInternalProperties), candidate) && - BestAvailableLocaleIgnoringDefault(numberFormatInternalProperties.availableLocales(), + BestAvailableLocaleIgnoringDefault(callFunction(numberFormatInternalProperties.availableLocales, + numberFormatInternalProperties), candidate) && - BestAvailableLocaleIgnoringDefault(dateTimeFormatInternalProperties.availableLocales(), + BestAvailableLocaleIgnoringDefault(callFunction(dateTimeFormatInternalProperties.availableLocales, + dateTimeFormatInternalProperties), candidate)) { locale = candidate; @@ -675,8 +678,8 @@ function CanonicalizeLocaleList(locales) { if (!IsStructurallyValidLanguageTag(tag)) ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, tag); tag = CanonicalizeLanguageTag(tag); - if (seen.indexOf(tag) === -1) - seen.push(tag); + if (callFunction(std_Array_indexOf, seen, tag) === -1) + callFunction(std_Array_push, seen, tag); } k++; } @@ -968,14 +971,14 @@ function LookupSupportedLocales(availableLocales, requestedLocales) { // Step 4.c-d. var availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale); if (availableLocale !== undefined) - subset.push(locale); + callFunction(std_Array_push, subset, locale); // Step 4.e. k++; } // Steps 5-6. - return subset.slice(0); + return callFunction(std_Array_slice, subset, 0); } @@ -1317,7 +1320,7 @@ function resolveCollatorInternals(lazyCollatorData) var relevantExtensionKeys = Collator.relevantExtensionKeys; // Step 15. - var r = ResolveLocale(Collator.availableLocales(), + var r = ResolveLocale(callFunction(Collator.availableLocales, Collator), lazyCollatorData.requestedLocales, lazyCollatorData.opt, relevantExtensionKeys, @@ -1507,7 +1510,8 @@ function InitializeCollator(collator, locales, options) { function Intl_Collator_supportedLocalesOf(locales /*, options*/) { var options = arguments.length > 1 ? arguments[1] : undefined; - var availableLocales = collatorInternalProperties.availableLocales(); + var availableLocales = callFunction(collatorInternalProperties.availableLocales, + collatorInternalProperties); var requestedLocales = CanonicalizeLocaleList(locales); return SupportedLocales(availableLocales, requestedLocales, options); } @@ -1675,7 +1679,7 @@ function resolveNumberFormatInternals(lazyNumberFormatData) { var localeData = NumberFormat.localeData; // Step 11. - var r = ResolveLocale(NumberFormat.availableLocales(), + var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat), lazyNumberFormatData.requestedLocales, lazyNumberFormatData.opt, NumberFormat.relevantExtensionKeys, @@ -1960,7 +1964,8 @@ function CurrencyDigits(currency) { function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) { var options = arguments.length > 1 ? arguments[1] : undefined; - var availableLocales = numberFormatInternalProperties.availableLocales(); + var availableLocales = callFunction(numberFormatInternalProperties.availableLocales, + numberFormatInternalProperties); var requestedLocales = CanonicalizeLocaleList(locales); return SupportedLocales(availableLocales, requestedLocales, options); } @@ -2119,7 +2124,7 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { var localeData = DateTimeFormat.localeData; // Step 10. - var r = ResolveLocale(DateTimeFormat.availableLocales(), + var r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat), lazyDateTimeFormatData.requestedLocales, lazyDateTimeFormatData.localeOpt, DateTimeFormat.relevantExtensionKeys, @@ -2660,7 +2665,8 @@ function BestFitFormatMatcher(options, formats) { function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) { var options = arguments.length > 1 ? arguments[1] : undefined; - var availableLocales = dateTimeFormatInternalProperties.availableLocales(); + var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales, + dateTimeFormatInternalProperties); var requestedLocales = CanonicalizeLocaleList(locales); return SupportedLocales(availableLocales, requestedLocales, options); } diff --git a/js/src/builtin/Iterator.js b/js/src/builtin/Iterator.js index 5f78547cad..a56b7228aa 100644 --- a/js/src/builtin/Iterator.js +++ b/js/src/builtin/Iterator.js @@ -11,7 +11,7 @@ var LegacyIteratorWrapperMap = new std_WeakMap(); function LegacyIteratorNext(arg) { var iter = callFunction(std_WeakMap_get, LegacyIteratorWrapperMap, this); try { - return { value: iter.next(arg), done: false }; + return { value: callFunction(iter.next, iter, arg), done: false }; } catch (e) { if (e instanceof std_StopIteration) return { value: undefined, done: true }; @@ -22,7 +22,7 @@ function LegacyIteratorNext(arg) { function LegacyIteratorThrow(exn) { var iter = callFunction(std_WeakMap_get, LegacyIteratorWrapperMap, this); try { - return { value: iter.throw(exn), done: false }; + return { value: callFunction(iter.throw, iter, exn), done: false }; } catch (e) { if (e instanceof std_StopIteration) return { value: undefined, done: true }; diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js index 2e82cfcdf2..15b36626a1 100644 --- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -43,7 +43,8 @@ function ModuleGetExportedNames(exportStarSet = []) for (let i = 0; i < starExportEntries.length; i++) { let e = starExportEntries[i]; let requestedModule = HostResolveImportedModule(module, e.moduleRequest); - let starNames = requestedModule.getExportedNames(exportStarSet); + let starNames = callFunction(requestedModule.getExportedNames, requestedModule, + exportStarSet); for (let j = 0; j < starNames.length; j++) { let n = starNames[j]; if (n !== "default" && !(n in exportedNames)) @@ -89,9 +90,8 @@ function ModuleResolveExport(exportName, resolveSet = [], exportStarSet = []) let e = indirectExportEntries[i]; if (exportName === e.exportName) { let importedModule = HostResolveImportedModule(module, e.moduleRequest); - let indirectResolution = importedModule.resolveExport(e.importName, - resolveSet, - exportStarSet); + let indirectResolution = callFunction(importedModule.resolveExport, importedModule, + e.importName, resolveSet, exportStarSet); if (indirectResolution !== null) return indirectResolution; } @@ -118,7 +118,8 @@ function ModuleResolveExport(exportName, resolveSet = [], exportStarSet = []) for (let i = 0; i < starExportEntries.length; i++) { let e = starExportEntries[i]; let importedModule = HostResolveImportedModule(module, e.moduleRequest); - let resolution = importedModule.resolveExport(exportName, resolveSet, exportStarSet); + let resolution = callFunction(importedModule.resolveExport, importedModule, + exportName, resolveSet, exportStarSet); if (resolution === "ambiguous") return resolution; @@ -146,11 +147,11 @@ function GetModuleNamespace(module) // Step 3 if (typeof namespace === "undefined") { - let exportedNames = module.getExportedNames(); + let exportedNames = callFunction(module.getExportedNames, module); let unambiguousNames = []; for (let i = 0; i < exportedNames.length; i++) { let name = exportedNames[i]; - let resolution = module.resolveExport(name); + let resolution = callFunction(module.resolveExport, module, name); if (resolution === null) ThrowSyntaxError(JSMSG_MISSING_NAMESPACE_EXPORT); if (resolution !== "ambiguous") @@ -166,7 +167,7 @@ function GetModuleNamespace(module) // 9.4.6.13 ModuleNamespaceCreate(module, exports) function ModuleNamespaceCreate(module, exports) { - exports.sort(); + callFunction(std_Array_sort, exports); let ns = NewModuleNamespace(module, exports); @@ -174,7 +175,7 @@ function ModuleNamespaceCreate(module, exports) // access. for (let i = 0; i < exports.length; i++) { let name = exports[i]; - let binding = module.resolveExport(name); + let binding = callFunction(module.resolveExport, module, name); assert(binding !== null && binding !== "ambiguous", "Failed to resolve binding"); AddModuleNamespaceBinding(ns, name, binding.module, binding.bindingName); } @@ -204,14 +205,14 @@ function ModuleDeclarationInstantiation() for (let i = 0; i < requestedModules.length; i++) { let required = requestedModules[i]; let requiredModule = HostResolveImportedModule(module, required); - requiredModule.declarationInstantiation(); + callFunction(requiredModule.declarationInstantiation, requiredModule); } // Step 9 let indirectExportEntries = module.indirectExportEntries; for (let i = 0; i < indirectExportEntries.length; i++) { let e = indirectExportEntries[i]; - let resolution = module.resolveExport(e.exportName); + let resolution = callFunction(module.resolveExport, module, e.exportName); if (resolution === null) ThrowSyntaxError(JSMSG_MISSING_INDIRECT_EXPORT); if (resolution === "ambiguous") @@ -227,7 +228,8 @@ function ModuleDeclarationInstantiation() let namespace = GetModuleNamespace(importedModule); CreateNamespaceBinding(env, imp.localName, namespace); } else { - let resolution = importedModule.resolveExport(imp.importName); + let resolution = callFunction(importedModule.resolveExport, importedModule, + imp.importName); if (resolution === null) ThrowSyntaxError(JSMSG_MISSING_IMPORT); if (resolution === "ambiguous") @@ -261,7 +263,7 @@ function ModuleEvaluation() for (let i = 0; i < requestedModules.length; i++) { let required = requestedModules[i]; let requiredModule = HostResolveImportedModule(module, required); - requiredModule.evaluation(); + callFunction(requiredModule.evaluation, requiredModule); } return EvaluateModule(module); diff --git a/js/src/builtin/Object.js b/js/src/builtin/Object.js index 83bbb03e04..12f4bb2fa0 100644 --- a/js/src/builtin/Object.js +++ b/js/src/builtin/Object.js @@ -57,7 +57,7 @@ function Object_toLocaleString() { var O = this; // Step 2. - return O.toString(); + return callFunction(O.toString, O); } function ObjectDefineSetter(name, setter) { diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index da397a5cb5..a78dce59a1 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2387,8 +2387,8 @@ ASTSerializer::switchCase(ParseNode* pn, MutableHandleValue dst) RootedValue expr(cx); - return optExpression(pn->pn_left, &expr) && - statements(pn->pn_right, stmts) && + return optExpression(pn->as().caseExpression(), &expr) && + statements(pn->as().statementList(), stmts) && builder.switchCase(expr, stmts, &pn->pn_pos, dst); } diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js index ad49e6f4a0..0b23ce8932 100644 --- a/js/src/builtin/String.js +++ b/js/src/builtin/String.js @@ -292,12 +292,12 @@ function String_static_fromCodePoint(codePoints) { // Step 5f. // Inlined UTF-16 Encoding if (nextCP <= 0xFFFF) { - elements.push(nextCP); + callFunction(std_Array_push, elements, nextCP); continue; } - elements.push((((nextCP - 0x10000) / 0x400) | 0) + 0xD800); - elements.push((nextCP - 0x10000) % 0x400 + 0xDC00); + callFunction(std_Array_push, elements, (((nextCP - 0x10000) / 0x400) | 0) + 0xD800); + callFunction(std_Array_push, elements, (nextCP - 0x10000) % 0x400 + 0xDC00); } // Step 6. diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index a8d2f87b81..536a57bc8c 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -216,7 +216,7 @@ function TypedArrayFilter(callbackfn, thisArg = undefined) { // Step 13.f. if (selected) { // Step 13.f.i. - kept.push(kValue); + callFunction(std_Array_push, kept, kValue); // Step 13.f.ii. captured++; } @@ -1059,14 +1059,14 @@ function TypedArrayFrom(constructor, target, items, mapfn, thisArg) { // Steps 10.d-e. while (true) { // Steps 10.e.i-ii. - var next = iterator.next(); + var next = callFunction(iterator.next, iterator); if (!IsObject(next)) ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); // Steps 10.e.iii-vi. if (next.done) break; - values.push(next.value); + callFunction(std_Array_push, values, next.value); } // Step 10.f. diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 25d1e0f537..93e576a319 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -53,16 +53,7 @@ var std_Map_iterator_next = MapIteratorNext; function List() { this.length = 0; } - -{ - let ListProto = std_Object_create(null); - ListProto.indexOf = std_Array_indexOf; - ListProto.join = std_Array_join; - ListProto.push = std_Array_push; - ListProto.slice = std_Array_slice; - ListProto.sort = std_Array_sort; - MakeConstructible(List, ListProto); -} +MakeConstructible(List, {__proto__: null}); /********** Record specification type **********/ diff --git a/js/src/builtin/embedjs.py b/js/src/builtin/embedjs.py index 88a79d271a..ece905dfef 100644 --- a/js/src/builtin/embedjs.py +++ b/js/src/builtin/embedjs.py @@ -62,7 +62,7 @@ HEADER_TEMPLATE = """\ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ namespace js { -namespace selfhosted { +namespace %(namespace)s { static const %(sources_type)s data[] = { %(sources_data)s }; static const %(sources_type)s * const %(sources_name)s = reinterpret_cast(data); @@ -78,7 +78,7 @@ namespace selfhosted { } // js """ -def embed(cxx, preprocessorOption, msgs, sources, c_out, js_out, env): +def embed(cxx, preprocessorOption, msgs, sources, c_out, js_out, namespace, env): combinedSources = '\n'.join([msgs] + ['#include "%(s)s"' % { 's': source } for source in sources]) args = ['-D%(k)s=%(v)s' % { 'k': k, 'v': env[k] } for k in env] preprocessed = preprocess(cxx, preprocessorOption, combinedSources, args) @@ -94,7 +94,8 @@ def embed(cxx, preprocessorOption, msgs, sources, c_out, js_out, env): 'sources_data': data, 'sources_name': 'compressedSources', 'compressed_total_length': len(compressed), - 'raw_total_length': len(processed) + 'raw_total_length': len(processed), + 'namespace': namespace }) def preprocess(cxx, preprocessorOption, source, args = []): @@ -141,8 +142,7 @@ def get_config_defines(buildconfig): env[pair[0]] = pair[1] return env -def generate_selfhosted(c_out, msg_file, *inputs): - # Called from moz.build to embed selfhosted JS. +def process_inputs(namespace, c_out, msg_file, inputs): deps = [path for path in inputs if path.endswith(".h")] sources = [path for path in inputs if path.endswith(".js")] assert len(deps) + len(sources) == len(inputs) @@ -152,4 +152,12 @@ def generate_selfhosted(c_out, msg_file, *inputs): js_path = re.sub(r"\.out\.h$", "", c_out.name) + ".js" msgs = messages(msg_file) with open(js_path, 'w') as js_out: - embed(cxx, cxx_option, msgs, sources, c_out, js_out, env) + embed(cxx, cxx_option, msgs, sources, c_out, js_out, namespace, env) + +def generate_selfhosted(c_out, msg_file, *inputs): + # Called from moz.build to embed selfhosted JS. + process_inputs('selfhosted', c_out, msg_file, inputs) + +def generate_shellmoduleloader(c_out, msg_file, *inputs): + # Called from moz.build to embed shell module loader JS. + process_inputs('moduleloader', c_out, msg_file, inputs) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 0dfd9ec679..e9cc0b7f34 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2147,7 +2147,6 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) *answer = true; return true; - case PNK_DEFAULT: case PNK_COLON: case PNK_CASE: MOZ_ASSERT(pn->isArity(PN_BINARY)); @@ -2599,10 +2598,8 @@ BytecodeEmitter::emitPropLHS(ParseNode* pn) ParseNode* pndot = pn2; ParseNode* pnup = nullptr; ParseNode* pndown; - ptrdiff_t top = offset(); for (;;) { /* Reverse pndot->pn_expr to point up, not down. */ - pndot->pn_offset = top; MOZ_ASSERT(!pndot->isUsed()); pndown = pndot->pn_expr; pndot->pn_expr = pnup; @@ -3068,7 +3065,7 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) ParseNode* cases = pn->pn_right; MOZ_ASSERT(cases->isKind(PNK_LEXICALSCOPE) || cases->isKind(PNK_STATEMENTLIST)); - /* Push the discriminant. */ + // Emit code for the discriminant. if (!emitTree(pn->pn_left)) return false; @@ -3080,7 +3077,8 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) stmtInfo.type = StmtType::SWITCH; stmtInfo.update = top = offset(); - /* Advance |cases| to refer to the switch case list. */ + + // Advance |cases| to refer to the switch case list. cases = cases->expr(); } else { MOZ_ASSERT(cases->isKind(PNK_STATEMENTLIST)); @@ -3088,21 +3086,22 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) pushStatement(&stmtInfo, StmtType::SWITCH, top); } - /* Switch bytecodes run from here till end of final case. */ + // Switch bytecodes run from here till end of final case. uint32_t caseCount = cases->pn_count; if (caseCount > JS_BIT(16)) { parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES); return false; } - /* Try for most optimal, fall back if not dense ints. */ + // Try for most optimal, fall back if not dense ints. JSOp switchOp = JSOP_TABLESWITCH; uint32_t tableLength = 0; int32_t low, high; bool hasDefault = false; + CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as() : nullptr; if (caseCount == 0 || - (caseCount == 1 && - (hasDefault = cases->pn_head->isKind(PNK_DEFAULT)))) { + (caseCount == 1 && (hasDefault = firstCase->isDefault()))) + { caseCount = 0; low = 0; high = -1; @@ -3113,20 +3112,19 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) low = JSVAL_INT_MAX; high = JSVAL_INT_MIN; - for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) { - if (caseNode->isKind(PNK_DEFAULT)) { + for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { + if (caseNode->isDefault()) { hasDefault = true; - caseCount--; /* one of the "cases" was the default */ + caseCount--; // one of the "cases" was the default continue; } - MOZ_ASSERT(caseNode->isKind(PNK_CASE)); if (switchOp == JSOP_CONDSWITCH) continue; MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); - ParseNode* caseValue = caseNode->pn_left; + ParseNode* caseValue = caseNode->caseExpression(); if (caseValue->getKind() != PNK_NUMBER) { switchOp = JSOP_CONDSWITCH; @@ -3148,11 +3146,9 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) if (i > high) high = i; - /* - * Check for duplicates, which require a JSOP_CONDSWITCH. - * We bias i by 65536 if it's negative, and hope that's a rare - * case (because it requires a malloc'd bitmap). - */ + // Check for duplicates, which require a JSOP_CONDSWITCH. + // We bias i by 65536 if it's negative, and hope that's a rare + // case (because it requires a malloc'd bitmap). if (i < 0) i += JS_BIT(16); if (i >= intmapBitLength) { @@ -3168,10 +3164,8 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) JS_SET_BIT(intmap, i); } - /* - * Compute table length and select condswitch instead if overlarge or - * more than half-sparse. - */ + // Compute table length and select condswitch instead if overlarge or + // more than half-sparse. if (switchOp == JSOP_TABLESWITCH) { tableLength = uint32_t(high - low + 1); if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount) @@ -3179,31 +3173,29 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) } } - /* - * The note has one or two offsets: first tells total switch code length; - * second (if condswitch) tells offset to first JSOP_CASE. - */ + // The note has one or two offsets: first tells total switch code length; + // second (if condswitch) tells offset to first JSOP_CASE. unsigned noteIndex; size_t switchSize; if (switchOp == JSOP_CONDSWITCH) { - /* 0 bytes of immediate for unoptimized switch. */ + // 0 bytes of immediate for unoptimized switch. switchSize = 0; if (!newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex)) return false; } else { MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); - /* 3 offsets (len, low, high) before the table, 1 per entry. */ - switchSize = (size_t)(JUMP_OFFSET_LEN * (3 + tableLength)); + // 3 offsets (len, low, high) before the table, 1 per entry. + switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength)); if (!newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex)) return false; } - /* Emit switchOp followed by switchSize bytes of jump or lookup table. */ + // Emit switchOp followed by switchSize bytes of jump or lookup table. if (!emitN(switchOp, switchSize)) return false; - Vector table; + Vector table; ptrdiff_t condSwitchDefaultOff = -1; if (switchOp == JSOP_CONDSWITCH) { @@ -3211,31 +3203,38 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) bool beforeCases = true; ptrdiff_t prevCaseOffset; - /* Emit code for evaluating cases and jumping to case statements. */ - for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) { - ParseNode* caseValue = caseNode->pn_left; - // If the expression is a literal, suppress line number - // emission so that debugging works more naturally. - if (caseValue && - !emitTree(caseValue, caseValue->isLiteral() ? SUPPRESS_LINENOTE : - EMIT_LINENOTE)) - return false; + // Emit code for evaluating cases and jumping to case statements. + for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { + ParseNode* caseValue = caseNode->caseExpression(); + + // If the expression is a literal, suppress line number emission so + // that debugging works more naturally. + if (caseValue) { + if (!emitTree(caseValue, + caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) + { + return false; + } + } + if (!beforeCases) { - /* prevCaseOffset is the previous JSOP_CASE's bytecode offset. */ + // prevCaseOffset is the previous JSOP_CASE's bytecode offset. if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - prevCaseOffset)) return false; } if (!caseValue) { - MOZ_ASSERT(caseNode->isKind(PNK_DEFAULT)); + // This is the default clause. continue; } + if (!newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex)) return false; if (!emitJump(JSOP_CASE, 0, &prevCaseOffset)) return false; - caseNode->pn_offset = prevCaseOffset; + caseNode->setOffset(prevCaseOffset); + if (beforeCases) { - /* Switch note's second offset is to first JSOP_CASE. */ + // Switch note's second offset is to first JSOP_CASE. unsigned noteCount = notes().length(); if (!setSrcNoteOffset(noteIndex, 1, prevCaseOffset - top)) return false; @@ -3246,12 +3245,10 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) } } - /* - * If we didn't have an explicit default (which could fall in between - * cases, preventing us from fusing this setSrcNoteOffset with the call - * in the loop above), link the last case to the implicit default for - * the benefit of IonBuilder. - */ + // If we didn't have an explicit default (which could fall in between + // cases, preventing us from fusing this setSrcNoteOffset with the call + // in the loop above), link the last case to the implicit default for + // the benefit of IonBuilder. if (!hasDefault && !beforeCases && !setSrcNoteOffset(caseNoteIndex, 0, offset() - prevCaseOffset)) @@ -3259,14 +3256,14 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) return false; } - /* Emit default even if no explicit default statement. */ + // Emit default even if no explicit default statement. if (!emitJump(JSOP_DEFAULT, 0, &condSwitchDefaultOff)) return false; } else { MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); jsbytecode* pc = code(top + JUMP_OFFSET_LEN); - /* Fill in switch bounds, which we know fit in 16-bit offsets. */ + // Fill in switch bounds, which we know fit in 16-bit offsets. SET_JUMP_OFFSET(pc, low); pc += JUMP_OFFSET_LEN; SET_JUMP_OFFSET(pc, high); @@ -3276,49 +3273,49 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) if (!table.growBy(tableLength)) return false; - for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) { - if (caseNode->isKind(PNK_DEFAULT)) - continue; + for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { + if (ParseNode* caseValue = caseNode->caseExpression()) { + MOZ_ASSERT(caseValue->isKind(PNK_NUMBER)); - MOZ_ASSERT(caseNode->isKind(PNK_CASE)); + int32_t i = int32_t(caseValue->pn_dval); + MOZ_ASSERT(double(i) == caseValue->pn_dval); - ParseNode* caseValue = caseNode->pn_left; - MOZ_ASSERT(caseValue->isKind(PNK_NUMBER)); - - int32_t i = int32_t(caseValue->pn_dval); - MOZ_ASSERT(double(i) == caseValue->pn_dval); - - i -= low; - MOZ_ASSERT(uint32_t(i) < tableLength); - MOZ_ASSERT(!table[i]); - table[i] = caseNode; + i -= low; + MOZ_ASSERT(uint32_t(i) < tableLength); + MOZ_ASSERT(!table[i]); + table[i] = caseNode; + } } } } ptrdiff_t defaultOffset = -1; - /* Emit code for each case's statements, copying pn_offset up to caseNode. */ - for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) { - if (switchOp == JSOP_CONDSWITCH && !caseNode->isKind(PNK_DEFAULT)) - setJumpOffsetAt(caseNode->pn_offset); - ParseNode* caseValue = caseNode->pn_right; - if (!emitTree(caseValue)) + // Emit code for each case's statements. + for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { + if (switchOp == JSOP_CONDSWITCH && !caseNode->isDefault()) + setJumpOffsetAt(caseNode->offset()); + + // If this is emitted as a TABLESWITCH, we'll need to know this case's + // offset later when emitting the table. Store it in the node's + // pn_offset (giving the field a different meaning vs. how we used it + // on the immediately preceding line of code). + ptrdiff_t here = offset(); + caseNode->setOffset(here); + if (caseNode->isDefault()) + defaultOffset = here - top; + + if (!emitTree(caseNode->statementList())) return false; - caseNode->pn_offset = caseValue->pn_offset; - if (caseNode->isKind(PNK_DEFAULT)) - defaultOffset = caseNode->pn_offset - top; } if (!hasDefault) { - /* If no default case, offset for default is to end of switch. */ + // If no default case, offset for default is to end of switch. defaultOffset = offset() - top; } - - /* We better have set "defaultOffset" by now. */ MOZ_ASSERT(defaultOffset != -1); - /* Set the default offset (to end of switch if no default). */ + // Set the default offset (to end of switch if no default). jsbytecode* pc; if (switchOp == JSOP_CONDSWITCH) { pc = nullptr; @@ -3329,18 +3326,18 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) pc += JUMP_OFFSET_LEN; } - /* Set the SRC_SWITCH note's offset operand to tell end of switch. */ + // Set the SRC_SWITCH note's offset operand to tell end of switch. if (!setSrcNoteOffset(noteIndex, 0, offset() - top)) return false; if (switchOp == JSOP_TABLESWITCH) { - /* Skip over the already-initialized switch bounds. */ + // Skip over the already-initialized switch bounds. pc += 2 * JUMP_OFFSET_LEN; - /* Fill in the jump table, if there is one. */ + // Fill in the jump table, if there is one. for (uint32_t i = 0; i < tableLength; i++) { - ParseNode* caseNode = table[i]; - ptrdiff_t off = caseNode ? caseNode->pn_offset - top : 0; + CaseClause* caseNode = table[i]; + ptrdiff_t off = caseNode ? caseNode->offset() - top : 0; SET_JUMP_OFFSET(pc, off); pc += JUMP_OFFSET_LEN; } @@ -5354,12 +5351,13 @@ BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl) } bool -BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn) { MOZ_ASSERT(type == StmtType::FOR_OF_LOOP || type == StmtType::SPREAD); MOZ_ASSERT_IF(type == StmtType::FOR_OF_LOOP, pn && pn->pn_left->isKind(PNK_FOROF)); MOZ_ASSERT_IF(type == StmtType::SPREAD, !pn); + ptrdiff_t top = offset(); ParseNode* forHead = pn ? pn->pn_left : nullptr; ParseNode* forHeadExpr = forHead ? forHead->pn_kid3 : nullptr; ParseNode* forBody = pn ? pn->pn_right : nullptr; @@ -5505,8 +5503,9 @@ BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top) } bool -BytecodeEmitter::emitForIn(ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitForIn(ParseNode* pn) { + ptrdiff_t top = offset(); ParseNode* forHead = pn->pn_left; ParseNode* forBody = pn->pn_right; @@ -5627,10 +5626,10 @@ BytecodeEmitter::emitForIn(ParseNode* pn, ptrdiff_t top) /* C-style `for (init; cond; update) ...` loop. */ bool -BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitCStyleFor(ParseNode* pn) { LoopStmtInfo stmtInfo(cx); - pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, top); + pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, offset()); ParseNode* forHead = pn->pn_left; ParseNode* forBody = pn->pn_right; @@ -5700,7 +5699,7 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top) return false; } - top = offset(); + ptrdiff_t top = offset(); stmtInfo.setTop(top); /* Emit code for the loop body. */ @@ -5802,19 +5801,19 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top) } bool -BytecodeEmitter::emitFor(ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitFor(ParseNode* pn) { if (pn->pn_left->isKind(PNK_FORHEAD)) - return emitCStyleFor(pn, top); + return emitCStyleFor(pn); if (!updateLineNumberNotes(pn->pn_pos.begin)) return false; if (pn->pn_left->isKind(PNK_FORIN)) - return emitForIn(pn, top); + return emitForIn(pn); MOZ_ASSERT(pn->pn_left->isKind(PNK_FOROF)); - return emitForOf(StmtType::FOR_OF_LOOP, pn, top); + return emitForOf(StmtType::FOR_OF_LOOP, pn); } MOZ_NEVER_INLINE bool @@ -6043,7 +6042,7 @@ BytecodeEmitter::emitDo(ParseNode* pn) } bool -BytecodeEmitter::emitWhile(ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitWhile(ParseNode* pn) { /* * Minimize bytecodes issued for one or more iterations by jumping to @@ -6073,7 +6072,7 @@ BytecodeEmitter::emitWhile(ParseNode* pn, ptrdiff_t top) return false; LoopStmtInfo stmtInfo(cx); - pushLoopStatement(&stmtInfo, StmtType::WHILE_LOOP, top); + pushLoopStatement(&stmtInfo, StmtType::WHILE_LOOP, offset()); unsigned noteIndex; if (!newSrcNote(SRC_WHILE, ¬eIndex)) @@ -6083,7 +6082,7 @@ BytecodeEmitter::emitWhile(ParseNode* pn, ptrdiff_t top) if (!emitJump(JSOP_GOTO, 0, &jmp)) return false; - top = offset(); + ptrdiff_t top = offset(); if (!emitLoopHead(pn->pn_right)) return false; @@ -6406,7 +6405,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) } bool -BytecodeEmitter::emitStatementList(ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitStatementList(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); for (ParseNode* pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { @@ -7286,7 +7285,7 @@ BytecodeEmitter::emitArrayComp(ParseNode* pn) bool BytecodeEmitter::emitSpread() { - return emitForOf(StmtType::SPREAD, nullptr, -1); + return emitForOf(StmtType::SPREAD, nullptr); } bool @@ -7610,8 +7609,6 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) EmitLevelManager elm(this); bool ok = true; - ptrdiff_t top = offset(); - pn->pn_offset = top; /* Emit notes to tell the current bytecode's source line number. However, a couple trees require special treatment; see the @@ -7723,7 +7720,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) break; case PNK_WHILE: - ok = emitWhile(pn, top); + ok = emitWhile(pn); break; case PNK_DOWHILE: @@ -7731,7 +7728,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) break; case PNK_FOR: - ok = emitFor(pn, top); + ok = emitFor(pn); break; case PNK_BREAK: @@ -7779,7 +7776,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) break; case PNK_STATEMENTLIST: - ok = emitStatementList(pn, top); + ok = emitStatementList(pn); break; case PNK_SEMI: diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 4fa1038e78..3b9b1a79dc 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -566,7 +566,7 @@ struct BytecodeEmitter bool emitReturn(ParseNode* pn); bool emitStatement(ParseNode* pn); - bool emitStatementList(ParseNode* pn, ptrdiff_t top); + bool emitStatementList(ParseNode* pn); bool emitDeleteName(ParseNode* pn); bool emitDeleteProperty(ParseNode* pn); @@ -589,11 +589,11 @@ struct BytecodeEmitter bool emitSelfHostedForceInterpreter(ParseNode* pn); bool emitDo(ParseNode* pn); - bool emitFor(ParseNode* pn, ptrdiff_t top); - bool emitForIn(ParseNode* pn, ptrdiff_t top); + bool emitFor(ParseNode* pn); + bool emitForIn(ParseNode* pn); bool emitForInOrOfVariables(ParseNode* pn, bool* letDecl); - bool emitCStyleFor(ParseNode* pn, ptrdiff_t top); - bool emitWhile(ParseNode* pn, ptrdiff_t top); + bool emitCStyleFor(ParseNode* pn); + bool emitWhile(ParseNode* pn); bool emitBreak(PropertyName* label); bool emitContinue(PropertyName* label); @@ -620,7 +620,7 @@ struct BytecodeEmitter // // Please refer the comment above emitSpread for additional information about // stack convention. - bool emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top); + bool emitForOf(StmtType type, ParseNode* pn); bool emitClass(ParseNode* pn); bool emitSuperPropLHS(bool isCall = false); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index edeb85f570..1d6d4ad4e2 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -238,13 +238,8 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) MOZ_ASSERT(node->isArity(PN_BINARY)); return ContainsHoistedDeclaration(cx, node->pn_right, result); - // A case/default node's right half is its statements. A default node's - // left half is null; a case node's left half is its expression. - case PNK_DEFAULT: - MOZ_ASSERT(!node->pn_left); case PNK_CASE: - MOZ_ASSERT(node->isArity(PN_BINARY)); - return ContainsHoistedDeclaration(cx, node->pn_right, result); + return ContainsHoistedDeclaration(cx, node->as().statementList(), result); case PNK_FOR: { MOZ_ASSERT(node->isArity(PN_BINARY)); @@ -1873,7 +1868,6 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo return FoldCall(cx, pn, parser, inGenexpLambda); case PNK_SWITCH: - case PNK_CASE: case PNK_COLON: case PNK_ASSIGN: case PNK_ADDASSIGN: @@ -1924,11 +1918,16 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo return FoldCondition(cx, &pn->pn_left, parser, inGenexpLambda) && Fold(cx, &pn->pn_right, parser, inGenexpLambda); - case PNK_DEFAULT: + case PNK_CASE: { MOZ_ASSERT(pn->isArity(PN_BINARY)); - MOZ_ASSERT(!pn->pn_left); - MOZ_ASSERT(pn->pn_right->isKind(PNK_STATEMENTLIST)); + + // pn_left is null for DefaultClauses. + if (pn->pn_left) { + if (!Fold(cx, &pn->pn_left, parser, inGenexpLambda)) + return false; + } return Fold(cx, &pn->pn_right, parser, inGenexpLambda); + } case PNK_WITH: MOZ_ASSERT(pn->isArity(PN_BINARY_OBJ)); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index d6dd032efe..5e7b30fe77 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -577,8 +577,7 @@ class FullParseHandler } ParseNode* newCaseOrDefault(uint32_t begin, ParseNode* expr, ParseNode* body) { - TokenPos pos(begin, body->pn_pos.end); - return new_(expr ? PNK_CASE : PNK_DEFAULT, JSOP_NOP, pos, expr, body); + return new_(expr, body, begin); } ParseNode* newContinueStatement(PropertyName* label, const TokenPos& pos) { diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index 667afaa271..c9830b8ea9 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -441,7 +441,6 @@ class NameResolver case PNK_MODASSIGN: case PNK_POWASSIGN: case PNK_COLON: - case PNK_CASE: case PNK_SHORTHAND: case PNK_DOWHILE: case PNK_WHILE: @@ -472,9 +471,12 @@ class NameResolver return false; break; - case PNK_DEFAULT: + case PNK_CASE: MOZ_ASSERT(cur->isArity(PN_BINARY)); - MOZ_ASSERT(!cur->pn_left); + if (ParseNode* caseExpr = cur->pn_left) { + if (!resolve(caseExpr, prefix)) + return false; + } if (!resolve(cur->pn_right, prefix)) return false; break; diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index cb35d11ed6..e3353b196e 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -273,7 +273,6 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_IMPORT_SPEC: case PNK_EXPORT_SPEC: case PNK_COLON: - case PNK_CASE: case PNK_SHORTHAND: case PNK_DOWHILE: case PNK_WHILE: @@ -288,7 +287,10 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushResult::Recyclable; } - // Named class expressions do not have outer binding nodes + // Default clauses are PNK_CASE but do not have case expressions. + // Named class expressions do not have outer binding nodes. + // So both are binary nodes with a possibly-null pn_left. + case PNK_CASE: case PNK_CLASSNAMES: { MOZ_ASSERT(pn->isArity(PN_BINARY)); if (pn->pn_left) @@ -308,16 +310,6 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushResult::Recyclable; } - // Default nodes, for dumb reasons that we're not changing now (mostly - // structural semi-consistency with PNK_CASE nodes), have a null left - // node and a non-null right node. - case PNK_DEFAULT: { - MOZ_ASSERT(pn->isArity(PN_BINARY)); - MOZ_ASSERT(pn->pn_left == nullptr); - stack->push(pn->pn_right); - return PushResult::Recyclable; - } - // The left half is the expression being yielded. The right half is // internal goop: a name reference to the invisible '.generator' local // variable, or an assignment of a PNK_GENERATOR node to the '.generator' diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 5d7771cd5d..6ed9850f95 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -123,7 +123,6 @@ class PackedScopeCoordinate F(IF) \ F(SWITCH) \ F(CASE) \ - F(DEFAULT) \ F(WHILE) \ F(DOWHILE) \ F(FOR) \ @@ -297,17 +296,15 @@ IsDeleteKind(ParseNodeKind kind) * PNK_STATEMENTLIST. * PNK_SWITCH binary pn_left: discriminant * pn_right: list of PNK_CASE nodes, with at most one - * PNK_DEFAULT node, or if there are let bindings + * default node, or if there are let bindings * in the top level of the switch body's cases, a * PNK_LEXICALSCOPE node that contains the list of * PNK_CASE nodes. - * PNK_CASE, binary pn_left: case expr + * PNK_CASE binary pn_left: case-expression if CaseClause, or + * null if DefaultClause * pn_right: PNK_STATEMENTLIST node for this case's * statements - * PNK_DEFAULT binary pn_left: null - * pn_right: PNK_STATEMENTLIST node for this default's - * statements - * pn_val: constant value if lookup or table switch + * pn_u.binary.offset: scratch space for the emitter * PNK_WHILE binary pn_left: cond, pn_right: body * PNK_DOWHILE binary pn_left: body, pn_right: cond * PNK_FOR binary pn_left: either PNK_FORIN (for-in statement), @@ -501,7 +498,6 @@ enum ParseNodeArity struct Definition; -class LabeledStatement; class LoopControlStatement; class BreakStatement; class ContinueStatement; @@ -523,7 +519,7 @@ class ParseNode public: ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity) : pn_type(kind), pn_op(op), pn_arity(arity), pn_parens(0), pn_used(0), pn_defn(0), - pn_pos(0, 0), pn_offset(0), pn_next(nullptr), pn_link(nullptr) + pn_pos(0, 0), pn_next(nullptr), pn_link(nullptr) { MOZ_ASSERT(kind < PNK_LIMIT); memset(&pn_u, 0, sizeof pn_u); @@ -531,7 +527,7 @@ class ParseNode ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity, const TokenPos& pos) : pn_type(kind), pn_op(op), pn_arity(arity), pn_parens(0), pn_used(0), pn_defn(0), - pn_pos(pos), pn_offset(0), pn_next(nullptr), pn_link(nullptr) + pn_pos(pos), pn_next(nullptr), pn_link(nullptr) { MOZ_ASSERT(kind < PNK_LIMIT); memset(&pn_u, 0, sizeof pn_u); @@ -583,8 +579,7 @@ class ParseNode "This is supposed to fit in a single uint32_t"); TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */ - int32_t pn_offset; /* first generated bytecode offset */ - ParseNode* pn_next; /* intrinsic link in parent PN_LIST */ + ParseNode* pn_next; /* intrinsic link in parent PN_LIST */ /* * Nodes that represent lexical bindings may, in addition to being @@ -619,8 +614,9 @@ class ParseNode ParseNode* right; union { unsigned iflags; /* JSITER_* flags for PNK_FOR node */ - ObjectBox* objbox; /* Only for PN_BINARY_OBJ */ - bool isStatic; /* Only for PNK_CLASSMETHOD */ + ObjectBox* objbox; /* only for PN_BINARY_OBJ */ + bool isStatic; /* only for PNK_CLASSMETHOD */ + uint32_t offset; /* for the emitter's use on PNK_CASE nodes */ }; } binary; struct { /* one kid if unary */ @@ -1152,6 +1148,34 @@ class LabeledStatement : public ParseNode } }; +// Inside a switch statement, a CaseClause is a case-label and the subsequent +// statements. The same node type is used for DefaultClauses. The only +// difference is that their caseExpression() is null. +class CaseClause : public BinaryNode +{ + public: + CaseClause(ParseNode* expr, ParseNode* stmts, uint32_t begin) + : BinaryNode(PNK_CASE, JSOP_NOP, TokenPos(begin, stmts->pn_pos.end), expr, stmts) {} + + ParseNode* caseExpression() const { return pn_left; } + bool isDefault() const { return !caseExpression(); } + ParseNode* statementList() const { return pn_right; } + + // The next CaseClause in the same switch statement. + CaseClause* next() const { return pn_next ? &pn_next->as() : nullptr; } + + // Scratch space used by the emitter. + uint32_t offset() const { return pn_u.binary.offset; } + void setOffset(uint32_t u) { pn_u.binary.offset = u; } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_CASE); + MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); + MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP)); + return match; + } +}; + class LoopControlStatement : public ParseNode { protected: diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index febd509a09..77e55e8a5b 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8644,6 +8644,11 @@ Parser::memberExpr(YieldHandling yieldHandling, TripledotHandling return nextMember; } + if (options().selfHostingMode && handler.isPropertyAccess(lhs)) { + report(ParseError, false, null(), JSMSG_SELFHOSTED_METHOD_CALL); + return null(); + } + nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate(); if (!nextMember) return null(); diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 936138da47..748b0f8cd7 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -2526,8 +2526,10 @@ struct UnmarkGrayTracer : public JS::CallbackTracer * The GC and CC are run independently. Consequently, the following sequence of * events can occur: * 1. GC runs and marks an object gray. - * 2. Some JS code runs that creates a pointer from a JS root to the gray - * object. If we re-ran a GC at this point, the object would now be black. + * 2. The mutator runs (specifically, some C++ code with access to gray + * objects) and creates a pointer from a JS root or other black object to + * the gray object. If we re-ran a GC at this point, the object would now be + * black. * 3. Now we run the CC. It may think it can collect the gray object, even * though it's reachable from the JS heap. * diff --git a/js/src/gdb/tests/test-asmjs.cpp b/js/src/gdb/tests/test-asmjs.cpp index d6ded2304e..a9f16f1a19 100644 --- a/js/src/gdb/tests/test-asmjs.cpp +++ b/js/src/gdb/tests/test-asmjs.cpp @@ -24,7 +24,7 @@ FRAGMENT(asmjs, segfault) { CompileOptions opts(cx); opts.setFileAndLine(__FILE__, line0 + 1); - opts.asmJSOption = true; + opts.asmJSOption = JS::AsmJSOption::Enabled; RootedValue rval(cx); bool ok; ok = false; diff --git a/js/src/jit-test/jit_test.py b/js/src/jit-test/jit_test.py index c2c9e27529..0cb11bdc13 100755 --- a/js/src/jit-test/jit_test.py +++ b/js/src/jit-test/jit_test.py @@ -17,7 +17,7 @@ def add_libdir_to_path(): add_libdir_to_path() import jittests -from tests import get_jitflags +from tests import get_jitflags, get_environment_overlay, change_env # Python 3.3 added shutil.which, but we can't use that yet. def which(name): @@ -149,8 +149,9 @@ def main(argv): options, args = op.parse_args(argv) if len(args) < 1: op.error('missing JS_SHELL argument') - # We need to make sure we are using backslashes on Windows. + js_shell = which(args[0]) test_args = args[1:] + test_environment = get_environment_overlay(js_shell) if jittests.stdio_might_be_broken(): # Prefer erring on the side of caution and not using stdio if @@ -209,7 +210,7 @@ def main(argv): file=sys.stderr) sys.exit(0) - test_list = [jittests.Test.from_file(_, options) for _ in test_list] + test_list = [jittests.JitTest.from_file(_, options) for _ in test_list] if not options.run_slow: test_list = [_ for _ in test_list if not _.slow] @@ -249,7 +250,7 @@ def main(argv): else: options.ignore_timeouts = set() - prefix = [which(args[0])] + shlex.split(options.shell_args) + prefix = [js_shell] + shlex.split(options.shell_args) prologue = os.path.join(jittests.LIB_DIR, 'prologue.js') if options.remote: prologue = posixpath.join(options.remote_test_root, @@ -277,7 +278,8 @@ def main(argv): else: debug_cmd = options.debugger.split() - subprocess.call(debug_cmd + tc.command(prefix, jittests.LIB_DIR)) + with change_env(test_environment): + subprocess.call(debug_cmd + tc.command(prefix, jittests.LIB_DIR)) sys.exit() try: @@ -287,7 +289,8 @@ def main(argv): elif options.max_jobs > 1 and jittests.HAVE_MULTIPROCESSING: ok = jittests.run_tests_parallel(job_list, prefix, options) else: - ok = jittests.run_tests(job_list, prefix, options) + with change_env(test_environment): + ok = jittests.run_tests(job_list, prefix, options) if not ok: sys.exit(2) except OSError: diff --git a/js/src/jit-test/lib/bytecode-cache.js b/js/src/jit-test/lib/bytecode-cache.js index 9e9905ca6a..d6b713d6dc 100644 --- a/js/src/jit-test/lib/bytecode-cache.js +++ b/js/src/jit-test/lib/bytecode-cache.js @@ -9,7 +9,7 @@ function evalWithCache(code, ctx) { // We create a new global ... if (!("global" in ctx)) - ctx.global = newGlobal(); + ctx.global = newGlobal({ cloneSingletons: true }); if (!("isRunOnce" in ctx)) ctx.isRunOnce = true; diff --git a/js/src/jit-test/lib/prologue.js b/js/src/jit-test/lib/prologue.js index ec2b85b6cd..18a5e61956 100644 --- a/js/src/jit-test/lib/prologue.js +++ b/js/src/jit-test/lib/prologue.js @@ -3,6 +3,8 @@ * 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/. */ +// explicitly run on ECMA 5 +version(185); var appendToActual = function(s) { actual += s + ','; diff --git a/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js b/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js index c0b4e84c4c..122cff61f4 100644 --- a/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js +++ b/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js @@ -15,7 +15,7 @@ function f(stdlib, foreign, buffer) if (isAsmJSCompilationAvailable()) assertEq(isAsmJSModule(f), true); -var i32 = new Int32Array(4096); +var i32 = new Int32Array(65536); var buffer = i32.buffer; var set = f(this, null, buffer); if (isAsmJSCompilationAvailable()) diff --git a/js/src/jit-test/tests/basic/bug1057571.js b/js/src/jit-test/tests/basic/bug1057571.js index 7b8e88431f..42a07f99b4 100644 --- a/js/src/jit-test/tests/basic/bug1057571.js +++ b/js/src/jit-test/tests/basic/bug1057571.js @@ -8,6 +8,7 @@ test = (function () { evalWithCache(test, {}); function evalWithCache(code, ctx) { code = cacheEntry(code); + ctx.global = newGlobal({ cloneSingletons: true }); ctx.isRunOnce = true; var res1 = evaluate(code, Object.create(ctx, {saveBytecode: { value: true } })); var res2 = evaluate(code, Object.create(ctx, {loadBytecode: { value: true }, saveBytecode: { value: true } })); diff --git a/js/src/jit-test/tests/basic/bug1061534.js b/js/src/jit-test/tests/basic/bug1061534.js index 4044155f5c..acfd1fb40c 100644 --- a/js/src/jit-test/tests/basic/bug1061534.js +++ b/js/src/jit-test/tests/basic/bug1061534.js @@ -6,6 +6,7 @@ test = (function () { evalWithCache(test, {}); function evalWithCache(code, ctx) { code = cacheEntry(code); + ctx.global = newGlobal({ cloneSingletons: true }); var res1 = evaluate(code, Object.create(ctx, {saveBytecode: { value: true } })); } if (typeof assertThrowsInstanceOf === 'undefined') { diff --git a/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js new file mode 100644 index 0000000000..1af32f6cab --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js @@ -0,0 +1,23 @@ +// Debugger.allowUnobservedAsmJS with off-thread parsing. + +load(libdir + "asm.js"); + +if (helperThreadCount() == 0) + quit(); + +var g = newGlobal(); +g.parent = this; +g.eval("dbg = new Debugger(parent);"); + +assertEq(g.dbg.allowUnobservedAsmJS, false); + +enableLastWarning(); + +var asmFunStr = USE_ASM + 'function f() {} return f'; +offThreadCompileScript("(function() {" + asmFunStr + "})"); +runOffThreadScript(); + +var msg = getLastWarning().message; +assertEq(msg === "asm.js type error: Disabled by debugger" || + msg === "asm.js type error: Disabled by lack of floating point support", + true); diff --git a/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js index 32cce6de7c..6575cfe748 100644 --- a/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js +++ b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js @@ -8,14 +8,38 @@ const root = newGlobal(); const dbg = new Debugger(); const wrappedRoot = dbg.addDebuggee(root); -// Out of range. +var mem = dbg.memory; -assertThrowsInstanceOf(() => dbg.memory.allocationSamplingProbability = -1, +// Out of range, negative +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Number.MAX_VALUE, TypeError); -assertThrowsInstanceOf(() => dbg.memory.allocationSamplingProbability = 2, +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -1, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Number.MIN_VALUE, TypeError); // In range -dbg.memory.allocationSamplingProbability = 0; -dbg.memory.allocationSamplingProbability = 1; -dbg.memory.allocationSamplingProbability = .5; +mem.allocationSamplingProbability = -0.0; +mem.allocationSamplingProbability = 0.0; +mem.allocationSamplingProbability = Number.MIN_VALUE; +mem.allocationSamplingProbability = 1 / 3; +mem.allocationSamplingProbability = .5; +mem.allocationSamplingProbability = 2 / 3; +mem.allocationSamplingProbability = 1 - Math.pow(2, -53); +mem.allocationSamplingProbability = 1; + +// Out of range, positive +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = 1 + Number.EPSILON, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = 2, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = Number.MAX_VALUE, + TypeError); + +// Out of range, non-finite +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Infinity, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = Infinity, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = NaN, + TypeError); diff --git a/js/src/jit-test/tests/gc/bug-1108007.js b/js/src/jit-test/tests/gc/bug-1108007.js index 937fa1cb39..2ee1864c51 100644 --- a/js/src/jit-test/tests/gc/bug-1108007.js +++ b/js/src/jit-test/tests/gc/bug-1108007.js @@ -5,7 +5,7 @@ gczeal(2); (function() { evaluate(cacheEntry((function() { return "".toSource() - })()), Object.create({}, { + })()), Object.create({ global: newGlobal({ cloneSingletons: true }) }, { saveBytecode: { value: true } diff --git a/js/src/jit-test/tests/xdr/bug1108603.js b/js/src/jit-test/tests/xdr/bug1108603.js new file mode 100644 index 0000000000..54ba21e79a --- /dev/null +++ b/js/src/jit-test/tests/xdr/bug1108603.js @@ -0,0 +1,9 @@ +var caught = false; +try { + evaluate(cacheEntry(""), {saveBytecode: {value: true}, global: this}); + [[0]]; +} catch (err) { + caught = true; + assertEq(err.message, "compartment cannot save singleton anymore."); +} +assertEq(caught, true); \ No newline at end of file diff --git a/js/src/jit-test/tests/xdr/bug1186973.js b/js/src/jit-test/tests/xdr/bug1186973.js new file mode 100644 index 0000000000..780ef55dc6 --- /dev/null +++ b/js/src/jit-test/tests/xdr/bug1186973.js @@ -0,0 +1,15 @@ +// |jit-test| error: cache does not have the same size +load(libdir + 'bytecode-cache.js'); + +var test = (function () { + function f(x) { + function ifTrue() {}; + function ifFalse() {}; + + if (generation % 2 == 0) + return ifTrue(); + return ifFalse(); + } + return f.toSource() + "; f()"; +})(); +evalWithCache(test, { assertEqBytecode: true, assertEqResult : true }); diff --git a/js/src/jit-test/tests/xdr/lazy.js b/js/src/jit-test/tests/xdr/lazy.js index e0ad97a5ef..429e8948ea 100644 --- a/js/src/jit-test/tests/xdr/lazy.js +++ b/js/src/jit-test/tests/xdr/lazy.js @@ -150,7 +150,7 @@ test = ` evalWithCache(test, { assertEqBytecode: true, assertEqResult: true }); // And more of the same, in a slightly different way -var g1 = newGlobal(); +var g1 = newGlobal({ cloneSingletons: true }); var g2 = newGlobal(); var res = "function f(){}"; var code = cacheEntry(res + "; f();"); diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index 8ef1ebcff0..0dac096d11 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -117,7 +117,8 @@ EnterBaseline(JSContext* cx, EnterJitData& data) data.result.setInt32(data.numActualArgs); { AssertCompartmentUnchanged pcc(cx); - JitActivation activation(cx, data.calleeToken); + ActivationEntryMonitor entryMonitor(cx, data.calleeToken); + JitActivation activation(cx); if (data.osrFrame) data.osrFrame->setRunningInJit(); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 243e1dc8a6..17a012dd5c 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2687,7 +2687,8 @@ EnterIon(JSContext* cx, EnterJitData& data) data.result.setInt32(data.numActualArgs); { AssertCompartmentUnchanged pcc(cx); - JitActivation activation(cx, data.calleeToken); + ActivationEntryMonitor entryMonitor(cx, data.calleeToken); + JitActivation activation(cx); CALL_GENERATED_CODE(enter, data.jitcode, data.maxArgc, data.maxArgv, /* osrFrame = */nullptr, data.calleeToken, /* scopeChain = */ nullptr, 0, data.result.address()); @@ -2816,7 +2817,8 @@ jit::FastInvoke(JSContext* cx, HandleFunction fun, CallArgs& args) MOZ_ASSERT(jit::IsIonEnabled(cx)); MOZ_ASSERT(!ion->bailoutExpected()); - JitActivation activation(cx, CalleeToToken(script)); + ActivationEntryMonitor entryMonitor(cx, CalleeToToken(script)); + JitActivation activation(cx); EnterJitCode enter = cx->runtime()->jitRuntime()->enterIon(); void* calleeToken = CalleeToToken(fun, /* constructing = */ false); diff --git a/js/src/js.msg b/js/src/js.msg index 988aed7e09..ee77b911cd 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -314,6 +314,7 @@ MSG_DEF(JSMSG_RESERVED_ID, 1, JSEXN_SYNTAXERR, "{0} is a reserved id MSG_DEF(JSMSG_REST_WITH_DEFAULT, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default") MSG_DEF(JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL, 1, JSEXN_SYNTAXERR, "self-hosted code cannot contain top-level {0} declarations") MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups") +MSG_DEF(JSMSG_SELFHOSTED_METHOD_CALL, 0, JSEXN_SYNTAXERR, "self-hosted code may not contain direct method calls") MSG_DEF(JSMSG_SEMI_AFTER_FOR_COND, 0, JSEXN_SYNTAXERR, "missing ; after for-loop condition") MSG_DEF(JSMSG_SEMI_AFTER_FOR_INIT, 0, JSEXN_SYNTAXERR, "missing ; after for-loop initializer") MSG_DEF(JSMSG_SEMI_BEFORE_STMNT, 0, JSEXN_SYNTAXERR, "missing ; before statement") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 9472c49dc4..8af5183c0f 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1220,6 +1220,14 @@ JS_GetErrorPrototype(JSContext* cx) return GlobalObject::getOrCreateCustomErrorPrototype(cx, global, JSEXN_ERR); } +JS_PUBLIC_API(JSObject*) +JS_GetIteratorPrototype(JSContext* cx) +{ + CHECK_REQUEST(cx); + Rooted global(cx, cx->global()); + return GlobalObject::getOrCreateIteratorPrototype(cx, global); +} + JS_PUBLIC_API(JSObject*) JS_GetGlobalForObject(JSContext* cx, JSObject* obj) { @@ -3983,7 +3991,12 @@ JS::CompileOptions::CompileOptions(JSContext* cx, JSVersion version) strictOption = cx->runtime()->options().strictMode(); extraWarningsOption = cx->compartment()->options().extraWarnings(cx); werrorOption = cx->runtime()->options().werror(); - asmJSOption = cx->runtime()->options().asmJS(); + if (!cx->runtime()->options().asmJS()) + asmJSOption = AsmJSOption::Disabled; + else if (cx->compartment()->debuggerObservesAsmJS()) + asmJSOption = AsmJSOption::DisabledByDebugger; + else + asmJSOption = AsmJSOption::Enabled; throwOnAsmJSValidationFailureOption = cx->runtime()->options().throwOnAsmJSValidationFailure(); } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index bb06a817fa..297fc3ef59 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1495,6 +1495,13 @@ JS_GetArrayPrototype(JSContext* cx, JS::HandleObject forObj); extern JS_PUBLIC_API(JSObject*) JS_GetErrorPrototype(JSContext* cx); +/** + * Returns the %IteratorPrototype% object that all built-in iterator prototype + * chains go through for the global object of the current compartment of cx. + */ +extern JS_PUBLIC_API(JSObject*) +JS_GetIteratorPrototype(JSContext* cx); + extern JS_PUBLIC_API(JSObject*) JS_GetGlobalForObject(JSContext* cx, JSObject* obj); @@ -3562,6 +3569,8 @@ namespace JS { * derived from ReadOnlyCompileOptions, so the compiler accepts it. */ +enum class AsmJSOption : uint8_t { Enabled, Disabled, DisabledByDebugger }; + /** * The common base class for the CompileOptions hierarchy. * @@ -3604,7 +3613,7 @@ class JS_FRIEND_API(TransitiveCompileOptions) strictOption(false), extraWarningsOption(false), werrorOption(false), - asmJSOption(false), + asmJSOption(AsmJSOption::Disabled), throwOnAsmJSValidationFailureOption(false), forceAsync(false), installedFile(false), @@ -3639,7 +3648,7 @@ class JS_FRIEND_API(TransitiveCompileOptions) bool strictOption; bool extraWarningsOption; bool werrorOption; - bool asmJSOption; + AsmJSOption asmJSOption; bool throwOnAsmJSValidationFailureOption; bool forceAsync; bool installedFile; // 'true' iff pre-compiling js file in packaged app diff --git a/js/src/shell/ModuleLoader.js b/js/src/shell/ModuleLoader.js new file mode 100644 index 0000000000..d8767f05f4 --- /dev/null +++ b/js/src/shell/ModuleLoader.js @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// A basic synchronous module loader for testing the shell. + +Reflect.Loader = new class { + constructor() { + this.registry = new Map(); + this.loadPath = getModuleLoadPath(); + } + + resolve(name) { + if (os.path.isAbsolute(name)) + return name; + + return os.path.join(this.loadPath, name); + } + + fetch(path) { + return os.file.readFile(path); + } + + loadAndParse(name) { + let path = this.resolve(name); + + if (this.registry.has(path)) + return this.registry.get(path); + + let source = this.fetch(path); + let module = parseModule(source, path); + this.registry.set(path, module); + return module; + } + + ["import"](name, referrer) { + let module = this.loadAndParse(name); + module.declarationInstantiation(); + return module.evaluation(); + } +}; + +setModuleResolveHook((module, requestName) => Reflect.Loader.loadAndParse(requestName)); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 15a3c0faa6..c674a5ddd1 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -54,6 +54,7 @@ # include "jswin.h" #endif #include "jswrapper.h" +#include "shellmoduleloader.out.h" #include "builtin/ModuleObject.h" #include "builtin/TestingFunctions.h" @@ -73,11 +74,13 @@ #include "shell/jsoptparse.h" #include "shell/OSObject.h" #include "vm/ArgumentsObject.h" +#include "vm/Compression.h" #include "vm/Debugger.h" #include "vm/HelperThreads.h" #include "vm/Monitor.h" #include "vm/Shape.h" #include "vm/SharedArrayObject.h" +#include "vm/StringBuffer.h" #include "vm/Time.h" #include "vm/TypedArrayObject.h" #include "vm/WrapperObject.h" @@ -109,30 +112,53 @@ enum JSShellExitCode { EXITCODE_TIMEOUT = 6 }; -static size_t gStackChunkSize = 8192; +static const size_t gStackChunkSize = 8192; /* * Note: This limit should match the stack limit set by the browser in * js/xpconnect/src/XPCJSRuntime.cpp */ #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN)) -static size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024; +static const size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024; #else -static size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; +static const size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; #endif /* * Limit the timeout to 30 minutes to prevent an overflow on platfoms * that represent the time internally in microseconds using 32-bit int. */ -static double MAX_TIMEOUT_INTERVAL = 1800.0; -static double gTimeoutInterval = -1.0; -static Atomic gServiceInterrupt; -static JS::PersistentRootedValue gInterruptFunc; +static const double MAX_TIMEOUT_INTERVAL = 1800.0; -static bool gLastWarningEnabled = false; -static JS::PersistentRootedValue gLastWarning; +// Per-runtime shell state. +struct ShellRuntime +{ + ShellRuntime(); + bool isWorker; + double timeoutInterval; + Atomic serviceInterrupt; + JS::PersistentRootedValue interruptFunc; + bool lastWarningEnabled; + JS::PersistentRootedValue lastWarning; + + /* + * Watchdog thread state. + */ + PRLock* watchdogLock; + PRCondVar* watchdogWakeup; + PRThread* watchdogThread; + bool watchdogHasTimeout; + int64_t watchdogTimeout; + + PRCondVar* sleepWakeup; + + int exitCode; + bool quitting; + bool gotError; +}; + +// Shell state set once at startup. static bool enableCodeCoverage = false; static bool enableDisassemblyDumps = false; static bool offthreadCompilation = false; @@ -144,11 +170,24 @@ static bool enableUnboxedArrays = false; #ifdef JS_GC_ZEAL static char gZealStr[128]; #endif - static bool printTiming = false; static const char* jsCacheDir = nullptr; static const char* jsCacheAsmJSPath = nullptr; -static bool jsCachingEnabled = false; +static FILE* gErrFile = nullptr; +static FILE* gOutFile = nullptr; +static bool reportWarnings = true; +static bool compileOnly = false; +static bool fuzzingSafe = false; +static bool disableOOMFunctions = false; +static const char* moduleLoadPath = "."; + +#ifdef DEBUG +static bool dumpEntrainedVariables = false; +static bool OOM_printAllocationCount = false; +#endif + +// Shell state this is only accessed on the main thread. +bool jsCachingEnabled = false; mozilla::Atomic jsCacheOpened(false); static bool @@ -158,7 +197,7 @@ static bool InitWatchdog(JSRuntime* rt); static void -KillWatchdog(); +KillWatchdog(JSRuntime *rt); static bool ScheduleWatchdog(JSRuntime* rt, double t); @@ -166,33 +205,6 @@ ScheduleWatchdog(JSRuntime* rt, double t); static void CancelExecution(JSRuntime* rt); -/* - * Watchdog thread state. - */ -static PRLock* gWatchdogLock = nullptr; -static PRCondVar* gWatchdogWakeup = nullptr; -static PRThread* gWatchdogThread = nullptr; -static bool gWatchdogHasTimeout = false; -static int64_t gWatchdogTimeout = 0; - -static PRCondVar* gSleepWakeup = nullptr; - -static int gExitCode = 0; -static bool gQuitting = false; -static bool gGotError = false; -static FILE* gErrFile = nullptr; -static FILE* gOutFile = nullptr; - -static bool reportWarnings = true; -static bool compileOnly = false; -static bool fuzzingSafe = false; -static bool disableOOMFunctions = false; - -#ifdef DEBUG -static bool dumpEntrainedVariables = false; -static bool OOM_printAllocationCount = false; -#endif - static JSContext* NewContext(JSRuntime* rt); @@ -267,6 +279,36 @@ extern JS_EXPORT_API(void) add_history(char* line); } // extern "C" #endif +ShellRuntime::ShellRuntime() + : isWorker(false), + timeoutInterval(-1.0), + serviceInterrupt(false), + lastWarningEnabled(false), + watchdogLock(nullptr), + watchdogWakeup(nullptr), + watchdogThread(nullptr), + watchdogHasTimeout(false), + watchdogTimeout(0), + sleepWakeup(nullptr), + exitCode(0), + quitting(false), + gotError(false) +{} + +static ShellRuntime* +GetShellRuntime(JSRuntime *rt) +{ + ShellRuntime* sr = static_cast(JS_GetRuntimePrivate(rt)); + MOZ_ASSERT(sr); + return sr; +} + +static ShellRuntime* +GetShellRuntime(JSContext* cx) +{ + return GetShellRuntime(cx->runtime()); +} + static char* GetLine(FILE* file, const char * prompt) { @@ -370,17 +412,18 @@ GetContextData(JSContext* cx) static bool ShellInterruptCallback(JSContext* cx) { - if (!gServiceInterrupt) + ShellRuntime* sr = GetShellRuntime(cx); + if (!sr->serviceInterrupt) return true; - // Reset gServiceInterrupt. CancelExecution or InterruptIf will set it to + // Reset serviceInterrupt. CancelExecution or InterruptIf will set it to // true to distinguish watchdog or user triggered interrupts. // Do this first to prevent other interrupts that may occur while the // user-supplied callback is executing from re-entering the handler. - gServiceInterrupt = false; + sr->serviceInterrupt = false; bool result; - RootedValue interruptFunc(cx, gInterruptFunc); + RootedValue interruptFunc(cx, sr->interruptFunc); if (!interruptFunc.isNull()) { JS::AutoSaveExceptionState savedExc(cx); JSAutoCompartment ac(cx, &interruptFunc.toObject()); @@ -398,8 +441,8 @@ ShellInterruptCallback(JSContext* cx) result = false; } - if (!result && gExitCode == 0) - gExitCode = EXITCODE_TIMEOUT; + if (!result && sr->exitCode == 0) + sr->exitCode = EXITCODE_TIMEOUT; return result; } @@ -432,6 +475,8 @@ SkipUTF8BOM(FILE* file) static void RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) { + ShellRuntime* sr = GetShellRuntime(cx); + SkipUTF8BOM(file); // To support the UNIX #! shell hack, gobble the first line if it starts @@ -456,9 +501,9 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) .setIsRunOnce(true) .setNoScriptRval(true); - gGotError = false; + sr->gotError = false; (void) JS::Compile(cx, options, file, &script); - MOZ_ASSERT_IF(!script, gGotError); + MOZ_ASSERT_IF(!script, sr->gotError); } #ifdef DEBUG @@ -467,8 +512,8 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) #endif if (script && !compileOnly) { if (!JS_ExecuteScript(cx, script)) { - if (!gQuitting && gExitCode != EXITCODE_TIMEOUT) - gExitCode = EXITCODE_RUNTIME_ERROR; + if (!sr->quitting && sr->exitCode != EXITCODE_TIMEOUT) + sr->exitCode = EXITCODE_RUNTIME_ERROR; } int64_t t2 = PRMJ_Now() - t1; if (printTiming) @@ -476,6 +521,93 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) } } +static bool +InitModuleLoader(JSContext* cx) +{ + // Decompress and evaluate the embedded module loader source to initialize + // the module loader for the current compartment. + + uint32_t srcLen = moduleloader::GetRawScriptsSize(); + ScopedJSFreePtr src(cx->pod_malloc(srcLen)); + if (!src || !DecompressString(moduleloader::compressedSources, moduleloader::GetCompressedSize(), + reinterpret_cast(src.get()), srcLen)) + { + return false; + } + + CompileOptions options(cx); + options.setIntroductionType("shell module loader"); + options.setFileAndLine("shell/ModuleLoader.js", 1); + options.setSelfHostingMode(false); + options.setCanLazilyParse(false); + options.setVersion(JSVERSION_LATEST); + options.werrorOption = true; + options.strictOption = true; + + RootedValue rv(cx); + return Evaluate(cx, options, src, srcLen, &rv); +} + +static bool +GetLoaderObject(JSContext* cx, MutableHandleObject resultOut) +{ + // Look up the |Reflect.Loader| object that has been defined by the module + // loader. + + RootedObject object(cx, cx->global()); + RootedValue value(cx); + if (!JS_GetProperty(cx, object, "Reflect", &value) || !value.isObject()) + return false; + + object = &value.toObject(); + if (!JS_GetProperty(cx, object, "Loader", &value) || !value.isObject()) + return false; + + resultOut.set(&value.toObject()); + return true; +} + +static bool +GetImportMethod(JSContext* cx, HandleObject loader, MutableHandleFunction resultOut) +{ + // Look up the module loader's |import| method. + + RootedValue value(cx); + if (!JS_GetProperty(cx, loader, "import", &value) || !value.isObject()) + return false; + + RootedObject object(cx, &value.toObject()); + if (!object->is()) + return false; + + resultOut.set(&object->as()); + return true; +} + +static void +RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly) +{ + // Exectute a module by calling |Reflect.Loader.import(filename)|. + + ShellRuntime* sr = GetShellRuntime(cx); + + RootedObject loaderObj(cx); + MOZ_ALWAYS_TRUE(GetLoaderObject(cx, &loaderObj)); + + RootedFunction importFun(cx); + MOZ_ALWAYS_TRUE(GetImportMethod(cx, loaderObj, &importFun)); + + AutoValueArray<2> args(cx); + args[0].setString(JS_NewStringCopyZ(cx, filename)); + args[1].setUndefined(); + + RootedValue value(cx); + if (!JS_CallFunction(cx, loaderObj, importFun, args, &value)) { + sr->exitCode = EXITCODE_RUNTIME_ERROR; + return; + } +} + static bool EvalAndPrint(JSContext* cx, const char* bytes, size_t length, int lineno, bool compileOnly, FILE* out) @@ -514,6 +646,7 @@ EvalAndPrint(JSContext* cx, const char* bytes, size_t length, static void ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly) { + ShellRuntime* sr = GetShellRuntime(cx); int lineno = 1; bool hitEOF = false; @@ -529,7 +662,7 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly) CharBuffer buffer(cx); do { ScheduleWatchdog(cx->runtime(), -1); - gServiceInterrupt = false; + sr->serviceInterrupt = false; errno = 0; char* line = GetLine(in, startline == lineno ? "js> " : ""); @@ -546,7 +679,7 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly) return; lineno++; - if (!ScheduleWatchdog(cx->runtime(), gTimeoutInterval)) { + if (!ScheduleWatchdog(cx->runtime(), sr->timeoutInterval)) { hitEOF = true; break; } @@ -561,13 +694,19 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly) // Catch the error, report it, and keep going. JS_ReportPendingException(cx); } - } while (!hitEOF && !gQuitting); + } while (!hitEOF && !sr->quitting); fprintf(out, "\n"); } +enum FileKind +{ + FileScript, + FileModule +}; + static void -Process(JSContext* cx, const char* filename, bool forceTTY) +Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind = FileScript) { FILE* file; if (forceTTY || !filename || strcmp(filename, "-") == 0) { @@ -584,9 +723,13 @@ Process(JSContext* cx, const char* filename, bool forceTTY) if (!forceTTY && !isatty(fileno(file))) { // It's not interactive - just execute it. - RunFile(cx, filename, file, compileOnly); + if (kind == FileScript) + RunFile(cx, filename, file, compileOnly); + else + RunModule(cx, filename, file, compileOnly); } else { // It's an interactive filehandle; drop into read-eval-print loop. + MOZ_ASSERT(kind == FileScript); ReadEvalPrintLoop(cx, file, gOutFile, compileOnly); } } @@ -1204,12 +1347,14 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp) { if (saveBytecode) { - if (!JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()) { + if (!JS::CompartmentOptionsRef(cx).cloneSingletons()) { JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_SINGLETON_FAILED); return false; } - JS::CompartmentOptionsRef(cx).setCloneSingletons(true); + + // cloneSingletons implies that singletons are used as template objects. + MOZ_ASSERT(JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()); } if (loadBytecode) { @@ -1273,8 +1418,13 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp) // replace the current bytecode by the same stream of bytes. if (loadBytecode && assertEqBytecode) { if (saveLength != loadLength) { + char loadLengthStr[16]; + JS_snprintf(loadLengthStr, sizeof(loadLengthStr), "%u", loadLength); + char saveLengthStr[16]; + JS_snprintf(saveLengthStr, sizeof(saveLengthStr), "%u", saveLength); + JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED, - loadLength, saveLength); + loadLengthStr, saveLengthStr); return false; } @@ -1543,6 +1693,8 @@ Help(JSContext* cx, unsigned argc, Value* vp); static bool Quit(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); + #ifdef JS_MORE_DETERMINISTIC // Print a message to stderr in more-deterministic builds to help jsfunfuzz // find uncatchable-exception bugs. @@ -1564,8 +1716,8 @@ Quit(JSContext* cx, unsigned argc, Value* vp) return false; } - gExitCode = code; - gQuitting = true; + sr->exitCode = code; + sr->quitting = true; return false; } @@ -2548,9 +2700,25 @@ WorkerMain(void* arg) js_delete(input); return; } + + mozilla::UniquePtr sr = MakeUnique(); + if (!sr) { + JS_DestroyRuntime(rt); + js_delete(input); + return; + } + + sr->isWorker = true; + JS_SetRuntimePrivate(rt, sr.get()); JS_SetErrorReporter(rt, my_ErrorReporter); SetWorkerRuntimeOptions(rt); + if (!InitWatchdog(rt)) { + JS_DestroyRuntime(rt); + js_delete(input); + return; + } + JSContext* cx = NewContext(rt); if (!cx) { JS_DestroyRuntime(rt); @@ -2564,7 +2732,7 @@ WorkerMain(void* arg) JSAutoRequest ar(cx); JS::CompartmentOptions compartmentOptions; - compartmentOptions.setVersion(JSVERSION_LATEST); + compartmentOptions.setVersion(JSVERSION_DEFAULT); RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr)); if (!global) break; @@ -2656,6 +2824,7 @@ IsBefore(int64_t t1, int64_t t2) static bool Sleep_fn(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); int64_t t_ticks; @@ -2676,61 +2845,63 @@ Sleep_fn(JSContext* cx, unsigned argc, Value* vp) ? 0 : int64_t(PRMJ_USEC_PER_SEC * t_secs); } - PR_Lock(gWatchdogLock); + PR_Lock(sr->watchdogLock); int64_t to_wakeup = PRMJ_Now() + t_ticks; for (;;) { - PR_WaitCondVar(gSleepWakeup, PR_MillisecondsToInterval(t_ticks / 1000)); - if (gServiceInterrupt) + PR_WaitCondVar(sr->sleepWakeup, PR_MillisecondsToInterval(t_ticks / 1000)); + if (sr->serviceInterrupt) break; int64_t now = PRMJ_Now(); if (!IsBefore(now, to_wakeup)) break; t_ticks = to_wakeup - now; } - PR_Unlock(gWatchdogLock); + PR_Unlock(sr->watchdogLock); args.rval().setUndefined(); - return !gServiceInterrupt; + return !sr->serviceInterrupt; } static bool InitWatchdog(JSRuntime* rt) { - MOZ_ASSERT(!gWatchdogThread); - gWatchdogLock = PR_NewLock(); - if (gWatchdogLock) { - gWatchdogWakeup = PR_NewCondVar(gWatchdogLock); - if (gWatchdogWakeup) { - gSleepWakeup = PR_NewCondVar(gWatchdogLock); - if (gSleepWakeup) + ShellRuntime* sr = GetShellRuntime(rt); + MOZ_ASSERT(!sr->watchdogThread); + sr->watchdogLock = PR_NewLock(); + if (sr->watchdogLock) { + sr->watchdogWakeup = PR_NewCondVar(sr->watchdogLock); + if (sr->watchdogWakeup) { + sr->sleepWakeup = PR_NewCondVar(sr->watchdogLock); + if (sr->sleepWakeup) return true; - PR_DestroyCondVar(gWatchdogWakeup); + PR_DestroyCondVar(sr->watchdogWakeup); } - PR_DestroyLock(gWatchdogLock); + PR_DestroyLock(sr->watchdogLock); } return false; } static void -KillWatchdog() +KillWatchdog(JSRuntime* rt) { + ShellRuntime* sr = GetShellRuntime(rt); PRThread* thread; - PR_Lock(gWatchdogLock); - thread = gWatchdogThread; + PR_Lock(sr->watchdogLock); + thread = sr->watchdogThread; if (thread) { /* * The watchdog thread is running, tell it to terminate waking it up * if necessary. */ - gWatchdogThread = nullptr; - PR_NotifyCondVar(gWatchdogWakeup); + sr->watchdogThread = nullptr; + PR_NotifyCondVar(sr->watchdogWakeup); } - PR_Unlock(gWatchdogLock); + PR_Unlock(sr->watchdogLock); if (thread) PR_JoinThread(thread); - PR_DestroyCondVar(gSleepWakeup); - PR_DestroyCondVar(gWatchdogWakeup); - PR_DestroyLock(gWatchdogLock); + PR_DestroyCondVar(sr->sleepWakeup); + PR_DestroyCondVar(sr->watchdogWakeup); + PR_DestroyLock(sr->watchdogLock); } static void @@ -2739,24 +2910,25 @@ WatchdogMain(void* arg) PR_SetCurrentThreadName("JS Watchdog"); JSRuntime* rt = (JSRuntime*) arg; + ShellRuntime* sr = GetShellRuntime(rt); - PR_Lock(gWatchdogLock); - while (gWatchdogThread) { + PR_Lock(sr->watchdogLock); + while (sr->watchdogThread) { int64_t now = PRMJ_Now(); - if (gWatchdogHasTimeout && !IsBefore(now, gWatchdogTimeout)) { + if (sr->watchdogHasTimeout && !IsBefore(now, sr->watchdogTimeout)) { /* * The timeout has just expired. Request an interrupt callback * outside the lock. */ - gWatchdogHasTimeout = false; - PR_Unlock(gWatchdogLock); + sr->watchdogHasTimeout = false; + PR_Unlock(sr->watchdogLock); CancelExecution(rt); - PR_Lock(gWatchdogLock); + PR_Lock(sr->watchdogLock); /* Wake up any threads doing sleep. */ - PR_NotifyAllCondVar(gSleepWakeup); + PR_NotifyAllCondVar(sr->sleepWakeup); } else { - if (gWatchdogHasTimeout) { + if (sr->watchdogHasTimeout) { /* * Time hasn't expired yet. Simulate an interrupt callback * which doesn't abort execution. @@ -2765,58 +2937,61 @@ WatchdogMain(void* arg) } uint64_t sleepDuration = PR_INTERVAL_NO_TIMEOUT; - if (gWatchdogHasTimeout) + if (sr->watchdogHasTimeout) sleepDuration = PR_TicksPerSecond() / 10; mozilla::DebugOnly status = - PR_WaitCondVar(gWatchdogWakeup, sleepDuration); + PR_WaitCondVar(sr->watchdogWakeup, sleepDuration); MOZ_ASSERT(status == PR_SUCCESS); } } - PR_Unlock(gWatchdogLock); + PR_Unlock(sr->watchdogLock); } static bool ScheduleWatchdog(JSRuntime* rt, double t) { + ShellRuntime* sr = GetShellRuntime(rt); + if (t <= 0) { - PR_Lock(gWatchdogLock); - gWatchdogHasTimeout = false; - PR_Unlock(gWatchdogLock); + PR_Lock(sr->watchdogLock); + sr->watchdogHasTimeout = false; + PR_Unlock(sr->watchdogLock); return true; } int64_t interval = int64_t(ceil(t * PRMJ_USEC_PER_SEC)); int64_t timeout = PRMJ_Now() + interval; - PR_Lock(gWatchdogLock); - if (!gWatchdogThread) { - MOZ_ASSERT(!gWatchdogHasTimeout); - gWatchdogThread = PR_CreateThread(PR_USER_THREAD, + PR_Lock(sr->watchdogLock); + if (!sr->watchdogThread) { + MOZ_ASSERT(!sr->watchdogHasTimeout); + sr->watchdogThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, rt, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); - if (!gWatchdogThread) { - PR_Unlock(gWatchdogLock); + if (!sr->watchdogThread) { + PR_Unlock(sr->watchdogLock); return false; } - } else if (!gWatchdogHasTimeout || IsBefore(timeout, gWatchdogTimeout)) { - PR_NotifyCondVar(gWatchdogWakeup); + } else if (!sr->watchdogHasTimeout || IsBefore(timeout, sr->watchdogTimeout)) { + PR_NotifyCondVar(sr->watchdogWakeup); } - gWatchdogHasTimeout = true; - gWatchdogTimeout = timeout; - PR_Unlock(gWatchdogLock); + sr->watchdogHasTimeout = true; + sr->watchdogTimeout = timeout; + PR_Unlock(sr->watchdogLock); return true; } static void CancelExecution(JSRuntime* rt) { - gServiceInterrupt = true; + ShellRuntime* sr = GetShellRuntime(rt); + sr->serviceInterrupt = true; JS_RequestInterruptCallback(rt); - if (!gInterruptFunc.isNull()) { + if (!sr->interruptFunc.isNull()) { static const char msg[] = "Script runs for too long, terminating.\n"; fputs(msg, stderr); } @@ -2830,7 +3005,7 @@ SetTimeoutValue(JSContext* cx, double t) JS_ReportError(cx, "Excessive timeout value"); return false; } - gTimeoutInterval = t; + GetShellRuntime(cx)->timeoutInterval = t; if (!ScheduleWatchdog(cx->runtime(), t)) { JS_ReportError(cx, "Failed to create the watchdog"); return false; @@ -2841,10 +3016,11 @@ SetTimeoutValue(JSContext* cx, double t) static bool Timeout(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); if (args.length() == 0) { - args.rval().setNumber(gTimeoutInterval); + args.rval().setNumber(sr->timeoutInterval); return true; } @@ -2863,7 +3039,7 @@ Timeout(JSContext* cx, unsigned argc, Value* vp) JS_ReportError(cx, "Second argument must be a timeout function"); return false; } - gInterruptFunc = value; + sr->interruptFunc = value; } args.rval().setUndefined(); @@ -2881,7 +3057,7 @@ InterruptIf(JSContext* cx, unsigned argc, Value* vp) } if (ToBoolean(args[0])) { - gServiceInterrupt = true; + GetShellRuntime(cx)->serviceInterrupt = true; JS_RequestInterruptCallback(cx->runtime()); } @@ -2898,7 +3074,7 @@ InvokeInterruptCallbackWrapper(JSContext* cx, unsigned argc, Value* vp) return false; } - gServiceInterrupt = true; + GetShellRuntime(cx)->serviceInterrupt = true; JS_RequestInterruptCallback(cx->runtime()); bool interruptRv = CheckForInterrupt(cx); @@ -2934,7 +3110,7 @@ SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) JS_ReportError(cx, "Argument must be a function"); return false; } - gInterruptFunc = value; + GetShellRuntime(cx)->interruptFunc = value; args.rval().setUndefined(); return true; @@ -2943,10 +3119,11 @@ SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) static bool EnableLastWarning(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); - gLastWarningEnabled = true; - gLastWarning.setNull(); + sr->lastWarningEnabled = true; + sr->lastWarning.setNull(); args.rval().setUndefined(); return true; @@ -2955,10 +3132,11 @@ EnableLastWarning(JSContext* cx, unsigned argc, Value* vp) static bool DisableLastWarning(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); - gLastWarningEnabled = false; - gLastWarning.setNull(); + sr->lastWarningEnabled = false; + sr->lastWarning.setNull(); args.rval().setUndefined(); return true; @@ -2967,31 +3145,33 @@ DisableLastWarning(JSContext* cx, unsigned argc, Value* vp) static bool GetLastWarning(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); - if (!gLastWarningEnabled) { + if (!sr->lastWarningEnabled) { JS_ReportError(cx, "Call enableLastWarning first."); return false; } - if (!JS_WrapValue(cx, &gLastWarning)) + if (!JS_WrapValue(cx, &sr->lastWarning)) return false; - args.rval().set(gLastWarning); + args.rval().set(sr->lastWarning); return true; } static bool ClearLastWarning(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); - if (!gLastWarningEnabled) { + if (!sr->lastWarningEnabled) { JS_ReportError(cx, "Call enableLastWarning first."); return false; } - gLastWarning.setNull(); + sr->lastWarning.setNull(); args.rval().setUndefined(); return true; @@ -3088,11 +3268,12 @@ static bool ParseModule(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() < 1) { + if (args.length() == 0) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, "parseModule", "0", "s"); return false; } + if (!args[0].isString()) { const char* typeName = InformalValueTypeName(args[0]); JS_ReportError(cx, "expected string to compile, got %s", typeName); @@ -3104,8 +3285,24 @@ ParseModule(JSContext* cx, unsigned argc, Value* vp) if (!scriptContents) return false; + mozilla::UniquePtr filename; CompileOptions options(cx); - options.setFileAndLine("", 1); + if (args.length() > 1) { + if (!args[1].isString()) { + JS_ReportError(cx, "expected filename string, got %s", + JS_TypeOfValue(cx, args[1])); + return false; + } + + RootedString str(cx, args[1].toString()); + filename.reset(JS_EncodeString(cx, str)); + if (!filename) + return false; + + options.setFileAndLine(filename.get(), 1); + } else { + options.setFileAndLine("", 1); + } AutoStableStringChars stableChars(cx); if (!stableChars.initTwoByte(cx, scriptContents)) @@ -3146,6 +3343,14 @@ SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(JS_NewStringCopyZ(cx, moduleLoadPath)); + return true; +} + static bool Parse(JSContext* cx, unsigned argc, Value* vp) { @@ -3720,7 +3925,7 @@ NewGlobal(JSContext* cx, unsigned argc, Value* vp) { JSPrincipals* principals = nullptr; JS::CompartmentOptions options; - options.setVersion(JSVERSION_LATEST); + options.setVersion(JSVERSION_DEFAULT); CallArgs args = CallArgsFromVp(argc, vp); if (args.length() == 1 && args[0].isObject()) { @@ -3737,6 +3942,11 @@ NewGlobal(JSContext* cx, unsigned argc, Value* vp) if (v.isBoolean()) options.setInvisibleToDebugger(v.toBoolean()); + if (!JS_GetProperty(cx, opts, "cloneSingletons", &v)) + return false; + if (v.isBoolean()) + options.setCloneSingletons(v.toBoolean()); + if (!JS_GetProperty(cx, opts, "principal", &v)) return false; if (!v.isUndefined()) { @@ -3883,6 +4093,11 @@ static bool SetCachingEnabled(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + if (GetShellRuntime(cx)->isWorker) { + JS_ReportError(cx, "Caching is not supported in workers"); + return false; + } + jsCachingEnabled = ToBoolean(args.get(0)); args.rval().setUndefined(); return true; @@ -3904,7 +4119,7 @@ PrintProfilerEvents(JSContext* cx, unsigned argc, Value* vp) return true; } -#if defined(JS_SIMULATOR_ARM) +#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64) typedef Vector StackChars; Vector stacks; @@ -3919,10 +4134,15 @@ SingleStepCallback(void* arg, jit::Simulator* sim, void* pc) JS::ProfilingFrameIterator::RegisterState state; state.pc = pc; +#if defined(JS_SIMULATOR_ARM) state.sp = (void*)sim->get_register(jit::Simulator::sp); state.lr = (void*)sim->get_register(jit::Simulator::lr); +#elif defined(JS_SIMULATOR_MIPS64) + state.sp = (void*)sim->getRegister(jit::Simulator::sp); + state.lr = (void*)sim->getRegister(jit::Simulator::ra); +#endif - DebugOnly lastStackAddress = nullptr; + mozilla::DebugOnly lastStackAddress = nullptr; StackChars stack; uint32_t frameNo = 0; for (JS::ProfilingFrameIterator i(rt, state); !i.done(); ++i) { @@ -3952,7 +4172,7 @@ SingleStepCallback(void* arg, jit::Simulator* sim, void* pc) static bool EnableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp) { -#if defined(JS_SIMULATOR_ARM) +#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64) CallArgs args = CallArgsFromVp(argc, vp); jit::Simulator* sim = cx->runtime()->simulator(); @@ -3969,7 +4189,7 @@ EnableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp) static bool DisableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp) { -#if defined(JS_SIMULATOR_ARM) +#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64) CallArgs args = CallArgsFromVp(argc, vp); jit::Simulator* sim = cx->runtime()->simulator(); @@ -4703,6 +4923,11 @@ static const JSFunctionSpecWithHelp shell_functions[] = { " This hook is used to look up a previously loaded module object. It should\n" " be implemented by the module loader."), + JS_FN_HELP("getModuleLoadPath", GetModuleLoadPath, 0, 0, +"getModuleLoadPath()", +" Return any --module-load-path argument passed to the shell. Used by the\n" +" module loader.\n"), + JS_FN_HELP("parse", Parse, 1, 0, "parse(code)", " Parses a string, potentially throwing."), @@ -5130,7 +5355,7 @@ CreateLastWarningObject(JSContext* cx, JSErrorReport* report) if (!DefineProperty(cx, warningObj, cx->names().columnNumber, columnVal)) return false; - gLastWarning.setObject(*warningObj); + GetShellRuntime(cx)->lastWarning.setObject(*warningObj); return true; } @@ -5151,9 +5376,10 @@ PrintStackTrace(JSContext* cx, HandleValue exn) if (!exnObj->is()) return true; + // Exceptions thrown while compiling top-level script have no stack. RootedObject stackObj(cx, exnObj->as().stack()); if (!stackObj) - return false; + return true; RootedString stackStr(cx); if (!BuildStackString(cx, stackObj, &stackStr, 2)) @@ -5172,7 +5398,9 @@ PrintStackTrace(JSContext* cx, HandleValue exn) void js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) { - if (report && JSREPORT_IS_WARNING(report->flags) && gLastWarningEnabled) { + ShellRuntime* sr = GetShellRuntime(cx); + + if (report && JSREPORT_IS_WARNING(report->flags) && sr->lastWarningEnabled) { JS::AutoSaveExceptionState savedExc(cx); if (!CreateLastWarningObject(cx, report)) { fputs("Unhandled error happened while creating last warning object.\n", gOutFile); @@ -5186,7 +5414,7 @@ js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* r if (JS_IsExceptionPending(cx)) (void) JS_GetPendingException(cx, &exn); - gGotError = PrintError(cx, gErrFile, message, report, reportWarnings); + sr->gotError = PrintError(cx, gErrFile, message, report, reportWarnings); if (!exn.isUndefined()) { JS::AutoSaveExceptionState savedExc(cx); if (!PrintStackTrace(cx, exn)) @@ -5195,11 +5423,10 @@ js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* r } if (report->exnType != JSEXN_NONE && !JSREPORT_IS_WARNING(report->flags)) { - if (report->errorNumber == JSMSG_OUT_OF_MEMORY) { - gExitCode = EXITCODE_OUT_OF_MEMORY; - } else { - gExitCode = EXITCODE_RUNTIME_ERROR; - } + if (report->errorNumber == JSMSG_OUT_OF_MEMORY) + sr->exitCode = EXITCODE_OUT_OF_MEMORY; + else + sr->exitCode = EXITCODE_RUNTIME_ERROR; } } @@ -5210,7 +5437,7 @@ my_OOMCallback(JSContext* cx, void* data) // memory", which may or may not be caught. Otherwise the engine will just // unwind and return null/false, with no exception set. if (!JS_IsRunning(cx)) - gGotError = true; + GetShellRuntime(cx)->gotError = true; } static bool @@ -5875,6 +6102,8 @@ OptionFailure(const char* option, const char* str) static int ProcessArgs(JSContext* cx, OptionParser* op) { + ShellRuntime* sr = GetShellRuntime(cx); + if (op->getBoolOption('s')) JS::RuntimeOptionsRef(cx).toggleExtraWarnings(); @@ -5884,48 +6113,68 @@ ProcessArgs(JSContext* cx, OptionParser* op) MultiStringRange filePaths = op->getMultiStringOption('f'); MultiStringRange codeChunks = op->getMultiStringOption('e'); + MultiStringRange modulePaths = op->getMultiStringOption('m'); - if (filePaths.empty() && codeChunks.empty() && !op->getStringArg("script")) { + if (filePaths.empty() && + codeChunks.empty() && + modulePaths.empty() && + !op->getStringArg("script")) + { Process(cx, nullptr, true); /* Interactive. */ - return gExitCode; + return sr->exitCode; } - while (!filePaths.empty() || !codeChunks.empty()) { - size_t fpArgno = filePaths.empty() ? -1 : filePaths.argno(); - size_t ccArgno = codeChunks.empty() ? -1 : codeChunks.argno(); - if (fpArgno < ccArgno) { + if (const char* path = op->getStringOption("module-load-path")) + moduleLoadPath = path; + + if (!modulePaths.empty() && !InitModuleLoader(cx)) + return EXIT_FAILURE; + + while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty()) { + size_t fpArgno = filePaths.empty() ? SIZE_MAX : filePaths.argno(); + size_t ccArgno = codeChunks.empty() ? SIZE_MAX : codeChunks.argno(); + size_t mpArgno = modulePaths.empty() ? SIZE_MAX : modulePaths.argno(); + + if (fpArgno < ccArgno && fpArgno < mpArgno) { char* path = filePaths.front(); - Process(cx, path, false); - if (gExitCode) - return gExitCode; + Process(cx, path, false, FileScript); + if (sr->exitCode) + return sr->exitCode; filePaths.popFront(); - } else { + } else if (ccArgno < fpArgno && ccArgno < mpArgno) { const char* code = codeChunks.front(); RootedValue rval(cx); JS::CompileOptions opts(cx); opts.setFileAndLine("-e", 1); if (!JS::Evaluate(cx, opts, code, strlen(code), &rval)) - return gExitCode ? gExitCode : EXITCODE_RUNTIME_ERROR; + return sr->exitCode ? sr->exitCode : EXITCODE_RUNTIME_ERROR; codeChunks.popFront(); - if (gQuitting) + if (sr->quitting) break; + } else { + MOZ_ASSERT(mpArgno < fpArgno && mpArgno < ccArgno); + char* path = modulePaths.front(); + Process(cx, path, false, FileModule); + if (sr->exitCode) + return sr->exitCode; + modulePaths.popFront(); } } - if (gQuitting) - return gExitCode ? gExitCode : EXIT_SUCCESS; + if (sr->quitting) + return sr->exitCode ? sr->exitCode : EXIT_SUCCESS; /* The |script| argument is processed after all options. */ if (const char* path = op->getStringArg("script")) { Process(cx, path, false); - if (gExitCode) - return gExitCode; + if (sr->exitCode) + return sr->exitCode; } if (op->getBoolOption('i')) Process(cx, nullptr, true); - return gExitCode ? gExitCode : EXIT_SUCCESS; + return sr->exitCode ? sr->exitCode : EXIT_SUCCESS; } static bool @@ -6136,7 +6385,7 @@ SetRuntimeOptions(JSRuntime* rt, const OptionParser& op) int32_t stopAt = op.getIntOption("arm-sim-stop-at"); if (stopAt >= 0) jit::Simulator::StopSimAt = stopAt; -#elif defined(JS_SIMULATOR_MIPS32) +#elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64) if (op.getBoolOption("mips-sim-icache-checks")) jit::Simulator::ICacheCheckingEnabled = true; @@ -6222,7 +6471,7 @@ Shell(JSContext* cx, OptionParser* op, char** envp) RootedObject glob(cx); JS::CompartmentOptions options; - options.setVersion(JSVERSION_LATEST); + options.setVersion(JSVERSION_DEFAULT); glob = NewGlobalObject(cx, options, nullptr); if (!glob) return 1; @@ -6290,11 +6539,14 @@ static void PreInit() { #ifdef XP_WIN - // Disable the segfault dialog. We want to fail the tests immediately - // instead of hanging automation. - UINT prevMode = SetErrorMode(0); - UINT newMode = SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX; - SetErrorMode(prevMode | newMode); + const char* crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG"); + if (crash_option && strncmp(crash_option, "1", 1)) { + // Disable the segfault dialog. We want to fail the tests immediately + // instead of hanging automation. + UINT newMode = SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX; + UINT prevMode = SetErrorMode(newMode); + SetErrorMode(prevMode | newMode); + } #endif } @@ -6309,15 +6561,6 @@ main(int argc, char** argv, char** envp) JSRuntime* rt; JSContext* cx; int result; -#ifdef XP_WIN - { - const char* crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG"); - if (crash_option && strncmp(crash_option, "1", 1)) { - DWORD oldmode = SetErrorMode(SEM_NOGPFAULTERRORBOX); - SetErrorMode(oldmode | SEM_NOGPFAULTERRORBOX); - } - } -#endif #ifdef HAVE_SETLOCALE setlocale(LC_ALL, ""); @@ -6338,6 +6581,7 @@ main(int argc, char** argv, char** envp) op.setVersion(JS_GetImplementationVersion()); if (!op.addMultiStringOption('f', "file", "PATH", "File path to run") + || !op.addMultiStringOption('m', "module", "PATH", "Module path to run") || !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run") || !op.addBoolOption('i', "shell", "Enter prompt after running code") || !op.addBoolOption('c', "compileonly", "Only compile, don't run (syntax checking mode)") @@ -6466,7 +6710,7 @@ main(int argc, char** argv, char** envp) "simulator.") || !op.addIntOption('\0', "arm-sim-stop-at", "NUMBER", "Stop the ARM simulator after the given " "NUMBER of instructions.", -1) -#elif defined(JS_SIMULATOR_MIPS32) +#elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64) || !op.addBoolOption('\0', "mips-sim-icache-checks", "Enable icache flush checks in the MIPS " "simulator.") || !op.addIntOption('\0', "mips-sim-stop-at", "NUMBER", "Stop the MIPS simulator after the given " @@ -6476,6 +6720,7 @@ main(int argc, char** argv, char** envp) #ifdef JS_GC_ZEAL || !op.addStringOption('z', "gc-zeal", "LEVEL[,N]", gc::ZealModeHelpText) #endif + || !op.addStringOption('\0', "module-load-path", "DIR", "Set directory to load modules from") ) { return EXIT_FAILURE; @@ -6556,13 +6801,18 @@ main(int argc, char** argv, char** envp) if (!rt) return 1; + mozilla::UniquePtr sr = MakeUnique(); + if (!sr) + return 1; + + JS_SetRuntimePrivate(rt, sr.get()); JS_SetErrorReporter(rt, my_ErrorReporter); JS::SetOutOfMemoryCallback(rt, my_OOMCallback, nullptr); if (!SetRuntimeOptions(rt, op)) return 1; - gInterruptFunc.init(rt, NullValue()); - gLastWarning.init(rt, NullValue()); + sr->interruptFunc.init(rt, NullValue()); + sr->lastWarning.init(rt, NullValue()); JS_SetGCParameter(rt, JSGC_MAX_BYTES, 0xffffffff); @@ -6620,7 +6870,7 @@ main(int argc, char** argv, char** envp) DestroyContext(cx, true); - KillWatchdog(); + KillWatchdog(rt); MOZ_ASSERT_IF(!CanUseExtraThreads(), workerThreads.empty()); for (size_t i = 0; i < workerThreads.length(); i++) diff --git a/js/src/shell/moz.build b/js/src/shell/moz.build index bb35df206c..eedead342b 100644 --- a/js/src/shell/moz.build +++ b/js/src/shell/moz.build @@ -34,3 +34,12 @@ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS'] if not CONFIG['GNU_CXX']: ALLOW_COMPILER_WARNINGS = True + +# Prepare module loader JS code for embedding +GENERATED_FILES += ['shellmoduleloader.out.h'] +shellmoduleloader = GENERATED_FILES['shellmoduleloader.out.h'] +shellmoduleloader.script = '../builtin/embedjs.py:generate_shellmoduleloader' +shellmoduleloader.inputs = [ + '../js.msg', + 'ModuleLoader.js', +] diff --git a/js/src/tests/jstests.py b/js/src/tests/jstests.py index 78b9ae06c0..9183c5efab 100755 --- a/js/src/tests/jstests.py +++ b/js/src/tests/jstests.py @@ -13,7 +13,8 @@ from contextlib import contextmanager from copy import copy from subprocess import list2cmdline, call -from lib.tests import TestCase, get_jitflags +from lib.tests import RefTestCase, get_jitflags, get_environment_overlay, \ + change_env from lib.results import ResultsSink from lib.progressbar import ProgressBar @@ -211,8 +212,8 @@ def parse_args(): abspath(dirname(abspath(__file__))), '..', '..', 'examples', 'jorendb.js')) js_cmd_args.extend(['-d', '-f', debugger_path, '--']) - prefix = TestCase.build_js_cmd_prefix(options.js_shell, js_cmd_args, - debugger_prefix) + prefix = RefTestCase.build_js_cmd_prefix(options.js_shell, js_cmd_args, + debugger_prefix) # If files with lists of tests to run were specified, add them to the # requested tests set. @@ -275,8 +276,8 @@ def load_tests(options, requested_paths, excluded_paths): test_dir = dirname(abspath(__file__)) test_count = manifest.count_tests(test_dir, requested_paths, excluded_paths) - test_gen = manifest.load(test_dir, requested_paths, excluded_paths, - xul_tester) + test_gen = manifest.load_reftests(test_dir, requested_paths, excluded_paths, + xul_tester) if options.make_manifests: manifest.make_manifests(options.make_manifests, test_gen) @@ -332,6 +333,7 @@ def main(): print('Could not find shell at given path.') return 1 test_count, test_gen = load_tests(options, requested_paths, excluded_paths) + test_environment = get_environment_overlay(options.js_shell) if test_count == 0: print('no tests selected') @@ -340,29 +342,25 @@ def main(): test_dir = dirname(abspath(__file__)) if options.debug: - if len(list(test_gen)) > 1: + tests = list(test_gen) + if len(tests) > 1: print('Multiple tests match command line arguments,' ' debugger can only run one') - for tc in test_gen: + for tc in tests: print(' {}'.format(tc.path)) return 2 - cmd = test_gen[0].get_command(TestCase.js_cmd_prefix) + cmd = tests[0].get_command(prefix) if options.show_cmd: print(list2cmdline(cmd)) - with changedir(test_dir): + with changedir(test_dir), change_env(test_environment): call(cmd) return 0 - with changedir(test_dir): - # Force Pacific time zone to avoid failures in Date tests. - os.environ['TZ'] = 'PST8PDT' - # Force date strings to English. - os.environ['LC_TIME'] = 'en_US.UTF-8' - + with changedir(test_dir), change_env(test_environment): results = ResultsSink(options, test_count) try: - for out in run_all_tests(test_gen, prefix, results, options): + for out in run_all_tests(test_gen, prefix, results.pb, options): results.push(out) results.finish(True) except KeyboardInterrupt: diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py index 9e036bbc9f..da9507dad0 100755 --- a/js/src/tests/lib/jittests.py +++ b/js/src/tests/lib/jittests.py @@ -78,7 +78,7 @@ def js_quote(quote, s): os.path.relpath = _relpath -class Test: +class JitTest: VALGRIND_CMD = [] paths = (d for d in os.environ['PATH'].split(os.pathsep)) @@ -122,7 +122,7 @@ class Test: self.expect_status = 0 # Exit status to expect from shell def copy(self): - t = Test(self.path) + t = JitTest(self.path) t.jitflags = self.jitflags[:] t.slow = self.slow t.allow_oom = self.allow_oom @@ -255,7 +255,7 @@ class Test: # We may have specified '-a' or '-d' twice: once via --jitflags, once # via the "|jit-test|" line. Remove dups because they are toggles. - cmd = prefix + ['--js-cache', Test.CacheDir] + cmd = prefix + ['--js-cache', JitTest.CacheDir] cmd += list(set(self.jitflags)) + ['-e', expr, '-f', path] if self.valgrind: cmd = self.VALGRIND_CMD + cmd @@ -831,8 +831,8 @@ def run_tests_remote(tests, prefix, options): push_progs(options, dm, [prefix[0]]) dm.chmodDir(options.remote_test_root) - Test.CacheDir = posixpath.join(options.remote_test_root, '.js-cache') - dm.mkDir(Test.CacheDir) + JitTest.CacheDir = posixpath.join(options.remote_test_root, '.js-cache') + dm.mkDir(JitTest.CacheDir) dm.pushDir(JS_TESTS_DIR, posixpath.join(jit_tests_dir, 'tests'), timeout=600) diff --git a/js/src/tests/lib/manifest.py b/js/src/tests/lib/manifest.py index 0425d2a6a1..3543a7669d 100644 --- a/js/src/tests/lib/manifest.py +++ b/js/src/tests/lib/manifest.py @@ -7,7 +7,7 @@ from __future__ import print_function import os, re, sys from subprocess import Popen, PIPE -from tests import TestCase +from tests import RefTestCase def split_path_into_dirs(path): @@ -168,13 +168,13 @@ def _build_manifest_script_entry(script_name, test): line.append(test.comment) return ' '.join(line) -def _map_prefixes_left(test_list): +def _map_prefixes_left(test_gen): """ Splits tests into a dictionary keyed on the first component of the test path, aggregating tests with a common base path into a list. """ byprefix = {} - for t in test_list: + for t in test_gen: left, sep, remainder = t.path.partition(os.sep) if left not in byprefix: byprefix[left] = [] @@ -183,14 +183,14 @@ def _map_prefixes_left(test_list): byprefix[left].append(t) return byprefix -def _emit_manifest_at(location, relative, test_list, depth): +def _emit_manifest_at(location, relative, test_gen, depth): """ location - str: absolute path where we want to write the manifest relative - str: relative path from topmost manifest directory to current - test_list - [str]: list of all test paths and directorys + test_gen - (str): generator of all test paths and directorys depth - int: number of dirs we are below the topmost manifest dir """ - manifests = _map_prefixes_left(test_list) + manifests = _map_prefixes_left(test_gen) filename = os.path.join(location, 'jstests.list') manifest = [] @@ -223,8 +223,8 @@ def _emit_manifest_at(location, relative, test_list, depth): finally: fp.close() -def make_manifests(location, test_list): - _emit_manifest_at(location, '', test_list, 0) +def make_manifests(location, test_gen): + _emit_manifest_at(location, '', test_gen, 0) def _find_all_js_files(base, location): for root, dirs, files in os.walk(location): @@ -362,7 +362,7 @@ def count_tests(location, requested_paths, excluded_paths): return count -def load(location, requested_paths, excluded_paths, xul_tester, reldir=''): +def load_reftests(location, requested_paths, excluded_paths, xul_tester, reldir=''): """ Locates all tests by walking the filesystem starting at |location|. Uses xul_tester to evaluate any test conditions in the test header. @@ -379,12 +379,12 @@ def load(location, requested_paths, excluded_paths, xul_tester, reldir=''): filename = os.path.join(root, basename) if not _is_test_file(root, basename, filename, requested_paths, excluded_paths): continue - + # Skip empty files. fullpath = os.path.join(location, filename) statbuf = os.stat(fullpath) - testcase = TestCase(os.path.join(reldir, filename)) + testcase = RefTestCase(os.path.join(reldir, filename)) _apply_external_manifests(filename, testcase, externalManifestEntries, xul_tester) _parse_test_header(fullpath, testcase, xul_tester) diff --git a/js/src/tests/lib/tasks_unix.py b/js/src/tests/lib/tasks_unix.py index 01b27c9868..6bed571bed 100644 --- a/js/src/tests/lib/tasks_unix.py +++ b/js/src/tests/lib/tasks_unix.py @@ -137,10 +137,10 @@ def timed_out(task, timeout): def reap_zombies(tasks, timeout): """ - Search for children of this process that have finished. If they are tasks, - then this routine will clean up the child and send a TestOutput to the - results channel. This method returns a new task list that has had the ended - tasks removed. + Search for children of this process that have finished. If they are tasks, + then this routine will clean up the child. This method returns a new task + list that has had the ended tasks removed, followed by the list of finished + tasks. """ finished = [] while True: @@ -182,7 +182,7 @@ def kill_undead(tasks, timeout): if timed_out(task, timeout): os.kill(task.pid, 9) -def run_all_tests(tests, prefix, results, options): +def run_all_tests(tests, prefix, pb, options): # Copy and reverse for fast pop off end. tests = list(tests) tests = tests[:] @@ -208,3 +208,9 @@ def run_all_tests(tests, prefix, results, options): # With Python3.4+ we could use yield from to remove this loop. for out in finished: yield out + + # If we did not finish any tasks, poke the progress bar to show that + # the test harness is at least not frozen. + if len(finished) == 0: + pb.poke() + diff --git a/js/src/tests/lib/tasks_win.py b/js/src/tests/lib/tasks_win.py index b121760c54..71449f414c 100644 --- a/js/src/tests/lib/tasks_win.py +++ b/js/src/tests/lib/tasks_win.py @@ -68,7 +68,7 @@ def _do_watch(qWatch, timeout): assert fin is TaskFinishedMarker, "invalid finish marker" -def run_all_tests(tests, prefix, results, options): +def run_all_tests(tests, prefix, pb, options): """ Uses scatter-gather to a thread-pool to manage children. """ @@ -114,7 +114,7 @@ def run_all_tests(tests, prefix, results, options): else: yield result except Empty: - results.pb.poke() + pb.poke() # Cleanup and exit. pusher.join() diff --git a/js/src/tests/lib/tests.py b/js/src/tests/lib/tests.py index 991cab1137..fe8c154eaf 100644 --- a/js/src/tests/lib/tests.py +++ b/js/src/tests/lib/tests.py @@ -4,6 +4,7 @@ # metadata, and know how to run the tests and determine failures. import datetime, os, sys, time +from contextlib import contextmanager from subprocess import Popen, PIPE from threading import Thread @@ -45,7 +46,57 @@ def get_jitflags(variant, **kwargs): return kwargs['none'] return JITFLAGS[variant] -class Test(object): + +def get_environment_overlay(js_shell): + """ + Build a dict of additional environment variables that must be set to run + tests successfully. + """ + env = { + # Force Pacific time zone to avoid failures in Date tests. + 'TZ': 'PST8PDT', + # Force date strings to English. + 'LC_TIME': 'en_US.UTF-8', + # Tell the shell to disable crash dialogs on windows. + 'XRE_NO_WINDOWS_CRASH_DIALOG': '1', + } + # Add the binary's directory to the library search path so that we find the + # nspr and icu we built, instead of the platform supplied ones (or none at + # all on windows). + if sys.platform.startswith('linux'): + env['LD_LIBRARY_PATH'] = os.path.dirname(js_shell) + elif sys.platform.startswith('darwin'): + env['DYLD_LIBRARY_PATH'] = os.path.dirname(js_shell) + elif sys.platform.startswith('win'): + env['PATH'] = os.path.dirname(js_shell) + return env + + +@contextmanager +def change_env(env_overlay): + # Apply the overlaid environment and record the current state. + prior_env = {} + for key, val in env_overlay.items(): + prior_env[key] = os.environ.get(key, None) + if 'PATH' in key and key in os.environ: + os.environ[key] = '{}{}{}'.format(val, os.pathsep, os.environ[key]) + else: + os.environ[key] = val + + try: + # Execute with the new environment. + yield + + finally: + # Restore the prior environment. + for key, val in prior_env.items(): + if val is not None: + os.environ[key] = val + else: + del os.environ[key] + + +class RefTest(object): """A runnable test.""" def __init__(self, path): self.path = path # str: path of JS file relative to tests root dir @@ -59,19 +110,19 @@ class Test(object): if path == '': return ['-f', 'shell.js'] head, base = os.path.split(path) - return Test.prefix_command(head) \ + return RefTest.prefix_command(head) \ + ['-f', os.path.join(path, 'shell.js')] def get_command(self, prefix): dirname, filename = os.path.split(self.path) cmd = prefix + self.jitflags + self.options \ - + Test.prefix_command(dirname) + ['-f', self.path] + + RefTest.prefix_command(dirname) + ['-f', self.path] return cmd -class TestCase(Test): +class RefTestCase(RefTest): """A test case consisting of a test and an expected result.""" def __init__(self, path): - Test.__init__(self, path) + RefTest.__init__(self, path) self.enable = True # bool: True => run test, False => don't run self.expect = True # bool: expected result, True => pass self.random = False # bool: True => ignore output as 'random' diff --git a/js/src/tests/shell.js b/js/src/tests/shell.js index a2021e90e2..a5702b96f8 100644 --- a/js/src/tests/shell.js +++ b/js/src/tests/shell.js @@ -3,13 +3,6 @@ * 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/. */ -// Explicitly set the default version. -// See https://bugzilla.mozilla.org/show_bug.cgi?id=522760#c11 -if (typeof version != 'undefined') -{ - version(0); -} - var STATUS = "STATUS: "; var VERBOSE = false; var SECT_PREFIX = 'Section '; diff --git a/js/src/vm/DebuggerMemory.cpp b/js/src/vm/DebuggerMemory.cpp index e08b4da6e8..950c7524a4 100644 --- a/js/src/vm/DebuggerMemory.cpp +++ b/js/src/vm/DebuggerMemory.cpp @@ -307,7 +307,8 @@ DebuggerMemory::setAllocationSamplingProbability(JSContext* cx, unsigned argc, V if (!ToNumber(cx, args[0], &probability)) return false; - if (probability < 0.0 || probability > 1.0) { + // Careful! This must also reject NaN. + if (!(0.0 <= probability && probability <= 1.0)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, "(set allocationSamplingProbability)'s parameter", "not a number between 0 and 1"); diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index ca1ead2200..a1cc767825 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -215,13 +215,6 @@ ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options) if (!this->options.copy(cx, options)) return false; - // If the main-thread global is a debuggee that observes asm.js, disable - // asm.js compilation. This is preferred to marking the task compartment - // as a debuggee, as the task compartment is (1) invisible to Debugger and - // (2) cannot have any Debuggers. - if (cx->compartment()->debuggerObservesAsmJS()) - this->options.asmJSOption = false; - return true; } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 247bc16cc7..3d0be63773 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1606,6 +1606,7 @@ Interpret(JSContext* cx, RunState& state) if (!entryFrame) return false; + ActivationEntryMonitor entryMonitor(cx, entryFrame); InterpreterActivation activation(state, cx, entryFrame); /* The script is used frequently, so keep a local copy. */ diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index e4bc3a6c77..f184bc3bfc 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -896,6 +896,14 @@ AbstractFramePtr::popWith(JSContext* cx) const asBaselineFrame()->popWith(cx); } +ActivationEntryMonitor::~ActivationEntryMonitor() +{ + if (entryMonitor_) + entryMonitor_->Exit(cx_); + + cx_->runtime()->entryMonitor = entryMonitor_; +} + Activation::Activation(JSContext* cx, Kind kind) : cx_(cx), compartment_(cx->compartment()), @@ -907,13 +915,11 @@ Activation::Activation(JSContext* cx, Kind kind) asyncStack_(cx, cx->runtime_->asyncStackForNewActivations), asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations), asyncCallIsExplicit_(cx->runtime_->asyncCallIsExplicit), - entryMonitor_(cx->runtime_->entryMonitor), kind_(kind) { cx->runtime_->asyncStackForNewActivations = nullptr; cx->runtime_->asyncCauseForNewActivations = nullptr; cx->runtime_->asyncCallIsExplicit = false; - cx->runtime_->entryMonitor = nullptr; cx->runtime_->activation_ = this; } @@ -923,7 +929,6 @@ Activation::~Activation() MOZ_ASSERT(cx_->runtime_->activation_ == this); MOZ_ASSERT(hideScriptedCallerCount_ == 0); cx_->runtime_->activation_ = prev_; - cx_->runtime_->entryMonitor = entryMonitor_; cx_->runtime_->asyncCauseForNewActivations = asyncCause_; cx_->runtime_->asyncStackForNewActivations = asyncStack_; cx_->runtime_->asyncCallIsExplicit = asyncCallIsExplicit_; @@ -969,17 +974,6 @@ InterpreterActivation::InterpreterActivation(RunState& state, JSContext* cx, regs_.prepareToRun(*entryFrame, state.script()); MOZ_ASSERT(regs_.pc == state.script()->code()); MOZ_ASSERT_IF(entryFrame_->isEvalFrame(), state.script()->isActiveEval()); - - if (entryMonitor_) { - RootedValue stack(cx_); - stack.setObjectOrNull(asyncStack_.get()); - if (!cx_->compartment()->wrap(cx_, &stack)) - stack.setUndefined(); - if (entryFrame->isFunctionFrame()) - entryMonitor_->Entry(cx_, entryFrame->fun(), stack, asyncCause_); - else - entryMonitor_->Entry(cx_, entryFrame->script(), stack, asyncCause_); - } } InterpreterActivation::~InterpreterActivation() @@ -992,9 +986,6 @@ InterpreterActivation::~InterpreterActivation() MOZ_ASSERT(oldFrameCount_ == cx->runtime()->interpreterStack().frameCount_); MOZ_ASSERT_IF(oldFrameCount_ == 0, cx->runtime()->interpreterStack().allocator_.used() == 0); - if (entryMonitor_) - entryMonitor_->Exit(cx_); - if (entryFrame_) cx->runtime()->interpreterStack().releaseFrame(entryFrame_); } diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index bd666acaf2..ea3e6e7eaf 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -1469,9 +1469,52 @@ NonBuiltinScriptFrameIter::settle() } } +ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx) + : cx_(cx), entryMonitor_(cx->runtime()->entryMonitor) +{ + cx->runtime()->entryMonitor = nullptr; +} + +Value +ActivationEntryMonitor::asyncStack(JSContext* cx) +{ + RootedValue stack(cx, ObjectOrNullValue(cx->runtime()->asyncStackForNewActivations)); + if (!cx->compartment()->wrap(cx, &stack)) { + cx->clearPendingException(); + return UndefinedValue(); + } + return stack; +} + +ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, InterpreterFrame* entryFrame) + : ActivationEntryMonitor(cx) +{ + if (entryMonitor_) { + RootedValue stack(cx, asyncStack(cx)); + RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations); + if (entryFrame->isFunctionFrame()) + entryMonitor_->Entry(cx, entryFrame->fun(), stack, asyncCause); + else + entryMonitor_->Entry(cx, entryFrame->script(), stack, asyncCause); + } +} + +ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, jit::CalleeToken entryToken) + : ActivationEntryMonitor(cx) +{ + if (entryMonitor_) { + RootedValue stack(cx, asyncStack(cx)); + RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations); + if (jit::CalleeTokenIsFunction(entryToken)) + entryMonitor_->Entry(cx_, jit::CalleeTokenToFunction(entryToken), stack, asyncCause); + else + entryMonitor_->Entry(cx_, jit::CalleeTokenToScript(entryToken), stack, asyncCause); + } +} + /*****************************************************************************/ -jit::JitActivation::JitActivation(JSContext* cx, CalleeToken entryPoint, bool active) +jit::JitActivation::JitActivation(JSContext* cx, bool active) : Activation(cx, Jit), active_(active), isLazyLinkExitFrame_(false), @@ -1494,26 +1537,10 @@ jit::JitActivation::JitActivation(JSContext* cx, CalleeToken entryPoint, bool ac prevJitJSContext_ = nullptr; prevJitActivation_ = nullptr; } - - if (entryMonitor_) { - MOZ_ASSERT(entryPoint); - - RootedValue stack(cx_); - stack.setObjectOrNull(asyncStack_.get()); - if (!cx_->compartment()->wrap(cx_, &stack)) - stack.setUndefined(); - if (CalleeTokenIsFunction(entryPoint)) - entryMonitor_->Entry(cx_, CalleeTokenToFunction(entryPoint), stack, asyncCause_); - else - entryMonitor_->Entry(cx_, CalleeTokenToScript(entryPoint), stack, asyncCause_); - } } jit::JitActivation::~JitActivation() { - if (entryMonitor_) - entryMonitor_->Exit(cx_); - if (active_) { if (isProfiling()) unregisterProfiling(); diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 8dd05a12e4..56eac8339a 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -1314,6 +1314,30 @@ namespace jit { class JitActivation; } // namespace jit +// This class is separate from Activation, because it calls JSCompartment::wrap() +// which can GC and walk the stack. It's not safe to do that within the +// JitActivation constructor. +class MOZ_RAII ActivationEntryMonitor +{ + JSContext* cx_; + + // The entry point monitor that was set on cx_->runtime() when this + // ActivationEntryMonitor was created. + JS::dbg::AutoEntryMonitor* entryMonitor_; + + explicit ActivationEntryMonitor(JSContext* cx); + + ActivationEntryMonitor(const ActivationEntryMonitor& other) = delete; + void operator=(const ActivationEntryMonitor& other) = delete; + + Value asyncStack(JSContext* cx); + + public: + ActivationEntryMonitor(JSContext* cx, InterpreterFrame* entryFrame); + ActivationEntryMonitor(JSContext* cx, jit::CalleeToken entryToken); + inline ~ActivationEntryMonitor(); +}; + class Activation { protected: @@ -1354,15 +1378,10 @@ class Activation // callFunctionWithAsyncStack. bool asyncCallIsExplicit_; - // The entry point monitor that was set on cx_->runtime() when this - // Activation was created. Subclasses should report their entry frame's - // function or script here. - JS::dbg::AutoEntryMonitor* entryMonitor_; - enum Kind { Interpreter, Jit, AsmJS }; Kind kind_; - inline Activation(JSContext* cx, Kind kind_); + inline Activation(JSContext* cx, Kind kind); inline ~Activation(); public: @@ -1614,11 +1633,7 @@ class JitActivation : public Activation #endif public: - // If non-null, |entryScript| should be the script we're about to begin - // executing, for the benefit of performance tooling. We can pass null for - // entryScript when we know we couldn't possibly be entering JS directly - // from the JSAPI: OSR, asm.js -> Ion transitions, and so on. - explicit JitActivation(JSContext* cx, CalleeToken entryPoint, bool active = true); + explicit JitActivation(JSContext* cx, bool active = true); ~JitActivation(); bool isActive() const { diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index b400f278ea..ab19320ada 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,11 +29,11 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 310; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 311; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 420, +static_assert(JSErr_Limit == 421, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's " diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index f68708d6d1..e373bacdd2 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -3882,6 +3882,13 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) aList->SetNeedsTransparentSurface(); } + nsDisplayItem::Type itemType = item->GetType(); + + if (mParameters.mForEventsOnly && !item->GetChildren() && + itemType != nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) { + continue; + } + LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters); if (layerState == LAYER_INACTIVE && nsDisplayItem::ForceActiveLayers()) { @@ -3920,8 +3927,6 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) animatedGeometryRootForScrollMetadata = animatedGeometryRoot; } - nsDisplayItem::Type itemType = item->GetType(); - if (animatedGeometryRoot != realAnimatedGeometryRootOfItem) { // Pick up any scroll clips that should apply to the item and apply them. DisplayItemClip clip = diff --git a/layout/base/UnitTransforms.h b/layout/base/UnitTransforms.h index 6d162f307f..5c6596822b 100644 --- a/layout/base/UnitTransforms.h +++ b/layout/base/UnitTransforms.h @@ -174,7 +174,8 @@ template static Maybe> UntransformTo(const gfx::Matrix4x4& aTransform, const gfx::IntPointTyped& aPoint) { - gfx::Point4D point = aTransform.ProjectPoint(aPoint.ToUnknownPoint()); + gfx::Point p = aPoint.ToUnknownPoint(); + gfx::Point4D point = aTransform.ProjectPoint(p); if (!point.HasPositiveWCoord()) { return Nothing(); } diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 90fb5019a0..b7693563c9 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -377,8 +377,8 @@ AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty, Nullable startTime = aAnimation->GetCurrentOrPendingStartTime(); animation->startTime() = startTime.IsNull() ? TimeStamp() - : aAnimation->GetTimeline()->ToTimeStamp( - startTime.Value() + timing.mDelay); + : aAnimation->AnimationTimeToTimeStamp( + StickyTimeDuration(timing.mDelay)); animation->initialCurrentTime() = aAnimation->GetCurrentTime().Value() - timing.mDelay; animation->duration() = timing.mIterationDuration; @@ -538,7 +538,7 @@ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(Layer* aLayer, // AnimationManager or TransitionManager need to know that we refused to // run this animation asynchronously so that they will not throttle the // main thread animation. - aFrame->Properties().Set(nsIFrame::RefusedAsyncAnimation(), + aFrame->Properties().Set(nsIFrame::RefusedAsyncAnimationProperty(), reinterpret_cast(intptr_t(true))); // We need to schedule another refresh driver run so that AnimationManager @@ -1250,7 +1250,7 @@ nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize) { bool onBudget = AddToWillChangeBudget(aFrame, aSize); - if (onBudget) { + if (!onBudget) { nsString usageStr; usageStr.AppendInt(GetWillChangeCost(aFrame, aSize)); @@ -1263,9 +1263,9 @@ nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame, nsPresContext::AppUnitsToIntCSSPixels(area.height); limitStr.AppendInt(budgetLimit); - const char16_t* params[] = { usageStr.get(), multiplierStr.get(), limitStr.get() }; + const char16_t* params[] = { multiplierStr.get(), limitStr.get() }; aFrame->PresContext()->Document()->WarnOnceAbout( - nsIDocument::eWillChangeBudget, false, + nsIDocument::eIgnoringWillChangeOverBudget, false, params, ArrayLength(params)); } return onBudget; @@ -1559,6 +1559,13 @@ already_AddRefed nsDisplayList::PaintRoot(nsDisplayListBuilder* aB props = Move(LayerProperties::CloneFrom(layerManager->GetRoot())); } + // Clear any FrameMetrics that may have been set on the root layer on a + // previous paint. This paint will set new metrics if necessary, and if we + // don't clear the old one here, we may be left with extra metrics. + if (Layer* root = layerManager->GetRoot()) { + root->SetFrameMetrics(nsTArray()); + } + ContainerLayerParameters containerParameters (presShell->GetResolution(), presShell->GetResolution()); RefPtr root = layerBuilder-> @@ -1634,9 +1641,6 @@ already_AddRefed nsDisplayList::PaintRoot(nsDisplayListBuilder* aB aBuilder->FindReferenceFrameFor(frame), root, FrameMetrics::NULL_SCROLL_ID, viewport, Nothing(), isRootContent, containerParameters)); - } else { - // Set empty metrics to clear any metrics that might be on a recycled layer. - root->SetFrameMetrics(nsTArray()); } // NS_WARNING is debug-only, so don't even bother checking the conditions in @@ -1829,11 +1833,28 @@ void FlushFramesArray(nsTArray& aSource, nsTArray* a void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, nsDisplayItem::HitTestState* aState, nsTArray *aOutFrames) const { - int32_t itemBufferStart = aState->mItemBuffer.Length(); nsDisplayItem* item; + + if (aState->mInPreserves3D) { + // Collect leaves of the current 3D rendering context. + for (item = GetBottom(); item; item = item->GetAbove()) { + MOZ_ASSERT(item->GetType() == nsDisplayTransform::TYPE_TRANSFORM); + if (item->Frame()->Extend3DContext() && + !static_cast(item)->IsTransformSeparator()) { + item->HitTest(aBuilder, aRect, aState, aOutFrames); + } else { + // One of leaves in the current 3D rendering context. + aState->mItemBuffer.AppendElement(item); + } + } + return; + } + + int32_t itemBufferStart = aState->mItemBuffer.Length(); for (item = GetBottom(); item; item = item->GetAbove()) { aState->mItemBuffer.AppendElement(item); } + nsAutoTArray temp; for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart; --i) { // Pop element off the end of the buffer. We want to shorten the buffer @@ -1844,11 +1865,26 @@ void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, bool snap; nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect); bool alwaysIntersect = - item->Frame()->Combines3DTransformWithAncestors() && - item->GetType() == nsDisplayItem::TYPE_TRANSFORM; + item->GetType() == nsDisplayItem::TYPE_TRANSFORM && + (item->Frame()->Combines3DTransformWithAncestors() || + item->Frame()->Extend3DContext() || + static_cast(item)->IsTransformSeparator()); if (alwaysIntersect || item->GetClip().MayIntersect(r)) { nsAutoTArray outFrames; - item->HitTest(aBuilder, aRect, aState, &outFrames); + if (item->Frame()->Extend3DContext() && + item->GetType() == nsDisplayItem::TYPE_TRANSFORM && + !static_cast(item)->IsTransformSeparator()) { + // Start gethering leaves of the 3D rendering context, and + // append leaves at the end of mItemBuffer. Leaves are + // processed at following iterations. + aState->mInPreserves3D = true; + item->HitTest(aBuilder, aRect, aState, &outFrames); + aState->mInPreserves3D = false; + i = aState->mItemBuffer.Length(); + continue; + } else { + item->HitTest(aBuilder, aRect, aState, &outFrames); + } // For 3d transforms with preserve-3d we add hit frames into the temp list // so we can sort them later, otherwise we add them directly to the output list. @@ -2433,14 +2469,6 @@ nsDisplayBackgroundImage::CanOptimizeToImageLayer(LayerManager* aManager, nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); const nsStyleBackground::Layer &layer = mBackgroundStyle->mLayers[mLayer]; - if (layer.mClip != NS_STYLE_BG_CLIP_BORDER) { - return false; - } - nscoord radii[8]; - if (mFrame->GetBorderRadii(radii)) { - return false; - } - nsBackgroundLayerState state = nsCSSRendering::PrepareBackgroundLayer(presContext, mFrame, flags, borderArea, borderArea, layer); @@ -2502,6 +2530,12 @@ nsDisplayBackgroundImage::ImageLayerization nsDisplayBackgroundImage::ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager) { + nsIFrame* backgroundStyleFrame = nsCSSRendering::FindBackgroundStyleFrame(mFrame); + if (ActiveLayerTracker::IsStyleAnimated(aBuilder, backgroundStyleFrame, + eCSSProperty_background_position)) { + return WHENEVER_POSSIBLE; + } + if (nsLayoutUtils::AnimatedImageLayersEnabled() && mBackgroundStyle) { const nsStyleBackground::Layer &layer = mBackgroundStyle->mLayers[mLayer]; const nsStyleImage* image = &layer.mImage; @@ -3229,14 +3263,9 @@ nsDisplayLayerEventRegions::AddFrame(nsDisplayListBuilder* aBuilder, } if (!aFrame->GetParent()) { MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::viewportFrame); - nsSubDocumentFrame* subdoc = static_cast( - nsLayoutUtils::GetCrossDocParentFrame(aFrame)); - if (subdoc && subdoc->PassPointerEventsToChildren()) { - // If this viewport frame is for a subdocument with - // mozpasspointerevents, then we don't want to add the viewport itself - // to the event regions. Instead we want to add only subframes. - return; - } + // Viewport frames are never event targets, other frames, like canvas frames, + // are the event targets for any regions viewport frames may cover. + return; } uint8_t pointerEvents = aFrame->StyleVisibility()->GetEffectivePointerEvents(aFrame); if (pointerEvents == NS_STYLE_POINTER_EVENTS_NONE) { @@ -4017,8 +4046,13 @@ nsDisplayOpacity::CanApplyOpacity() const bool nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { - if (NeedsActiveLayer(aBuilder)) + if (NeedsActiveLayer(aBuilder) || mOpacity == 0.0) { + // If our opacity is zero then we'll discard all descendant display items + // except for layer event regions, so there's no point in doing this + // optimization (and if we do do it, then invalidations of those descendants + // might trigger repainting). return false; + } nsDisplayItem* child = mList.GetBottom(); // Only try folding our opacity down if we have at most three children @@ -4033,6 +4067,10 @@ nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) bool snap; uint32_t numChildren = 0; for (; numChildren < ArrayLength(children) && child; numChildren++, child = child->GetAbove()) { + if (child->GetType() == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) { + numChildren--; + continue; + } if (!child->CanApplyOpacity()) { return false; } @@ -5171,17 +5209,6 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp result = Matrix4x4::From2D(svgTransform); } - if (aProperties.mChildPerspective > 0.0) { - Matrix4x4 perspective; - perspective._34 = - -1.0 / NSAppUnitsToFloatPixels(aProperties.mChildPerspective, aAppUnitsPerPixel); - /* At the point when perspective is applied, we have been translated to the transform origin. - * The translation to the perspective origin is the difference between these values. - */ - perspective.ChangeBasis(aProperties.GetToPerspectiveOrigin() - aProperties.mToTransformOrigin); - result = result * perspective; - } - /* Account for the transform-origin property by translating the * coordinate space to the new origin. */ @@ -5193,12 +5220,15 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp hasSVGTransforms ? newOrigin.y : NS_round(newOrigin.y), 0); + bool hasPerspective = aProperties.mChildPerspective > 0.0; + if (!hasSVGTransforms || !hasTransformFromSVGParent) { // This is a simplification of the following |else| block, the // simplification being possible because we don't need to apply // mToTransformOrigin between two transforms. Point3D offsets = roundedOrigin + aProperties.mToTransformOrigin; - if (aOffsetByOrigin) { + if (aOffsetByOrigin && + !hasPerspective) { // We can fold the final translation by roundedOrigin into the first matrix // basis change translation. This is more stable against variation due to // insufficient floating point precision than reversing the translation @@ -5232,7 +5262,8 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp // for mToTransformOrigin so we don't include that. We also need to reapply // refBoxOffset. Point3D offsets = roundedOrigin + refBoxOffset; - if (aOffsetByOrigin) { + if (aOffsetByOrigin && + !hasPerspective) { result.PreTranslate(-refBoxOffset); result.PostTranslate(offsets); } else { @@ -5240,6 +5271,19 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp } } + if (hasPerspective) { + Matrix4x4 perspective; + perspective._34 = + -1.0 / NSAppUnitsToFloatPixels(aProperties.mChildPerspective, aAppUnitsPerPixel); + + perspective.ChangeBasis(aProperties.GetToPerspectiveOrigin() + roundedOrigin); + result = result * perspective; + + if (aOffsetByOrigin) { + result.PreTranslate(roundedOrigin); + } + } + if (aDoPreserves3D && frame && frame->Combines3DTransformWithAncestors()) { // Include the transform set on our parent NS_ASSERTION(frame->GetParent() && @@ -5421,7 +5465,7 @@ nsDisplayTransform::GetTransform() } const Matrix4x4& -nsDisplayTransform::GetAccumulatedPreserved3DTransform() +nsDisplayTransform::GetAccumulatedPreserved3DTransform(nsDisplayListBuilder* aBuilder) { // XXX: should go back to fix mTransformGetter. if (!mTransformPreserves3DInited) { @@ -5430,13 +5474,22 @@ nsDisplayTransform::GetAccumulatedPreserved3DTransform() mTransformPreserves3D = GetTransform(); return mTransformPreserves3D; } + MOZ_ASSERT(!mFrame->Extend3DContext() || IsTransformSeparator()); + + const nsIFrame* establisher; // Establisher of the 3D rendering context. + for (establisher = nsLayoutUtils::GetCrossDocParentFrame(mFrame); + establisher && establisher->Combines3DTransformWithAncestors(); + establisher = nsLayoutUtils::GetCrossDocParentFrame(establisher)) { + } + establisher = nsLayoutUtils::GetCrossDocParentFrame(establisher); + const nsIFrame* establisherReference = + aBuilder->FindReferenceFrameFor(establisher); + + nsPoint offset = mFrame->GetOffsetToCrossDoc(establisherReference); float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); - bool isReference = - mFrame->IsTransformed() || - mFrame->Combines3DTransformWithAncestors() || mFrame->Extend3DContext(); mTransformPreserves3D = - GetResultingTransformMatrixP3D(mFrame, ToReferenceFrame(), scale, - nullptr, nullptr, isReference); + GetResultingTransformMatrixP3D(mFrame, offset, scale, + nullptr, nullptr, true); } return mTransformPreserves3D; } @@ -5557,6 +5610,11 @@ void nsDisplayTransform::HitTest(nsDisplayListBuilder *aBuilder, HitTestState *aState, nsTArray *aOutFrames) { + if (aState->mInPreserves3D) { + mStoredList.HitTest(aBuilder, aRect, aState, aOutFrames); + return; + } + /* Here's how this works: * 1. Get the matrix. If it's singular, abort (clearly we didn't hit * anything). @@ -5566,9 +5624,9 @@ void nsDisplayTransform::HitTest(nsDisplayListBuilder *aBuilder, */ // GetTransform always operates in dev pixels. float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); - Matrix4x4 matrix = GetTransform(); + Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); - if (!IsFrameVisible(mFrame, GetAccumulatedPreserved3DTransform())) { + if (!IsFrameVisible(mFrame, matrix)) { return; } @@ -5643,9 +5701,9 @@ nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsP { // GetTransform always operates in dev pixels. float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); - Matrix4x4 matrix = GetTransform(); + Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); - NS_ASSERTION(IsFrameVisible(mFrame, GetAccumulatedPreserved3DTransform()), + NS_ASSERTION(IsFrameVisible(mFrame, matrix), "We can't have hit a frame that isn't visible!"); Matrix4x4 inverse = matrix; @@ -5898,15 +5956,15 @@ bool nsDisplayTransform::UntransformRect(const nsRect &aTransformedBounds, return false; } - Rect result(NSAppUnitsToFloatPixels(aTransformedBounds.x, factor), - NSAppUnitsToFloatPixels(aTransformedBounds.y, factor), - NSAppUnitsToFloatPixels(aTransformedBounds.width, factor), - NSAppUnitsToFloatPixels(aTransformedBounds.height, factor)); + RectDouble result(NSAppUnitsToFloatPixels(aTransformedBounds.x, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.y, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.width, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.height, factor)); - Rect childGfxBounds(NSAppUnitsToFloatPixels(aChildBounds.x, factor), - NSAppUnitsToFloatPixels(aChildBounds.y, factor), - NSAppUnitsToFloatPixels(aChildBounds.width, factor), - NSAppUnitsToFloatPixels(aChildBounds.height, factor)); + RectDouble childGfxBounds(NSAppUnitsToFloatPixels(aChildBounds.x, factor), + NSAppUnitsToFloatPixels(aChildBounds.y, factor), + NSAppUnitsToFloatPixels(aChildBounds.width, factor), + NSAppUnitsToFloatPixels(aChildBounds.height, factor)); result = transform.Inverse().ProjectRectBounds(result, childGfxBounds); *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor); @@ -5922,17 +5980,17 @@ bool nsDisplayTransform::UntransformVisibleRect(nsDisplayListBuilder* aBuilder, // GetTransform always operates in dev pixels. float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); - Rect result(NSAppUnitsToFloatPixels(mVisibleRect.x, factor), - NSAppUnitsToFloatPixels(mVisibleRect.y, factor), - NSAppUnitsToFloatPixels(mVisibleRect.width, factor), - NSAppUnitsToFloatPixels(mVisibleRect.height, factor)); + RectDouble result(NSAppUnitsToFloatPixels(mVisibleRect.x, factor), + NSAppUnitsToFloatPixels(mVisibleRect.y, factor), + NSAppUnitsToFloatPixels(mVisibleRect.width, factor), + NSAppUnitsToFloatPixels(mVisibleRect.height, factor)); bool snap; nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap); - Rect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), - NSAppUnitsToFloatPixels(childBounds.y, factor), - NSAppUnitsToFloatPixels(childBounds.width, factor), - NSAppUnitsToFloatPixels(childBounds.height, factor)); + RectDouble childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); /* We want to untransform the matrix, so invert the transformation first! */ result = matrix.Inverse().ProjectRectBounds(result, childGfxBounds); diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index f223c77841..f026506083 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -1200,7 +1200,7 @@ protected: * class represents an entity that can be drawn on the screen, e.g., a * frame's CSS background, or a frame's text string. * - * nsDisplayListItems can be containers --- i.e., they can perform hit testing + * nsDisplayItems can be containers --- i.e., they can perform hit testing * and painting by recursively traversing a list of child items. * * These are arena-allocated during display list construction. A typical @@ -1248,13 +1248,15 @@ public: #include "nsDisplayItemTypes.h" struct HitTestState { - explicit HitTestState() {} + explicit HitTestState() : mInPreserves3D(false) {} ~HitTestState() { NS_ASSERTION(mItemBuffer.Length() == 0, "mItemBuffer should have been cleared"); } + // Handling transform items for preserve 3D frames. + bool mInPreserves3D; nsAutoTArray mItemBuffer; }; @@ -1823,8 +1825,8 @@ public: } /** - * Append a new item to the top of the list. If the item is null we return - * NS_ERROR_OUT_OF_MEMORY. The intended usage is AppendNewToTop(new ...); + * Append a new item to the top of the list. The intended usage is + * AppendNewToTop(new ...); */ void AppendNewToTop(nsDisplayItem* aItem) { if (aItem) { @@ -1833,8 +1835,8 @@ public: } /** - * Append a new item to the bottom of the list. If the item is null we return - * NS_ERROR_OUT_OF_MEMORY. The intended usage is AppendNewToBottom(new ...); + * Append a new item to the bottom of the list. The intended usage is + * AppendNewToBottom(new ...); */ void AppendNewToBottom(nsDisplayItem* aItem) { if (aItem) { @@ -3757,7 +3759,7 @@ public: * Return the transform that is aggregation of all transform on the * preserves3d chain. */ - const Matrix4x4& GetAccumulatedPreserved3DTransform(); + const Matrix4x4& GetAccumulatedPreserved3DTransform(nsDisplayListBuilder* aBuilder); float GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsPoint& aPoint); diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index 7513521c86..fefaa3aea1 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -1089,7 +1089,7 @@ public: * such as the file name in a file upload widget, and we might choose not * to paint themes. * set RENDER_IGNORE_VIEWPORT_SCROLLING to ignore - * clipping/scrolling/scrollbar painting due to scrolling in the viewport + * clipping and scrollbar painting due to scrolling in the viewport * set RENDER_CARET to draw the caret if one would be visible * (by default the caret is never drawn) * set RENDER_USE_LAYER_MANAGER to force rendering to go through @@ -1102,8 +1102,11 @@ public: * set RENDER_ASYNC_DECODE_IMAGES to avoid having images synchronously * decoded during rendering. * (by default images decode synchronously with RenderDocument) - * set RENDER_DOCUMENT_RELATIVE to interpret |aRect| relative to the - * document instead of the CSS viewport + * set RENDER_DOCUMENT_RELATIVE to render the document as if there has been + * no scrolling and interpret |aRect| relative to the document instead of the + * CSS viewport. Only considered if RENDER_IGNORE_VIEWPORT_SCROLLING is set + * or the document is in ignore viewport scrolling mode + * (nsIPresShell::SetIgnoreViewportScrolling/IgnoringViewportScrolling). * @param aBackgroundColor a background color to render onto * @param aRenderedContext the gfxContext to render to. We render so that * one CSS pixel in the source document is rendered to one unit in the current diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index da159ae441..fb0600f88b 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -130,6 +130,7 @@ using namespace mozilla::layout; using namespace mozilla::gfx; #define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled" +#define GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME "layout.css.grid-template-subgrid-value.enabled" #define RUBY_ENABLED_PREF_NAME "layout.css.ruby.enabled" #define STICKY_ENABLED_PREF_NAME "layout.css.sticky.enabled" #define DISPLAY_CONTENTS_ENABLED_PREF_NAME "layout.css.display-contents.enabled" @@ -729,6 +730,22 @@ nsLayoutUtils::UnsetValueEnabled() return sUnsetValueEnabled; } +bool +nsLayoutUtils::IsGridTemplateSubgridValueEnabled() +{ + static bool sGridTemplateSubgridValueEnabled; + static bool sGridTemplateSubgridValueEnabledPrefCached = false; + + if (!sGridTemplateSubgridValueEnabledPrefCached) { + sGridTemplateSubgridValueEnabledPrefCached = true; + Preferences::AddBoolVarCache(&sGridTemplateSubgridValueEnabled, + GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME, + false); + } + + return sGridTemplateSubgridValueEnabled; +} + bool nsLayoutUtils::IsTextAlignUnsafeValueEnabled() { @@ -6266,11 +6283,12 @@ ComputeSnappedImageDrawingParameters(gfxContext* aCtx, // Avoid unnecessarily large offsets. bool doTile = !aDest.Contains(aFill); - nsRect dest = doTile ? TileNearRect(aDest, aFill.Intersect(aDirty)) : aDest; - nsPoint anchor = aAnchor + (dest.TopLeft() - aDest.TopLeft()); + nsRect appUnitDest = doTile ? TileNearRect(aDest, aFill.Intersect(aDirty)) + : aDest; + nsPoint anchor = aAnchor + (appUnitDest.TopLeft() - aDest.TopLeft()); gfxRect devPixelDest = - nsLayoutUtils::RectToGfxRect(dest, aAppUnitsPerDevPixel); + nsLayoutUtils::RectToGfxRect(appUnitDest, aAppUnitsPerDevPixel); gfxRect devPixelFill = nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel); gfxRect devPixelDirty = @@ -6278,52 +6296,57 @@ ComputeSnappedImageDrawingParameters(gfxContext* aCtx, gfxMatrix currentMatrix = aCtx->CurrentMatrix(); gfxRect fill = devPixelFill; + gfxRect dest = devPixelDest; bool didSnap; // Snap even if we have a scale in the context. But don't snap if // we have something that's not translation+scale, or if the scale flips in // the X or Y direction, because snapped image drawing can't handle that yet. if (!currentMatrix.HasNonAxisAlignedTransform() && currentMatrix._11 > 0.0 && currentMatrix._22 > 0.0 && - aCtx->UserToDevicePixelSnapped(fill, true)) { + aCtx->UserToDevicePixelSnapped(fill, true) && + aCtx->UserToDevicePixelSnapped(dest, true)) { + // We snapped. On this code path, |fill| and |dest| take into account + // currentMatrix's transform. didSnap = true; - if (fill.IsEmpty()) { - return SnappedImageDrawingParameters(); - } } else { + // We didn't snap. On this code path, |fill| and |dest| do not take into + // account currentMatrix's transform. didSnap = false; fill = devPixelFill; + dest = devPixelDest; } - // Apply the context's scale to the dest rect. - gfxSize destScale = didSnap ? gfxSize(currentMatrix._11, currentMatrix._22) - : currentMatrix.ScaleFactors(true); - gfxSize appUnitScaledDest(dest.width * destScale.width, - dest.height * destScale.height); - gfxSize scaledDest = appUnitScaledDest / aAppUnitsPerDevPixel; - if (scaledDest.IsEmpty()) { + // If we snapped above, |dest| already takes into account |currentMatrix|'s scale + // and has integer coordinates. If not, we need these properties to compute + // the optimal drawn image size, so compute |snappedDestSize| here. + gfxSize snappedDestSize = dest.Size(); + if (!didSnap) { + gfxSize scaleFactors = currentMatrix.ScaleFactors(true); + snappedDestSize.Scale(scaleFactors.width, scaleFactors.height); + snappedDestSize.width = NS_round(snappedDestSize.width); + snappedDestSize.height = NS_round(snappedDestSize.height); + } + + // We need to be sure that this is at least one pixel in width and height, + // or we'll end up drawing nothing even if we have a nonempty fill. + snappedDestSize.width = std::max(snappedDestSize.width, 1.0); + snappedDestSize.height = std::max(snappedDestSize.height, 1.0); + + // Bail if we're not going to end up drawing anything. + if (fill.IsEmpty() || snappedDestSize.IsEmpty()) { return SnappedImageDrawingParameters(); } - // Compute a snapped version of the scaled dest rect, which we'll use to - // determine the optimal image size to draw with. We need to be sure that - // this rect is at least one pixel in width and height, or we'll end up - // drawing nothing even if we have a nonempty fill. - gfxSize snappedScaledDest = - gfxSize(NSAppUnitsToIntPixels(appUnitScaledDest.width, aAppUnitsPerDevPixel), - NSAppUnitsToIntPixels(appUnitScaledDest.height, aAppUnitsPerDevPixel)); - snappedScaledDest.width = std::max(snappedScaledDest.width, 1.0); - snappedScaledDest.height = std::max(snappedScaledDest.height, 1.0); - nsIntSize intImageSize = - aImage->OptimalImageSizeForDest(snappedScaledDest, + aImage->OptimalImageSizeForDest(snappedDestSize, imgIContainer::FRAME_CURRENT, aGraphicsFilter, aImageFlags); gfxSize imageSize(intImageSize.width, intImageSize.height); + // XXX(seth): May be buggy; see bug 1151016. CSSIntSize svgViewportSize = currentMatrix.IsIdentity() ? CSSIntSize(intImageSize.width, intImageSize.height) - : CSSIntSize(NSAppUnitsToIntPixels(dest.width, aAppUnitsPerDevPixel), //XXX BUG! - NSAppUnitsToIntPixels(dest.height, aAppUnitsPerDevPixel)); //XXX BUG! + : CSSIntSize(devPixelDest.width, devPixelDest.height); // Compute the set of pixels that would be sampled by an ideal rendering gfxPoint subimageTopLeft = @@ -6339,8 +6362,9 @@ ComputeSnappedImageDrawingParameters(gfxContext* aCtx, gfxMatrix transform; gfxMatrix invTransform; - bool anchorAtUpperLeft = anchor.x == dest.x && anchor.y == dest.y; - bool exactlyOneImageCopy = aFill.IsEqualEdges(dest); + bool anchorAtUpperLeft = anchor.x == appUnitDest.x && + anchor.y == appUnitDest.y; + bool exactlyOneImageCopy = aFill.IsEqualEdges(appUnitDest); if (anchorAtUpperLeft && exactlyOneImageCopy) { // The simple case: we can ignore the anchor point and compute the // transformation from the sampled region (the subimage) to the fill rect. @@ -6368,7 +6392,14 @@ ComputeSnappedImageDrawingParameters(gfxContext* aCtx, anchorPoint = StableRound(anchorPoint); } - gfxRect anchoredDestRect(anchorPoint, scaledDest); + // Compute an unsnapped version of the dest rect's size. We continue to + // follow the pattern that we take |currentMatrix| into account only if + // |didSnap| is true. + gfxSize unsnappedDestSize + = didSnap ? devPixelDest.Size() * currentMatrix.ScaleFactors(true) + : devPixelDest.Size(); + + gfxRect anchoredDestRect(anchorPoint, unsnappedDestSize); gfxRect anchoredImageRect(imageSpaceAnchorPoint, imageSize); // Calculate anchoredDestRect with snapped fill rect when the devPixelFill rect @@ -6507,7 +6538,7 @@ nsLayoutUtils::DrawSingleUnscaledImage(gfxContext& aContext, uint32_t aImageFlags, const nsRect* aSourceArea) { - nsIntSize imageSize; + CSSIntSize imageSize; aImage->GetWidth(&imageSize.width); aImage->GetHeight(&imageSize.height); if (imageSize.width < 1 || imageSize.height < 1) { @@ -6515,10 +6546,7 @@ nsLayoutUtils::DrawSingleUnscaledImage(gfxContext& aContext, return DrawResult::TEMPORARY_ERROR; } - nscoord appUnitsPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel(); - nsSize size(imageSize.width*appUnitsPerCSSPixel, - imageSize.height*appUnitsPerCSSPixel); - + nsSize size(CSSPixel::ToAppUnits(imageSize)); nsRect source; if (aSourceArea) { source = *aSourceArea; @@ -6558,9 +6586,7 @@ nsLayoutUtils::DrawSingleImage(gfxContext& aContext, return DrawResult::SUCCESS; // no point in drawing a zero size image } - nsSize imageSize(pixelImageSize.width * appUnitsPerCSSPixel, - pixelImageSize.height * appUnitsPerCSSPixel); - + nsSize imageSize(CSSPixel::ToAppUnits(pixelImageSize)); nsRect source; nsCOMPtr image; if (aSourceArea) { diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 4f748bd5e7..4ba495f079 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1055,9 +1055,11 @@ public: * to force rendering to use the widget's layer manager for testing * or speed. PAINT_WIDGET_LAYERS must be set if aRenderingContext is null. * If PAINT_DOCUMENT_RELATIVE is used, the visible region is interpreted - * as being relative to the document. (Normally it's relative to the CSS - * viewport.) PAINT_TO_WINDOW sets painting to window to true on the display - * list builder even if we can't tell that we are painting to the window. + * as being relative to the document (normally it's relative to the CSS + * viewport) and the document is painted as if no scrolling has occured. + * Only considered if nsIPresShell::IgnoringViewportScrolling is true. + * PAINT_TO_WINDOW sets painting to window to true on the display list + * builder even if we can't tell that we are painting to the window. * If PAINT_EXISTING_TRANSACTION is set, then BeginTransaction() has already * been called on aFrame's widget's layer manager and should not be * called again. @@ -2318,6 +2320,12 @@ public: */ static bool UnsetValueEnabled(); + /** + * Checks whether support for the CSS grid-template-{columns,rows} 'subgrid X' + * value is enabled. + */ + static bool IsGridTemplateSubgridValueEnabled(); + /** * Checks whether support for the CSS text-align (and -moz-text-align-last) * 'true' value is enabled. diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index c6c9c38289..3956c2ff66 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -2008,10 +2008,9 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, nsRect dirtyRectOutsideTransform = dirtyRect; if (isTransformed) { const nsRect overflow = GetVisualOverflowRectRelativeToSelf(); - if (aBuilder->IsForPainting() && - (nsDisplayTransform::ShouldPrerenderTransformedContent(aBuilder, - this) || - Extend3DContext() || Combines3DTransformWithAncestors())) { + if (Extend3DContext() || Combines3DTransformWithAncestors() || + nsDisplayTransform::ShouldPrerenderTransformedContent(aBuilder, + this)) { dirtyRect = overflow; } else { if (overflow.IsEmpty()) { diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 6a8c85ec43..258fca394e 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -904,7 +904,7 @@ public: NS_DECLARE_FRAME_PROPERTY(InvalidationRect, DeleteValue) - NS_DECLARE_FRAME_PROPERTY(RefusedAsyncAnimation, nullptr) + NS_DECLARE_FRAME_PROPERTY(RefusedAsyncAnimationProperty, nullptr) NS_DECLARE_FRAME_PROPERTY(GenConProperty, DestroyContentArray) @@ -1209,6 +1209,12 @@ public: */ virtual bool NeedsView() { return false; } + bool RefusedAsyncAnimation() const + { + void* prop = Properties().Get(nsIFrame::RefusedAsyncAnimationProperty()); + return bool(reinterpret_cast(prop)); + } + /** * Returns true if this frame is transformed (e.g. has CSS or SVG transforms) * or if its parent is an SVG frame that has children-only transforms (e.g. diff --git a/layout/reftests/invalidation/reftest.list b/layout/reftests/invalidation/reftest.list index cc4944e725..36bedc4816 100644 --- a/layout/reftests/invalidation/reftest.list +++ b/layout/reftests/invalidation/reftest.list @@ -71,3 +71,5 @@ fuzzy-if(gtkWidget,2,4) == image-scrolling-zoom-1.html image-scrolling-zoom-1-re != fractional-transform-2.html about:blank != fractional-transform-3.html about:blank == background-position-1.html background-position-1-ref.html +== zero-opacity-animation.html about:blank +== zero-opacity-text.html about:blank diff --git a/layout/reftests/invalidation/zero-opacity-animation.html b/layout/reftests/invalidation/zero-opacity-animation.html new file mode 100644 index 0000000000..0e4f4dd48f --- /dev/null +++ b/layout/reftests/invalidation/zero-opacity-animation.html @@ -0,0 +1,15 @@ + + + +
+
+
+ + + diff --git a/layout/reftests/invalidation/zero-opacity-text.html b/layout/reftests/invalidation/zero-opacity-text.html new file mode 100644 index 0000000000..5524d29ef9 --- /dev/null +++ b/layout/reftests/invalidation/zero-opacity-text.html @@ -0,0 +1,15 @@ + + + +
+
abc
+
+ + + diff --git a/layout/reftests/pixel-rounding/green-circle-with-blue-border.png b/layout/reftests/pixel-rounding/green-circle-with-blue-border.png new file mode 100644 index 0000000000..570fd24944 Binary files /dev/null and b/layout/reftests/pixel-rounding/green-circle-with-blue-border.png differ diff --git a/layout/reftests/pixel-rounding/image-high-quality-scaling-1-ref.html b/layout/reftests/pixel-rounding/image-high-quality-scaling-1-ref.html new file mode 100644 index 0000000000..62df7fa511 --- /dev/null +++ b/layout/reftests/pixel-rounding/image-high-quality-scaling-1-ref.html @@ -0,0 +1,22 @@ + + + + + + + +
+ +
+ + diff --git a/layout/reftests/pixel-rounding/image-high-quality-scaling-1.html b/layout/reftests/pixel-rounding/image-high-quality-scaling-1.html new file mode 100644 index 0000000000..e3d14a61f9 --- /dev/null +++ b/layout/reftests/pixel-rounding/image-high-quality-scaling-1.html @@ -0,0 +1,22 @@ + + + + + + + +
+ +
+ + diff --git a/layout/reftests/pixel-rounding/reftest.list b/layout/reftests/pixel-rounding/reftest.list index 5a775b4157..8afb104139 100644 --- a/layout/reftests/pixel-rounding/reftest.list +++ b/layout/reftests/pixel-rounding/reftest.list @@ -123,6 +123,10 @@ fails == collapsed-border-top-6.html border-top-10-ref.html == image-width-left-5.html image-width-5.html == image-width-left-6.html image-width-6.html + +skip pref(image.downscale-during-decode.enabled,true) == image-high-quality-scaling-1.html image-high-quality-scaling-1-ref.html + + != offscreen-0-ref.html offscreen-10-ref.html == offscreen-background-color-pos-4.html offscreen-0-ref.html == offscreen-background-color-pos-5.html offscreen-10-ref.html diff --git a/layout/reftests/table-overflow/reftest.list b/layout/reftests/table-overflow/reftest.list index 6937f8dae1..9bec79dc9e 100644 --- a/layout/reftests/table-overflow/reftest.list +++ b/layout/reftests/table-overflow/reftest.list @@ -1,5 +1,6 @@ == bug785684-x.html bug785684-ref.html == bug785684-y.html bug785684-ref.html -== table-row-pagination.html table-row-pagination-ref.html +skip-if(B2G||Mulet) == table-row-pagination.html table-row-pagination-ref.html # Frequently failing on b2g (bug 1155426) == 963441.html 963441-ref.html == table-caption-scroll.html table-caption-scroll-ref.html +== table-cell-block-overflow.html table-cell-block-overflow-ref.html diff --git a/layout/reftests/table-overflow/table-cell-block-overflow-ref.html b/layout/reftests/table-overflow/table-cell-block-overflow-ref.html new file mode 100644 index 0000000000..9d9ecdfc79 --- /dev/null +++ b/layout/reftests/table-overflow/table-cell-block-overflow-ref.html @@ -0,0 +1,43 @@ + + + + + CSS Reftest Reference + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/layout/reftests/table-overflow/table-cell-block-overflow.html b/layout/reftests/table-overflow/table-cell-block-overflow.html new file mode 100644 index 0000000000..27236217c6 --- /dev/null +++ b/layout/reftests/table-overflow/table-cell-block-overflow.html @@ -0,0 +1,42 @@ + + + + + Test table-cell content overflowing in the block direction for each + vertical-align value + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+ + diff --git a/layout/style/AnimationCommon.cpp b/layout/style/AnimationCommon.cpp index 74d70476a5..6f15512e60 100644 --- a/layout/style/AnimationCommon.cpp +++ b/layout/style/AnimationCommon.cpp @@ -17,9 +17,7 @@ #include "nsStyleContext.h" #include "nsIFrame.h" #include "nsLayoutUtils.h" -#include "mozilla/LookAndFeel.h" #include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords -#include "Layers.h" #include "FrameLayerBuilder.h" #include "nsDisplayList.h" #include "mozilla/MemoryReporting.h" @@ -29,28 +27,11 @@ #include "nsStyleSet.h" #include "nsStyleChangeList.h" -using mozilla::layers::Layer; using mozilla::dom::Animation; using mozilla::dom::KeyframeEffectReadOnly; namespace mozilla { -/* static */ bool -IsGeometricProperty(nsCSSProperty aProperty) -{ - switch (aProperty) { - case eCSSProperty_bottom: - case eCSSProperty_height: - case eCSSProperty_left: - case eCSSProperty_right: - case eCSSProperty_top: - case eCSSProperty_width: - return true; - default: - return false; - } -} - CommonAnimationManager::CommonAnimationManager(nsPresContext *aPresContext) : mPresContext(aPresContext) { @@ -126,8 +107,7 @@ CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, AnimationCollection* collection = GetAnimationCollection(aFrame); if (!collection || !collection->HasCurrentAnimationOfProperty(aProperty) || - !collection->CanPerformOnCompositorThread( - AnimationCollection::CanAnimate_AllowPartial)) { + !collection->CanPerformOnCompositorThread(aFrame)) { return nullptr; } @@ -441,101 +421,18 @@ AnimValuesStyleRule::List(FILE* out, int32_t aIndent) const #endif bool -AnimationCollection::CanAnimatePropertyOnCompositor( - const dom::Element *aElement, - nsCSSProperty aProperty, - CanAnimateFlags aFlags) +AnimationCollection::CanPerformOnCompositorThread(const nsIFrame* aFrame) const { - bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled(); - if (!gfxPlatform::OffMainThreadCompositingEnabled()) { - if (shouldLog) { + if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) { + if (nsLayoutUtils::IsAnimationLoggingEnabled()) { nsCString message; - message.AppendLiteral("Performance warning: Compositor disabled"); + message.AppendLiteral("Performance warning: Async animations are disabled"); LogAsyncAnimationFailure(message); } return false; } - nsIFrame* frame = nsLayoutUtils::GetStyleFrame(aElement); - if (IsGeometricProperty(aProperty)) { - if (shouldLog) { - nsCString message; - message.AppendLiteral("Performance warning: Async animation of geometric property '"); - message.Append(nsCSSProps::GetStringValue(aProperty)); - message.AppendLiteral("' is disabled"); - LogAsyncAnimationFailure(message, aElement); - } - return false; - } - if (aProperty == eCSSProperty_transform) { - if (frame->Combines3DTransformWithAncestors() || - frame->Extend3DContext()) { - if (shouldLog) { - nsCString message; - message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' transforms is not supported. See bug 779598"); - LogAsyncAnimationFailure(message, aElement); - } - return false; - } - // Note that testing BackfaceIsHidden() is not a sufficient test for - // what we need for animating backface-visibility correctly if we - // remove the above test for Extend3DContext(); that would require - // looking at backface-visibility on descendants as well. - if (frame->StyleDisplay()->BackfaceIsHidden()) { - if (shouldLog) { - nsCString message; - message.AppendLiteral("Gecko bug: Async animation of 'backface-visibility: hidden' transforms is not supported. See bug 1186204."); - LogAsyncAnimationFailure(message, aElement); - } - return false; - } - if (frame->IsSVGTransformed()) { - if (shouldLog) { - nsCString message; - message.AppendLiteral("Gecko bug: Async 'transform' animations of frames with SVG transforms is not supported. See bug 779599"); - LogAsyncAnimationFailure(message, aElement); - } - return false; - } - if (aFlags & CanAnimate_HasGeometricProperty) { - if (shouldLog) { - nsCString message; - message.AppendLiteral("Performance warning: Async animation of 'transform' not possible due to presence of geometric properties"); - LogAsyncAnimationFailure(message, aElement); - } - return false; - } - } - bool enabled = nsLayoutUtils::AreAsyncAnimationsEnabled(); - if (!enabled && shouldLog) { - nsCString message; - message.AppendLiteral("Performance warning: Async animations are disabled"); - LogAsyncAnimationFailure(message); - } - bool propertyAllowed = (aProperty == eCSSProperty_transform) || - (aProperty == eCSSProperty_opacity) || - (aFlags & CanAnimate_AllowPartial); - return enabled && propertyAllowed; -} - -/* static */ bool -AnimationCollection::IsCompositorAnimationDisabledForFrame( - nsIFrame* aFrame) -{ - void* prop = aFrame->Properties().Get(nsIFrame::RefusedAsyncAnimation()); - return bool(reinterpret_cast(prop)); -} - -bool -AnimationCollection::CanPerformOnCompositorThread( - CanAnimateFlags aFlags) const -{ - dom::Element* element = GetElementToRestyle(); - if (!element) { - return false; - } - nsIFrame* frame = nsLayoutUtils::GetStyleFrame(element); - if (!frame) { + if (aFrame->RefusedAsyncAnimation()) { return false; } @@ -548,44 +445,17 @@ AnimationCollection::CanPerformOnCompositorThread( const KeyframeEffectReadOnly* effect = anim->GetEffect(); MOZ_ASSERT(effect, "A playing animation should have an effect"); - for (size_t propIdx = 0, propEnd = effect->Properties().Length(); - propIdx != propEnd; ++propIdx) { - if (IsGeometricProperty(effect->Properties()[propIdx].mProperty)) { - aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty); - break; - } - } - } - - bool existsProperty = false; - for (size_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { - const Animation* anim = mAnimations[animIdx]; - if (!anim->IsPlaying()) { - continue; - } - - const KeyframeEffectReadOnly* effect = anim->GetEffect(); - MOZ_ASSERT(effect, "A playing animation should have an effect"); - - existsProperty = existsProperty || effect->Properties().Length() > 0; - for (size_t propIdx = 0, propEnd = effect->Properties().Length(); propIdx != propEnd; ++propIdx) { const AnimationProperty& prop = effect->Properties()[propIdx]; - if (!CanAnimatePropertyOnCompositor(element, - prop.mProperty, - aFlags) || - IsCompositorAnimationDisabledForFrame(frame)) { + if (!KeyframeEffectReadOnly::CanAnimatePropertyOnCompositor( + aFrame, + prop.mProperty)) { return false; } } } - // No properties to animate - if (!existsProperty) { - return false; - } - return true; } @@ -730,91 +600,6 @@ AnimationCollection::EnsureStyleRuleFor(TimeStamp aRefreshTime) } } -bool -AnimationCollection::CanThrottleTransformChanges(TimeStamp aTime) -{ - if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) { - return false; - } - - // If we know that the animation cannot cause overflow, - // we can just disable flushes for this animation. - - // If we don't show scrollbars, we don't care about overflow. - if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) { - return true; - } - - // If this animation can cause overflow, we can throttle some of the ticks. - if (!mStyleRuleRefreshTime.IsNull() && - (aTime - mStyleRuleRefreshTime) < TimeDuration::FromMilliseconds(200)) { - return true; - } - - dom::Element* element = GetElementToRestyle(); - if (!element) { - return false; - } - - // If the nearest scrollable ancestor has overflow:hidden, - // we don't care about overflow. - nsIScrollableFrame* scrollable = nsLayoutUtils::GetNearestScrollableFrame( - nsLayoutUtils::GetStyleFrame(element)); - if (!scrollable) { - return true; - } - - ScrollbarStyles ss = scrollable->GetScrollbarStyles(); - if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN && - ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && - scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) { - return true; - } - - return false; -} - -bool -AnimationCollection::CanThrottleAnimation(TimeStamp aTime) -{ - dom::Element* element = GetElementToRestyle(); - if (!element) { - return false; - } - nsIFrame* frame = nsLayoutUtils::GetStyleFrame(element); - if (!frame) { - return false; - } - - for (const LayerAnimationInfo::Record& record : - LayerAnimationInfo::sRecords) { - // We only need to worry about *current* animations here. - // - If we have a newly-finished animation, Animation::CanThrottle will - // detect that and force an unthrottled sample. - // - If we have a newly-idle animation, then whatever caused the animation - // to be idle will update the animation generation so we'll return false - // from the layer generation check below for any other running compositor - // animations (and if no other compositor animations exist we won't get - // this far). - if (!HasCurrentAnimationOfProperty(record.mProperty)) { - continue; - } - - Layer* layer = FrameLayerBuilder::GetDedicatedLayer( - frame, record.mLayerType); - if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) { - return false; - } - - if (record.mProperty == eCSSProperty_transform && - !CanThrottleTransformChanges(aTime)) { - return false; - } - } - - return true; -} - void AnimationCollection::ClearIsRunningOnCompositor(nsCSSProperty aProperty) { @@ -856,16 +641,6 @@ AnimationCollection::RequestRestyle(RestyleType aRestyleType) return; } - // Upgrade throttled restyles if other factors prevent - // throttling (e.g. async animations are not enabled). - if (aRestyleType == RestyleType::Throttled) { - TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh(); - if (!CanPerformOnCompositorThread(CanAnimateFlags(0)) || - !CanThrottleAnimation(now)) { - aRestyleType = RestyleType::Standard; - } - } - if (aRestyleType >= RestyleType::Standard) { mHasPendingAnimationRestyle = true; PostRestyleForAnimation(presContext); diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h index 8cf4881f32..73d1e82499 100644 --- a/layout/style/AnimationCommon.h +++ b/layout/style/AnimationCommon.h @@ -37,8 +37,6 @@ namespace mozilla { class RestyleTracker; struct AnimationCollection; -bool IsGeometricProperty(nsCSSProperty aProperty); - class CommonAnimationManager : public nsIStyleRuleProcessor { public: explicit CommonAnimationManager(nsPresContext *aPresContext); @@ -242,14 +240,6 @@ struct AnimationCollection : public LinkedListElement void EnsureStyleRuleFor(TimeStamp aRefreshTime); - enum CanAnimateFlags { - // Testing for width, height, top, right, bottom, or left. - CanAnimate_HasGeometricProperty = 1, - // Allow the case where OMTA is allowed in general, but not for the - // specified property. - CanAnimate_AllowPartial = 2 - }; - enum class RestyleType { // Animation style has changed but the compositor is applying the same // change so we might be able to defer updating the main thread until it @@ -269,36 +259,18 @@ struct AnimationCollection : public LinkedListElement void RequestRestyle(RestyleType aRestyleType); void ClearIsRunningOnCompositor(nsCSSProperty aProperty); -private: - static bool - CanAnimatePropertyOnCompositor(const dom::Element *aElement, - nsCSSProperty aProperty, - CanAnimateFlags aFlags); - - bool CanThrottleAnimation(TimeStamp aTime); - bool CanThrottleTransformChanges(TimeStamp aTime); - public: - static bool IsCompositorAnimationDisabledForFrame(nsIFrame* aFrame); - // True if this animation can be performed on the compositor thread. // - // If aFlags contains CanAnimate_AllowPartial, returns whether the - // state of this element's animations at the current refresh driver - // time contains animation data that can be done on the compositor - // thread. (This is useful for determining whether a layer should be - // active, or whether to send data to the layer.) - // - // If aFlags does not contain CanAnimate_AllowPartial, returns whether - // the state of this element's animations at the current refresh driver - // time can be fully represented by data sent to the compositor. - // (This is useful for determining whether throttle the animation - // (suppress main-thread style updates).) + // Returns whether the state of this element's animations at the current + // refresh driver time contains animation data that can be done on the + // compositor thread. (This is used for determining whether a layer + // should be active, or whether to send data to the layer.) // // Note that this does not test whether the element's layer uses // off-main-thread compositing, although it does check whether // off-main-thread compositing is enabled as a whole. - bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const; + bool CanPerformOnCompositorThread(const nsIFrame* aFrame) const; bool HasCurrentAnimationOfProperty(nsCSSProperty aProperty) const; diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 9f0d38d6f2..7d1268e04c 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -8755,6 +8755,10 @@ CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSProperty aPropID) nsSubstring* ident = NextIdent(); if (ident) { if (ident->LowerCaseEqualsLiteral("subgrid")) { + if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) { + REPORT_UNEXPECTED(PESubgridNotSupported); + return false; + } if (!ParseOptionalLineNameListAfterSubgrid(value)) { return false; } @@ -8926,6 +8930,10 @@ CSSParserImpl::ParseGridTemplate() nsSubstring* ident = NextIdent(); if (ident) { if (ident->LowerCaseEqualsLiteral("subgrid")) { + if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) { + REPORT_UNEXPECTED(PESubgridNotSupported); + return false; + } if (!ParseOptionalLineNameListAfterSubgrid(value)) { return false; } @@ -8999,6 +9007,10 @@ CSSParserImpl::ParseGridTemplateAfterSlash(bool aColumnsIsTrackList) nsSubstring* ident = NextIdent(); if (ident) { if (ident->LowerCaseEqualsLiteral("subgrid")) { + if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) { + REPORT_UNEXPECTED(PESubgridNotSupported); + return false; + } if (!ParseOptionalLineNameListAfterSubgrid(rowsValue)) { return false; } diff --git a/layout/style/test/file_animations_playbackrate.html b/layout/style/test/file_animations_playbackrate.html index 951ef5eeec..93206594e1 100644 --- a/layout/style/test/file_animations_playbackrate.html +++ b/layout/style/test/file_animations_playbackrate.html @@ -67,6 +67,36 @@ addAsyncAnimTest(function *() { done_div(); }); + +addAsyncAnimTest(function *() { + var [ div, cs ] = new_div("animation: anim 10s 1s"); + var animation = div.getAnimations()[0]; + animation.playbackRate = 0.5; + + advance_clock(2000); // 1s * (1 / playbackRate) + + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.Compositor, + "animation with positive delay and playbackRate > 1 should " + + "start from the initial position at the beginning of the " + + "active duration"); + done_div(); +}); + +addAsyncAnimTest(function *() { + var [ div, cs ] = new_div("animation: anim 10s 1s"); + var animation = div.getAnimations()[0]; + animation.playbackRate = 2.0; + + advance_clock(500); // 1s * (1 / playbackRate) + + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.Compositor, + "animation with positive delay and playbackRate < 1 should " + + "start from the initial position at the beginning of the " + + "active duration"); + done_div(); +}); diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 4a3d1af8e2..be46dba7ed 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -5929,7 +5929,10 @@ if (SpecialPowers.getBoolPref("layout.css.ruby.enabled")) { }; } -if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { +if (IsCSSPropertyPrefEnabled("layout.css.grid.enabled")) { + var isGridTemplateSubgridValueEnabled = + IsCSSPropertyPrefEnabled("layout.css.grid-template-subgrid-value.enabled"); + gCSSProperties["display"].other_values.push("grid", "inline-grid"); gCSSProperties["grid-auto-flow"] = { domProp: "gridAutoFlow", @@ -6024,15 +6027,7 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto [b c]) [d]", "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto) [d]", "[a] 2.5fr [z] Repeat(4, 20px [b c] auto [b c]) [d]", - "[a] 2.5fr [z] Repeat(4, 20px auto) [d]", - - // See https://bugzilla.mozilla.org/show_bug.cgi?id=981300 - "[none auto subgrid min-content max-content foo] 40px", - - "subgrid", - "subgrid [] [foo bar]", - "subgrid repeat(1, [])", - "subgrid Repeat(4, [a] [b c] [] [d])", + "[a] 2.5fr [z] Repeat(4, 20px auto) [d]" ], invalid_values: [ "", @@ -6069,6 +6064,23 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "repeat(2.5, 20px)", "repeat(2, (foo))", "repeat(2, foo)", + "40px calc(0px + rubbish)" + ], + unbalanced_values: [ + "(foo] 40px", + ] + }; + if (isGridTemplateSubgridValueEnabled) { + gCSSProperties["grid-template-columns"].other_values.push( + // See https://bugzilla.mozilla.org/show_bug.cgi?id=981300 + "[none auto subgrid min-content max-content foo] 40px", + + "subgrid", + "subgrid [] [foo bar]", + "subgrid repeat(1, [])", + "subgrid Repeat(4, [a] [b c] [] [d])" + ); + gCSSProperties["grid-template-columns"].invalid_values.push( "subgrid (foo) 40px", "subgrid (foo 40px)", "(foo) subgrid", @@ -6081,13 +6093,9 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "subgrid repeat(1)", "subgrid repeat(1, )", "subgrid repeat(2, (40px))", - "subgrid repeat(2, foo)", - "40px calc(0px + rubbish)", - ], - unbalanced_values: [ - "(foo] 40px", - ] - }; + "subgrid repeat(2, foo)" + ); + } gCSSProperties["grid-template-rows"] = { domProp: "gridTemplateRows", inherited: false, @@ -6137,18 +6145,11 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "none / none", ], other_values: [ - "subgrid", // <'grid-template-columns'> / <'grid-template-rows'> "40px / 100px", "[foo] 40px [bar] / [baz] 100px [fizz]", " none/100px", "40px/none", - "subgrid/40px 20px", - "subgrid [foo] [] [bar baz] / 40px 20px", - "40px 20px/subgrid", - "40px 20px/subgrid [foo] [] repeat(3, [a] [b]) [bar baz]", - "subgrid/subgrid", - "subgrid [foo] [] [bar baz]/subgrid [foo] [] [bar baz]", // [ / ]? [ ? ? ? ]+ "'fizz'", "[bar] 'fizz'", @@ -6160,15 +6161,28 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "[foo] 40px / [bar] 'fizz' 100px [buzz] \n [a] '.' 200px [b]", ], invalid_values: [ - "subgrid []", - "subgrid [] / 'fizz'", - "subgrid / 'fizz'", "[foo] [bar] 40px / 100px", "40px / [fizz] [buzz] 100px", "40px / [fizz] [buzz] 'foo'", "none / 'foo'" ] }; + if (isGridTemplateSubgridValueEnabled) { + gCSSProperties["grid-template"].other_values.push( + "subgrid", + "subgrid/40px 20px", + "subgrid [foo] [] [bar baz] / 40px 20px", + "40px 20px/subgrid", + "40px 20px/subgrid [foo] [] repeat(3, [a] [b]) [bar baz]", + "subgrid/subgrid", + "subgrid [foo] [] [bar baz]/subgrid [foo] [] [bar baz]" + ); + gCSSProperties["grid-template"].invalid_values.push( + "subgrid []", + "subgrid [] / 'fizz'", + "subgrid / 'fizz'" + ); + } gCSSProperties["grid"] = { domProp: "grid", diff --git a/layout/style/test/test_grid_container_shorthands.html b/layout/style/test/test_grid_container_shorthands.html index 4de4aacfc8..4cef96369d 100644 --- a/layout/style/test/test_grid_container_shorthands.html +++ b/layout/style/test/test_grid_container_shorthands.html @@ -12,6 +12,9 @@ diff --git a/layout/tables/reftests/1220621-1b.html b/layout/tables/reftests/1220621-1b.html new file mode 100644 index 0000000000..82ab75544f --- /dev/null +++ b/layout/tables/reftests/1220621-1b.html @@ -0,0 +1,31 @@ + + + + + + + + + + + + + +
OneTwoThree
+ diff --git a/layout/tables/reftests/1220621-1c.html b/layout/tables/reftests/1220621-1c.html new file mode 100644 index 0000000000..3d0949abc9 --- /dev/null +++ b/layout/tables/reftests/1220621-1c.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + +
OneTwoThree
+ diff --git a/layout/tables/reftests/1220621-1d.html b/layout/tables/reftests/1220621-1d.html new file mode 100644 index 0000000000..cf6291d87b --- /dev/null +++ b/layout/tables/reftests/1220621-1d.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + +
OneTwoThree
+ diff --git a/layout/tables/reftests/1220621-1e.html b/layout/tables/reftests/1220621-1e.html new file mode 100644 index 0000000000..44e8b94e28 --- /dev/null +++ b/layout/tables/reftests/1220621-1e.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + +
OneTwoThree
+ diff --git a/layout/tables/reftests/1220621-1f.html b/layout/tables/reftests/1220621-1f.html new file mode 100644 index 0000000000..0b5f9a84e2 --- /dev/null +++ b/layout/tables/reftests/1220621-1f.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + +
OneTwoThree
+ diff --git a/layout/tables/reftests/1220621-2-ref.html b/layout/tables/reftests/1220621-2-ref.html new file mode 100644 index 0000000000..b6a02820b2 --- /dev/null +++ b/layout/tables/reftests/1220621-2-ref.html @@ -0,0 +1,21 @@ + + + + + + + + + + +
One
diff --git a/layout/tables/reftests/1220621-2a.html b/layout/tables/reftests/1220621-2a.html new file mode 100644 index 0000000000..a66768e0fc --- /dev/null +++ b/layout/tables/reftests/1220621-2a.html @@ -0,0 +1,29 @@ + + + + + + + + + + + +
One
+ diff --git a/layout/tables/reftests/1220621-2b.html b/layout/tables/reftests/1220621-2b.html new file mode 100644 index 0000000000..379857235b --- /dev/null +++ b/layout/tables/reftests/1220621-2b.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + +
One
+ diff --git a/layout/tables/reftests/reftest.list b/layout/tables/reftests/reftest.list index 96ceeafe8f..f6f7d5bcef 100644 --- a/layout/tables/reftests/reftest.list +++ b/layout/tables/reftests/reftest.list @@ -1 +1,9 @@ == 1031934.html 1031934-ref.html +== 1220621-1a.html 1220621-1-ref.html +== 1220621-1b.html 1220621-1-ref.html +== 1220621-1c.html 1220621-1-ref.html +== 1220621-1d.html 1220621-1-ref.html +== 1220621-1e.html 1220621-1-ref.html +== 1220621-1f.html 1220621-1-ref.html +== 1220621-2a.html 1220621-2-ref.html +== 1220621-2b.html 1220621-2-ref.html diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index dcd8cfebda..dea65bae7a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2676,6 +2676,9 @@ pref("layout.css.overflow-clip-box.enabled", false); // Is support for CSS grid enabled? pref("layout.css.grid.enabled", false); +// Is support for CSS "grid-template-{columns,rows}: subgrid X" enabled? +pref("layout.css.grid-template-subgrid-value.enabled", false); + // Is support for CSS contain enabled? pref("layout.css.contain.enabled", false); diff --git a/testing/web-platform/tests/mediacapture-streams/obtaining-local-multimedia-content/navigatorusermedia/deny.html b/testing/web-platform/tests/mediacapture-streams/obtaining-local-multimedia-content/navigatorusermedia/deny.html index 0cc3f93cf9..3c22591a9f 100644 --- a/testing/web-platform/tests/mediacapture-streams/obtaining-local-multimedia-content/navigatorusermedia/deny.html +++ b/testing/web-platform/tests/mediacapture-streams/obtaining-local-multimedia-content/navigatorusermedia/deny.html @@ -26,7 +26,7 @@ t.step(function() { t.done(); }), t.step_func(function (error) { - assert_equals(error.name, "PermissionDeniedError", "Permission denied error returned"); + assert_equals(error.name, "SecurityError", "SecurityError returned"); assert_equals(error.constraintName, null, "constraintName attribute not set for permission denied"); t.done(); }));