author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Tue, 28 Jun 2016 16:09:05 +0200 | |
changeset 302871 | e45890951ce77c3df05575bd54072b9f300d77b0 |
parent 302753 | cf243ad179e9929358583f4b8e6f8ce709f85eb6 (current diff) |
parent 302870 | 581d221488b88275575d6b11dc1a999918deb6bd (diff) |
child 302872 | 910bb13e5929010e0e34b6a08fd3bcf6f5657b6e |
child 302905 | e774866bf8a12537e451ccc4b38ffbf0610b5d49 |
child 302949 | e5bc108c46ea9b00bb639e73c857ecc6ddf05379 |
push id | 30376 |
push user | cbook@mozilla.com |
push date | Tue, 28 Jun 2016 14:09:36 +0000 |
treeherder | mozilla-central@e45890951ce7 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 50.0a1 |
first release with | nightly linux32
e45890951ce7
/
50.0a1
/
20160629030209
/
files
nightly linux64
e45890951ce7
/
50.0a1
/
20160629030209
/
files
nightly mac
e45890951ce7
/
50.0a1
/
20160629030209
/
files
nightly win32
e45890951ce7
/
50.0a1
/
20160629030209
/
files
nightly win64
e45890951ce7
/
50.0a1
/
20160629030209
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
50.0a1
/
20160629030209
/
pushlog to previous
nightly linux64
50.0a1
/
20160629030209
/
pushlog to previous
nightly mac
50.0a1
/
20160629030209
/
pushlog to previous
nightly win32
50.0a1
/
20160629030209
/
pushlog to previous
nightly win64
50.0a1
/
20160629030209
/
pushlog to previous
|
--- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -208,16 +208,17 @@ @RESPATH@/components/dom_tv.xpt @RESPATH@/components/dom_inputport.xpt @RESPATH@/components/dom_views.xpt @RESPATH@/components/dom_voicemail.xpt #ifdef MOZ_WEBSPEECH @RESPATH@/components/dom_webspeechrecognition.xpt #endif @RESPATH@/components/dom_xbl.xpt +@RESPATH@/components/dom_xhr.xpt @RESPATH@/components/dom_xpath.xpt @RESPATH@/components/dom_xul.xpt @RESPATH@/components/dom_time.xpt @RESPATH@/components/dom_presentation.xpt @RESPATH@/components/downloads.xpt @RESPATH@/components/editor.xpt @RESPATH@/components/embed_base.xpt @RESPATH@/components/extensions.xpt
--- a/browser/base/content/contentSearchUI.js +++ b/browser/base/content/contentSearchUI.js @@ -77,16 +77,19 @@ ContentSearchUIController.prototype = { // if it's set when the suggestions table is actually opened. _pendingOneOffRefresh: undefined, get defaultEngine() { return this._defaultEngine; }, set defaultEngine(engine) { + if (this._defaultEngine && this._defaultEngine.icon) { + URL.revokeObjectURL(this._defaultEngine.icon); + } let icon; if (engine.iconBuffer) { icon = this._getFaviconURIFromBuffer(engine.iconBuffer); } else { icon = this._getImageURIForCurrentResolution( "chrome://mozapps/skin/places/defaultFavicon.png"); } @@ -863,16 +866,20 @@ ContentSearchUIController.prototype = { if (engine.iconBuffer) { uri = this._getFaviconURIFromBuffer(engine.iconBuffer); } else { uri = this._getImageURIForCurrentResolution( "chrome://browser/skin/search-engine-placeholder.png"); } img.setAttribute("src", uri); + img.addEventListener("load", function imgLoad() { + img.removeEventListener("load", imgLoad); + URL.revokeObjectURL(uri); + }); button.appendChild(img); button.style.width = buttonWidth + "px"; button.setAttribute("title", engine.name); button.engineName = engine.name; button.addEventListener("click", this); button.addEventListener("mousemove", this);
--- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -211,21 +211,19 @@ @RESPATH@/components/dom_traversal.xpt @RESPATH@/components/dom_tv.xpt @RESPATH@/components/dom_voicemail.xpt #ifdef MOZ_WEBSPEECH @RESPATH@/components/dom_webspeechrecognition.xpt #endif @RESPATH@/components/dom_workers.xpt @RESPATH@/components/dom_xbl.xpt +@RESPATH@/components/dom_xhr.xpt @RESPATH@/components/dom_xpath.xpt @RESPATH@/components/dom_xul.xpt -#ifdef MOZ_GAMEPAD -@RESPATH@/components/dom_gamepad.xpt -#endif @RESPATH@/components/dom_payment.xpt @RESPATH@/components/dom_presentation.xpt @RESPATH@/components/downloads.xpt @RESPATH@/components/editor.xpt @RESPATH@/components/embed_base.xpt @RESPATH@/components/extensions.xpt @RESPATH@/components/exthandler.xpt @RESPATH@/components/exthelper.xpt
--- a/build/annotationProcessors/CodeGenerator.java +++ b/build/annotationProcessors/CodeGenerator.java @@ -37,23 +37,24 @@ public class CodeGenerator { this.clsName = annotatedClass.generatedName; final String unqualifiedName = Utils.getUnqualifiedName(clsName); header.append( "class " + clsName + " : public mozilla::jni::ObjectBase<" + unqualifiedName + ", jobject>\n" + "{\n" + "public:\n" + + " static const char name[];\n" + + "\n" + " explicit " + unqualifiedName + "(const Context& ctx) : ObjectBase<" + unqualifiedName + ", jobject>(ctx) {}\n" + "\n"); cpp.append( - "template<> const char mozilla::jni::Context<" + - clsName + ", jobject>::name[] =\n" + + "const char " + clsName + "::name[] =\n" + " \"" + cls.getName().replace('.', '/') + "\";\n" + "\n"); natives.append( "template<class Impl>\n" + "class " + clsName + "::Natives : " + "public mozilla::jni::NativeImpl<" + unqualifiedName + ", Impl>\n" + "{\n" +
--- a/devtools/client/locales/en-US/markers.properties +++ b/devtools/client/locales/en-US/markers.properties @@ -98,17 +98,16 @@ marker.value.DOMEventTargetPhase=Target marker.value.DOMEventCapturingPhase=Capture marker.value.DOMEventBubblingPhase=Bubbling # LOCALIZATION NOTE (marker.gcreason.label.*): # These strings are used to give a concise but readable description of a GC reason. marker.gcreason.label.API=API Call marker.gcreason.label.EAGER_ALLOC_TRIGGER=Eager Allocation Trigger marker.gcreason.label.DESTROY_RUNTIME=Shutdown -marker.gcreason.label.DESTROY_CONTEXT=Shutdown marker.gcreason.label.LAST_DITCH=Out of Memory marker.gcreason.label.TOO_MUCH_MALLOC=Too Many Bytes Allocated marker.gcreason.label.ALLOC_TRIGGER=Too Many Allocations marker.gcreason.label.DEBUG_GC=Debug GC marker.gcreason.label.COMPARTMENT_REVIVED=Dead Global Revived marker.gcreason.label.RESET=Finish Incremental Cycle marker.gcreason.label.OUT_OF_NURSERY=Nursery is Full marker.gcreason.label.EVICT_NURSERY=Nursery Eviction @@ -138,17 +137,16 @@ marker.gcreason.label.USER_INACTIVE=User # The name of a nursery collection. marker.nurseryCollection=Nursery Collection # LOCALIZATION NOTE (marker.gcreason.description.*): # These strings are used to give an expanded description of why a GC occurred. marker.gcreason.description.API=There was an API call to force garbage collection. marker.gcreason.description.EAGER_ALLOC_TRIGGER=JavaScript returned to the event loop and there were enough bytes allocated since the last GC that a new GC cycle was triggered. marker.gcreason.description.DESTROY_RUNTIME=Firefox destroyed a JavaScript runtime or context, and this was the final garbage collection before shutting down. -marker.gcreason.description.DESTROY_CONTEXT=Firefox destroyed a JavaScript runtime or context, and this was the final garbage collection before shutting down. marker.gcreason.description.LAST_DITCH=JavaScript attempted to allocate, but there was no memory available. Doing a full compacting garbage collection as an attempt to free up memory for the allocation. marker.gcreason.description.TOO_MUCH_MALLOC=JavaScript allocated too many bytes, and forced a garbage collection. marker.gcreason.description.ALLOC_TRIGGER=JavaScript allocated too many times, and forced a garbage collection. marker.gcreason.description.DEBUG_GC=GC due to Zeal debug settings. marker.gcreason.description.COMPARTMENT_REVIVED=A global object that was thought to be dead at the start of the GC cycle was revived by the end of the GC cycle. marker.gcreason.description.RESET=The active incremental GC cycle was forced to finish immediately. marker.gcreason.description.OUT_OF_NURSERY=JavaScript allocated enough new objects in the nursery that it became full and triggered a minor GC. marker.gcreason.description.EVICT_NURSERY=Work needed to be done on the tenured heap, requiring the nursery to be empty.
--- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -10,21 +10,23 @@ #include "mozilla/dom/KeyframeEffectBinding.h" #include "mozilla/AnimationUtils.h" #include "mozilla/FloatingPoint.h" #include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt #include "mozilla/KeyframeUtils.h" #include "mozilla/StyleAnimationValue.h" #include "Layers.h" // For Layer #include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetStyleContextForElement +#include "nsContentUtils.h" // nsContentUtils::ReportToConsole #include "nsCSSPropertySet.h" #include "nsCSSProps.h" // For nsCSSProps::PropHasFlags #include "nsCSSPseudoElements.h" // For CSSPseudoElementType #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch #include "nsIPresShell.h" // For nsIPresShell +#include "nsIScriptError.h" namespace mozilla { // Helper functions for generating a ComputedTimingProperties dictionary static void GetComputedTimingDictionary(const ComputedTiming& aComputedTiming, const Nullable<TimeDuration>& aLocalTime, const TimingParams& aTiming, @@ -72,30 +74,34 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_ NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly) NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly) NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly) KeyframeEffectReadOnly::KeyframeEffectReadOnly( nsIDocument* aDocument, const Maybe<OwningAnimationTarget>& aTarget, - const TimingParams& aTiming) + const TimingParams& aTiming, + const KeyframeEffectParams& aOptions) : KeyframeEffectReadOnly(aDocument, aTarget, new AnimationEffectTimingReadOnly(aDocument, - aTiming)) + aTiming), + aOptions) { } KeyframeEffectReadOnly::KeyframeEffectReadOnly( nsIDocument* aDocument, const Maybe<OwningAnimationTarget>& aTarget, - AnimationEffectTimingReadOnly* aTiming) + AnimationEffectTimingReadOnly* aTiming, + const KeyframeEffectParams& aOptions) : AnimationEffectReadOnly(aDocument) , mTarget(aTarget) , mTiming(aTiming) + , mEffectOptions(aOptions) , mInEffectOnLastAnimationTimingUpdate(false) , mCumulativeChangeHint(nsChangeHint(0)) { MOZ_ASSERT(aTiming); } JSObject* KeyframeEffectReadOnly::WrapObject(JSContext* aCx, @@ -437,39 +443,33 @@ KeyframeEffectReadOnly::SetKeyframes(JSC } nsTArray<Keyframe> keyframes = KeyframeUtils::GetKeyframesFromObject(aContext, aKeyframes, aRv); if (aRv.Failed()) { return; } - RefPtr<nsStyleContext> styleContext; - nsIPresShell* shell = doc->GetShell(); - if (shell && mTarget) { - nsIAtom* pseudo = - mTarget->mPseudoType < CSSPseudoElementType::Count ? - nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType) : nullptr; - styleContext = - nsComputedDOMStyle::GetStyleContextForElement(mTarget->mElement, - pseudo, shell); - } - + RefPtr<nsStyleContext> styleContext = GetTargetStyleContext(doc); SetKeyframes(Move(keyframes), styleContext); } void KeyframeEffectReadOnly::SetKeyframes(nsTArray<Keyframe>&& aKeyframes, nsStyleContext* aStyleContext) { if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) { return; } mKeyframes = Move(aKeyframes); + // Apply distribute spacing irrespective of the spacing mode. We will apply + // the specified spacing mode when we generate computed animation property + // values from the keyframes since both operations require a style context + // and need to be performed whenever the style context changes. KeyframeUtils::ApplyDistributeSpacing(mKeyframes); if (mAnimation && mAnimation->IsRelevant()) { nsNodeUtils::AnimationChanged(mAnimation); } if (aStyleContext) { UpdateProperties(aStyleContext); @@ -507,21 +507,30 @@ KeyframeEffectReadOnly::HasAnimationOfPr void KeyframeEffectReadOnly::UpdateProperties(nsStyleContext* aStyleContext) { MOZ_ASSERT(aStyleContext); nsTArray<AnimationProperty> properties; if (mTarget) { + nsTArray<ComputedKeyframeValues> computedValues = + KeyframeUtils::GetComputedKeyframeValues(mKeyframes, mTarget->mElement, + aStyleContext); + + if (mEffectOptions.mSpacingMode == SpacingMode::paced) { + KeyframeUtils::ApplySpacing(mKeyframes, SpacingMode::paced, + mEffectOptions.mPacedProperty, + computedValues); + } + properties = - KeyframeUtils::GetAnimationPropertiesFromKeyframes(aStyleContext, - mTarget->mElement, - mTarget->mPseudoType, - mKeyframes); + KeyframeUtils::GetAnimationPropertiesFromKeyframes(mKeyframes, + computedValues, + aStyleContext); } if (mProperties == properties) { return; } // Preserve the state of mWinsInCascade and mIsRunningOnCompositor flags. nsCSSPropertySet winningInCascadeProperties; @@ -701,16 +710,51 @@ KeyframeEffectReadOnly::ResetIsRunningOn property.mIsRunningOnCompositor = false; } } KeyframeEffectReadOnly::~KeyframeEffectReadOnly() { } +static const KeyframeEffectOptions& +KeyframeEffectOptionsFromUnion( + const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) +{ + MOZ_ASSERT(aOptions.IsKeyframeEffectOptions()); + return aOptions.GetAsKeyframeEffectOptions(); +} + +static const KeyframeEffectOptions& +KeyframeEffectOptionsFromUnion( + const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) +{ + MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions()); + return aOptions.GetAsKeyframeAnimationOptions(); +} + +template <class OptionsType> +static KeyframeEffectParams +KeyframeEffectParamsFromUnion(const OptionsType& aOptions, + nsAString& aInvalidPacedProperty, + ErrorResult& aRv) +{ + KeyframeEffectParams result; + if (!aOptions.IsUnrestrictedDouble()) { + const KeyframeEffectOptions& options = + KeyframeEffectOptionsFromUnion(aOptions); + KeyframeEffectParams::ParseSpacing(options.mSpacing, + result.mSpacingMode, + result.mPacedProperty, + aInvalidPacedProperty, + aRv); + } + return result; +} + static Maybe<OwningAnimationTarget> ConvertTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget) { // Return value optimization. Maybe<OwningAnimationTarget> result; if (aTarget.IsNull()) { return result; @@ -745,19 +789,36 @@ KeyframeEffectReadOnly::ConstructKeyfram } TimingParams timingParams = TimingParams::FromOptionsUnion(aOptions, doc, aRv); if (aRv.Failed()) { return nullptr; } + nsAutoString invalidPacedProperty; + KeyframeEffectParams effectOptions = + KeyframeEffectParamsFromUnion(aOptions, invalidPacedProperty, aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (!invalidPacedProperty.IsEmpty()) { + const char16_t* params[] = { invalidPacedProperty.get() }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Animation"), + doc, + nsContentUtils::eDOM_PROPERTIES, + "UnanimatablePacedProperty", + params, ArrayLength(params)); + } + Maybe<OwningAnimationTarget> target = ConvertTarget(aTarget); RefPtr<KeyframeEffectType> effect = - new KeyframeEffectType(doc, target, timingParams); + new KeyframeEffectType(doc, target, timingParams, effectOptions); effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv); if (aRv.Failed()) { return nullptr; } return effect.forget(); } @@ -815,16 +876,38 @@ KeyframeEffectReadOnly::RequestRestyle( nsPresContext* presContext = GetPresContext(); if (presContext && mTarget && mAnimation) { presContext->EffectCompositor()-> RequestRestyle(mTarget->mElement, mTarget->mPseudoType, aRestyleType, mAnimation->CascadeLevel()); } } +already_AddRefed<nsStyleContext> +KeyframeEffectReadOnly::GetTargetStyleContext(nsIDocument* aDoc) +{ + if (!mTarget) { + return nullptr; + } + + if (!aDoc) { + aDoc = mTarget->mElement->OwnerDoc(); + if (!aDoc) { + return nullptr; + } + } + + nsIAtom* pseudo = mTarget->mPseudoType < CSSPseudoElementType::Count + ? nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType) + : nullptr; + return nsComputedDOMStyle::GetStyleContextForElement(mTarget->mElement, + pseudo, + aDoc->GetShell()); +} + #ifdef DEBUG void DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties) { for (auto& p : aAnimationProperties) { printf("%s\n", nsCSSProps::GetStringValue(p.mProperty).get()); for (auto& s : p.mSegments) { nsString fromValue, toValue; @@ -983,16 +1066,18 @@ KeyframeEffectReadOnly::GetKeyframes(JSC } for (const Keyframe& keyframe : mKeyframes) { // Set up a dictionary object for the explicit members BaseComputedKeyframe keyframeDict; if (keyframe.mOffset) { keyframeDict.mOffset.SetValue(keyframe.mOffset.value()); } + MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet, + "Invalid computed offset"); keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset); if (keyframe.mTimingFunction) { keyframeDict.mEasing.Truncate(); keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing); } // else if null, leave easing as its default "linear". JS::Rooted<JS::Value> keyframeJSValue(aCx); if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) { @@ -1362,19 +1447,21 @@ KeyframeEffectReadOnly::CanIgnoreIfNotVi //--------------------------------------------------------------------- // // KeyframeEffect // //--------------------------------------------------------------------- KeyframeEffect::KeyframeEffect(nsIDocument* aDocument, const Maybe<OwningAnimationTarget>& aTarget, - const TimingParams& aTiming) + const TimingParams& aTiming, + const KeyframeEffectParams& aOptions) : KeyframeEffectReadOnly(aDocument, aTarget, - new AnimationEffectTiming(aDocument, aTiming, this)) + new AnimationEffectTiming(aDocument, aTiming, this), + aOptions) { } JSObject* KeyframeEffect::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return KeyframeEffectBinding::Wrap(aCx, this, aGivenProto); @@ -1444,55 +1531,38 @@ KeyframeEffect::SetTarget(const Nullable nsNodeUtils::AnimationRemoved(mAnimation); } } mTarget = newTarget; if (mTarget) { UpdateTargetRegistration(); - MaybeUpdateProperties(); + RefPtr<nsStyleContext> styleContext = GetTargetStyleContext(); + if (styleContext) { + UpdateProperties(styleContext); + } else if (mEffectOptions.mSpacingMode == SpacingMode::paced) { + KeyframeUtils::ApplyDistributeSpacing(mKeyframes); + } RequestRestyle(EffectCompositor::RestyleType::Layer); nsAutoAnimationMutationBatch mb(mTarget->mElement->OwnerDoc()); if (mAnimation) { nsNodeUtils::AnimationAdded(mAnimation); } + } else if (mEffectOptions.mSpacingMode == SpacingMode::paced) { + // New target is null, so fall back to distribute spacing. + KeyframeUtils::ApplyDistributeSpacing(mKeyframes); } } KeyframeEffect::~KeyframeEffect() { // mTiming is cycle collected, so we have to do null check first even though // mTiming shouldn't be null during the lifetime of KeyframeEffect. if (mTiming) { mTiming->Unlink(); } } -void -KeyframeEffect::MaybeUpdateProperties() -{ - if (!mTarget) { - return; - } - - nsIDocument* doc = mTarget->mElement->OwnerDoc(); - if (!doc) { - return; - } - - nsIAtom* pseudo = mTarget->mPseudoType < CSSPseudoElementType::Count ? - nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType) : - nullptr; - RefPtr<nsStyleContext> styleContext = - nsComputedDOMStyle::GetStyleContextForElement(mTarget->mElement, pseudo, - doc->GetShell()); - if (!styleContext) { - return; - } - - UpdateProperties(styleContext); -} - } // namespace dom } // namespace mozilla
--- a/dom/animation/KeyframeEffect.h +++ b/dom/animation/KeyframeEffect.h @@ -14,19 +14,19 @@ #include "nsTArray.h" #include "nsWrapperCache.h" #include "mozilla/AnimationPerformanceWarning.h" #include "mozilla/AnimationTarget.h" #include "mozilla/Attributes.h" #include "mozilla/ComputedTiming.h" #include "mozilla/ComputedTimingFunction.h" #include "mozilla/EffectCompositor.h" +#include "mozilla/KeyframeEffectParams.h" #include "mozilla/LayerAnimationInfo.h" // LayerAnimations::kRecords #include "mozilla/Maybe.h" -#include "mozilla/OwningNonNull.h" // OwningNonNull<...> #include "mozilla/StickyTimeDuration.h" #include "mozilla/StyleAnimationValue.h" #include "mozilla/TimeStamp.h" #include "mozilla/TimingParams.h" #include "mozilla/dom/AnimationEffectReadOnly.h" #include "mozilla/dom/AnimationEffectTimingReadOnly.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Nullable.h" @@ -102,17 +102,18 @@ struct Keyframe mOffset = aOther.mOffset; mComputedOffset = aOther.mComputedOffset; mTimingFunction = Move(aOther.mTimingFunction); mPropertyValues = Move(aOther.mPropertyValues); return *this; } Maybe<double> mOffset; - double mComputedOffset = 0.0; + static MOZ_CONSTEXPR_VAR double kComputedOffsetNotSet = -1.0; + double mComputedOffset = kComputedOffsetNotSet; Maybe<ComputedTimingFunction> mTimingFunction; // Nothing() here means // "linear" nsTArray<PropertyValuePair> mPropertyValues; }; struct AnimationPropertySegment { float mFromKey, mToKey; @@ -191,17 +192,18 @@ namespace dom { class Animation; class KeyframeEffectReadOnly : public AnimationEffectReadOnly { public: KeyframeEffectReadOnly(nsIDocument* aDocument, const Maybe<OwningAnimationTarget>& aTarget, - const TimingParams& aTiming); + const TimingParams& aTiming, + const KeyframeEffectParams& aOptions); NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly) virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; @@ -231,18 +233,19 @@ public: void GetKeyframes(JSContext*& aCx, nsTArray<JSObject*>& aResult, ErrorResult& aRv); void GetProperties(nsTArray<AnimationPropertyDetails>& aProperties, ErrorResult& aRv) const; IterationCompositeOperation IterationComposite() const; CompositeOperation Composite() const; - void GetSpacing(nsString& aRetVal) const { - aRetVal.AssignLiteral("distribute"); + void GetSpacing(nsString& aRetVal) const + { + mEffectOptions.GetSpacingAsString(aRetVal); } already_AddRefed<AnimationEffectTimingReadOnly> Timing() const override; const TimingParams& SpecifiedTiming() const { return mTiming->AsTimingParams(); } @@ -350,17 +353,18 @@ public: // can ignore painting if the animation is not visible. // See nsChangeHint_Hints_CanIgnoreIfNotVisible in nsChangeHint.h // in detail which change hint can be ignored. bool CanIgnoreIfNotVisible() const; protected: KeyframeEffectReadOnly(nsIDocument* aDocument, const Maybe<OwningAnimationTarget>& aTarget, - AnimationEffectTimingReadOnly* aTiming); + AnimationEffectTimingReadOnly* aTiming, + const KeyframeEffectParams& aOptions); virtual ~KeyframeEffectReadOnly(); template<class KeyframeEffectType, class OptionsType> static already_AddRefed<KeyframeEffectType> ConstructKeyframeEffect(const GlobalObject& aGlobal, const Nullable<ElementOrCSSPseudoElement>& aTarget, JS::Handle<JSObject*> aKeyframes, @@ -380,20 +384,30 @@ protected: // owning Animation's timing. void UpdateTargetRegistration(); // Remove the current effect target from its EffectSet. void UnregisterTarget(); void RequestRestyle(EffectCompositor::RestyleType aRestyleType); + // Looks up the style context associated with the target element, if any. + // We need to be careful to *not* call this when we are updating the style + // context. That's because calling GetStyleContextForElement when we are in + // the process of building a style context may trigger various forms of + // infinite recursion. + // If aDoc is nullptr, we will use the owner doc of the target element. + already_AddRefed<nsStyleContext> + GetTargetStyleContext(nsIDocument* aDoc = nullptr); + Maybe<OwningAnimationTarget> mTarget; RefPtr<Animation> mAnimation; RefPtr<AnimationEffectTimingReadOnly> mTiming; + KeyframeEffectParams mEffectOptions; // The specified keyframes. nsTArray<Keyframe> mKeyframes; // A set of per-property value arrays, derived from |mKeyframes|. nsTArray<AnimationProperty> mProperties; // The computed progress last time we composed the style rule. This is @@ -424,17 +438,18 @@ private: static const TimeDuration OverflowRegionRefreshInterval(); }; class KeyframeEffect : public KeyframeEffectReadOnly { public: KeyframeEffect(nsIDocument* aDocument, const Maybe<OwningAnimationTarget>& aTarget, - const TimingParams& aTiming); + const TimingParams& aTiming, + const KeyframeEffectParams& aOptions); JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; static already_AddRefed<KeyframeEffect> Constructor(const GlobalObject& aGlobal, const Nullable<ElementOrCSSPseudoElement>& aTarget, JS::Handle<JSObject*> aKeyframes, @@ -448,29 +463,23 @@ public: Constructor(const GlobalObject& aGlobal, const Nullable<ElementOrCSSPseudoElement>& aTarget, JS::Handle<JSObject*> aKeyframes, const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, ErrorResult& aRv); void NotifySpecifiedTimingUpdated(); - // This method calls MaybeUpdateProperties which is not safe to use when + // This method calls GetTargetStyleContext which is not safe to use when // we are in the middle of updating style. If we need to use this when // updating style, we should pass the nsStyleContext into this method and use // that to update the properties rather than calling // GetStyleContextForElement. void SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget); protected: ~KeyframeEffect() override; - - // We need to be careful to *not* call this when we are updating the style - // context. That's because calling GetStyleContextForElement when we are in - // the process of building a style context may trigger various forms of - // infinite recursion. - void MaybeUpdateProperties(); }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_KeyframeEffect_h
new file mode 100644 --- /dev/null +++ b/dom/animation/KeyframeEffectParams.cpp @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/KeyframeEffectParams.h" + +#include "mozilla/KeyframeUtils.h" +#include "mozilla/RangedPtr.h" +#include "nsReadableUtils.h" + +namespace mozilla { + +static inline bool +IsLetter(char16_t aCh) +{ + return (0x41 <= aCh && aCh <= 0x5A) || (0x61 <= aCh && aCh <= 0x7A); +} + +static inline bool +IsDigit(char16_t aCh) +{ + return 0x30 <= aCh && aCh <= 0x39; +} + +static inline bool +IsNameStartCode(char16_t aCh) +{ + return IsLetter(aCh) || aCh >= 0x80 || aCh == '_'; +} + +static inline bool +IsNameCode(char16_t aCh) +{ + return IsNameStartCode(aCh) || IsDigit(aCh) || aCh == '-'; +} + +static inline bool +IsNewLine(char16_t aCh) +{ + // 0x0A (LF), 0x0C (FF), 0x0D (CR), or pairs of CR followed by LF are + // replaced by LF. + return aCh == 0x0A || aCh == 0x0C || aCh == 0x0D; +} + +static inline bool +IsValidEscape(char16_t aFirst, char16_t aSecond) +{ + return aFirst == '\\' && !IsNewLine(aSecond); +} + +static bool +IsIdentStart(RangedPtr<const char16_t> aIter, + const char16_t* const aEnd) +{ + if (aIter == aEnd) { + return false; + } + + if (*aIter == '-') { + if (aIter + 1 == aEnd) { + return false; + } + char16_t second = *(aIter + 1); + return IsNameStartCode(second) || + second == '-' || + (aIter + 2 != aEnd && IsValidEscape(second, *(aIter + 2))); + } + return IsNameStartCode(*aIter) || + (aIter + 1 != aEnd && IsValidEscape(*aIter, *(aIter + 1))); +} + +static void +ConsumeIdentToken(RangedPtr<const char16_t>& aIter, + const char16_t* const aEnd, + nsAString& aResult) +{ + aResult.Truncate(); + + // Check if it starts with an identifier. + if (!IsIdentStart(aIter, aEnd)) { + return; + } + + // Start to consume. + while (aIter != aEnd) { + if (IsNameCode(*aIter)) { + aResult.Append(*aIter); + } else if (*aIter == '\\') { + const RangedPtr<const char16_t> secondChar = aIter + 1; + if (secondChar == aEnd || !IsValidEscape(*aIter, *secondChar)) { + break; + } + // Consume '\\' and append the character following this '\\'. + ++aIter; + aResult.Append(*aIter); + } else { + break; + } + ++aIter; + } +} + +/* static */ void +KeyframeEffectParams::ParseSpacing(const nsAString& aSpacing, + SpacingMode& aSpacingMode, + nsCSSProperty& aPacedProperty, + nsAString& aInvalidPacedProperty, + ErrorResult& aRv) +{ + aInvalidPacedProperty.Truncate(); + + // Parse spacing. + // distribute | paced({ident}) + // https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-spacing + // 1. distribute spacing. + if (aSpacing.EqualsLiteral("distribute")) { + aSpacingMode = SpacingMode::distribute; + return; + } + + // 2. paced spacing. + static const nsLiteralString kPacedPrefix = NS_LITERAL_STRING("paced("); + if (!StringBeginsWith(aSpacing, kPacedPrefix)) { + aRv.ThrowTypeError<dom::MSG_INVALID_SPACING_MODE_ERROR>(aSpacing); + return; + } + + RangedPtr<const char16_t> iter(aSpacing.Data() + kPacedPrefix.Length(), + aSpacing.Data(), aSpacing.Length()); + const char16_t* const end = aSpacing.EndReading(); + + nsAutoString identToken; + ConsumeIdentToken(iter, end, identToken); + if (identToken.IsEmpty()) { + aRv.ThrowTypeError<dom::MSG_INVALID_SPACING_MODE_ERROR>(aSpacing); + return; + } + + aPacedProperty = + nsCSSProps::LookupProperty(identToken, CSSEnabledState::eForAllContent); + if (aPacedProperty == eCSSProperty_UNKNOWN || + aPacedProperty == eCSSPropertyExtra_variable || + !KeyframeUtils::IsAnimatableProperty(aPacedProperty)) { + aPacedProperty = eCSSProperty_UNKNOWN; + aInvalidPacedProperty = identToken; + } + + if (end - iter.get() != 1 || *iter != ')') { + aRv.ThrowTypeError<dom::MSG_INVALID_SPACING_MODE_ERROR>(aSpacing); + return; + } + + aSpacingMode = aPacedProperty == eCSSProperty_UNKNOWN + ? SpacingMode::distribute + : SpacingMode::paced; +} + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/animation/KeyframeEffectParams.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_KeyframeEffectParams_h +#define mozilla_KeyframeEffectParams_h + +#include "nsCSSProps.h" +#include "nsString.h" + +namespace mozilla { + +enum class SpacingMode +{ + distribute, + paced +}; + +struct KeyframeEffectParams +{ + void GetSpacingAsString(nsAString& aSpacing) const + { + if (mSpacingMode == SpacingMode::distribute) { + aSpacing.AssignLiteral("distribute"); + } else { + aSpacing.AssignLiteral("paced("); + aSpacing.AppendASCII(nsCSSProps::GetStringValue(mPacedProperty).get()); + aSpacing.AppendLiteral(")"); + } + } + + /** + * Parse spacing string. + * + * @param aSpacing The input spacing string. + * @param [out] aSpacingMode The parsed spacing mode. + * @param [out] aPacedProperty The parsed CSS property if using paced spacing. + * @param [out] aInvalidPacedProperty A string that, if we parsed a string of + * the form 'paced(<ident>)' where <ident> + * is not a recognized animatable property, + * will be set to <ident>. + * @param [out] aRv The error result. + */ + static void ParseSpacing(const nsAString& aSpacing, + SpacingMode& aSpacingMode, + nsCSSProperty& aPacedProperty, + nsAString& aInvalidPacedProperty, + ErrorResult& aRv); + + // FIXME: Bug 1216843: Add IterationCompositeOperations and + // Bug 1216844: Add CompositeOperation + SpacingMode mSpacingMode = SpacingMode::distribute; + nsCSSProperty mPacedProperty = eCSSProperty_UNKNOWN; +}; + +} // namespace mozilla + +#endif // mozilla_KeyframeEffectParams_h
--- a/dom/animation/KeyframeUtils.cpp +++ b/dom/animation/KeyframeUtils.cpp @@ -3,16 +3,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/KeyframeUtils.h" #include "mozilla/AnimationUtils.h" #include "mozilla/ErrorResult.h" #include "mozilla/Move.h" +#include "mozilla/StyleAnimationValue.h" #include "mozilla/TimingParams.h" #include "mozilla/dom/BaseKeyframeTypesBinding.h" // For FastBaseKeyframe etc. #include "mozilla/dom/Element.h" #include "mozilla/dom/KeyframeEffect.h" #include "mozilla/dom/KeyframeEffectBinding.h" #include "jsapi.h" // For ForOfIterator etc. #include "nsClassHashtable.h" #include "nsCSSParser.h" @@ -24,16 +25,20 @@ namespace mozilla { // ------------------------------------------------------------------ // // Internal data types // // ------------------------------------------------------------------ +// This is used while calculating paced spacing. If the keyframe is not pacable, +// we set its cumulative distance to kNotPaceable, so we can use this to check. +const double kNotPaceable = -1.0; + // For the aAllowList parameter of AppendStringOrStringSequence and // GetPropertyValuesPairs. enum class ListAllowance { eDisallow, eAllow }; /** * A comparator to sort nsCSSProperty values such that longhands are sorted * before shorthands, and shorthands with less components are sorted before * shorthands with more components. @@ -347,19 +352,16 @@ ConvertKeyframeSequence(JSContext* aCx, static bool GetPropertyValuesPairs(JSContext* aCx, JS::Handle<JSObject*> aObject, ListAllowance aAllowLists, nsTArray<PropertyValuesPair>& aResult); static bool -IsAnimatableProperty(nsCSSProperty aProperty); - -static bool AppendStringOrStringSequenceToArray(JSContext* aCx, JS::Handle<JS::Value> aValue, ListAllowance aAllowLists, nsTArray<nsString>& aValues); static bool AppendValueAsString(JSContext* aCx, nsTArray<nsString>& aValues, @@ -368,30 +370,50 @@ AppendValueAsString(JSContext* aCx, static PropertyValuePair MakePropertyValuePair(nsCSSProperty aProperty, const nsAString& aStringValue, nsCSSParser& aParser, nsIDocument* aDocument); static bool HasValidOffsets(const nsTArray<Keyframe>& aKeyframes); static void +MarkAsComputeValuesFailureKey(PropertyValuePair& aPair); + +static bool +IsComputeValuesFailureKey(const PropertyValuePair& aPair); + +static void BuildSegmentsFromValueEntries(nsStyleContext* aStyleContext, nsTArray<KeyframeValueEntry>& aEntries, nsTArray<AnimationProperty>& aResult); static void GetKeyframeListFromPropertyIndexedKeyframe(JSContext* aCx, JS::Handle<JS::Value> aValue, nsTArray<Keyframe>& aResult, ErrorResult& aRv); static bool RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes, nsIDocument* aDocument); +static void +DistributeRange(const Range<Keyframe>& aSpacingRange, + const Range<Keyframe>& aRangeToAdjust); + +static void +DistributeRange(const Range<Keyframe>& aSpacingRange); + +static void +PaceRange(const Range<Keyframe>& aKeyframes, + const Range<double>& aCumulativeDistances); + +static nsTArray<double> +GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues, + nsCSSProperty aProperty); // ------------------------------------------------------------------ // // Public API // // ------------------------------------------------------------------ /* static */ nsTArray<Keyframe> @@ -448,82 +470,163 @@ KeyframeUtils::GetKeyframesFromObject(JS aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR); keyframes.Clear(); } return keyframes; } /* static */ void -KeyframeUtils::ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes) +KeyframeUtils::ApplySpacing(nsTArray<Keyframe>& aKeyframes, + SpacingMode aSpacingMode, + nsCSSProperty aProperty, + nsTArray<ComputedKeyframeValues>& aComputedValues) { if (aKeyframes.IsEmpty()) { return; } - // If the first or last keyframes have an unspecified offset, - // fill them in with 0% and 100%. If there is only a single keyframe, - // then it gets 100%. - Keyframe& lastElement = aKeyframes.LastElement(); - lastElement.mComputedOffset = lastElement.mOffset.valueOr(1.0); + nsTArray<double> cumulativeDistances; + if (aSpacingMode == SpacingMode::paced) { + MOZ_ASSERT(IsAnimatableProperty(aProperty), + "Paced property should be animatable"); + + cumulativeDistances = GetCumulativeDistances(aComputedValues, aProperty); + // Reset the computed offsets if using paced spacing. + for (Keyframe& keyframe : aKeyframes) { + keyframe.mComputedOffset = Keyframe::kComputedOffsetNotSet; + } + } + + // If the first keyframe has an unspecified offset, fill it in with 0%. + // If there is only a single keyframe, then it gets 100%. if (aKeyframes.Length() > 1) { Keyframe& firstElement = aKeyframes[0]; firstElement.mComputedOffset = firstElement.mOffset.valueOr(0.0); + // We will fill in the last keyframe's offset below + } else { + Keyframe& lastElement = aKeyframes.LastElement(); + lastElement.mComputedOffset = lastElement.mOffset.valueOr(1.0); } // Fill in remaining missing offsets. - size_t i = 0; - while (i < aKeyframes.Length() - 1) { - double start = aKeyframes[i].mComputedOffset; - size_t j = i + 1; - while (aKeyframes[j].mOffset.isNothing() && j < aKeyframes.Length() - 1) { - ++j; + const Keyframe* const last = aKeyframes.cend() - 1; + const RangedPtr<Keyframe> begin(aKeyframes.begin(), aKeyframes.Length()); + RangedPtr<Keyframe> keyframeA = begin; + while (keyframeA != last) { + // Find keyframe A and keyframe B *between* which we will apply spacing. + RangedPtr<Keyframe> keyframeB = keyframeA + 1; + while (keyframeB->mOffset.isNothing() && keyframeB != last) { + ++keyframeB; } - double end = aKeyframes[j].mOffset.valueOr(1.0); - size_t n = j - i; - for (size_t k = 1; k < n; ++k) { - double offset = start + double(k) / n * (end - start); - aKeyframes[i + k].mComputedOffset = offset; + keyframeB->mComputedOffset = keyframeB->mOffset.valueOr(1.0); + + // Fill computed offsets in (keyframe A, keyframe B). + if (aSpacingMode == SpacingMode::distribute) { + // Bug 1276573: Use the new constructor accepting two RangedPtr<T> + // arguments, so we can make the code simpler. + DistributeRange(Range<Keyframe>(keyframeA.get(), + keyframeB - keyframeA + 1)); + } else { + // a) Find Paced A (first paceable keyframe) and + // Paced B (last paceable keyframe) in [keyframe A, keyframe B]. + RangedPtr<Keyframe> pacedA = keyframeA; + while (pacedA < keyframeB && + cumulativeDistances[pacedA - begin] == kNotPaceable) { + ++pacedA; + } + RangedPtr<Keyframe> pacedB = keyframeB; + while (pacedB > keyframeA && + cumulativeDistances[pacedB - begin] == kNotPaceable) { + --pacedB; + } + // As spec says, if there is no paceable keyframe + // in [keyframe A, keyframe B], we let Paced A and Paced B refer to + // keyframe B. + if (pacedA > pacedB) { + pacedA = pacedB = keyframeB; + } + // b) Apply distributing offsets in (keyframe A, Paced A] and + // [Paced B, keyframe B). + DistributeRange(Range<Keyframe>(keyframeA.get(), + keyframeB - keyframeA + 1), + Range<Keyframe>((keyframeA + 1).get(), + pacedA - keyframeA)); + DistributeRange(Range<Keyframe>(keyframeA.get(), + keyframeB - keyframeA + 1), + Range<Keyframe>(pacedB.get(), + keyframeB - pacedB)); + // c) Apply paced offsets to each paceable keyframe in (Paced A, Paced B). + // We pass the range [Paced A, Paced B] since PaceRange needs the end + // points of the range in order to calculate the correct offset. + PaceRange(Range<Keyframe>(pacedA.get(), pacedB - pacedA + 1), + Range<double>(&cumulativeDistances[pacedA - begin], + pacedB - pacedA + 1)); + // d) Fill in any computed offsets in (Paced A, Paced B) that are still + // not set (e.g. because the keyframe was not paceable, or because the + // cumulative distance between paceable properties was zero). + for (RangedPtr<Keyframe> frame = pacedA + 1; frame < pacedB; ++frame) { + if (frame->mComputedOffset != Keyframe::kComputedOffsetNotSet) { + continue; + } + + RangedPtr<Keyframe> start = frame - 1; + RangedPtr<Keyframe> end = frame + 1; + while (end < pacedB && + end->mComputedOffset == Keyframe::kComputedOffsetNotSet) { + ++end; + } + DistributeRange(Range<Keyframe>(start.get(), end - start + 1)); + frame = end; + } } - i = j; - aKeyframes[j].mComputedOffset = end; + keyframeA = keyframeB; } } -/* static */ nsTArray<AnimationProperty> -KeyframeUtils::GetAnimationPropertiesFromKeyframes( - nsStyleContext* aStyleContext, - dom::Element* aElement, - CSSPseudoElementType aPseudoType, - const nsTArray<Keyframe>& aFrames) +/* static */ void +KeyframeUtils::ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes) +{ + nsTArray<ComputedKeyframeValues> emptyArray; + ApplySpacing(aKeyframes, SpacingMode::distribute, eCSSProperty_UNKNOWN, + emptyArray); +} + +/* static */ nsTArray<ComputedKeyframeValues> +KeyframeUtils::GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes, + dom::Element* aElement, + nsStyleContext* aStyleContext) { MOZ_ASSERT(aStyleContext); MOZ_ASSERT(aElement); - nsTArray<KeyframeValueEntry> entries; + const size_t len = aKeyframes.Length(); + nsTArray<ComputedKeyframeValues> result(len); - for (const Keyframe& frame : aFrames) { + for (const Keyframe& frame : aKeyframes) { nsCSSPropertySet propertiesOnThisKeyframe; + ComputedKeyframeValues* computedValues = result.AppendElement(); for (const PropertyValuePair& pair : PropertyPriorityIterator(frame.mPropertyValues)) { if (IsInvalidValuePair(pair)) { continue; } // Expand each value into the set of longhands and produce // a KeyframeValueEntry for each value. nsTArray<PropertyStyleAnimationValuePair> values; // For shorthands, we store the string as a token stream so we need to // extract that first. if (nsCSSProps::IsShorthand(pair.mProperty)) { nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue(); if (!StyleAnimationValue::ComputeValues(pair.mProperty, CSSEnabledState::eForAllContent, aElement, aStyleContext, - tokenStream->mTokenStream, /* aUseSVGMode */ false, values)) { + tokenStream->mTokenStream, /* aUseSVGMode */ false, values) || + IsComputeValuesFailureKey(pair)) { continue; } } else { if (!StyleAnimationValue::ComputeValues(pair.mProperty, CSSEnabledState::eForAllContent, aElement, aStyleContext, pair.mValue, /* aUseSVGMode */ false, values)) { continue; } @@ -533,34 +636,76 @@ KeyframeUtils::GetAnimationPropertiesFro } for (auto& value : values) { // If we already got a value for this property on the keyframe, // skip this one. if (propertiesOnThisKeyframe.HasProperty(value.mProperty)) { continue; } - - KeyframeValueEntry* entry = entries.AppendElement(); - entry->mOffset = frame.mComputedOffset; - entry->mProperty = value.mProperty; - entry->mValue = value.mValue; - entry->mTimingFunction = frame.mTimingFunction; - + computedValues->AppendElement(value); propertiesOnThisKeyframe.AddProperty(value.mProperty); } } } - nsTArray<AnimationProperty> result; - BuildSegmentsFromValueEntries(aStyleContext, entries, result); - + MOZ_ASSERT(result.Length() == aKeyframes.Length(), "Array length mismatch"); return result; } +/* static */ nsTArray<AnimationProperty> +KeyframeUtils::GetAnimationPropertiesFromKeyframes( + const nsTArray<Keyframe>& aKeyframes, + const nsTArray<ComputedKeyframeValues>& aComputedValues, + nsStyleContext* aStyleContext) +{ + MOZ_ASSERT(aKeyframes.Length() == aComputedValues.Length(), + "Array length mismatch"); + + nsTArray<KeyframeValueEntry> entries(aKeyframes.Length()); + + const size_t len = aKeyframes.Length(); + for (size_t i = 0; i < len; ++i) { + const Keyframe& frame = aKeyframes[i]; + for (auto& value : aComputedValues[i]) { + MOZ_ASSERT(frame.mComputedOffset != Keyframe::kComputedOffsetNotSet, + "Invalid computed offset"); + KeyframeValueEntry* entry = entries.AppendElement(); + entry->mOffset = frame.mComputedOffset; + entry->mProperty = value.mProperty; + entry->mValue = value.mValue; + entry->mTimingFunction = frame.mTimingFunction; + } + } + + nsTArray<AnimationProperty> result; + BuildSegmentsFromValueEntries(aStyleContext, entries, result); + return result; +} + +/* static */ bool +KeyframeUtils::IsAnimatableProperty(nsCSSProperty aProperty) +{ + if (aProperty == eCSSProperty_UNKNOWN) { + return false; + } + + if (!nsCSSProps::IsShorthand(aProperty)) { + return nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_None; + } + + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, aProperty, + CSSEnabledState::eForAllContent) { + if (nsCSSProps::kAnimTypeTable[*subprop] != eStyleAnimType_None) { + return true; + } + } + + return false; +} // ------------------------------------------------------------------ // // Internal helpers // // ------------------------------------------------------------------ /** @@ -672,16 +817,27 @@ ConvertKeyframeSequence(JSContext* aCx, return false; } } for (PropertyValuesPair& pair : propertyValuePairs) { MOZ_ASSERT(pair.mValues.Length() == 1); keyframe->mPropertyValues.AppendElement( MakePropertyValuePair(pair.mProperty, pair.mValues[0], parser, doc)); + + // When we go to convert keyframes into arrays of property values we + // call StyleAnimation::ComputeValues. This should normally return true + // but in order to test the case where it does not, BaseKeyframeDict + // includes a chrome-only member that can be set to indicate that + // ComputeValues should fail for shorthand property values on that + // keyframe. + if (nsCSSProps::IsShorthand(pair.mProperty) && + keyframeDict.mSimulateComputeValuesFailure) { + MarkAsComputeValuesFailureKey(keyframe->mPropertyValues.LastElement()); + } } } return true; } /** * Reads the property-values pairs from the specified JS object. @@ -716,17 +872,17 @@ GetPropertyValuesPairs(JSContext* aCx, for (size_t i = 0, n = ids.length(); i < n; i++) { nsAutoJSString propName; if (!propName.init(aCx, ids[i])) { return false; } nsCSSProperty property = nsCSSProps::LookupPropertyByIDLName(propName, CSSEnabledState::eForAllContent); - if (IsAnimatableProperty(property)) { + if (KeyframeUtils::IsAnimatableProperty(property)) { AdditionalProperty* p = properties.AppendElement(); p->mProperty = property; p->mJsidIndex = i; } } // Sort the entries by IDL name and then get each value and // convert it either to a DOMString or to a @@ -746,41 +902,16 @@ GetPropertyValuesPairs(JSContext* aCx, return false; } } return true; } /** - * Returns true if |aProperty| or, for shorthands, one or more of - * |aProperty|'s subproperties, is animatable. - */ -static bool -IsAnimatableProperty(nsCSSProperty aProperty) -{ - if (aProperty == eCSSProperty_UNKNOWN) { - return false; - } - - if (!nsCSSProps::IsShorthand(aProperty)) { - return nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_None; - } - - CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, aProperty, - CSSEnabledState::eForAllContent) { - if (nsCSSProps::kAnimTypeTable[*subprop] != eStyleAnimType_None) { - return true; - } - } - - return false; -} - -/** * Converts aValue to DOMString, if aAllowLists is eDisallow, or * to (DOMString or sequence<DOMString>) if aAllowLists is aAllow. * The resulting strings are appended to aValues. */ static bool AppendStringOrStringSequenceToArray(JSContext* aCx, JS::Handle<JS::Value> aValue, ListAllowance aAllowLists, @@ -904,16 +1035,61 @@ HasValidOffsets(const nsTArray<Keyframe> return false; } offset = thisOffset; } } return true; } +/** + * Takes a property-value pair for a shorthand property and modifies the + * value to indicate that when we call StyleAnimationValue::ComputeValues on + * that value we should behave as if that function had failed. + * + * @param aPair The PropertyValuePair to modify. |aPair.mProperty| must be + * a shorthand property. + */ +static void +MarkAsComputeValuesFailureKey(PropertyValuePair& aPair) +{ + MOZ_ASSERT(nsCSSProps::IsShorthand(aPair.mProperty), + "Only shorthand property values can be marked as failure values"); + + // We store shorthand values as nsCSSValueTokenStream objects whose mProperty + // and mShorthandPropertyID are eCSSProperty_UNKNOWN and whose mTokenStream + // member contains the shorthand property's value as a string. + // + // We need to leave mShorthandPropertyID as eCSSProperty_UNKNOWN so that + // nsCSSValue::AppendToString returns the mTokenStream value, but we can + // update mPropertyID to a special value to indicate that this is + // a special failure sentinel. + nsCSSValueTokenStream* tokenStream = aPair.mValue.GetTokenStreamValue(); + MOZ_ASSERT(tokenStream->mPropertyID == eCSSProperty_UNKNOWN, + "Shorthand value should initially have an unknown property ID"); + tokenStream->mPropertyID = eCSSPropertyExtra_no_properties; +} + +/** + * Returns true if |aPair| is a property-value pair on which we have + * previously called MarkAsComputeValuesFailureKey (and hence we should + * simulate failure when calling StyleAnimationValue::ComputeValues using its + * value). + * + * @param aPair The property-value pair to test. + * @return True if |aPair| represents a failure value. + */ +static bool +IsComputeValuesFailureKey(const PropertyValuePair& aPair) +{ + return nsCSSProps::IsShorthand(aPair.mProperty) && + aPair.mValue.GetTokenStreamValue()->mPropertyID == + eCSSPropertyExtra_no_properties; +} + static already_AddRefed<nsStyleContext> CreateStyleContextForAnimationValue(nsCSSProperty aProperty, StyleAnimationValue aValue, nsStyleContext* aBaseStyleContext) { MOZ_ASSERT(aBaseStyleContext, "CreateStyleContextForAnimationValue needs to be called " "with a valid nsStyleContext"); @@ -1228,17 +1404,17 @@ RequiresAdditiveAnimation(const nsTArray } else if (aOffset == 1.0) { propertiesWithToValue.AddProperty(aProperty); } }; for (size_t i = 0, len = aKeyframes.Length(); i < len; i++) { const Keyframe& frame = aKeyframes[i]; - // We won't have called ApplyDistributeSpacing when this is called so + // We won't have called ApplySpacing when this is called so // we can't use frame.mComputedOffset. Instead we do a rough version // of that algorithm that substitutes null offsets with 0.0 for the first // frame, 1.0 for the last frame, and 0.5 for everything else. double computedOffset = i == len - 1 ? 1.0 : i == 0 ? 0.0 : 0.5; double offsetToUse = frame.mOffset ? frame.mOffset.value() @@ -1265,9 +1441,209 @@ RequiresAdditiveAnimation(const nsTArray } } } return !propertiesWithFromValue.Equals(properties) || !propertiesWithToValue.Equals(properties); } +/** + * Evenly distribute the computed offsets in (A, B). + * We pass the range keyframes in [A, B] and use A, B to calculate distributing + * computed offsets in (A, B). The second range, aRangeToAdjust, is passed, so + * we can know which keyframe we want to apply to. aRangeToAdjust should be in + * the range of aSpacingRange. + * + * @param aSpacingRange The sequence of keyframes between whose endpoints we + * should apply distribute spacing. + * @param aRangeToAdjust The range of keyframes we want to apply to. + */ +static void +DistributeRange(const Range<Keyframe>& aSpacingRange, + const Range<Keyframe>& aRangeToAdjust) +{ + MOZ_ASSERT(aRangeToAdjust.start() >= aSpacingRange.start() && + aRangeToAdjust.end() <= aSpacingRange.end(), + "Out of range"); + const size_t n = aSpacingRange.length() - 1; + const double startOffset = aSpacingRange[0].mComputedOffset; + const double diffOffset = aSpacingRange[n].mComputedOffset - startOffset; + for (auto iter = aRangeToAdjust.start(); + iter != aRangeToAdjust.end(); + ++iter) { + size_t index = iter - aSpacingRange.start(); + iter->mComputedOffset = startOffset + double(index) / n * diffOffset; + } +} + +/** + * Overload of DistributeRange to apply distribute spacing to all keyframes in + * between the endpoints of the given range. + * + * @param aSpacingRange The sequence of keyframes between whose endpoints we + * should apply distribute spacing. + */ +static void +DistributeRange(const Range<Keyframe>& aSpacingRange) +{ + // We don't need to apply distribute spacing to keyframe A and keyframe B. + DistributeRange(aSpacingRange, + Range<Keyframe>((aSpacingRange.start() + 1).get(), + aSpacingRange.end() - aSpacingRange.start() + - 2)); +} + +/** + * Apply paced spacing to all paceable keyframes in between the endpoints of the + * given range. + * + * @param aKeyframes The range of keyframes between whose endpoints we should + * apply paced spacing. Both endpoints should be paceable, i.e. the + * corresponding elements in |aCumulativeDist| should not be kNotPaceable. + * Within this function, we refer to the start and end points of this range + * as Paced A and Paced B respectively in keeping with the notation used in + * the spec. + * @param aCumulativeDistances The sequence of cumulative distances of the paced + * property as returned by GetCumulativeDistances(). This acts as a + * parallel range to |aKeyframes|. + */ +static void +PaceRange(const Range<Keyframe>& aKeyframes, + const Range<double>& aCumulativeDistances) +{ + MOZ_ASSERT(aKeyframes.length() == aCumulativeDistances.length(), + "Range length mismatch"); + + const size_t len = aKeyframes.length(); + // If there is nothing between the end points, there is nothing to space. + if (len < 3) { + return; + } + + const double distA = *(aCumulativeDistances.start()); + const double distB = *(aCumulativeDistances.end() - 1); + MOZ_ASSERT(distA != kNotPaceable && distB != kNotPaceable, + "Both Paced A and Paced B should be paceable"); + + // If the total distance is zero, we should fall back to distribute spacing. + // The caller will fill-in any keyframes without a computed offset using + // distribute spacing so we can just return here. + if (distA == distB) { + return; + } + + const RangedPtr<Keyframe> pacedA = aKeyframes.start(); + const RangedPtr<Keyframe> pacedB = aKeyframes.end() - 1; + MOZ_ASSERT(pacedA->mComputedOffset != Keyframe::kComputedOffsetNotSet && + pacedB->mComputedOffset != Keyframe::kComputedOffsetNotSet, + "Both Paced A and Paced B should have valid computed offsets"); + + // Apply computed offset. + const double offsetA = pacedA->mComputedOffset; + const double diffOffset = pacedB->mComputedOffset - offsetA; + const double initialDist = distA; + const double totalDist = distB - initialDist; + for (auto iter = pacedA + 1; iter != pacedB; ++iter) { + size_t k = iter - aKeyframes.start(); + if (aCumulativeDistances[k] == kNotPaceable) { + continue; + } + + double dist = aCumulativeDistances[k] - initialDist; + iter->mComputedOffset = offsetA + diffOffset * dist / totalDist; + } +} + +/** + * Get cumulative distances for the paced property. + * + * @param aValues The computed values returned by GetComputedKeyframeValues. + * @param aPacedProperty The paced property. + * @return The cumulative distances for the paced property. The length will be + * the same as aValues. + */ +static nsTArray<double> +GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues, + nsCSSProperty aPacedProperty) +{ + // a) If aPacedProperty is a shorthand property, get its components. + // Otherwise, just add the longhand property into the set. + size_t pacedPropertyCount = 0; + nsCSSPropertySet pacedPropertySet; + bool isShorthand = nsCSSProps::IsShorthand(aPacedProperty); + if (isShorthand) { + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPacedProperty, + CSSEnabledState::eForAllContent) { + pacedPropertySet.AddProperty(*p); + ++pacedPropertyCount; + } + } else { + pacedPropertySet.AddProperty(aPacedProperty); + pacedPropertyCount = 1; + } + + // b) Search each component (shorthand) or the longhand property, and + // calculate the cumulative distances of paceable keyframe pairs. + const size_t len = aValues.Length(); + nsTArray<double> cumulativeDistances(len); + // cumulativeDistances is a parallel array to |aValues|, so set its length to + // the length of |aValues|. + cumulativeDistances.SetLength(len); + ComputedKeyframeValues prevPacedValues; + size_t preIdx = 0; + for (size_t i = 0; i < len; ++i) { + // Find computed values of the paced property. + ComputedKeyframeValues pacedValues; + for (const PropertyStyleAnimationValuePair& pair : aValues[i]) { + if (pacedPropertySet.HasProperty(pair.mProperty)) { + pacedValues.AppendElement(pair); + } + } + + // Check we have values for all the paceable longhand components. + if (pacedValues.Length() != pacedPropertyCount) { + // This keyframe is not paceable, assign kNotPaceable and skip it. + cumulativeDistances[i] = kNotPaceable; + continue; + } + + if (prevPacedValues.IsEmpty()) { + // This is the first paceable keyframe so its cumulative distance is 0.0. + cumulativeDistances[i] = 0.0; + } else { + double dist = 0.0; + if (isShorthand) { + // Apply the distance by the square root of the sum of squares of + // longhand component distances. + for (size_t propIdx = 0; propIdx < pacedPropertyCount; ++propIdx) { + nsCSSProperty prop = prevPacedValues[propIdx].mProperty; + MOZ_ASSERT(pacedValues[propIdx].mProperty == prop, + "Property mismatch"); + + double componentDistance = 0.0; + if (StyleAnimationValue::ComputeDistance( + prop, + prevPacedValues[propIdx].mValue, + pacedValues[propIdx].mValue, + componentDistance)) { + dist += componentDistance * componentDistance; + } + } + dist = sqrt(dist); + } else { + // If the property is longhand, we just use the 1st value. + // If ComputeDistance() fails, |dist| will remain zero so there will be + // no distance between the previous paced value and this value. + StyleAnimationValue::ComputeDistance(aPacedProperty, + prevPacedValues[0].mValue, + pacedValues[0].mValue, + dist); + } + cumulativeDistances[i] = cumulativeDistances[preIdx] + dist; + } + prevPacedValues.SwapElements(pacedValues); + preIdx = i; + } + return cumulativeDistances; +} + } // namespace mozilla
--- a/dom/animation/KeyframeUtils.h +++ b/dom/animation/KeyframeUtils.h @@ -13,25 +13,30 @@ struct JSContext; class JSObject; namespace mozilla { struct AnimationProperty; enum class CSSPseudoElementType : uint8_t; class ErrorResult; struct Keyframe; +struct PropertyStyleAnimationValuePair; namespace dom { class Element; } // namespace dom } // namespace mozilla namespace mozilla { +// Represents the set of property-value pairs on a Keyframe converted to +// computed values. +using ComputedKeyframeValues = nsTArray<PropertyStyleAnimationValuePair>; + /** * Utility methods for processing keyframes. */ class KeyframeUtils { public: /** * Converts a JS value representing a property-indexed keyframe or a sequence @@ -47,39 +52,92 @@ public: * returned. */ static nsTArray<Keyframe> GetKeyframesFromObject(JSContext* aCx, JS::Handle<JSObject*> aFrames, ErrorResult& aRv); /** - * Fills in the mComputedOffset member of each keyframe in the given array - * using the "distribute" spacing algorithm. + * Calculate the StyleAnimationValues of properties of each keyframe. + * This involves expanding shorthand properties into longhand properties, + * removing the duplicated properties for each keyframe, and creating an + * array of |property:computed value| pairs for each keyframe. + * + * These computed values are used *both* when computing the final set of + * per-property animation values (see GetAnimationPropertiesFromKeyframes) as + * well when applying paced spacing. By returning these values here, we allow + * the result to be re-used in both operations. * - * http://w3c.github.io/web-animations/#distribute-keyframe-spacing-mode + * @param aKeyframes The input keyframes. + * @param aElement The context element. + * @param aStyleContext The style context to use when computing values. + * @return The set of ComputedKeyframeValues. The length will be the same as + * aFrames. + */ + static nsTArray<ComputedKeyframeValues> + GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes, + dom::Element* aElement, + nsStyleContext* aStyleContext); + + /** + * Fills in the mComputedOffset member of each keyframe in the given array + * using the specified spacing mode. + * + * https://w3c.github.io/web-animations/#spacing-keyframes * - * @param keyframes The set of keyframes to adjust. + * @param aKeyframes The set of keyframes to adjust. + * @param aSpacingMode The spacing mode to apply. + * @param aProperty The paced property. Only used when |aSpacingMode| is + * SpacingMode::paced. In all other cases it is ignored and hence may be + * any value, e.g. eCSSProperty_UNKNOWN. + * @param aComputedValues The set of computed keyframe values as returned by + * GetComputedKeyframeValues. Only used when |aSpacingMode| is + * SpacingMode::paced. In all other cases this parameter is unused and may + * be any value including an empty array. + */ + static void ApplySpacing(nsTArray<Keyframe>& aKeyframes, + SpacingMode aSpacingMode, + nsCSSProperty aProperty, + nsTArray<ComputedKeyframeValues>& aComputedValues); + + /** + * Wrapper for ApplySpacing to simplify using distribute spacing. + * + * @param aKeyframes The set of keyframes to adjust. */ static void ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes); /** * Converts an array of Keyframe objects into an array of AnimationProperty - * objects. This involves expanding shorthand properties into longhand - * properties, creating an array of computed values for each longhand - * property and determining the offset and timing function to use for each - * value. + * objects. This involves creating an array of computed values for each + * longhand property and determining the offset and timing function to use + * for each value. * - * @param aStyleContext The style context to use when computing values. - * @param aFrames The input keyframes. + * @param aKeyframes The input keyframes. + * @param aComputedValues The computed keyframe values (as returned by + * GetComputedKeyframeValues) used to fill in the individual + * AnimationPropertySegment objects. Although these values could be + * calculated from |aKeyframes|, passing them in as a separate parameter + * allows the result of GetComputedKeyframeValues to be re-used both + * here and in ApplySpacing. + * @param aStyleContext The style context to calculate the style difference. * @return The set of animation properties. If an error occurs, the returned * array will be empty. */ - static nsTArray<AnimationProperty> - GetAnimationPropertiesFromKeyframes(nsStyleContext* aStyleContext, - dom::Element* aElement, - CSSPseudoElementType aPseudoType, - const nsTArray<Keyframe>& aFrames); + static nsTArray<AnimationProperty> GetAnimationPropertiesFromKeyframes( + const nsTArray<Keyframe>& aKeyframes, + const nsTArray<ComputedKeyframeValues>& aComputedValues, + nsStyleContext* aStyleContext); + + /** + * Check if the property or, for shorthands, one or more of + * its subproperties, is animatable. + * + * @param aProperty The property to check. + * @return true if |aProperty| is animatable. + */ + static bool IsAnimatableProperty(nsCSSProperty aProperty); }; } // namespace mozilla #endif // mozilla_KeyframeUtils_h
--- a/dom/animation/moz.build +++ b/dom/animation/moz.build @@ -23,16 +23,17 @@ EXPORTS.mozilla += [ 'AnimationPerformanceWarning.h', 'AnimationTarget.h', 'AnimationUtils.h', 'AnimValuesStyleRule.h', 'ComputedTiming.h', 'ComputedTimingFunction.h', 'EffectCompositor.h', 'EffectSet.h', + 'KeyframeEffectParams.h', 'KeyframeUtils.h', 'PendingAnimationTracker.h', 'PseudoElementHashEntry.h', 'TimingParams.h', ] UNIFIED_SOURCES += [ 'Animation.cpp', @@ -44,16 +45,17 @@ UNIFIED_SOURCES += [ 'AnimationUtils.cpp', 'AnimValuesStyleRule.cpp', 'ComputedTimingFunction.cpp', 'CSSPseudoElement.cpp', 'DocumentTimeline.cpp', 'EffectCompositor.cpp', 'EffectSet.cpp', 'KeyframeEffect.cpp', + 'KeyframeEffectParams.cpp', 'KeyframeUtils.cpp', 'PendingAnimationTracker.cpp', 'TimingParams.cpp', ] LOCAL_INCLUDES += [ '/dom/base', '/layout/base',
--- a/dom/animation/test/chrome/test_animation_properties.html +++ b/dom/animation/test/chrome/test_animation_properties.html @@ -500,16 +500,17 @@ var gTests = [ value(1, 'calc(100px + 10%)', 'replace') ] } ] }, // --------------------------------------------------------------------- // // Tests for CSS variable handling conversion // // --------------------------------------------------------------------- + { desc: 'CSS variables are resolved to their corresponding values', frames: { left: ['10px', 'var(--var-100px)'] }, expected: [ { property: 'left', values: [ value(0, '10px', 'replace', 'linear'), value(1, '100px', 'replace') ] } ] }, { desc: 'CSS variables in calc() expressions are resolved', frames: { left: ['10px', 'calc(var(--var-100px) / 2 - 10%)'] }, @@ -528,16 +529,300 @@ var gTests = [ value(1, '200px', 'replace') ] }, { property: 'margin-bottom', values: [ value(0, '10px', 'replace', 'linear'), value(1, '100px', 'replace') ] }, { property: 'margin-left', values: [ value(0, '10px', 'replace', 'linear'), value(1, '200px', 'replace') ] } ] }, + + // --------------------------------------------------------------------- + // + // Tests for properties that parse correctly but which we fail to + // convert to computed values. + // + // --------------------------------------------------------------------- + + { desc: 'a property that can\'t be resolved to computed values in' + + ' initial keyframe', + frames: [ { margin: '5px', simulateComputeValuesFailure: true }, + { margin: '5px' } ], + expected: [ ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' initial keyframe where we have enough values to create' + + ' a final segment', + frames: [ { margin: '5px', simulateComputeValuesFailure: true }, + { margin: '5px' }, + { margin: '5px' } ], + expected: [ ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' initial overlapping keyframes (first in series of two)', + frames: [ { margin: '5px', offset: 0, + simulateComputeValuesFailure: true }, + { margin: '5px', offset: 0 }, + { margin: '5px' } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' initial overlapping keyframes (second in series of two)', + frames: [ { margin: '5px', offset: 0 }, + { margin: '5px', offset: 0, + simulateComputeValuesFailure: true }, + { margin: '5px' } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' initial overlapping keyframes (second in series of three)', + frames: [ { margin: '5px', offset: 0 }, + { margin: '5px', offset: 0, + simulateComputeValuesFailure: true }, + { margin: '5px', offset: 0 }, + { margin: '5px' } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace'), + value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace'), + value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace'), + value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace'), + value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' final keyframe', + frames: [ { margin: '5px' }, + { margin: '5px', simulateComputeValuesFailure: true } ], + expected: [ ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' final keyframe where it forms the last segment in the series', + frames: [ { margin: '5px' }, + { margin: '5px', + marginLeft: '5px', + marginRight: '5px', + marginBottom: '5px', + // margin-top sorts last and only it will be missing since + // the other longhand components are specified + simulateComputeValuesFailure: true } ], + expected: [ { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' final keyframe where we have enough values to create' + + ' an initial segment', + frames: [ { margin: '5px' }, + { margin: '5px' }, + { margin: '5px', simulateComputeValuesFailure: true } ], + expected: [ ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' final overlapping keyframes (first in series of two)', + frames: [ { margin: '5px' }, + { margin: '5px', offset: 1, + simulateComputeValuesFailure: true }, + { margin: '5px', offset: 1 } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' final overlapping keyframes (second in series of two)', + frames: [ { margin: '5px' }, + { margin: '5px', offset: 1 }, + { margin: '5px', offset: 1, + simulateComputeValuesFailure: true } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' final overlapping keyframes (second in series of three)', + frames: [ { margin: '5px' }, + { margin: '5px', offset: 1 }, + { margin: '5px', offset: 1, + simulateComputeValuesFailure: true }, + { margin: '5px', offset: 1 } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace'), + value(1, '5px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' intermediate keyframe', + frames: [ { margin: '5px' }, + { margin: '5px', simulateComputeValuesFailure: true }, + { margin: '5px' } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' initial keyframe along with other values', + // simulateComputeValuesFailure only applies to shorthands so we can set + // it on the same keyframe and it will only apply to |margin| and not + // |left|. + frames: [ { margin: '77%', left: '10px', + simulateComputeValuesFailure: true }, + { margin: '5px', left: '20px' } ], + expected: [ { property: 'left', + values: [ value(0, '10px', 'replace', 'linear'), + value(1, '20px', 'replace') ] } ], + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' initial keyframe along with other values where those' + + ' values sort after the property with missing values', + frames: [ { margin: '77%', right: '10px', + simulateComputeValuesFailure: true }, + { margin: '5px', right: '20px' } ], + expected: [ { property: 'right', + values: [ value(0, '10px', 'replace', 'linear'), + value(1, '20px', 'replace') ] } ], + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' final keyframe along with other values', + frames: [ { margin: '5px', left: '10px' }, + { margin: '5px', left: '20px', + simulateComputeValuesFailure: true } ], + expected: [ { property: 'left', + values: [ value(0, '10px', 'replace', 'linear'), + value(1, '20px', 'replace') ] } ], + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' final keyframe along with other values where those' + + ' values sort after the property with missing values', + frames: [ { margin: '5px', right: '10px' }, + { margin: '5px', right: '20px', + simulateComputeValuesFailure: true } ], + expected: [ { property: 'right', + values: [ value(0, '10px', 'replace', 'linear'), + value(1, '20px', 'replace') ] } ], + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' an intermediate keyframe along with other values', + frames: [ { margin: '5px', left: '10px' }, + { margin: '5px', left: '20px', + simulateComputeValuesFailure: true }, + { margin: '5px', left: '30px' } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'left', + values: [ value(0, '10px', 'replace', 'linear'), + value(0.5, '20px', 'replace', 'linear'), + value(1, '30px', 'replace') ] } ] + }, + { desc: 'a property that can\'t be resolved to computed values in' + + ' an intermediate keyframe by itself', + frames: [ { margin: '5px', left: '10px' }, + { margin: '5px', + simulateComputeValuesFailure: true }, + { margin: '5px', left: '30px' } ], + expected: [ { property: 'margin-top', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-right', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-bottom', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'margin-left', + values: [ value(0, '5px', 'replace', 'linear'), + value(1, '5px', 'replace') ] }, + { property: 'left', + values: [ value(0, '10px', 'replace', 'linear'), + value(1, '30px', 'replace') ] } ] + }, ]; gTests.forEach(function(subtest) { test(function(t) { var div = addDiv(t); var animation = div.animate(subtest.frames, 100 * MS_PER_SEC); assert_properties_equal(animation.effect.getProperties(), subtest.expected);
--- a/dom/base/BlobSet.h +++ b/dom/base/BlobSet.h @@ -7,16 +7,17 @@ #ifndef mozilla_dom_BlobSet_h #define mozilla_dom_BlobSet_h #include "mozilla/RefPtr.h" namespace mozilla { namespace dom { +class Blob; class BlobImpl; class BlobSet final { public: BlobSet() : mData(nullptr) , mDataLen(0)
--- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -27,16 +27,19 @@ #include "nsIScriptSecurityManager.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsUnicharUtils.h" #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "BatteryManager.h" #include "mozilla/dom/DeviceStorageAreaListener.h" +#ifdef MOZ_GAMEPAD +#include "mozilla/dom/GamepadServiceTest.h" +#endif #include "mozilla/dom/PowerManager.h" #include "mozilla/dom/WakeLock.h" #include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/CellBroadcast.h" #include "mozilla/dom/FlyWebPublishedServer.h" #include "mozilla/dom/FlyWebService.h" #include "mozilla/dom/IccManager.h" #include "mozilla/dom/InputPortManager.h" @@ -260,16 +263,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN( NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) #ifdef MOZ_EME NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager) #endif NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeviceStorageAreaListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation) +#ifdef MOZ_GAMEPAD + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest) +#endif NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRGetDevicesPromises) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Navigator) void Navigator::Invalidate() @@ -402,16 +408,23 @@ Navigator::Invalidate() mMediaKeySystemAccessManager = nullptr; } #endif if (mDeviceStorageAreaListener) { mDeviceStorageAreaListener = nullptr; } +#ifdef MOZ_GAMEPAD + if (mGamepadServiceTest) { + mGamepadServiceTest->Shutdown(); + mGamepadServiceTest = nullptr; + } +#endif + mVRGetDevicesPromises.Clear(); } //***************************************************************************** // Navigator::nsIDOMNavigator //***************************************************************************** NS_IMETHODIMP @@ -2015,16 +2028,25 @@ Navigator::GetGamepads(nsTArray<RefPtr<G aRv.Throw(NS_ERROR_UNEXPECTED); return; } NS_ENSURE_TRUE_VOID(mWindow->GetDocShell()); nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow); win->SetHasGamepadEventListener(true); win->GetGamepads(aGamepads); } + +GamepadServiceTest* +Navigator::RequestGamepadServiceTest() +{ + if (!mGamepadServiceTest) { + mGamepadServiceTest = GamepadServiceTest::CreateTestService(mWindow); + } + return mGamepadServiceTest; +} #endif already_AddRefed<Promise> Navigator::GetVRDevices(ErrorResult& aRv) { if (!mWindow || !mWindow->GetDocShell()) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr;
--- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -66,16 +66,17 @@ class FMRadio; class Promise; class DesktopNotificationCenter; class MobileMessageManager; class MozIdleObserver; #ifdef MOZ_GAMEPAD class Gamepad; +class GamepadServiceTest; #endif // MOZ_GAMEPAD class NavigatorUserMediaSuccessCallback; class NavigatorUserMediaErrorCallback; class MozGetUserMediaDevicesSuccessCallback; namespace network { class Connection; } // namespace network @@ -260,16 +261,17 @@ public: already_AddRefed<Promise> GetMobileIdAssertion(const MobileIdOptions& options, ErrorResult& aRv); #endif #ifdef MOZ_B2G_RIL MobileConnectionArray* GetMozMobileConnections(ErrorResult& aRv); #endif // MOZ_B2G_RIL #ifdef MOZ_GAMEPAD void GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads, ErrorResult& aRv); + GamepadServiceTest* RequestGamepadServiceTest(); #endif // MOZ_GAMEPAD already_AddRefed<Promise> GetVRDevices(ErrorResult& aRv); void NotifyVRDevicesUpdated(); #ifdef MOZ_B2G_FM FMRadio* GetMozFMRadio(ErrorResult& aRv); #endif #ifdef MOZ_B2G_BT bluetooth::BluetoothManager* GetMozBluetooth(ErrorResult& aRv); @@ -393,17 +395,19 @@ private: RefPtr<MediaDevices> mMediaDevices; nsCOMPtr<nsIDOMNavigatorSystemMessages> mMessagesManager; nsTArray<nsWeakPtr> mDeviceStorageStores; RefPtr<time::TimeManager> mTimeManager; RefPtr<ServiceWorkerContainer> mServiceWorkerContainer; nsCOMPtr<nsPIDOMWindowInner> mWindow; RefPtr<DeviceStorageAreaListener> mDeviceStorageAreaListener; RefPtr<Presentation> mPresentation; - +#ifdef MOZ_GAMEPAD + RefPtr<GamepadServiceTest> mGamepadServiceTest; +#endif nsTArray<RefPtr<Promise> > mVRGetDevicesPromises; nsTArray<uint32_t> mRequestedVibrationPattern; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_Navigator_h
--- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -30,17 +30,16 @@ XPIDL_SOURCES += [ 'nsISelection.idl', 'nsISelectionController.idl', 'nsISelectionDisplay.idl', 'nsISelectionListener.idl', 'nsISelectionPrivate.idl', 'nsISimpleContentPolicy.idl', 'nsISiteSpecificUserAgent.idl', 'nsISlowScriptDebug.idl', - 'nsIXMLHttpRequest.idl', ] XPIDL_MODULE = 'dom' EXPORTS += [ 'AutocompleteFieldList.h', 'Crypto.h', 'HTMLSplitOnSpacesTokenizer.h', @@ -317,17 +316,16 @@ UNIFIED_SOURCES += [ 'nsTraversal.cpp', 'nsTreeSanitizer.cpp', 'nsViewportInfo.cpp', 'nsWindowMemoryReporter.cpp', 'nsWindowRoot.cpp', 'nsWrapperCache.cpp', 'nsXHTMLContentSerializer.cpp', 'nsXMLContentSerializer.cpp', - 'nsXMLHttpRequest.cpp', 'nsXMLNameSpaceMap.cpp', 'PostMessageEvent.cpp', 'ProcessGlobal.cpp', 'ResponsiveImageSelector.cpp', 'SameProcessMessageQueue.cpp', 'ScreenOrientation.cpp', 'ScriptSettings.cpp', 'ShadowRoot.cpp',
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -2293,17 +2293,17 @@ nsDOMWindowUtils::GetSupportsHardwareH26 NS_ENSURE_STATE(promise); aPromise.setObject(*promise->PromiseObj()); #else ErrorResult rv; RefPtr<Promise> promise = Promise::Create(parentObject, rv); if (rv.Failed()) { return rv.StealNSResult(); } - promise.MaybeResolve(NS_LITERAL_STRING("No; Compiled without MP4 support.")); + promise->MaybeResolve(NS_LITERAL_STRING("No; Compiled without MP4 support.")); aPromise.setObject(*promise->PromiseObj()); #endif return NS_OK; } NS_IMETHODIMP nsDOMWindowUtils::StartFrameTimeRecording(uint32_t *startIndex) {
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -184,17 +184,17 @@ #include "prprf.h" #include "mozilla/dom/IDBFactory.h" #include "mozilla/dom/MessageChannel.h" #include "mozilla/dom/Promise.h" #ifdef MOZ_GAMEPAD #include "mozilla/dom/Gamepad.h" -#include "mozilla/dom/GamepadService.h" +#include "mozilla/dom/GamepadManager.h" #endif #include "mozilla/dom/VRDevice.h" #include "nsRefreshDriver.h" #include "Layers.h" #include "mozilla/AddonPathService.h" @@ -9914,34 +9914,34 @@ void nsGlobalWindow::MaybeUpdateTouchSta void nsGlobalWindow::EnableGamepadUpdates() { MOZ_ASSERT(IsInnerWindow()); if (mHasGamepad) { #ifdef MOZ_GAMEPAD - RefPtr<GamepadService> gamepadsvc(GamepadService::GetService()); - if (gamepadsvc) { - gamepadsvc->AddListener(this); + RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService()); + if (gamepadManager) { + gamepadManager->AddListener(this); } #endif } } void nsGlobalWindow::DisableGamepadUpdates() { MOZ_ASSERT(IsInnerWindow()); if (mHasGamepad) { #ifdef MOZ_GAMEPAD - RefPtr<GamepadService> gamepadsvc(GamepadService::GetService()); - if (gamepadsvc) { - gamepadsvc->RemoveListener(this); + RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService()); + if (gamepadManager) { + gamepadManager->RemoveListener(this); } #endif } } void nsGlobalWindow::SetChromeEventHandler(EventTarget* aChromeEventHandler) { @@ -13398,19 +13398,19 @@ nsGlobalWindow::HasSeenGamepadInput() return mHasSeenGamepadInput; } void nsGlobalWindow::SyncGamepadState() { MOZ_ASSERT(IsInnerWindow()); if (mHasSeenGamepadInput) { - RefPtr<GamepadService> gamepadsvc(GamepadService::GetService()); + RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService()); for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) { - gamepadsvc->SyncGamepadState(iter.Key(), iter.UserData()); + gamepadManager->SyncGamepadState(iter.Key(), iter.UserData()); } } } #endif // MOZ_GAMEPAD bool nsGlobalWindow::UpdateVRDevices(nsTArray<RefPtr<mozilla::dom::VRDevice>>& aDevices) {
--- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -2259,17 +2259,20 @@ nsObjectLoadingContent::LoadObject(bool if (mType != eType_Null) { bool allowLoad = true; if (IsJavaMIME(mContentType)) { allowLoad = CheckJavaCodebase(); } int16_t contentPolicy = nsIContentPolicy::ACCEPT; // If mChannelLoaded is set we presumably already passed load policy - if (allowLoad && mURI && !mChannelLoaded) { + // If mType == eType_Loading then we call OpenChannel() which internally + // creates a new channel and calls asyncOpen2() on that channel which + // then enforces content policy checks. + if (allowLoad && mURI && !mChannelLoaded && mType != eType_Loading) { allowLoad = CheckLoadPolicy(&contentPolicy); } // If we're loading a type now, check ProcessPolicy. Note that we may check // both now in the case of plugins whose type is determined before opening a // channel. if (allowLoad && mType != eType_Loading) { allowLoad = CheckProcessPolicy(&contentPolicy); } @@ -2612,44 +2615,39 @@ nsObjectLoadingContent::CloseChannel() return NS_OK; } nsresult nsObjectLoadingContent::OpenChannel() { nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); - nsCOMPtr<nsIScriptSecurityManager> secMan = - nsContentUtils::GetSecurityManager(); NS_ASSERTION(thisContent, "must be a content"); nsIDocument* doc = thisContent->OwnerDoc(); NS_ASSERTION(doc, "No owner document?"); nsresult rv; mChannel = nullptr; // E.g. mms:// if (!mURI || !CanHandleURI(mURI)) { return NS_ERROR_NOT_AVAILABLE; } - rv = secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(), mURI, 0); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr<nsILoadGroup> group = doc->GetDocumentLoadGroup(); nsCOMPtr<nsIChannel> chan; RefPtr<ObjectInterfaceRequestorShim> shim = new ObjectInterfaceRequestorShim(this); bool isSandBoxed = doc->GetSandboxFlags() & SANDBOXED_ORIGIN; bool inherit = nsContentUtils::ChannelShouldInheritPrincipal(thisContent->NodePrincipal(), mURI, true, // aInheritForAboutBlank false); // aForceInherit - nsSecurityFlags securityFlags = nsILoadInfo::SEC_NORMAL; + nsSecurityFlags securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; if (inherit) { securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; } if (isSandBoxed) { securityFlags |= nsILoadInfo::SEC_SANDBOXED; } nsContentPolicyType contentPolicyType = GetContentPolicyType(); @@ -2681,18 +2679,18 @@ nsObjectLoadingContent::OpenChannel() } nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(chan); if (scriptChannel) { // Allow execution against our context if the principals match scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); } - // AsyncOpen can fail if a file does not exist. - rv = chan->AsyncOpen(shim, nullptr); + // AsyncOpen2 can fail if a file does not exist. + rv = chan->AsyncOpen2(shim); NS_ENSURE_SUCCESS(rv, rv); LOG(("OBJLC [%p]: Channel opened", this)); mChannel = chan; return NS_OK; } uint32_t nsObjectLoadingContent::GetCapabilities() const
--- a/dom/base/test/fileutils.js +++ b/dom/base/test/fileutils.js @@ -44,17 +44,17 @@ function testFile(file, contents, test) var fd = new FormData; fd.append("hello", "world"); fd.append("myfile", file); xhr.send(fd); expectedTestCount++; // Send file to server using plain XMLHttpRequest var xhr = new XMLHttpRequest; - xhr.open("POST", "file_XHRSendData.sjs"); + xhr.open("POST", "../../../dom/xhr/tests/file_XHRSendData.sjs"); xhr.onload = function (event) { is(event.target.getResponseHeader("Result-Content-Type"), file.type ? file.type : null, "request content-type in XMLHttpRequest send of " + test); is(event.target.getResponseHeader("Result-Content-Length"), String(file.size), "request content-length in XMLHttpRequest send of " + test); };
--- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -53,52 +53,22 @@ support-files = bug696301-script-2.js bug704320.sjs bug704320_counter.sjs bug819051.sjs chrome/bug418986-1.js copypaste.js create_file_objects.js delayedServerEvents.sjs - echo.sjs eventsource.resource eventsource.resource^headers^ eventsource_redirect.resource eventsource_redirect.resource^headers^ eventsource_redirect_to.resource eventsource_redirect_to.resource^headers^ - file_XHRDocURI.text - file_XHRDocURI.text^headers^ - file_XHRDocURI.xml - file_XHRDocURI.xml^headers^ - file_XHRDocURI.html - file_XHRDocURI.html^headers^ - file_XHRDocURI.sjs - file_XHRResponseURL.js - file_XHRResponseURL.sjs - file_XHRResponseURL.text - file_XHRResponseURL.text^headers^ - file_XHRResponseURL_nocors.text - file_XHRSendData.sjs - file_XHRSendData_doc.xml - file_XHRSendData_doc.xml^headers^ - file_XHR_anon.sjs - file_XHR_binary1.bin - file_XHR_binary1.bin^headers^ - file_XHR_binary2.bin - file_XHR_fail1.txt - file_XHR_fail1.txt^headers^ - file_XHR_header.sjs - file_XHR_pass1.xml - file_XHR_pass2.txt - file_XHR_pass3.txt - file_XHR_pass3.txt^headers^ - file_XHR_system_redirect.html - file_XHR_system_redirect.html^headers^ - file_XHR_timeout.sjs file_base_xbl.xml file_bug1091883_frame.html file_bug1091883_subframe.html file_bug1091883_target.html file_bug28293.sjs file_bug326337.xml file_bug326337_inner.html file_bug326337_outer.html @@ -157,20 +127,16 @@ support-files = file_bug902350.html file_bug902350_frame.html file_bug907892.html file_bug945152.jar file_bug1263696_frame_pass.html file_bug1263696_frame_fail.html file_bug1274806.html file_general_document.html - file_html_in_xhr.html - file_html_in_xhr.sjs - file_html_in_xhr2.html - file_html_in_xhr3.html file_htmlserializer_1.html file_htmlserializer_1_bodyonly.html file_htmlserializer_1_format.html file_htmlserializer_1_linebreak.html file_htmlserializer_1_links.html file_htmlserializer_1_nested_body.html file_htmlserializer_1_no_body.html file_htmlserializer_1_noflag.html @@ -227,18 +193,16 @@ support-files = fileutils.js forRemoval.resource forRemoval.resource^headers^ formReset.html invalid_accesscontrol.resource invalid_accesscontrol.resource^headers^ mutationobserver_dialog.html orientationcommon.js - progressserver.sjs - responseIdentical.sjs script-1_bug597345.sjs script-2_bug597345.js script_bug602838.sjs send_gzip_content.sjs somedatas.resource somedatas.resource^headers^ variable_style_sheet.sjs viewport_helpers.js @@ -264,27 +228,26 @@ support-files = file_bug1198095.js file_bug1250148.sjs mozbrowser_api_utils.js websocket_helpers.js websocket_tests.js !/dom/html/test/form_submit_server.sjs !/dom/security/test/cors/file_CrossSiteXHR_server.sjs !/image/test/mochitest/blue.png + !/dom/xhr/tests/file_XHRSendData.sjs script_bug1238440.js [test_anchor_area_referrer.html] [test_anchor_area_referrer_changing.html] [test_anchor_area_referrer_invalid.html] [test_anchor_area_referrer_rel.html] [test_anonymousContent_api.html] [test_anonymousContent_append_after_reflow.html] [test_anonymousContent_canvas.html] -[test_xhr_overridemimetype_throws_on_invalid_state.html] -skip-if = buildapp == 'b2g' # Requires webgl support [test_anonymousContent_insert.html] [test_anonymousContent_manipulate_content.html] [test_anonymousContent_style_csp.html] [test_applet_alternate_content.html] [test_appname_override.html] [test_async_setTimeout_stack.html] [test_async_setTimeout_stack_across_globals.html] [test_audioWindowUtils.html] @@ -721,17 +684,16 @@ skip-if = e10s || os != 'linux' || build [test_gsp-qualified.html] [test_gsp-quirks.html] [test_gsp-standards.html] [test_hasFeature.html] [test_history_document_open.html] [test_history_state_null.html] [test_html_colors_quirks.html] [test_html_colors_standards.html] -[test_html_in_xhr.html] [test_htmlcopyencoder.html] [test_htmlcopyencoder.xhtml] [test_iframe_referrer.html] [test_iframe_referrer_changing.html] [test_iframe_referrer_invalid.html] [test_Image_constructor.html] [test_img_referrer.html] [test_innersize_scrollport.html] @@ -822,18 +784,16 @@ skip-if = toolkit == 'android' [test_setInterval_uncatchable_exception.html] skip-if = debug == false [test_settimeout_extra_arguments.html] [test_settimeout_inner.html] [test_setting_opener.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_simplecontentpolicy.html] skip-if = e10s || buildapp == 'b2g' # Bug 1156489. -[test_sync_xhr_timer.xhtml] -skip-if = toolkit == 'android' [test_text_wholeText.html] [test_textnode_normalize_in_selection.html] [test_textnode_split_in_selection.html] [test_title.html] [test_treewalker_nextsibling.xml] [test_unknown_url_origin.html] [test_url.html] [test_url_data.html] @@ -884,32 +844,10 @@ skip-if = release_build [test_window_indexing.html] [test_window_named_frame_enumeration.html] [test_window_orientation.html] skip-if = toolkit != 'gonk' [test_window_proto.html] [test_writable-replaceable.html] [test_x-frame-options.html] [test_xbl_userdata.xhtml] -[test_XHR.html] -[test_xhr_abort_after_load.html] -skip-if = toolkit == 'android' -[test_XHR_anon.html] -[test_xhr_forbidden_headers.html] -[test_XHR_header.html] -[test_XHR_onuploadprogress.html] -[test_XHR_parameters.html] -skip-if = buildapp == 'b2g' # b2g(86 total, 4 failing - testing mozAnon - got false, expected true) b2g-debug(86 total, 4 failing - testing mozAnon - got false, expected true) b2g-desktop(86 total, 4 failing - testing mozAnon - got false, expected true) -[test_xhr_progressevents.html] -skip-if = toolkit == 'android' -[test_xhr_send.html] -[test_xhr_send_readystate.html] -[test_XHR_system.html] -skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(12 total, 2 failing - .mozSystem == true - got false, expected true + ) b2g-desktop(12 total, 2 failing - .mozSystem == true - got false, expected true + ) -[test_XHR_timeout.html] -skip-if = buildapp == 'b2g' || (android_version == '18' && debug) # b2g(flaky on B2G, bug 960743) b2g-debug(flaky on B2G, bug 960743) b2g-desktop(flaky on B2G, bug 960743) -support-files = test_XHR_timeout.js -[test_xhr_withCredentials.html] -[test_XHRDocURI.html] -[test_XHRResponseURL.html] -[test_XHRSendData.html] [test_youtube_flash_embed.html] # Please keep alphabetical order.
--- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -1572,40 +1572,24 @@ DOMInterfaces = { ], # Rename a few things so we don't have both classes and methods # with the same name 'binaryNames': { 'performance': 'getPerformance', }, }, -'XMLHttpRequest': [ -{ - 'nativeType': 'nsXMLHttpRequest', +'XMLHttpRequest': { 'implicitJSContext': [ 'send'], }, -{ - 'workers': True, -}], 'XMLHttpRequestEventTarget': { - 'nativeType': 'nsXHREventTarget', - 'headerFile': 'nsXMLHttpRequest.h', 'concrete': False }, -'XMLHttpRequestUpload': [ -{ - 'nativeType': 'nsXMLHttpRequestUpload', - 'headerFile': 'nsXMLHttpRequest.h' -}, -{ - 'workers': True, -}], - 'XMLSerializer': { 'nativeType': 'nsDOMSerializer', }, 'XPathEvaluator': { 'wrapperCache': False },
--- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -90,12 +90,13 @@ MSG_DEF(MSG_PROMISE_ARG_NOT_ITERABLE, 1, MSG_DEF(MSG_IS_NOT_PROMISE, 1, JSEXN_TYPEERR, "{0} is not a Promise") MSG_DEF(MSG_SW_INSTALL_ERROR, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} encountered an error during installation.") MSG_DEF(MSG_SW_SCRIPT_THREW, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} threw an exception during script evaluation.") MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 1, JSEXN_TYPEERR, "{0} can't be a typed array on SharedArrayBuffer") MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 3, JSEXN_TYPEERR, "Cache got {0} response with bad status {1} while trying to add request {2}") MSG_DEF(MSG_SW_UPDATE_BAD_REGISTRATION, 2, JSEXN_TYPEERR, "Failed to update the ServiceWorker for scope {0] because the registration has been {1} since the update was scheduled.") MSG_DEF(MSG_INVALID_DURATION_ERROR, 1, JSEXN_TYPEERR, "Invalid duration '{0}'.") MSG_DEF(MSG_INVALID_EASING_ERROR, 1, JSEXN_TYPEERR, "Invalid easing '{0}'.") +MSG_DEF(MSG_INVALID_SPACING_MODE_ERROR, 1, JSEXN_TYPEERR, "Invalid spacing '{0}'.") MSG_DEF(MSG_USELESS_SETTIMEOUT, 1, JSEXN_TYPEERR, "Useless {0} call (missing quotes around argument?)") MSG_DEF(MSG_TOKENLIST_NO_SUPPORTED_TOKENS, 2, JSEXN_TYPEERR, "{0} attribute of <{1}> does not define any supported tokens") MSG_DEF(MSG_CACHE_STREAM_CLOSED, 0, JSEXN_TYPEERR, "Response body is a cache file stream that has already been closed.") MSG_DEF(MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN, 1, JSEXN_TYPEERR, "Request mode '{0}' was used, but request cache mode 'only-if-cached' can only be used with request mode 'same-origin'.")
--- a/dom/bluetooth/common/webapi/BluetoothPbapRequestHandle.h +++ b/dom/bluetooth/common/webapi/BluetoothPbapRequestHandle.h @@ -5,17 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_bluetooth_bluetoothpbaprequesthandle_h #define mozilla_dom_bluetooth_bluetoothpbaprequesthandle_h #include "nsCOMPtr.h" #include "mozilla/dom/bluetooth/BluetoothCommon.h" #include "mozilla/dom/DOMRequest.h" -#include "mozilla/dom/BlobSet.h" namespace mozilla { class ErrorResult; namespace dom { class Blob; class DOMRequest; } }
--- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -62,16 +62,17 @@ #include "WebGLExtensions.h" #include "WebGLFramebuffer.h" #include "WebGLMemoryTracker.h" #include "WebGLObjectModel.h" #include "WebGLProgram.h" #include "WebGLQuery.h" #include "WebGLSampler.h" #include "WebGLShader.h" +#include "WebGLSync.h" #include "WebGLTimerQuery.h" #include "WebGLTransformFeedback.h" #include "WebGLVertexArray.h" #include "WebGLVertexAttribData.h" #ifdef MOZ_WIDGET_COCOA #include "nsCocoaFeatures.h" #endif @@ -261,16 +262,17 @@ WebGLContext::DestroyResourcesAndContext ClearLinkedList(mBuffers); ClearLinkedList(mFramebuffers); ClearLinkedList(mPrograms); ClearLinkedList(mQueries); ClearLinkedList(mRenderbuffers); ClearLinkedList(mSamplers); ClearLinkedList(mShaders); + ClearLinkedList(mSyncs); ClearLinkedList(mTextures); ClearLinkedList(mTimerQueries); ClearLinkedList(mTransformFeedbacks); ClearLinkedList(mVertexArrays); ////// mFakeBlack_2D_0000 = nullptr;
--- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -95,16 +95,17 @@ class WebGLBuffer; class WebGLExtensionBase; class WebGLFramebuffer; class WebGLProgram; class WebGLQuery; class WebGLRenderbuffer; class WebGLSampler; class WebGLShader; class WebGLShaderPrecisionFormat; +class WebGLSync; class WebGLTexture; class WebGLTimerQuery; class WebGLTransformFeedback; class WebGLUniformLocation; class WebGLVertexArray; namespace dom { class Element; @@ -1397,16 +1398,17 @@ protected: LinkedList<WebGLBuffer> mBuffers; LinkedList<WebGLFramebuffer> mFramebuffers; LinkedList<WebGLProgram> mPrograms; LinkedList<WebGLQuery> mQueries; LinkedList<WebGLRenderbuffer> mRenderbuffers; LinkedList<WebGLSampler> mSamplers; LinkedList<WebGLShader> mShaders; + LinkedList<WebGLSync> mSyncs; LinkedList<WebGLTexture> mTextures; LinkedList<WebGLTimerQuery> mTimerQueries; LinkedList<WebGLTransformFeedback> mTransformFeedbacks; LinkedList<WebGLVertexArray> mVertexArrays; WebGLRefPtr<WebGLTransformFeedback> mDefaultTransformFeedback; WebGLRefPtr<WebGLVertexArray> mDefaultVertexArray;
--- a/dom/canvas/WebGLContextDraw.cpp +++ b/dom/canvas/WebGLContextDraw.cpp @@ -839,16 +839,32 @@ WebGLContext::FakeBlackTexture::FakeBlac // minimize the risk of running into a driver bug in texImage2D, as it is a bit // unusual maybe to create 1x1 textures, and the stack may not have the alignment that // TexImage2D expects. const webgl::DriverUnpackInfo dui = {texFormat, texFormat, LOCAL_GL_UNSIGNED_BYTE}; UniqueBuffer zeros = moz_xcalloc(1, 16); // Infallible allocation. MOZ_ASSERT(gl->IsCurrent()); + auto logANGLEError = [](GLenum source, GLenum type, GLuint id, GLenum severity, + GLsizei length, const GLchar* message, const GLvoid* userParam) + { + gfxCriticalNote << message; + }; + + if (gl->IsANGLE()) { + gl->fEnable(LOCAL_GL_DEBUG_OUTPUT); + gl->fDebugMessageCallback(logANGLEError, nullptr); + gl->fDebugMessageControl(LOCAL_GL_DONT_CARE, + LOCAL_GL_DONT_CARE, + LOCAL_GL_DONT_CARE, + 0, nullptr, + true); + } + if (target == LOCAL_GL_TEXTURE_CUBE_MAP) { for (int i = 0; i < 6; ++i) { const TexImageTarget curTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i; const GLenum error = DoTexImage(mGL, curTarget.get(), 0, &dui, 1, 1, 1, zeros.get()); if (error) { const nsPrintfCString text("DoTexImage failed with `error`: 0x%04x, " "for `curTarget`: 0x%04x, " @@ -867,16 +883,20 @@ WebGLContext::FakeBlackTexture::FakeBlac "for `target`: 0x%04x, " "`dui`: {0x%04x, 0x%04x, 0x%04x}.", error, target.get(), dui.internalFormat, dui.unpackFormat, dui.unpackType); gfxCriticalError() << text.BeginReading(); MOZ_CRASH("GFX: Unexpected error during FakeBlack creation."); } } + + if (gl->IsANGLE()) { + gl->fDisable(LOCAL_GL_DEBUG_OUTPUT); + } } WebGLContext::FakeBlackTexture::~FakeBlackTexture() { mGL->MakeCurrent(); mGL->fDeleteTextures(1, &mGLName); }
--- a/dom/canvas/WebGLSync.cpp +++ b/dom/canvas/WebGLSync.cpp @@ -9,31 +9,32 @@ #include "mozilla/dom/WebGL2RenderingContextBinding.h" #include "WebGLContext.h" namespace mozilla { WebGLSync::WebGLSync(WebGLContext* webgl, GLenum condition, GLbitfield flags) : WebGLContextBoundObject(webgl) { + mContext->mSyncs.insertBack(this); mGLName = mContext->gl->fFenceSync(condition, flags); } WebGLSync::~WebGLSync() { DeleteOnce(); } void WebGLSync::Delete() { mContext->MakeContextCurrent(); mContext->gl->fDeleteSync(mGLName); mGLName = 0; - LinkedListElement<WebGLSync>::remove(); + LinkedListElement<WebGLSync>::removeFrom(mContext->mSyncs); } WebGLContext* WebGLSync::GetParentObject() const { return mContext; }
deleted file mode 100644 --- a/dom/gamepad/GamepadFunctions.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "mozilla/dom/GamepadFunctions.h" -#include "mozilla/dom/GamepadService.h" -#include "nsHashKeys.h" -#include "mozilla/dom/ContentParent.h" -#include "mozilla/unused.h" - -namespace mozilla { -namespace dom { -namespace GamepadFunctions { - -namespace { -// Increments as gamepads are added -uint32_t gGamepadIndex = 0; -} - -template<class T> -void -NotifyGamepadChange(const T& aInfo) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(XRE_IsParentProcess()); - GamepadChangeEvent e(aInfo); - nsTArray<ContentParent*> t; - ContentParent::GetAll(t); - for(uint32_t i = 0; i < t.Length(); ++i) { - Unused << t[i]->SendGamepadUpdate(e); - } - // If we have a GamepadService in the main process, send directly to it. - if (GamepadService::IsServiceRunning()) { - RefPtr<GamepadService> svc = GamepadService::GetService(); - svc->Update(e); - } -} - -uint32_t -AddGamepad(const char* aID, - GamepadMappingType aMapping, - uint32_t aNumButtons, uint32_t aNumAxes) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(XRE_IsParentProcess()); - - int index = gGamepadIndex; - gGamepadIndex++; - GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index, - (uint32_t)aMapping, aNumButtons, aNumAxes); - NotifyGamepadChange<GamepadAdded>(a); - return index; -} - -void -RemoveGamepad(uint32_t aIndex) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(XRE_IsParentProcess()); - GamepadRemoved a(aIndex); - NotifyGamepadChange<GamepadRemoved>(a); -} - -void -NewButtonEvent(uint32_t aIndex, uint32_t aButton, - bool aPressed, double aValue) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(XRE_IsParentProcess()); - GamepadButtonInformation a(aIndex, aButton, aPressed, aValue); - NotifyGamepadChange<GamepadButtonInformation>(a); -} - -void -NewButtonEvent(uint32_t aIndex, uint32_t aButton, - bool aPressed) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(XRE_IsParentProcess()); - // When only a digital button is available the value will be synthesized. - NewButtonEvent(aIndex, aButton, aPressed, aPressed ? 1.0L : 0.0L); -} - -void -NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, - double aValue) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(XRE_IsParentProcess()); - GamepadAxisInformation a(aIndex, aAxis, aValue); - NotifyGamepadChange<GamepadAxisInformation>(a); -} - -void -ResetGamepadIndexes() -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(XRE_IsParentProcess()); - gGamepadIndex = 0; -} - -} // namespace GamepadFunctions -} // namespace dom -} // namespace mozilla
deleted file mode 100644 --- a/dom/gamepad/GamepadFunctions.h +++ /dev/null @@ -1,47 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef mozilla_dom_GamepadFunctions_h_ -#define mozilla_dom_GamepadFunctions_h_ - -#include "mozilla/dom/GamepadBinding.h" - -namespace mozilla { -namespace dom { -namespace GamepadFunctions { - -// Functions for building and transmitting IPDL messages through the HAL -// sandbox. Used by platform specific Gamepad implementations - -// Add a gamepad to the list of known gamepads, and return its index. -uint32_t AddGamepad(const char* aID, GamepadMappingType aMapping, - uint32_t aNumButtons, uint32_t aNumAxes); -// Remove the gamepad at |aIndex| from the list of known gamepads. -void RemoveGamepad(uint32_t aIndex); - -// Update the state of |aButton| for the gamepad at |aIndex| for all -// windows that are listening and visible, and fire one of -// a gamepadbutton{up,down} event at them as well. -// aPressed is used for digital buttons, aValue is for analog buttons. -void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, - double aValue); -// When only a digital button is available the value will be synthesized. -void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed); - -// Update the state of |aAxis| for the gamepad at |aIndex| for all -// windows that are listening and visible, and fire a gamepadaxismove -// event at them as well. -void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue); - -// When shutting down the platform communications for gamepad, also reset the -// indexes. -void ResetGamepadIndexes(); - -} // namespace GamepadFunctions -} // namespace dom -} // namespace mozilla - -#endif
new file mode 100644 --- /dev/null +++ b/dom/gamepad/GamepadManager.cpp @@ -0,0 +1,596 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/GamepadManager.h" + +#include "mozilla/dom/Gamepad.h" +#include "mozilla/dom/GamepadAxisMoveEvent.h" +#include "mozilla/dom/GamepadButtonEvent.h" +#include "mozilla/dom/GamepadEvent.h" +#include "mozilla/dom/GamepadEventChannelChild.h" +#include "mozilla/dom/GamepadMonitoring.h" + +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPtr.h" + +#include "nsAutoPtr.h" +#include "nsGlobalWindow.h" +#include "nsIDOMEvent.h" +#include "nsIDOMDocument.h" +#include "nsIDOMWindow.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIServiceManager.h" +#include "nsThreadUtils.h" +#include "mozilla/Services.h" +#include "mozilla/unused.h" + +#include <cstddef> + +using namespace mozilla::ipc; + +namespace mozilla { +namespace dom { + +namespace { + +const char* kGamepadEnabledPref = "dom.gamepad.enabled"; +const char* kGamepadEventsEnabledPref = + "dom.gamepad.non_standard_events.enabled"; + +const nsTArray<RefPtr<nsGlobalWindow>>::index_type NoIndex = + nsTArray<RefPtr<nsGlobalWindow>>::NoIndex; + +bool sShutdown = false; + +StaticRefPtr<GamepadManager> gGamepadManagerSingleton; + +} // namespace + +NS_IMPL_ISUPPORTS(GamepadManager, nsIObserver) + +GamepadManager::GamepadManager() + : mEnabled(false), + mNonstandardEventsEnabled(false), + mShuttingDown(false), + mChild(nullptr) +{} + +nsresult +GamepadManager::Init() +{ + mEnabled = IsAPIEnabled(); + mNonstandardEventsEnabled = + Preferences::GetBool(kGamepadEventsEnabledPref, false); + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (NS_WARN_IF(!observerService)) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + rv = observerService->AddObserver(this, + NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, + false); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +GamepadManager::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + } + BeginShutdown(); + return NS_OK; +} + +void +GamepadManager::StopMonitoring() +{ + if(mChild) { + mChild->SendGamepadListenerRemoved(); + mChild = nullptr; + } + mGamepads.Clear(); +} + +void +GamepadManager::BeginShutdown() +{ + mShuttingDown = true; + StopMonitoring(); + // Don't let windows call back to unregister during shutdown + for (uint32_t i = 0; i < mListeners.Length(); i++) { + mListeners[i]->SetHasGamepadEventListener(false); + } + mListeners.Clear(); + sShutdown = true; +} + +void +GamepadManager::AddListener(nsGlobalWindow* aWindow) +{ + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aWindow->IsInnerWindow()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mEnabled || mShuttingDown) { + return; + } + + if (mListeners.IndexOf(aWindow) != NoIndex) { + return; // already exists + } + + mListeners.AppendElement(aWindow); + + // IPDL child has been created + if (mChild) { + return; + } + PBackgroundChild *actor = BackgroundChild::GetForCurrentThread(); + //Try to get the PBackground Child actor + if (actor) { + ActorCreated(actor); + } else { + Unused << BackgroundChild::GetOrCreateForCurrentThread(this); + } +} + +void +GamepadManager::RemoveListener(nsGlobalWindow* aWindow) +{ + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aWindow->IsInnerWindow()); + + if (mShuttingDown) { + // Doesn't matter at this point. It's possible we're being called + // as a result of our own destructor here, so just bail out. + return; + } + + if (mListeners.IndexOf(aWindow) == NoIndex) { + return; // doesn't exist + } + + mListeners.RemoveElement(aWindow); + + if (mListeners.IsEmpty()) { + StopMonitoring(); + } +} + +already_AddRefed<Gamepad> +GamepadManager::GetGamepad(uint32_t aIndex) const +{ + RefPtr<Gamepad> gamepad; + if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) { + return gamepad.forget(); + } + + return nullptr; +} + +void +GamepadManager::AddGamepad(uint32_t aIndex, + const nsAString& aId, + GamepadMappingType aMapping, + uint32_t aNumButtons, + uint32_t aNumAxes) +{ + //TODO: bug 852258: get initial button/axis state + RefPtr<Gamepad> gamepad = + new Gamepad(nullptr, + aId, + 0, // index is set by global window + aMapping, + aNumButtons, + aNumAxes); + + // We store the gamepad related to its index given by the parent process, + // and no duplicate index is allowed. + MOZ_ASSERT(!mGamepads.Get(aIndex, nullptr)); + mGamepads.Put(aIndex, gamepad); + NewConnectionEvent(aIndex, true); +} + +void +GamepadManager::RemoveGamepad(uint32_t aIndex) +{ + RefPtr<Gamepad> gamepad = GetGamepad(aIndex); + if (!gamepad) { + NS_WARNING("Trying to delete gamepad with invalid index"); + return; + } + gamepad->SetConnected(false); + NewConnectionEvent(aIndex, false); + mGamepads.Remove(aIndex); +} + +void +GamepadManager::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, + double aValue) +{ + if (mShuttingDown) { + return; + } + + RefPtr<Gamepad> gamepad = GetGamepad(aIndex); + if (!gamepad) { + return; + } + + gamepad->SetButton(aButton, aPressed, aValue); + + // Hold on to listeners in a separate array because firing events + // can mutate the mListeners array. + nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners); + MOZ_ASSERT(!listeners.IsEmpty()); + + for (uint32_t i = 0; i < listeners.Length(); i++) { + + MOZ_ASSERT(listeners[i]->IsInnerWindow()); + + // Only send events to non-background windows + if (!listeners[i]->AsInner()->IsCurrentInnerWindow() || + listeners[i]->GetOuterWindow()->IsBackground()) { + continue; + } + + bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], aIndex); + + RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex); + if (listenerGamepad) { + listenerGamepad->SetButton(aButton, aPressed, aValue); + if (firstTime) { + FireConnectionEvent(listeners[i], listenerGamepad, true); + } + if (mNonstandardEventsEnabled) { + // Fire event + FireButtonEvent(listeners[i], listenerGamepad, aButton, aValue); + } + } + } +} + +void +GamepadManager::FireButtonEvent(EventTarget* aTarget, + Gamepad* aGamepad, + uint32_t aButton, + double aValue) +{ + nsString name = aValue == 1.0L ? NS_LITERAL_STRING("gamepadbuttondown") : + NS_LITERAL_STRING("gamepadbuttonup"); + GamepadButtonEventInit init; + init.mBubbles = false; + init.mCancelable = false; + init.mGamepad = aGamepad; + init.mButton = aButton; + RefPtr<GamepadButtonEvent> event = + GamepadButtonEvent::Constructor(aTarget, name, init); + + event->SetTrusted(true); + + bool defaultActionEnabled = true; + aTarget->DispatchEvent(event, &defaultActionEnabled); +} + +void +GamepadManager::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue) +{ + if (mShuttingDown) { + return; + } + + RefPtr<Gamepad> gamepad = GetGamepad(aIndex); + if (!gamepad) { + return; + } + gamepad->SetAxis(aAxis, aValue); + + // Hold on to listeners in a separate array because firing events + // can mutate the mListeners array. + nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners); + MOZ_ASSERT(!listeners.IsEmpty()); + + for (uint32_t i = 0; i < listeners.Length(); i++) { + + MOZ_ASSERT(listeners[i]->IsInnerWindow()); + + // Only send events to non-background windows + if (!listeners[i]->AsInner()->IsCurrentInnerWindow() || + listeners[i]->GetOuterWindow()->IsBackground()) { + continue; + } + + bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], aIndex); + + RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex); + if (listenerGamepad) { + listenerGamepad->SetAxis(aAxis, aValue); + if (firstTime) { + FireConnectionEvent(listeners[i], listenerGamepad, true); + } + if (mNonstandardEventsEnabled) { + // Fire event + FireAxisMoveEvent(listeners[i], listenerGamepad, aAxis, aValue); + } + } + } +} + +void +GamepadManager::FireAxisMoveEvent(EventTarget* aTarget, + Gamepad* aGamepad, + uint32_t aAxis, + double aValue) +{ + GamepadAxisMoveEventInit init; + init.mBubbles = false; + init.mCancelable = false; + init.mGamepad = aGamepad; + init.mAxis = aAxis; + init.mValue = aValue; + RefPtr<GamepadAxisMoveEvent> event = + GamepadAxisMoveEvent::Constructor(aTarget, + NS_LITERAL_STRING("gamepadaxismove"), + init); + + event->SetTrusted(true); + + bool defaultActionEnabled = true; + aTarget->DispatchEvent(event, &defaultActionEnabled); +} + +void +GamepadManager::NewConnectionEvent(uint32_t aIndex, bool aConnected) +{ + if (mShuttingDown) { + return; + } + + RefPtr<Gamepad> gamepad = GetGamepad(aIndex); + if (!gamepad) { + return; + } + + // Hold on to listeners in a separate array because firing events + // can mutate the mListeners array. + nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners); + MOZ_ASSERT(!listeners.IsEmpty()); + + if (aConnected) { + for (uint32_t i = 0; i < listeners.Length(); i++) { + + MOZ_ASSERT(listeners[i]->IsInnerWindow()); + + // Only send events to non-background windows + if (!listeners[i]->AsInner()->IsCurrentInnerWindow() || + listeners[i]->GetOuterWindow()->IsBackground()) { + continue; + } + + // We don't fire a connected event here unless the window + // has seen input from at least one device. + if (!listeners[i]->HasSeenGamepadInput()) { + continue; + } + + SetWindowHasSeenGamepad(listeners[i], aIndex); + + RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex); + if (listenerGamepad) { + // Fire event + FireConnectionEvent(listeners[i], listenerGamepad, aConnected); + } + } + } else { + // For disconnection events, fire one at every window that has received + // data from this gamepad. + for (uint32_t i = 0; i < listeners.Length(); i++) { + + // Even background windows get these events, so we don't have to + // deal with the hassle of syncing the state of removed gamepads. + + if (WindowHasSeenGamepad(listeners[i], aIndex)) { + RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex); + if (listenerGamepad) { + listenerGamepad->SetConnected(false); + // Fire event + FireConnectionEvent(listeners[i], listenerGamepad, false); + listeners[i]->RemoveGamepad(aIndex); + } + } + } + } +} + +void +GamepadManager::FireConnectionEvent(EventTarget* aTarget, + Gamepad* aGamepad, + bool aConnected) +{ + nsString name = aConnected ? NS_LITERAL_STRING("gamepadconnected") : + NS_LITERAL_STRING("gamepaddisconnected"); + GamepadEventInit init; + init.mBubbles = false; + init.mCancelable = false; + init.mGamepad = aGamepad; + RefPtr<GamepadEvent> event = + GamepadEvent::Constructor(aTarget, name, init); + + event->SetTrusted(true); + + bool defaultActionEnabled = true; + aTarget->DispatchEvent(event, &defaultActionEnabled); +} + +void +GamepadManager::SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad) +{ + if (mShuttingDown || !mEnabled) { + return; + } + + RefPtr<Gamepad> gamepad = GetGamepad(aIndex); + if (!gamepad) { + return; + } + + aGamepad->SyncState(gamepad); +} + +// static +bool +GamepadManager::IsServiceRunning() +{ + return !!gGamepadManagerSingleton; +} + +// static +already_AddRefed<GamepadManager> +GamepadManager::GetService() +{ + if (sShutdown) { + return nullptr; + } + + if (!gGamepadManagerSingleton) { + RefPtr<GamepadManager> manager = new GamepadManager(); + nsresult rv = manager->Init(); + if(NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + gGamepadManagerSingleton = manager; + ClearOnShutdown(&gGamepadManagerSingleton); + } + + RefPtr<GamepadManager> service(gGamepadManagerSingleton); + return service.forget(); +} + +// static +bool +GamepadManager::IsAPIEnabled() { + return Preferences::GetBool(kGamepadEnabledPref, false); +} + +bool +GamepadManager::MaybeWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex) +{ + if (!WindowHasSeenGamepad(aWindow, aIndex)) { + // This window hasn't seen this gamepad before, so + // send a connection event first. + SetWindowHasSeenGamepad(aWindow, aIndex); + return true; + } + return false; +} + +bool +GamepadManager::WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex) const +{ + RefPtr<Gamepad> gamepad = aWindow->GetGamepad(aIndex); + return gamepad != nullptr; +} + +void +GamepadManager::SetWindowHasSeenGamepad(nsGlobalWindow* aWindow, + uint32_t aIndex, + bool aHasSeen) +{ + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aWindow->IsInnerWindow()); + + if (mListeners.IndexOf(aWindow) == NoIndex) { + // This window isn't even listening for gamepad events. + return; + } + + if (aHasSeen) { + aWindow->SetHasSeenGamepadInput(true); + nsCOMPtr<nsISupports> window = ToSupports(aWindow); + RefPtr<Gamepad> gamepad = GetGamepad(aIndex); + if (!gamepad) { + return; + } + RefPtr<Gamepad> clonedGamepad = gamepad->Clone(window); + aWindow->AddGamepad(aIndex, clonedGamepad); + } else { + aWindow->RemoveGamepad(aIndex); + } +} + +void +GamepadManager::Update(const GamepadChangeEvent& aEvent) +{ + if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) { + const GamepadAdded& a = aEvent.get_GamepadAdded(); + AddGamepad(a.index(), a.id(), + static_cast<GamepadMappingType>(a.mapping()), + a.num_buttons(), a.num_axes()); + return; + } + if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) { + const GamepadRemoved& a = aEvent.get_GamepadRemoved(); + RemoveGamepad(a.index()); + return; + } + if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) { + const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation(); + NewButtonEvent(a.index(), a.button(), a.pressed(), a.value()); + return; + } + if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) { + const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation(); + NewAxisMoveEvent(a.index(), a.axis(), a.value()); + return; + } + + MOZ_CRASH("We shouldn't be here!"); + +} + +//Override nsIIPCBackgroundChildCreateCallback +void +GamepadManager::ActorCreated(PBackgroundChild *aActor) +{ + MOZ_ASSERT(aActor); + GamepadEventChannelChild *child = new GamepadEventChannelChild(); + PGamepadEventChannelChild *initedChild = + aActor->SendPGamepadEventChannelConstructor(child); + if (NS_WARN_IF(!initedChild)) { + ActorFailed(); + return; + } + MOZ_ASSERT(initedChild == child); + mChild = child; + mChild->SendGamepadListenerAdded(); +} + +//Override nsIIPCBackgroundChildCreateCallback +void +GamepadManager::ActorFailed() +{ + MOZ_CRASH("Gamepad IPC actor create failed!"); +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/gamepad/GamepadManager.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_GamepadManager_h_ +#define mozilla_dom_GamepadManager_h_ + +#include "nsIIPCBackgroundChildCreateCallback.h" +#include "nsIObserver.h" +// Needed for GamepadMappingType +#include "mozilla/dom/GamepadBinding.h" + +class nsGlobalWindow; + +namespace mozilla { +namespace dom { + +class EventTarget; +class Gamepad; +class GamepadChangeEvent; +class GamepadEventChannelChild; + +class GamepadManager final : public nsIObserver, + public nsIIPCBackgroundChildCreateCallback +{ + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK + + // Returns true if we actually have a service up and running + static bool IsServiceRunning(); + // Get the singleton service + static already_AddRefed<GamepadManager> GetService(); + // Return true if the API is preffed on. + static bool IsAPIEnabled(); + + void BeginShutdown(); + void StopMonitoring(); + + // Indicate that |aWindow| wants to receive gamepad events. + void AddListener(nsGlobalWindow* aWindow); + // Indicate that |aWindow| should no longer receive gamepad events. + void RemoveListener(nsGlobalWindow* aWindow); + + // Add a gamepad to the list of known gamepads. + void AddGamepad(uint32_t aIndex, const nsAString& aID, GamepadMappingType aMapping, + uint32_t aNumButtons, uint32_t aNumAxes); + + // Remove the gamepad at |aIndex| from the list of known gamepads. + void RemoveGamepad(uint32_t aIndex); + + // Update the state of |aButton| for the gamepad at |aIndex| for all + // windows that are listening and visible, and fire one of + // a gamepadbutton{up,down} event at them as well. + // aPressed is used for digital buttons, aValue is for analog buttons. + void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, + double aValue); + + // Update the state of |aAxis| for the gamepad at |aIndex| for all + // windows that are listening and visible, and fire a gamepadaxismove + // event at them as well. + void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue); + + // Synchronize the state of |aGamepad| to match the gamepad stored at |aIndex| + void SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad); + + // Returns gamepad object if index exists, null otherwise + already_AddRefed<Gamepad> GetGamepad(uint32_t aIndex) const; + + // Receive GamepadChangeEvent messages from parent process to fire DOM events + void Update(const GamepadChangeEvent& aGamepadEvent); + + protected: + GamepadManager(); + ~GamepadManager() {}; + + // Fire a gamepadconnected or gamepaddisconnected event for the gamepad + // at |aIndex| to all windows that are listening and have received + // gamepad input. + void NewConnectionEvent(uint32_t aIndex, bool aConnected); + + // Fire a gamepadaxismove event to the window at |aTarget| for |aGamepad|. + void FireAxisMoveEvent(EventTarget* aTarget, + Gamepad* aGamepad, + uint32_t axis, + double value); + + // Fire one of gamepadbutton{up,down} event at the window at |aTarget| for + // |aGamepad|. + void FireButtonEvent(EventTarget* aTarget, + Gamepad* aGamepad, + uint32_t aButton, + double aValue); + + // Fire one of gamepad{connected,disconnected} event at the window at + // |aTarget| for |aGamepad|. + void FireConnectionEvent(EventTarget* aTarget, + Gamepad* aGamepad, + bool aConnected); + + // true if this feature is enabled in preferences + bool mEnabled; + // true if non-standard events are enabled in preferences + bool mNonstandardEventsEnabled; + // true when shutdown has begun + bool mShuttingDown; + + // Gamepad IPDL child + // This pointer is only used by this singleton instance and + // will be destroyed during the IPDL shutdown chain, so we + // don't need to refcount it here + GamepadEventChannelChild MOZ_NON_OWNING_REF *mChild; + + private: + + nsresult Init(); + + bool MaybeWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex); + // Returns true if we have already sent data from this gamepad + // to this window. This should only return true if the user + // explicitly interacted with a gamepad while this window + // was focused, by pressing buttons or similar actions. + bool WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex) const; + // Indicate that a window has recieved data from a gamepad. + void SetWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex, + bool aHasSeen = true); + + // Gamepads connected to the system. Copies of these are handed out + // to each window. + nsRefPtrHashtable<nsUint32HashKey, Gamepad> mGamepads; + // Inner windows that are listening for gamepad events. + // has been sent to that window. + nsTArray<RefPtr<nsGlobalWindow>> mListeners; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_GamepadManager_h_
--- a/dom/gamepad/GamepadMonitoring.cpp +++ b/dom/gamepad/GamepadMonitoring.cpp @@ -1,33 +1,32 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/GamepadMonitoring.h" -#include "mozilla/dom/GamepadFunctions.h" -#include "mozilla/dom/PContentParent.h" +#include "mozilla/dom/GamepadPlatformService.h" +#include "mozilla/ipc/BackgroundParent.h" + +using namespace mozilla::ipc; namespace mozilla { namespace dom { -using namespace GamepadFunctions; - void MaybeStopGamepadMonitoring() { - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(XRE_IsParentProcess()); - nsTArray<ContentParent*> t; - ContentParent::GetAll(t); - for(uint32_t i = 0; i < t.Length(); ++i) { - if (t[i]->HasGamepadListener()) { - return; - } + AssertIsOnBackgroundThread(); + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + MOZ_ASSERT(service); + if(service->HasGamepadListeners()) { + return; } StopGamepadMonitoring(); - ResetGamepadIndexes(); + service->ResetGamepadIndexes(); + service->MaybeShutdown(); } } // namespace dom } // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/gamepad/GamepadPlatformService.cpp @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/GamepadPlatformService.h" + +#include "mozilla/dom/GamepadEventChannelParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/Mutex.h" +#include "mozilla/unused.h" + +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIThread.h" + +using namespace mozilla::ipc; + +namespace mozilla { +namespace dom { + +namespace { + +// This is the singleton instance of GamepadPlatformService, can be called +// by both background and monitor thread. +StaticRefPtr<GamepadPlatformService> gGamepadPlatformServiceSingleton; + +} //namepsace + +GamepadPlatformService::GamepadPlatformService() + : mGamepadIndex(0), + mMutex("mozilla::dom::GamepadPlatformService") +{} + +GamepadPlatformService::~GamepadPlatformService() +{ + Cleanup(); +} + +// static +already_AddRefed<GamepadPlatformService> +GamepadPlatformService::GetParentService() +{ + //GamepadPlatformService can only be accessed in parent process + MOZ_ASSERT(XRE_IsParentProcess()); + if (!gGamepadPlatformServiceSingleton) { + // Only Background Thread can create new GamepadPlatformService instance. + if (IsOnBackgroundThread()) { + gGamepadPlatformServiceSingleton = new GamepadPlatformService(); + } else { + return nullptr; + } + } + RefPtr<GamepadPlatformService> service(gGamepadPlatformServiceSingleton); + return service.forget(); +} + +template<class T> +void +GamepadPlatformService::NotifyGamepadChange(const T& aInfo) +{ + // This method is called by monitor populated in + // platform-dependent backends + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!NS_IsMainThread()); + + GamepadChangeEvent e(aInfo); + + // mChannelParents may be accessed by background thread in the + // same time, we use mutex to prevent possible race condtion + MutexAutoLock autoLock(mMutex); + for(uint32_t i = 0; i < mChannelParents.Length(); ++i) { + mChannelParents[i]->DispatchUpdateEvent(e); + } +} + +uint32_t +GamepadPlatformService::AddGamepad(const char* aID, + GamepadMappingType aMapping, + uint32_t aNumButtons, uint32_t aNumAxes) +{ + // This method is called by monitor thread populated in + // platform-dependent backends + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!NS_IsMainThread()); + + uint32_t index = ++mGamepadIndex; + GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index, + (uint32_t)aMapping, aNumButtons, aNumAxes); + NotifyGamepadChange<GamepadAdded>(a); + return index; +} + +void +GamepadPlatformService::RemoveGamepad(uint32_t aIndex) +{ + // This method is called by monitor thread populated in + // platform-dependent backends + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!NS_IsMainThread()); + GamepadRemoved a(aIndex); + NotifyGamepadChange<GamepadRemoved>(a); +} + +void +GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, + bool aPressed, double aValue) +{ + // This method is called by monitor thread populated in + // platform-dependent backends + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!NS_IsMainThread()); + GamepadButtonInformation a(aIndex, aButton, aPressed, aValue); + NotifyGamepadChange<GamepadButtonInformation>(a); +} + +void +GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, + bool aPressed) +{ + // This method is called by monitor thread populated in + // platform-dependent backends + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!NS_IsMainThread()); + // When only a digital button is available the value will be synthesized. + NewButtonEvent(aIndex, aButton, aPressed, aPressed ? 1.0L : 0.0L); +} + +void +GamepadPlatformService::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, + double aValue) +{ + // This method is called by monitor thread populated in + // platform-dependent backends + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!NS_IsMainThread()); + GamepadAxisInformation a(aIndex, aAxis, aValue); + NotifyGamepadChange<GamepadAxisInformation>(a); +} + +void +GamepadPlatformService::ResetGamepadIndexes() +{ + // This method is called by monitor thread populated in + // platform-dependent backends + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!NS_IsMainThread()); + mGamepadIndex = 0; +} + +void +GamepadPlatformService::AddChannelParent(GamepadEventChannelParent* aParent) +{ + // mChannelParents can only be modified once GamepadEventChannelParent + // is created or removed in Background thread + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParent); + MOZ_ASSERT(!mChannelParents.Contains(aParent)); + + // We use mutex here to prevent race condition with monitor thread + MutexAutoLock autoLock(mMutex); + mChannelParents.AppendElement(aParent); +} + +void +GamepadPlatformService::RemoveChannelParent(GamepadEventChannelParent* aParent) +{ + // mChannelParents can only be modified once GamepadEventChannelParent + // is created or removed in Background thread + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParent); + MOZ_ASSERT(mChannelParents.Contains(aParent)); + + // We use mutex here to prevent race condition with monitor thread + MutexAutoLock autoLock(mMutex); + mChannelParents.RemoveElement(aParent); +} + +bool +GamepadPlatformService::HasGamepadListeners() +{ + // mChannelParents may be accessed by background thread in the + // same time, we use mutex to prevent possible race condtion + AssertIsOnBackgroundThread(); + + // We use mutex here to prevent race condition with monitor thread + MutexAutoLock autoLock(mMutex); + for (uint32_t i = 0; i < mChannelParents.Length(); i++) { + if(mChannelParents[i]->HasGamepadListener()) { + return true; + } + } + return false; +} + +void +GamepadPlatformService::MaybeShutdown() +{ + // This method is invoked in MaybeStopGamepadMonitoring when + // an IPDL channel is going to be destroyed + AssertIsOnBackgroundThread(); + + // We have to release gGamepadPlatformServiceSingleton ouside + // the mutex as well as making upcoming GetParentService() call + // recreate new singleton, so we use this RefPtr to temporarily + // hold the reference, postponing the release process until this + // method ends. + RefPtr<GamepadPlatformService> kungFuDeathGrip; + + bool isChannelParentEmpty; + { + MutexAutoLock autoLock(mMutex); + isChannelParentEmpty = mChannelParents.IsEmpty(); + if(isChannelParentEmpty) { + kungFuDeathGrip = gGamepadPlatformServiceSingleton; + gGamepadPlatformServiceSingleton = nullptr; + } + } +} + +void +GamepadPlatformService::Cleanup() +{ + // This method is called when GamepadPlatformService is + // successfully distructed in background thread + AssertIsOnBackgroundThread(); + + MutexAutoLock autoLock(mMutex); + mChannelParents.Clear(); +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/gamepad/GamepadPlatformService.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_GamepadPlatformService_h_ +#define mozilla_dom_GamepadPlatformService_h_ + +#include "mozilla/dom/GamepadBinding.h" + +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +namespace dom { + +class GamepadEventChannelParent; + +// Platform Service for building and transmitting IPDL messages +// through the HAL sandbox. Used by platform specific +// Gamepad implementations +// +// This class can be accessed by the following 2 threads : +// 1. Background thread: +// This thread takes charge of IPDL communications +// between here and DOM side +// +// 2. Monitor Thread: +// This thread is populated in platform-dependent backends, which +// is in charge of processing gamepad hardware events from OS +class GamepadPlatformService final +{ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadPlatformService) + public: + //Get the singleton service + static already_AddRefed<GamepadPlatformService> GetParentService(); + + // Add a gamepad to the list of known gamepads, and return its index. + uint32_t AddGamepad(const char* aID, GamepadMappingType aMapping, + uint32_t aNumButtons, uint32_t aNumAxes); + // Remove the gamepad at |aIndex| from the list of known gamepads. + void RemoveGamepad(uint32_t aIndex); + + // Update the state of |aButton| for the gamepad at |aIndex| for all + // windows that are listening and visible, and fire one of + // a gamepadbutton{up,down} event at them as well. + // aPressed is used for digital buttons, aValue is for analog buttons. + void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, + double aValue); + // When only a digital button is available the value will be synthesized. + void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed); + + // Update the state of |aAxis| for the gamepad at |aIndex| for all + // windows that are listening and visible, and fire a gamepadaxismove + // event at them as well. + void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue); + + // When shutting down the platform communications for gamepad, also reset the + // indexes. + void ResetGamepadIndexes(); + + //Add IPDL parent instance + void AddChannelParent(GamepadEventChannelParent* aParent); + + //Remove IPDL parent instance + void RemoveChannelParent(GamepadEventChannelParent* aParent); + + bool HasGamepadListeners(); + + void MaybeShutdown(); + + private: + GamepadPlatformService(); + ~GamepadPlatformService(); + template<class T> void NotifyGamepadChange(const T& aInfo); + void Cleanup(); + + // mGamepadIndex can only be accessed by monitor thread + uint32_t mGamepadIndex; + + // mChannelParents stores all the GamepadEventChannelParent instances + // which may be accessed by both background thread and monitor thread + // simultaneously, so we have a mutex to prevent race condition + nsTArray<RefPtr<GamepadEventChannelParent>> mChannelParents; + + // This mutex protects mChannelParents from race condition + // between background and monitor thread + Mutex mMutex; +}; + +} // namespace dom +} // namespace mozilla + +#endif
--- a/dom/gamepad/GamepadServiceTest.cpp +++ b/dom/gamepad/GamepadServiceTest.cpp @@ -1,86 +1,278 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GamepadServiceTest.h" -#include "mozilla/dom/GamepadService.h" -#include "mozilla/dom/GamepadFunctions.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/unused.h" + +#include "mozilla/dom/GamepadManager.h" +#include "mozilla/dom/GamepadPlatformService.h" +#include "mozilla/dom/GamepadServiceTestBinding.h" +#include "mozilla/dom/GamepadTestChannelChild.h" -using namespace mozilla::dom; +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" + +#include "mozilla/unused.h" + +#include "nsIObserver.h" +#include "nsIObserverService.h" + +namespace mozilla { +namespace dom { /* * Implementation of the test service. This is just to provide a simple binding - * of the GamepadService to JavaScript via XPCOM so that we can write Mochitests + * of the GamepadService to JavaScript via WebIDL so that we can write Mochitests * that add and remove fake gamepads, avoiding the platform-specific backends. */ -NS_IMPL_ISUPPORTS(GamepadServiceTest, nsIGamepadServiceTest) + +NS_IMPL_CYCLE_COLLECTION_CLASS(GamepadServiceTest) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(GamepadServiceTest, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -GamepadServiceTest* GamepadServiceTest::sSingleton = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(GamepadServiceTest, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(GamepadServiceTest) + NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(GamepadServiceTest, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(GamepadServiceTest, DOMEventTargetHelper) // static already_AddRefed<GamepadServiceTest> -GamepadServiceTest::CreateService() +GamepadServiceTest::CreateTestService(nsPIDOMWindowInner* aWindow) { - if (sSingleton == nullptr) { - sSingleton = new GamepadServiceTest(); - } - RefPtr<GamepadServiceTest> service = sSingleton; + MOZ_ASSERT(aWindow); + RefPtr<GamepadServiceTest> service = new GamepadServiceTest(aWindow); + service->InitPBackgroundActor(); return service.forget(); } -GamepadServiceTest::GamepadServiceTest() : - mService(GamepadService::GetService()) +void +GamepadServiceTest::Shutdown() { + MOZ_ASSERT(!mShuttingDown); + mShuttingDown = true; + DestroyPBackgroundActor(); + mWindow = nullptr; } -GamepadServiceTest::~GamepadServiceTest() +GamepadServiceTest::GamepadServiceTest(nsPIDOMWindowInner* aWindow) + : mService(GamepadManager::GetService()), + mWindow(aWindow), + mEventNumber(0), + mShuttingDown(false), + mChild(nullptr) +{} + +GamepadServiceTest::~GamepadServiceTest() {} + +void +GamepadServiceTest::InitPBackgroundActor() { + MOZ_ASSERT(!mChild); + PBackgroundChild *actor = BackgroundChild::GetForCurrentThread(); + //Try to get the PBackground Child actor + if (actor) { + ActorCreated(actor); + } else { + Unused << BackgroundChild::GetOrCreateForCurrentThread(this); + } } -NS_IMETHODIMP -GamepadServiceTest::AddGamepad(const char* aID, +void +GamepadServiceTest::DestroyPBackgroundActor() +{ + if (mChild) { + // If mChild exists, which means that IPDL channel + // has been created, our pending operations should + // be empty. + MOZ_ASSERT(mPendingOperations.IsEmpty()); + mChild->SendShutdownChannel(); + mChild = nullptr; + } else { + // If the IPDL channel has not been created and we + // want to destroy it now, just cancel all pending + // operations. + mPendingOperations.Clear(); + } +} + +already_AddRefed<Promise> +GamepadServiceTest::AddGamepad(const nsAString& aID, uint32_t aMapping, uint32_t aNumButtons, uint32_t aNumAxes, - uint32_t* aGamepadIndex) + ErrorResult& aRv) { - *aGamepadIndex = GamepadFunctions::AddGamepad(aID, - static_cast<GamepadMappingType>(aMapping), - aNumButtons, - aNumAxes); - return NS_OK; + if (mShuttingDown) { + return nullptr; + } + + GamepadAdded a(nsString(aID), 0, + (uint32_t)aMapping, aNumButtons, aNumAxes); + GamepadChangeEvent e(a); + nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow); + + RefPtr<Promise> p = Promise::Create(go, aRv); + if (aRv.Failed()) { + return nullptr; + } + + uint32_t id = ++mEventNumber; + if (mChild) { + mChild->AddPromise(id, p); + mChild->SendGamepadTestEvent(id, e); + } else { + PendingOperation op(id, e, p); + mPendingOperations.AppendElement(op); + } + return p.forget(); } -NS_IMETHODIMP GamepadServiceTest::RemoveGamepad(uint32_t aIndex) +void +GamepadServiceTest::RemoveGamepad(uint32_t aIndex) { - GamepadFunctions::RemoveGamepad(aIndex); - return NS_OK; + if (mShuttingDown) { + return; + } + + GamepadRemoved a(aIndex); + GamepadChangeEvent e(a); + + uint32_t id = ++mEventNumber; + if (mChild) { + mChild->SendGamepadTestEvent(id, e); + } else { + PendingOperation op(id, e); + mPendingOperations.AppendElement(op); + } +} + +void +GamepadServiceTest::NewButtonEvent(uint32_t aIndex, + uint32_t aButton, + bool aPressed) +{ + if (mShuttingDown) { + return; + } + + GamepadButtonInformation a(aIndex, aButton, aPressed, aPressed ? 1.0 : 0); + GamepadChangeEvent e(a); + + uint32_t id = ++mEventNumber; + if (mChild) { + mChild->SendGamepadTestEvent(id, e); + } else { + PendingOperation op(id, e); + mPendingOperations.AppendElement(op); + } } -NS_IMETHODIMP GamepadServiceTest::NewButtonEvent(uint32_t aIndex, - uint32_t aButton, - bool aPressed) +void +GamepadServiceTest::NewButtonValueEvent(uint32_t aIndex, + uint32_t aButton, + bool aPressed, + double aValue) { - GamepadFunctions::NewButtonEvent(aIndex, aButton, aPressed); - return NS_OK; + if (mShuttingDown) { + return; + } + + GamepadButtonInformation a(aIndex, aButton, aPressed, aValue); + GamepadChangeEvent e(a); + + uint32_t id = ++mEventNumber; + if (mChild) { + mChild->SendGamepadTestEvent(id, e); + } else { + PendingOperation op(id, e); + mPendingOperations.AppendElement(op); + } +} + +void +GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex, + uint32_t aAxis, + double aValue) +{ + if (mShuttingDown) { + return; + } + + GamepadAxisInformation a(aIndex, aAxis, aValue); + GamepadChangeEvent e(a); + + uint32_t id = ++mEventNumber; + if (mChild) { + mChild->SendGamepadTestEvent(id, e); + } else { + PendingOperation op(id, e); + mPendingOperations.AppendElement(op); + } } -NS_IMETHODIMP GamepadServiceTest::NewButtonValueEvent(uint32_t aIndex, - uint32_t aButton, - bool aPressed, - double aValue) +void +GamepadServiceTest::FlushPendingOperations() { - GamepadFunctions::NewButtonEvent(aIndex, aButton, aPressed, aValue); - return NS_OK; + for (uint32_t i=0; i < mPendingOperations.Length(); ++i) { + PendingOperation op = mPendingOperations[i]; + if (op.mPromise) { + mChild->AddPromise(op.mID, op.mPromise); + } + mChild->SendGamepadTestEvent(op.mID, op.mEvent); + } + mPendingOperations.Clear(); } -NS_IMETHODIMP GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex, - uint32_t aAxis, - double aValue) +void +GamepadServiceTest::ActorCreated(PBackgroundChild* aActor) { - GamepadFunctions::NewAxisMoveEvent(aIndex, aAxis, aValue); - return NS_OK; + MOZ_ASSERT(aActor); + // If we are shutting down, we don't need to create the + // IPDL child/parent pair anymore. + if (mShuttingDown) { + // mPendingOperations should be cleared in + // DestroyPBackgroundActor() + MOZ_ASSERT(mPendingOperations.IsEmpty()); + return; + } + + mChild = new GamepadTestChannelChild(); + PGamepadTestChannelChild* initedChild = + aActor->SendPGamepadTestChannelConstructor(mChild); + if (NS_WARN_IF(!initedChild)) { + ActorFailed(); + return; + } + FlushPendingOperations(); } +void +GamepadServiceTest::ActorFailed() +{ + MOZ_CRASH("Failed to create background child actor!"); +} + +JSObject* +GamepadServiceTest::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) +{ + return GamepadServiceTestBinding::Wrap(aCx, this, aGivenProto); +} + +} // dom +} // mozilla
--- a/dom/gamepad/GamepadServiceTest.h +++ b/dom/gamepad/GamepadServiceTest.h @@ -2,43 +2,86 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_GamepadServiceTest_h_ #define mozilla_dom_GamepadServiceTest_h_ -#include "nsIGamepadServiceTest.h" +#include "nsIIPCBackgroundChildCreateCallback.h" namespace mozilla { namespace dom { -class GamepadService; +class GamepadChangeEvent; +class GamepadManager; +class GamepadTestChannelChild; +class Promise; // Service for testing purposes -class GamepadServiceTest : public nsIGamepadServiceTest +class GamepadServiceTest final : public DOMEventTargetHelper, + public nsIIPCBackgroundChildCreateCallback { public: - NS_DECL_ISUPPORTS - NS_DECL_NSIGAMEPADSERVICETEST + NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GamepadServiceTest, + DOMEventTargetHelper) + + uint32_t NoMapping() const { return 0; } + uint32_t StandardMapping() const { return 1; } - GamepadServiceTest(); + already_AddRefed<Promise> AddGamepad(const nsAString& aID, + uint32_t aMapping, + uint32_t aNumButtons, + uint32_t aNumAxes, + ErrorResult& aRv); + void RemoveGamepad(uint32_t aIndex); + void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed); + void NewButtonValueEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, double aValue); + void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue); + void Shutdown(); - static already_AddRefed<GamepadServiceTest> CreateService(); + static already_AddRefed<GamepadServiceTest> CreateTestService(nsPIDOMWindowInner* aWindow); + nsPIDOMWindowInner* GetParentObject() const { return mWindow; } + JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override; private: - static GamepadServiceTest* sSingleton; + + // We need to asynchronously create IPDL channel, it is possible that + // we send commands before the channel is created, so we have to buffer + // them until the channel is created in that case. + struct PendingOperation { + explicit PendingOperation(const uint32_t& aID, + const GamepadChangeEvent& aEvent, + Promise* aPromise = nullptr) + : mID(aID), mEvent(aEvent), mPromise(aPromise) {} + uint32_t mID; + const GamepadChangeEvent& mEvent; + RefPtr<Promise> mPromise; + }; + // Hold a reference to the gamepad service so we don't have to worry about // execution order in tests. - RefPtr<GamepadService> mService; - virtual ~GamepadServiceTest(); + RefPtr<GamepadManager> mService; + nsCOMPtr<nsPIDOMWindowInner> mWindow; + nsTArray<PendingOperation> mPendingOperations; + uint32_t mEventNumber; + bool mShuttingDown; + + // IPDL Channel for us to send test events to GamepadPlatformService, it + // will only be used in this singleton class and deleted during the IPDL + // shutdown chain + GamepadTestChannelChild* MOZ_NON_OWNING_REF mChild; + + explicit GamepadServiceTest(nsPIDOMWindowInner* aWindow); + ~GamepadServiceTest(); + void InitPBackgroundActor(); + void DestroyPBackgroundActor(); + void FlushPendingOperations(); + }; } // namespace dom } // namespace mozilla -#define NS_GAMEPAD_TEST_CID \ -{ 0xfb1fcb57, 0xebab, 0x4cf4, \ -{ 0x96, 0x3b, 0x1e, 0x4d, 0xb8, 0x52, 0x16, 0x96 } } -#define NS_GAMEPAD_TEST_CONTRACTID "@mozilla.org/gamepad-test;1" - #endif
--- a/dom/gamepad/cocoa/CocoaGamepad.cpp +++ b/dom/gamepad/cocoa/CocoaGamepad.cpp @@ -2,30 +2,31 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // mostly derived from the Allegro source code at: // http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup -#include "mozilla/dom/GamepadFunctions.h" +#include "mozilla/dom/GamepadPlatformService.h" #include "mozilla/ArrayUtils.h" +#include "nsThreadUtils.h" #include <CoreFoundation/CoreFoundation.h> #include <IOKit/hid/IOHIDBase.h> #include <IOKit/hid/IOHIDKeys.h> #include <IOKit/hid/IOHIDManager.h> #include <stdio.h> #include <vector> namespace { using namespace mozilla; -using namespace mozilla::dom::GamepadFunctions; +using namespace mozilla::dom; using std::vector; struct Button { int id; bool analog; IOHIDElementRef element; CFIndex min; CFIndex max; @@ -195,37 +196,68 @@ void Gamepad::init(IOHIDDeviceRef device } } class DarwinGamepadService { private: IOHIDManagerRef mManager; vector<Gamepad> mGamepads; + //Workaround to support running in background thread + CFRunLoopRef mMonitorRunLoop; + nsCOMPtr<nsIThread> mMonitorThread; + static void DeviceAddedCallback(void* data, IOReturn result, void* sender, IOHIDDeviceRef device); static void DeviceRemovedCallback(void* data, IOReturn result, void* sender, IOHIDDeviceRef device); static void InputValueChangedCallback(void* data, IOReturn result, void* sender, IOHIDValueRef newValue); void DeviceAdded(IOHIDDeviceRef device); void DeviceRemoved(IOHIDDeviceRef device); void InputValueChanged(IOHIDValueRef value); + void StartupInternal(); public: DarwinGamepadService(); ~DarwinGamepadService(); void Startup(); void Shutdown(); + friend class DarwinGamepadServiceStartupRunnable; +}; + +class DarwinGamepadServiceStartupRunnable final : public Runnable +{ + private: + ~DarwinGamepadServiceStartupRunnable() {} + // This Runnable schedules startup of DarwinGamepadService + // in a new thread, pointer to DarwinGamepadService is only + // used by this Runnable within its thread. + DarwinGamepadService MOZ_NON_OWNING_REF *mService; + public: + explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService *service) + : mService(service) {} + NS_IMETHOD Run() override + { + MOZ_ASSERT(mService); + mService->StartupInternal(); + return NS_OK; + } }; void DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + size_t slot = size_t(-1); for (size_t i = 0; i < mGamepads.size(); i++) { if (mGamepads[i] == device) return; if (slot == size_t(-1) && mGamepads[i].empty()) slot = i; } @@ -245,28 +277,34 @@ DarwinGamepadService::DeviceAdded(IOHIDD int vendorId, productId; CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId); CFNumberGetValue(productIdRef, kCFNumberIntType, &productId); char product_name[128]; CFStringGetCString(productRef, product_name, sizeof(product_name), kCFStringEncodingASCII); char buffer[256]; sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name); - mGamepads[slot].mSuperIndex = AddGamepad(buffer, - mozilla::dom::GamepadMappingType::_empty, - (int)mGamepads[slot].numButtons(), - (int)mGamepads[slot].numAxes()); + uint32_t index = service->AddGamepad(buffer, + mozilla::dom::GamepadMappingType::_empty, + (int)mGamepads[slot].numButtons(), + (int)mGamepads[slot].numAxes()); + mGamepads[slot].mSuperIndex = index; } void DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } for (size_t i = 0; i < mGamepads.size(); i++) { if (mGamepads[i] == device) { - RemoveGamepad(mGamepads[i].mSuperIndex); + service->RemoveGamepad(mGamepads[i].mSuperIndex); mGamepads[i].clear(); return; } } } /* * Given a value from a d-pad (POV hat in USB HID terminology), @@ -305,57 +343,66 @@ UnpackDpad(int dpad_value, int min, int if (value > 0 && value < 4) { buttons[kRight] = true; } } void DarwinGamepadService::InputValueChanged(IOHIDValueRef value) { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + uint32_t value_length = IOHIDValueGetLength(value); if (value_length > 4) { // Workaround for bizarre issue with PS3 controllers that try to return // massive (30+ byte) values and crash IOHIDValueGetIntegerValue return; } IOHIDElementRef element = IOHIDValueGetElement(value); IOHIDDeviceRef device = IOHIDElementGetDevice(element); + for (unsigned i = 0; i < mGamepads.size(); i++) { Gamepad &gamepad = mGamepads[i]; if (gamepad == device) { if (gamepad.isDpad(element)) { const dpad_buttons& oldState = gamepad.getDpadState(); dpad_buttons newState = { false, false, false, false }; UnpackDpad(IOHIDValueGetIntegerValue(value), IOHIDElementGetLogicalMin(element), IOHIDElementGetLogicalMax(element), newState); const int numButtons = gamepad.numButtons(); for (unsigned b = 0; b < ArrayLength(newState); b++) { if (newState[b] != oldState[b]) { - NewButtonEvent(gamepad.mSuperIndex, numButtons - 4 + b, newState[b]); + service->NewButtonEvent(gamepad.mSuperIndex, + numButtons - 4 + b, + newState[b]); } } gamepad.setDpadState(newState); } else if (const Axis* axis = gamepad.lookupAxis(element)) { double d = IOHIDValueGetIntegerValue(value); double v = 2.0f * (d - axis->min) / (double)(axis->max - axis->min) - 1.0f; - NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v); + service->NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v); } else if (const Button* button = gamepad.lookupButton(element)) { int iv = IOHIDValueGetIntegerValue(value); bool pressed = iv != 0; double v = 0; if (button->analog) { double dv = iv; v = (dv - button->min) / (double)(button->max - button->min); } else { v = pressed ? 1.0 : 0.0; } - NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v); + service->NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v); } return; } } } void DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result, @@ -417,17 +464,18 @@ MatchingDictionary(UInt32 inUsagePage, U DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {} DarwinGamepadService::~DarwinGamepadService() { if (mManager != nullptr) CFRelease(mManager); } -void DarwinGamepadService::Startup() +void +DarwinGamepadService::StartupInternal() { if (mManager != nullptr) return; IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); CFMutableDictionaryRef criteria_arr[2]; @@ -474,26 +522,44 @@ void DarwinGamepadService::Startup() kCFRunLoopDefaultMode); IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone); if (rv != kIOReturnSuccess) { CFRelease(manager); return; } mManager = manager; + + // We held the handle of the CFRunLoop to make sure we + // can shut it down explicitly by CFRunLoopStop in another + // thread. + mMonitorRunLoop = CFRunLoopGetCurrent(); + + // CFRunLoopRun() is a blocking message loop when it's called in + // non-main thread so this thread cannot receive any other runnables + // and nsITimer timeout events after it's called. + CFRunLoopRun(); +} + +void DarwinGamepadService::Startup() +{ + Unused << NS_NewThread(getter_AddRefs(mMonitorThread), + new DarwinGamepadServiceStartupRunnable(this)); } void DarwinGamepadService::Shutdown() { IOHIDManagerRef manager = (IOHIDManagerRef)mManager; + CFRunLoopStop(mMonitorRunLoop); if (manager) { IOHIDManagerClose(manager, 0); CFRelease(manager); mManager = nullptr; } + mMonitorThread->Shutdown(); } } // namespace namespace mozilla { namespace dom { DarwinGamepadService* gService = nullptr;
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadEventChannelChild.cpp @@ -0,0 +1,42 @@ +/* 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 "GamepadEventChannelChild.h" +#include "mozilla/dom/GamepadManager.h" + +namespace mozilla { +namespace dom{ + +namespace { + +class GamepadUpdateRunnable final : public Runnable +{ + public: + explicit GamepadUpdateRunnable(const GamepadChangeEvent& aGamepadEvent) + : mEvent(aGamepadEvent) {} + NS_IMETHOD Run() override + { + RefPtr<GamepadManager> svc(GamepadManager::GetService()); + if (svc) { + svc->Update(mEvent); + } + return NS_OK; + } + protected: + GamepadChangeEvent mEvent; +}; + +} // namespace + +bool +GamepadEventChannelChild::RecvGamepadUpdate( + const GamepadChangeEvent& aGamepadEvent) +{ + nsresult rv; + rv = NS_DispatchToMainThread(new GamepadUpdateRunnable(aGamepadEvent)); + NS_WARN_IF(NS_FAILED(rv)); + return true; +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadEventChannelChild.h @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/dom/PGamepadEventChannelChild.h" + +#ifndef mozilla_dom_GamepadEventChannelChild_h_ +#define mozilla_dom_GamepadEventChannelChild_h_ + +namespace mozilla{ +namespace dom{ + +class GamepadEventChannelChild final : public PGamepadEventChannelChild +{ + public: + GamepadEventChannelChild() {} + ~GamepadEventChannelChild() {} + virtual bool + RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override; +}; + +}// namespace dom +}// namespace mozilla + +#endif
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp @@ -0,0 +1,101 @@ +/* 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 "GamepadEventChannelParent.h" +#include "GamepadPlatformService.h" +#include "mozilla/dom/GamepadMonitoring.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace dom { + +using namespace mozilla::ipc; + +namespace { + +class SendGamepadUpdateRunnable final : public Runnable +{ + private: + ~SendGamepadUpdateRunnable() {} + RefPtr<GamepadEventChannelParent> mParent; + GamepadChangeEvent mEvent; + public: + SendGamepadUpdateRunnable(GamepadEventChannelParent* aParent, + GamepadChangeEvent aEvent) + : mEvent(aEvent) + { + MOZ_ASSERT(aParent); + mParent = aParent; + } + NS_IMETHOD Run() override + { + AssertIsOnBackgroundThread(); + if(mParent->HasGamepadListener()) { + Unused << mParent->SendGamepadUpdate(mEvent); + } + return NS_OK; + } +}; + +} // namespace + +GamepadEventChannelParent::GamepadEventChannelParent() + : mHasGamepadListener(false) +{ + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + MOZ_ASSERT(service); + service->AddChannelParent(this); + mBackgroundThread = NS_GetCurrentThread(); +} + +bool +GamepadEventChannelParent::RecvGamepadListenerAdded() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mHasGamepadListener); + mHasGamepadListener = true; + StartGamepadMonitoring(); + return true; +} + +bool +GamepadEventChannelParent::RecvGamepadListenerRemoved() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mHasGamepadListener); + mHasGamepadListener = false; + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + MOZ_ASSERT(service); + service->RemoveChannelParent(this); + Unused << Send__delete__(this); + return true; +} + +void +GamepadEventChannelParent::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnBackgroundThread(); + + // It may be called because IPDL child side crashed, we'll + // not receive RecvGamepadListenerRemoved in that case + if (mHasGamepadListener) { + mHasGamepadListener = false; + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + MOZ_ASSERT(service); + service->RemoveChannelParent(this); + } + MaybeStopGamepadMonitoring(); +} + +void +GamepadEventChannelParent::DispatchUpdateEvent(const GamepadChangeEvent& aEvent) +{ + mBackgroundThread->Dispatch(new SendGamepadUpdateRunnable(this, aEvent), + NS_DISPATCH_NORMAL); +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadEventChannelParent.h @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/dom/PGamepadEventChannelParent.h" + +#ifndef mozilla_dom_GamepadEventChannelParent_h_ +#define mozilla_dom_GamepadEventChannelParent_h_ + +namespace mozilla{ +namespace dom{ + +class GamepadEventChannelParent final : public PGamepadEventChannelParent +{ + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelParent) + GamepadEventChannelParent(); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual bool RecvGamepadListenerAdded() override; + virtual bool RecvGamepadListenerRemoved() override; + void DispatchUpdateEvent(const GamepadChangeEvent& aEvent); + bool HasGamepadListener() const { return mHasGamepadListener; } + private: + ~GamepadEventChannelParent() {} + bool mHasGamepadListener; + nsCOMPtr<nsIThread> mBackgroundThread; +}; + +}// namespace dom +}// namespace mozilla + +#endif
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadEventTypes.ipdlh @@ -0,0 +1,41 @@ +/* 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/. */ + +namespace mozilla { +namespace dom { + +struct GamepadAdded { + nsString id; + uint32_t index; + uint32_t mapping; + uint32_t num_buttons; + uint32_t num_axes; +}; + +struct GamepadRemoved { + uint32_t index; +}; + +struct GamepadAxisInformation { + uint32_t index; + uint32_t axis; + double value; +}; + +struct GamepadButtonInformation { + uint32_t index; + uint32_t button; + bool pressed; + double value; +}; + +union GamepadChangeEvent { + GamepadAdded; + GamepadRemoved; + GamepadAxisInformation; + GamepadButtonInformation; +}; + +} // namespace dom +} // namespace mozilla \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadTestChannelChild.cpp @@ -0,0 +1,32 @@ +/* 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 "GamepadTestChannelChild.h" + +namespace mozilla { +namespace dom { + +void +GamepadTestChannelChild::AddPromise(const uint32_t& aID, Promise* aPromise) +{ + MOZ_ASSERT(!mPromiseList.Get(aID, nullptr)); + mPromiseList.Put(aID, aPromise); +} + +bool +GamepadTestChannelChild::RecvReplyGamepadIndex(const uint32_t& aID, + const uint32_t& aIndex) +{ + RefPtr<Promise> p; + if (!mPromiseList.Get(aID, getter_AddRefs(p))) { + MOZ_CRASH("We should always have a promise."); + } + + p->MaybeResolve(aIndex); + mPromiseList.Remove(aID); + return true; +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadTestChannelChild.h @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/PGamepadTestChannelChild.h" +#include "mozilla/dom/Promise.h" + +#ifndef mozilla_dom_GamepadTestChannelChild_h_ +#define mozilla_dom_GamepadTestChannelChild_h_ + +namespace mozilla { +namespace dom { + +class GamepadTestChannelChild final : public PGamepadTestChannelChild +{ + public: + GamepadTestChannelChild() {} + ~GamepadTestChannelChild() {} + void AddPromise(const uint32_t& aID, Promise* aPromise); + private: + virtual bool RecvReplyGamepadIndex(const uint32_t& aID, + const uint32_t& aIndex) override; + nsRefPtrHashtable<nsUint32HashKey, Promise> mPromiseList; +}; + +}// namespace dom +}// namespace mozilla + +#endif
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadTestChannelParent.cpp @@ -0,0 +1,63 @@ +/* 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 "GamepadTestChannelParent.h" + +#include "mozilla/dom/GamepadPlatformService.h" +#include "mozilla/unused.h" + +namespace mozilla { +namespace dom { + +bool +GamepadTestChannelParent::RecvGamepadTestEvent(const uint32_t& aID, + const GamepadChangeEvent& aEvent) +{ + mozilla::ipc::AssertIsOnBackgroundThread(); + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + MOZ_ASSERT(service); + if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) { + const GamepadAdded& a = aEvent.get_GamepadAdded(); + nsCString gamepadID; + LossyCopyUTF16toASCII(a.id(), gamepadID); + uint32_t index = service->AddGamepad(gamepadID.get(), + (GamepadMappingType)a.mapping(), + a.num_buttons(), + a.num_axes()); + if (!mShuttingdown) { + Unused << SendReplyGamepadIndex(aID, index); + } + return true; + } + if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) { + const GamepadRemoved& a = aEvent.get_GamepadRemoved(); + service->RemoveGamepad(a.index()); + return true; + } + if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) { + const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation(); + service->NewButtonEvent(a.index(), a.button(), a.pressed(), a.value()); + return true; + } + if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) { + const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation(); + service->NewAxisMoveEvent(a.index(), a.axis(), a.value()); + return true; + } + + NS_WARNING("Unknown event type."); + return false; +} + +bool +GamepadTestChannelParent::RecvShutdownChannel() +{ + mShuttingdown = true; + Unused << Send__delete__(this); + return true; +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/GamepadTestChannelParent.h @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/PGamepadTestChannelParent.h" + +#ifndef mozilla_dom_GamepadTestChannelParent_h_ +#define mozilla_dom_GamepadTestChannelParent_h_ + +namespace mozilla { +namespace dom { + +class GamepadTestChannelParent final : public PGamepadTestChannelParent +{ + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadTestChannelParent) + GamepadTestChannelParent() + : mShuttingdown(false) {} + virtual void ActorDestroy(ActorDestroyReason aWhy) override {} + virtual bool + RecvGamepadTestEvent(const uint32_t& aID, + const GamepadChangeEvent& aGamepadEvent) override; + virtual bool + RecvShutdownChannel() override; + private: + ~GamepadTestChannelParent() {} + bool mShuttingdown; +}; + +}// namespace dom +}// namespace mozilla + +#endif
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/PGamepadEventChannel.ipdl @@ -0,0 +1,21 @@ +/* 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 protocol PBackground; +include GamepadEventTypes; + +namespace mozilla { +namespace dom { + +async protocol PGamepadEventChannel { + manager PBackground; + parent: + async GamepadListenerAdded(); + async GamepadListenerRemoved(); + child: + async __delete__(); + async GamepadUpdate(GamepadChangeEvent aGamepadEvent); +}; + +} +}
new file mode 100644 --- /dev/null +++ b/dom/gamepad/ipc/PGamepadTestChannel.ipdl @@ -0,0 +1,21 @@ +/* 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 protocol PBackground; +include GamepadEventTypes; + +namespace mozilla { +namespace dom { + +async protocol PGamepadTestChannel { + manager PBackground; + parent: + async GamepadTestEvent(uint32_t aID, GamepadChangeEvent aGamepadEvent); + async ShutdownChannel(); + child: + async __delete__(); + async ReplyGamepadIndex(uint32_t aID, uint32_t aIndex); +}; + +} +}
--- a/dom/gamepad/linux/LinuxGamepad.cpp +++ b/dom/gamepad/linux/LinuxGamepad.cpp @@ -14,22 +14,22 @@ #include <glib.h> #include <linux/joystick.h> #include <stdio.h> #include <stdint.h> #include <sys/ioctl.h> #include <unistd.h> #include "nscore.h" -#include "mozilla/dom/GamepadFunctions.h" +#include "mozilla/dom/GamepadPlatformService.h" #include "udev.h" namespace { -using namespace mozilla::dom::GamepadFunctions; +using namespace mozilla::dom; using mozilla::udev_lib; using mozilla::udev_device; using mozilla::udev_list_entry; using mozilla::udev_enumerate; using mozilla::udev_monitor; static const float kMaxAxisValue = 32767.0; static const char kJoystickPath[] = "/dev/input/js"; @@ -81,16 +81,22 @@ private: }; // singleton instance LinuxGamepadService* gService = nullptr; void LinuxGamepadService::AddDevice(struct udev_device* dev) { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + const char* devpath = mUdev.udev_device_get_devnode(dev); if (!devpath) { return; } // Ensure that this device hasn't already been added. for (unsigned int i = 0; i < mGamepads.Length(); i++) { if (strcmp(mGamepads[i].devpath, devpath) == 0) { @@ -134,43 +140,49 @@ LinuxGamepadService::AddDevice(struct ud name); char numAxes = 0, numButtons = 0; ioctl(fd, JSIOCGAXES, &numAxes); gamepad.numAxes = numAxes; ioctl(fd, JSIOCGBUTTONS, &numButtons); gamepad.numButtons = numButtons; - gamepad.index = AddGamepad(gamepad.idstring, - mozilla::dom::GamepadMappingType::_empty, - gamepad.numButtons, - gamepad.numAxes); + gamepad.index = service->AddGamepad(gamepad.idstring, + mozilla::dom::GamepadMappingType::_empty, + gamepad.numButtons, + gamepad.numAxes); gamepad.source_id = g_io_add_watch(channel, GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP), OnGamepadData, GINT_TO_POINTER(gamepad.index)); g_io_channel_unref(channel); mGamepads.AppendElement(gamepad); } void LinuxGamepadService::RemoveDevice(struct udev_device* dev) { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + const char* devpath = mUdev.udev_device_get_devnode(dev); if (!devpath) { return; } for (unsigned int i = 0; i < mGamepads.Length(); i++) { if (strcmp(mGamepads[i].devpath, devpath) == 0) { g_source_remove(mGamepads[i].source_id); - RemoveGamepad(mGamepads[i].index); + service->RemoveGamepad(mGamepads[i].index); mGamepads.RemoveElementAt(i); break; } } } void LinuxGamepadService::ScanForDevices() @@ -290,16 +302,21 @@ LinuxGamepadService::ReadUdevChange() } // static gboolean LinuxGamepadService::OnGamepadData(GIOChannel* source, GIOCondition condition, gpointer data) { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return TRUE; + } int index = GPOINTER_TO_INT(data); //TODO: remove gamepad? if (condition & G_IO_ERR || condition & G_IO_HUP) return FALSE; while (true) { struct js_event event; gsize count; @@ -315,21 +332,21 @@ LinuxGamepadService::OnGamepadData(GIOCh //TODO: store device state? if (event.type & JS_EVENT_INIT) { continue; } switch (event.type) { case JS_EVENT_BUTTON: - NewButtonEvent(index, event.number, !!event.value); + service->NewButtonEvent(index, event.number, !!event.value); break; case JS_EVENT_AXIS: - NewAxisMoveEvent(index, event.number, - ((float)event.value) / kMaxAxisValue); + service->NewAxisMoveEvent(index, event.number, + ((float)event.value) / kMaxAxisValue); break; } } return TRUE; } // static
--- a/dom/gamepad/moz.build +++ b/dom/gamepad/moz.build @@ -2,29 +2,37 @@ # vim: set filetype=python: # 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/. EXPORTS.mozilla.dom += [ 'Gamepad.h', 'GamepadButton.h', - 'GamepadFunctions.h', + 'GamepadManager.h', 'GamepadMonitoring.h', - 'GamepadService.h', - 'GamepadServiceTest.h' + 'GamepadPlatformService.h', + 'GamepadServiceTest.h', + 'ipc/GamepadEventChannelChild.h', + 'ipc/GamepadEventChannelParent.h', + 'ipc/GamepadTestChannelChild.h', + 'ipc/GamepadTestChannelParent.h' ] UNIFIED_SOURCES = [ 'Gamepad.cpp', 'GamepadButton.cpp', - 'GamepadFunctions.cpp', + 'GamepadManager.cpp', 'GamepadMonitoring.cpp', - 'GamepadService.cpp', - 'GamepadServiceTest.cpp' + 'GamepadPlatformService.cpp', + 'GamepadServiceTest.cpp', + 'ipc/GamepadEventChannelChild.cpp', + 'ipc/GamepadEventChannelParent.cpp', + 'ipc/GamepadTestChannelChild.cpp', + 'ipc/GamepadTestChannelParent.cpp' ] if CONFIG['MOZ_GAMEPAD_BACKEND'] == 'stub': UNIFIED_SOURCES += [ 'fallback/FallbackGamepad.cpp' ] elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'cocoa': UNIFIED_SOURCES += [ @@ -38,16 +46,26 @@ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'l UNIFIED_SOURCES += [ 'linux/LinuxGamepad.cpp' ] elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'android': UNIFIED_SOURCES += [ 'android/AndroidGamepad.cpp' ] +LOCAL_INCLUDES += [ + 'ipc', +] + +IPDL_SOURCES += [ + 'ipc/GamepadEventTypes.ipdlh', + 'ipc/PGamepadEventChannel.ipdl', + 'ipc/PGamepadTestChannel.ipdl' +] + include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '/dom/base', ] CFLAGS += CONFIG['GLIB_CFLAGS']
--- a/dom/gamepad/windows/WindowsGamepad.cpp +++ b/dom/gamepad/windows/WindowsGamepad.cpp @@ -11,28 +11,30 @@ #define UNICODE #endif #include <windows.h> #include <hidsdi.h> #include <stdio.h> #include <xinput.h> #include "nsIComponentManager.h" -#include "nsIObserver.h" -#include "nsIObserverService.h" #include "nsITimer.h" #include "nsTArray.h" +#include "nsThreadUtils.h" + #include "mozilla/ArrayUtils.h" -#include "mozilla/dom/GamepadFunctions.h" #include "mozilla/Services.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/dom/GamepadPlatformService.h" + namespace { +using namespace mozilla; using namespace mozilla::dom; -using namespace mozilla::dom::GamepadFunctions; using mozilla::ArrayLength; // USB HID usage tables, page 1 (Hat switch) const unsigned kUsageDpad = 0x39; // USB HID usage tables, page 1, 0x30 = X const unsigned kFirstAxis = 0x30; // USB HID usage tables @@ -90,17 +92,22 @@ const size_t kNumMappings = ArrayLength( enum GamepadType { kNoGamepad = 0, kRawInputGamepad, kXInputGamepad }; class WindowsGamepadService; -WindowsGamepadService* gService = nullptr; +// This pointer holds a windows gamepad backend service, +// it will be created and destroyed by background thread and +// used by gMonitorThread +WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr; +nsCOMPtr<nsIThread> gMonitorThread = nullptr; +static bool sIsShutdown = false; struct Gamepad { GamepadType type; // Handle to raw input device HANDLE handle; // XInput Index of the user's controller. Passed to XInputGetState. @@ -250,70 +257,16 @@ SupportedUsage(USHORT page, USHORT usage for (unsigned i = 0; i < ArrayLength(kUsagePages); i++) { if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) { return true; } } return false; } -class Observer : public nsIObserver { -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER - - Observer(WindowsGamepadService& svc) : mSvc(svc), - mObserving(true) - { - nsresult rv; - mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - nsCOMPtr<nsIObserverService> observerService = - mozilla::services::GetObserverService(); - observerService->AddObserver(this, - NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, - false); - } - - void Stop() - { - if (mTimer) { - mTimer->Cancel(); - } - if (mObserving) { - nsCOMPtr<nsIObserverService> observerService = - mozilla::services::GetObserverService(); - observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); - mObserving = false; - } - } - - void SetDeviceChangeTimer() - { - // Set stable timer, since we will get multiple devices-changed - // notifications at once - if (mTimer) { - mTimer->Cancel(); - mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT); - } - } - -private: - virtual ~Observer() - { - Stop(); - } - - // Gamepad service owns us, we just hold a reference back to it. - WindowsGamepadService& mSvc; - nsCOMPtr<nsITimer> mTimer; - bool mObserving; -}; - -NS_IMPL_ISUPPORTS(Observer, nsIObserver); - class HIDLoader { public: HIDLoader() : mModule(LoadLibraryW(L"hid.dll")), mHidD_GetProductString(nullptr), mHidP_GetCaps(nullptr), mHidP_GetButtonCaps(nullptr), mHidP_GetValueCaps(nullptr), mHidP_GetUsages(nullptr), @@ -355,69 +308,65 @@ public: decltype(HidP_GetUsages) *mHidP_GetUsages; decltype(HidP_GetUsageValue) *mHidP_GetUsageValue; decltype(HidP_GetScaledUsageValue) *mHidP_GetScaledUsageValue; private: HMODULE mModule; }; -class WindowsGamepadService { -public: - WindowsGamepadService(); +class WindowsGamepadService +{ + public: + WindowsGamepadService() + { + mXInputTimer = do_CreateInstance("@mozilla.org/timer;1"); + mDeviceChangeTimer = do_CreateInstance("@mozilla.org/timer;1"); + } virtual ~WindowsGamepadService() { Cleanup(); } - enum DeviceChangeType { - DeviceChangeNotification, - DeviceChangeStable - }; - void DevicesChanged(DeviceChangeType type); + void DevicesChanged(bool aIsStablizing); void Startup(); void Shutdown(); // Parse gamepad input from a WM_INPUT message. bool HandleRawInput(HRAWINPUT handle); -private: + static void XInputMessageLoopOnceCallback(nsITimer *aTimer, void* aClosure); + static void DevicesChangeCallback(nsITimer *aTimer, void* aService); + + private: void ScanForDevices(); // Look for connected raw input devices. void ScanForRawInputDevices(); // Look for connected XInput devices. bool ScanForXInputDevices(); bool HaveXInputGamepad(int userIndex); - // Timer callback for XInput polling - static void XInputPollTimerCallback(nsITimer* aTimer, void* aClosure); + bool mIsXInputMonitoring; void PollXInput(); void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state); // Get information about a raw input gamepad. bool GetRawGamepad(HANDLE handle); void Cleanup(); // List of connected devices. nsTArray<Gamepad> mGamepads; - RefPtr<Observer> mObserver; - nsCOMPtr<nsITimer> mXInputPollTimer; - HIDLoader mHID; XInputLoader mXInput; + + nsCOMPtr<nsITimer> mXInputTimer; + nsCOMPtr<nsITimer> mDeviceChangeTimer; }; -WindowsGamepadService::WindowsGamepadService() -{ - nsresult rv; - mXInputPollTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - mObserver = new Observer(*this); -} - void WindowsGamepadService::ScanForRawInputDevices() { if (!mHID) { return; } UINT numDevices; @@ -434,16 +383,40 @@ WindowsGamepadService::ScanForRawInputDe for (unsigned i = 0; i < devices.Length(); i++) { if (devices[i].dwType == RIM_TYPEHID) { GetRawGamepad(devices[i].hDevice); } } } +// static +void +WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer *aTimer, + void* aService) +{ + MOZ_ASSERT(aService); + WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService); + self->PollXInput(); + if (self->mIsXInputMonitoring) { + aTimer->Cancel(); + aTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, self, + kXInputPollInterval, nsITimer::TYPE_ONE_SHOT); + } +} + +// static +void +WindowsGamepadService::DevicesChangeCallback(nsITimer *aTimer, void* aService) +{ + MOZ_ASSERT(aService); + WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService); + self->DevicesChanged(false); +} + bool WindowsGamepadService::HaveXInputGamepad(int userIndex) { for (unsigned int i = 0; i < mGamepads.Length(); i++) { if (mGamepads[i].type == kXInputGamepad && mGamepads[i].userIndex == userIndex) { mGamepads[i].present = true; return true; @@ -453,16 +426,22 @@ WindowsGamepadService::HaveXInputGamepad } bool WindowsGamepadService::ScanForXInputDevices() { MOZ_ASSERT(mXInput, "XInput should be present!"); bool found = false; + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return found; + } + for (int i = 0; i < XUSER_MAX_COUNT; i++) { XINPUT_STATE state = {}; if (mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) { continue; } found = true; // See if this device is already present in our list. if (HaveXInputGamepad(i)) { @@ -472,65 +451,63 @@ WindowsGamepadService::ScanForXInputDevi // Not already present, add it. Gamepad gamepad = {}; gamepad.type = kXInputGamepad; gamepad.present = true; gamepad.state = state; gamepad.userIndex = i; gamepad.numButtons = kStandardGamepadButtons; gamepad.numAxes = kStandardGamepadAxes; - gamepad.id = AddGamepad("xinput", - GamepadMappingType::Standard, - kStandardGamepadButtons, - kStandardGamepadAxes); + gamepad.id = service->AddGamepad("xinput", + GamepadMappingType::Standard, + kStandardGamepadButtons, + kStandardGamepadAxes); mGamepads.AppendElement(gamepad); } return found; } void WindowsGamepadService::ScanForDevices() { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + for (int i = mGamepads.Length() - 1; i >= 0; i--) { mGamepads[i].present = false; } if (mHID) { ScanForRawInputDevices(); } if (mXInput) { - mXInputPollTimer->Cancel(); + mXInputTimer->Cancel(); if (ScanForXInputDevices()) { - mXInputPollTimer->InitWithFuncCallback(XInputPollTimerCallback, - this, - kXInputPollInterval, - nsITimer::TYPE_REPEATING_SLACK); + mIsXInputMonitoring = true; + mXInputTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, this, + kXInputPollInterval, + nsITimer::TYPE_ONE_SHOT); + } else { + mIsXInputMonitoring = false; } } // Look for devices that are no longer present and remove them. for (int i = mGamepads.Length() - 1; i >= 0; i--) { if (!mGamepads[i].present) { - RemoveGamepad(mGamepads[i].id); + service->RemoveGamepad(mGamepads[i].id); mGamepads.RemoveElementAt(i); } } } -// static -void -WindowsGamepadService::XInputPollTimerCallback(nsITimer* aTimer, - void* aClosure) -{ - WindowsGamepadService* self = - reinterpret_cast<WindowsGamepadService*>(aClosure); - self->PollXInput(); -} - void WindowsGamepadService::PollXInput() { for (unsigned int i = 0; i < mGamepads.Length(); i++) { if (mGamepads[i].type != kXInputGamepad) { continue; } @@ -540,60 +517,65 @@ WindowsGamepadService::PollXInput() && state.dwPacketNumber != mGamepads[i].state.dwPacketNumber) { CheckXInputChanges(mGamepads[i], state); } } } void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state) { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } // Handle digital buttons first for (size_t b = 0; b < kNumMappings; b++) { if (state.Gamepad.wButtons & kXIButtonMap[b].button && !(gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button)) { // Button pressed - NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, true); + service->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, true); } else if (!(state.Gamepad.wButtons & kXIButtonMap[b].button) && gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button) { // Button released - NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, false); + service->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, false); } } // Then triggers if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) { bool pressed = state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; - NewButtonEvent(gamepad.id, kButtonLeftTrigger, - pressed, state.Gamepad.bLeftTrigger / 255.0); + service->NewButtonEvent(gamepad.id, kButtonLeftTrigger, + pressed, state.Gamepad.bLeftTrigger / 255.0); } if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) { bool pressed = state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; - NewButtonEvent(gamepad.id, kButtonRightTrigger, - pressed, state.Gamepad.bRightTrigger / 255.0); + service->NewButtonEvent(gamepad.id, kButtonRightTrigger, + pressed, state.Gamepad.bRightTrigger / 255.0); } // Finally deal with analog sticks // TODO: bug 1001955 - Support deadzones. if (state.Gamepad.sThumbLX != gamepad.state.Gamepad.sThumbLX) { - NewAxisMoveEvent(gamepad.id, kLeftStickXAxis, - state.Gamepad.sThumbLX / 32767.0); + service->NewAxisMoveEvent(gamepad.id, kLeftStickXAxis, + state.Gamepad.sThumbLX / 32767.0); } if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) { - NewAxisMoveEvent(gamepad.id, kLeftStickYAxis, - -1.0 * state.Gamepad.sThumbLY / 32767.0); + service->NewAxisMoveEvent(gamepad.id, kLeftStickYAxis, + -1.0 * state.Gamepad.sThumbLY / 32767.0); } if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) { - NewAxisMoveEvent(gamepad.id, kRightStickXAxis, - state.Gamepad.sThumbRX / 32767.0); + service->NewAxisMoveEvent(gamepad.id, kRightStickXAxis, + state.Gamepad.sThumbRX / 32767.0); } if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) { - NewAxisMoveEvent(gamepad.id, kRightStickYAxis, - -1.0 * state.Gamepad.sThumbRY / 32767.0); + service->NewAxisMoveEvent(gamepad.id, kRightStickYAxis, + -1.0 * state.Gamepad.sThumbRY / 32767.0); } gamepad.state = state; } // Used to sort a list of axes by HID usage. class HidValueComparator { public: bool Equals(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const @@ -607,16 +589,22 @@ public: } return c1.UsagePage < c2.UsagePage; } }; bool WindowsGamepadService::GetRawGamepad(HANDLE handle) { + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return true; + } + if (!mHID) { return false; } for (unsigned i = 0; i < mGamepads.Length(); i++) { if (mGamepads[i].type == kRawInputGamepad && mGamepads[i].handle == handle) { mGamepads[i].present = true; return true; @@ -743,32 +731,37 @@ WindowsGamepadService::GetRawGamepad(HAN if (i >= kMaxAxes) { break; } gamepad.axes[i].caps = axes[i]; } gamepad.type = kRawInputGamepad; gamepad.handle = handle; gamepad.present = true; - - gamepad.id = GamepadFunctions::AddGamepad(gamepad_id, - GamepadMappingType::_empty, - gamepad.numButtons, - gamepad.numAxes); + gamepad.id = service->AddGamepad(gamepad_id, + GamepadMappingType::_empty, + gamepad.numButtons, + gamepad.numAxes); mGamepads.AppendElement(gamepad); return true; } bool WindowsGamepadService::HandleRawInput(HRAWINPUT handle) { if (!mHID) { return false; } + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (service) { + return true; + } + // First, get data from the handle UINT size; GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)); nsTArray<uint8_t> data(size); data.SetLength(size); if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size, sizeof(RAWINPUTHEADER)) == kRawInputError) { return false; @@ -816,17 +809,17 @@ WindowsGamepadService::HandleRawInput(HR ULONG value; if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) { UnpackDpad(static_cast<LONG>(value), gamepad, buttons); } } for (unsigned i = 0; i < gamepad->numButtons; i++) { if (gamepad->buttons[i] != buttons[i]) { - NewButtonEvent(gamepad->id, i, buttons[i]); + service->NewButtonEvent(gamepad->id, i, buttons[i]); gamepad->buttons[i] = buttons[i]; } } // Get all axis values. for (unsigned i = 0; i < gamepad->numAxes; i++) { double new_value; if (gamepad->axes[i].caps.LogicalMin < 0) { @@ -836,31 +829,30 @@ LONG value; &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) { continue; } new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin, gamepad->axes[i].caps.LogicalMax); - } - else { + } else { ULONG value; if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0, gamepad->axes[i].caps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) { continue; } new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin, gamepad->axes[i].caps.LogicalMax); } if (gamepad->axes[i].value != new_value) { - NewAxisMoveEvent(gamepad->id, i, new_value); + service->NewAxisMoveEvent(gamepad->id, i, new_value); gamepad->axes[i].value = new_value; } } return true; } void @@ -873,51 +865,39 @@ void WindowsGamepadService::Shutdown() { Cleanup(); } void WindowsGamepadService::Cleanup() { - if (mXInputPollTimer) { - mXInputPollTimer->Cancel(); + mIsXInputMonitoring = false; + if (mXInputTimer) { + mXInputTimer->Cancel(); + } + if (mDeviceChangeTimer) { + mDeviceChangeTimer->Cancel(); } mGamepads.Clear(); - if (mObserver) { - mObserver->Stop(); - mObserver = nullptr; - } } void -WindowsGamepadService::DevicesChanged(DeviceChangeType type) +WindowsGamepadService::DevicesChanged(bool aIsStablizing) { - if (type == DeviceChangeNotification) { - if (mObserver) { - mObserver->SetDeviceChangeTimer(); - } - } else if (type == DeviceChangeStable) { + if (aIsStablizing) { + mDeviceChangeTimer->Cancel(); + mDeviceChangeTimer->InitWithFuncCallback(DevicesChangeCallback, this, + kDevicesChangedStableDelay, + nsITimer::TYPE_ONE_SHOT); + } else { ScanForDevices(); } } -NS_IMETHODIMP -Observer::Observe(nsISupports* aSubject, - const char* aTopic, - const char16_t* aData) -{ - if (strcmp(aTopic, "timer-callback") == 0) { - mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable); - } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) { - Stop(); - } - return NS_OK; -} - HWND sHWnd = nullptr; bool RegisterRawInput(HWND hwnd, bool enable) { nsTArray<RAWINPUTDEVICE> rid(ArrayLength(kUsagePages)); rid.SetLength(ArrayLength(kUsagePages)); @@ -945,74 +925,141 @@ GamepadWindowProc(HWND hwnd, UINT msg, W const unsigned int DBT_DEVNODES_CHANGED = 0x7; switch (msg) { case WM_DEVICECHANGE: if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE || wParam == DBT_DEVNODES_CHANGED) { if (gService) { - gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification); + gService->DevicesChanged(true); } } break; case WM_INPUT: if (gService) { gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam)); } break; } return DefWindowProc(hwnd, msg, wParam, lParam); } +class WindowGamepadMessageLoopOnceRunnable final : public Runnable +{ +public: + WindowGamepadMessageLoopOnceRunnable() {} + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread); + MSG msg; + while (PeekMessageW(&msg, sHWnd, 0, 0, PM_REMOVE) > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if (!sIsShutdown) { + NS_DispatchToCurrentThread(new WindowGamepadMessageLoopOnceRunnable()); + } + return NS_OK; + } +private: + ~WindowGamepadMessageLoopOnceRunnable() {} +}; + +class StartWindowsGamepadServiceRunnable final : public Runnable +{ +public: + StartWindowsGamepadServiceRunnable() {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread); + gService = new WindowsGamepadService(); + gService->Startup(); + + if (sHWnd == nullptr) { + WNDCLASSW wc; + HMODULE hSelf = GetModuleHandle(nullptr); + + if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) { + ZeroMemory(&wc, sizeof(WNDCLASSW)); + wc.hInstance = hSelf; + wc.lpfnWndProc = GamepadWindowProc; + wc.lpszClassName = L"MozillaGamepadClass"; + RegisterClassW(&wc); + } + + sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", + 0, 0, 0, 0, 0, + nullptr, nullptr, hSelf, nullptr); + RegisterRawInput(sHWnd, true); + } + + // Explicitly start the message loop + NS_DispatchToCurrentThread(new WindowGamepadMessageLoopOnceRunnable()); + + return NS_OK; + } +private: + ~StartWindowsGamepadServiceRunnable() {} +}; + +class StopWindowsGamepadServiceRunnable final : public Runnable +{ + public: + StopWindowsGamepadServiceRunnable() {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread); + if (sHWnd) { + RegisterRawInput(sHWnd, false); + DestroyWindow(sHWnd); + sHWnd = nullptr; + } + + gService->Shutdown(); + delete gService; + gService = nullptr; + + return NS_OK; + } + private: + ~StopWindowsGamepadServiceRunnable() {} +}; + } // namespace namespace mozilla { namespace dom { -void StartGamepadMonitoring() +using namespace mozilla::ipc; + +void +StartGamepadMonitoring() { - if (gService) { + AssertIsOnBackgroundThread(); + + if (gMonitorThread || gService) { return; } - - gService = new WindowsGamepadService(); - gService->Startup(); - - if (sHWnd == nullptr) { - WNDCLASSW wc; - HMODULE hSelf = GetModuleHandle(nullptr); - - if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) { - ZeroMemory(&wc, sizeof(WNDCLASSW)); - wc.hInstance = hSelf; - wc.lpfnWndProc = GamepadWindowProc; - wc.lpszClassName = L"MozillaGamepadClass"; - RegisterClassW(&wc); - } - - sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", - 0, 0, 0, 0, 0, - nullptr, nullptr, hSelf, nullptr); - RegisterRawInput(sHWnd, true); - } + sIsShutdown = false; + NS_NewThread(getter_AddRefs(gMonitorThread)); + gMonitorThread->Dispatch(new StartWindowsGamepadServiceRunnable(), + NS_DISPATCH_NORMAL); } -void StopGamepadMonitoring() +void +StopGamepadMonitoring() { - if (!gService) { + AssertIsOnBackgroundThread(); + + if (sIsShutdown) { return; } - - if (sHWnd) { - RegisterRawInput(sHWnd, false); - DestroyWindow(sHWnd); - sHWnd = nullptr; - } - - gService->Shutdown(); - delete gService; - gService = nullptr; + sIsShutdown = true; + gMonitorThread->Dispatch(new StopWindowsGamepadServiceRunnable(), NS_DISPATCH_NORMAL); + gMonitorThread->Shutdown(); + gMonitorThread = nullptr; } } // namespace dom } // namespace mozilla -
deleted file mode 100644 --- a/dom/interfaces/gamepad/moz.build +++ /dev/null @@ -1,11 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -XPIDL_MODULE = 'dom_gamepad' - -XPIDL_SOURCES = [ - 'nsIGamepadServiceTest.idl', - ]
deleted file mode 100644 --- a/dom/interfaces/gamepad/nsIGamepadServiceTest.idl +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIVariant; - -/* - * This interface is intended only for use in tests. - */ -[scriptable, uuid(c03ec4ed-8a7e-40e7-99da-c609f1760d0c)] -interface nsIGamepadServiceTest : nsISupports -{ - const unsigned long NO_MAPPING = 0; - const unsigned long STANDARD_MAPPING = 1; - - unsigned long addGamepad(in string id, - in unsigned long mapping, - in unsigned long numButtons, - in unsigned long numAxes); - void removeGamepad(in unsigned long index); - void newButtonEvent(in unsigned long index, in unsigned long button, - in boolean pressed); - void newButtonValueEvent(in unsigned long index, in unsigned long button, - in boolean pressed, in double value); - void newAxisMoveEvent(in unsigned long index, in unsigned long axis, - in double value); -};
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -168,20 +168,16 @@ #include "nsIAccessibilityService.h" #endif #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #endif #include "NuwaChild.h" -#ifdef MOZ_GAMEPAD -#include "mozilla/dom/GamepadService.h" -#endif - #ifndef MOZ_SIMPLEPUSH #include "mozilla/dom/PushNotifier.h" #endif #include "mozilla/dom/File.h" #include "mozilla/dom/cellbroadcast/CellBroadcastIPCService.h" #include "mozilla/dom/icc/IccChild.h" #include "mozilla/dom/mobileconnection/MobileConnectionChild.h" @@ -3225,28 +3221,16 @@ ContentChild::RecvPWebBrowserPersistDocu bool ContentChild::DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor) { delete aActor; return true; } bool -ContentChild::RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) -{ -#ifdef MOZ_GAMEPAD - RefPtr<GamepadService> svc(GamepadService::GetService()); - if (svc) { - svc->Update(aGamepadEvent); - } -#endif - return true; -} - -bool ContentChild::RecvSetAudioSessionData(const nsID& aId, const nsString& aDisplayName, const nsString& aIconPath) { #if defined(XP_WIN) if (NS_FAILED(mozilla::widget::RecvAudioSessionData(aId, aDisplayName, aIconPath))) { return true;
--- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -614,18 +614,16 @@ public: virtual PContentPermissionRequestChild* AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests, const IPC::Principal& aPrincipal, const TabId& aTabId) override; virtual bool DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override; - virtual bool RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override; - // Windows specific - set up audio session virtual bool RecvSetAudioSessionData(const nsID& aId, const nsString& aDisplayName, const nsString& aIconPath) override; private: static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -256,20 +256,16 @@ using namespace mozilla::system; #include "nsIBrowserSearchService.h" #endif #ifdef MOZ_ENABLE_PROFILER_SPS #include "nsIProfiler.h" #include "nsIProfileSaveEvent.h" #endif -#ifdef MOZ_GAMEPAD -#include "mozilla/dom/GamepadMonitoring.h" -#endif - #ifndef MOZ_SIMPLEPUSH #include "mozilla/dom/PushNotifier.h" #endif #ifdef XP_WIN #include "mozilla/widget/AudioSession.h" #endif @@ -2303,17 +2299,16 @@ ContentParent::InitializeMembers() mMetamorphosed = false; mSendPermissionUpdates = false; mCalledClose = false; mCalledKillHard = false; mCreatedPairedMinidumps = false; mShutdownPending = false; mIPCOpen = true; mHangMonitorActor = nullptr; - mHasGamepadListener = false; } bool ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */) { PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); std::vector<std::string> extraArgs; @@ -5623,44 +5618,16 @@ ContentParent::GetBrowserConfiguration(c swr->GetRegistrations(aConfig.serviceWorkerRegistrations()); return true; } return ContentChild::GetSingleton()->SendGetBrowserConfiguration(aURI, &aConfig); } bool -ContentParent::RecvGamepadListenerAdded() -{ -#ifdef MOZ_GAMEPAD - if (mHasGamepadListener) { - NS_WARNING("Gamepad listener already started, cannot start again!"); - return false; - } - mHasGamepadListener = true; - StartGamepadMonitoring(); -#endif - return true; -} - -bool -ContentParent::RecvGamepadListenerRemoved() -{ -#ifdef MOZ_GAMEPAD - if (!mHasGamepadListener) { - NS_WARNING("Gamepad listener already stopped, cannot stop again!"); - return false; - } - mHasGamepadListener = false; - MaybeStopGamepadMonitoring(); -#endif - return true; -} - -bool ContentParent::RecvProfile(const nsCString& aProfile) { #ifdef MOZ_ENABLE_PROFILER_SPS if (NS_WARN_IF(!mGatherer)) { return true; } mProfile = aProfile; mGatherer->GatheredOOPProfile();
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -541,18 +541,16 @@ public: const IPC::Principal& aPrincipal, const TabId& aTabId) override; virtual bool DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor) override; virtual bool HandleWindowsMessages(const Message& aMsg) const override; - bool HasGamepadListener() const { return mHasGamepadListener; } - void SetNuwaParent(NuwaParent* aNuwaParent) { mNuwaParent = aNuwaParent; } void ForkNewProcess(bool aBlocking); virtual bool RecvCreateWindow(PBrowserParent* aThisTabParent, PBrowserParent* aOpener, layout::PRenderFrameParent* aRenderFrame, const uint32_t& aChromeFlags, @@ -1140,20 +1138,16 @@ private: virtual bool RecvUpdateDropEffect(const uint32_t& aDragAction, const uint32_t& aDropEffect) override; virtual bool RecvGetBrowserConfiguration(const nsCString& aURI, BrowserConfiguration* aConfig) override; - virtual bool RecvGamepadListenerAdded() override; - - virtual bool RecvGamepadListenerRemoved() override; - virtual bool RecvProfile(const nsCString& aProfile) override; virtual bool RecvGetGraphicsDeviceInitData(DeviceInitData* aOut) override; void StartProfiler(nsIProfilerStartParams* aParams); virtual bool RecvGetDeviceStorageLocation(const nsString& aType, nsString* aPath) override; @@ -1220,17 +1214,16 @@ private: // True only the if process is already a browser or app or has // been transformed into one. bool mMetamorphosed; bool mSendPermissionUpdates; bool mIsForBrowser; bool mIsNuwaProcess; - bool mHasGamepadListener; // These variables track whether we've called Close() and KillHard() on our // channel. bool mCalledClose; bool mCalledKillHard; bool mCreatedPairedMinidumps; bool mShutdownPending; bool mIPCOpen;
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -334,47 +334,17 @@ struct DomainPolicyClone { bool active; URIParams[] blacklist; URIParams[] whitelist; URIParams[] superBlacklist; URIParams[] superWhitelist; }; -struct GamepadAdded { - nsString id; - uint32_t index; - uint32_t mapping; - uint32_t num_buttons; - uint32_t num_axes; -}; -struct GamepadRemoved { - uint32_t index; -}; - -struct GamepadAxisInformation { - uint32_t index; - uint32_t axis; - double value; -}; - -struct GamepadButtonInformation { - uint32_t index; - uint32_t button; - bool pressed; - double value; -}; - -union GamepadChangeEvent { - GamepadAdded; - GamepadRemoved; - GamepadAxisInformation; - GamepadButtonInformation; -}; struct FrameScriptInfo { nsString url; bool runInGlobalScope; }; struct AndroidSystemInfo @@ -632,21 +602,16 @@ child: /** * Requests a full native update of a native plugin child window. This is * a Windows specific call. */ async UpdateWindow(uintptr_t aChildId); /** - * Send gamepad status update to child. - */ - async GamepadUpdate(GamepadChangeEvent aGamepadEvent); - - /** * Notify the child that presentation receiver has been launched with the * correspondent iframe. */ async NotifyPresentationReceiverLaunched(PBrowser aIframe, nsString aSessionId); /** * Notify the child that the info about a presentation receiver needs to be * cleaned up. @@ -1122,26 +1087,16 @@ parent: TabId tabId); /** * Send ServiceWorkerRegistrationData to child process. */ sync GetBrowserConfiguration(nsCString aUri) returns (BrowserConfiguration aConfig); - /* - * Tells the parent to start the gamepad listening service if it hasn't already. - */ - async GamepadListenerAdded(); - - /** - * Tells the parent to stop the gamepad listening service if it hasn't already. - */ - async GamepadListenerRemoved(); - async Profile(nsCString aProfile); /** * Request graphics initialization information from the parent. */ sync GetGraphicsDeviceInitData() returns (DeviceInitData aData);
--- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -229,8 +229,10 @@ RewriteYouTubeEmbedPathParams=Rewriting # LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string. PushMessageDecryptionFailure=The ServiceWorker for scope ‘%1$S’ encountered an error decrypting a push message: ‘%2$S’. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption # LOCALIZATION NOTE: %1$S is the type of a DOM event. 'passive' is a literal parameter from the DOM spec. PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’. FileLastModifiedDateWarning=File.lastModifiedDate is deprecated. Use File.lastModified instead. ChromeScriptedDOMParserWithoutPrincipal=Creating DOMParser without a principal is deprecated. IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches. BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches. +# LOCALIZATION NOTE: %1$S is the unanimatable paced property. +UnanimatablePacedProperty=Paced property ‘%1$S’ is not an animatable property.
--- a/dom/media/Benchmark.cpp +++ b/dom/media/Benchmark.cpp @@ -202,18 +202,17 @@ BenchmarkPlayback::DemuxNextSample() } void BenchmarkPlayback::InitDecoder(TrackInfo&& aInfo) { MOZ_ASSERT(OnThread()); RefPtr<PDMFactory> platform = new PDMFactory(); - mDecoder = platform->CreateDecoder(aInfo, mDecoderTaskQueue, this, - /* DecoderDoctorDiagnostics* */ nullptr); + mDecoder = platform->CreateDecoder({ aInfo, mDecoderTaskQueue, reinterpret_cast<MediaDataDecoderCallback*>(this) }); if (!mDecoder) { MainThreadShutdown(); return; } RefPtr<Benchmark> ref(mMainThreadState); mDecoder->Init()->Then( Thread(), __func__, [this, ref](TrackInfo::TrackType aTrackType) {
--- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -402,38 +402,37 @@ MediaFormatReader::EnsureDecoderCreated( } } decoder.mDecoderInitialized = false; MonitorAutoLock mon(decoder.mMonitor); switch (aTrack) { - case TrackType::kAudioTrack: - decoder.mDecoder = - mPlatform->CreateDecoder(decoder.mInfo ? - *decoder.mInfo->GetAsAudioInfo() : - mInfo.mAudio, - decoder.mTaskQueue, - decoder.mCallback, - /* DecoderDoctorDiagnostics* */ nullptr); + case TrackType::kAudioTrack: { + decoder.mDecoder = mPlatform->CreateDecoder({ + decoder.mInfo ? *decoder.mInfo->GetAsAudioInfo() : mInfo.mAudio, + decoder.mTaskQueue, + decoder.mCallback.get() + }); break; - case TrackType::kVideoTrack: + } + + case TrackType::kVideoTrack: { // Decoders use the layers backend to decide if they can use hardware decoding, // so specify LAYERS_NONE if we want to forcibly disable it. - decoder.mDecoder = - mPlatform->CreateDecoder(mVideo.mInfo ? - *mVideo.mInfo->GetAsVideoInfo() : - mInfo.mVideo, - decoder.mTaskQueue, - decoder.mCallback, - /* DecoderDoctorDiagnostics* */ nullptr, - mLayersBackendType, - GetImageContainer()); + decoder.mDecoder = mPlatform->CreateDecoder({ + mVideo.mInfo ? *mVideo.mInfo->GetAsVideoInfo() : mInfo.mVideo, + decoder.mTaskQueue, + decoder.mCallback.get(), + mLayersBackendType, + GetImageContainer(), + }); break; + } default: break; } if (decoder.mDecoder ) { decoder.mDescription = decoder.mDecoder->GetDescriptionName(); } else { decoder.mDescription = "error creating decoder"; }
--- a/dom/media/MediaInfo.h +++ b/dom/media/MediaInfo.h @@ -183,26 +183,31 @@ public: kDegree_180 = 180, kDegree_270 = 270, }; VideoInfo() : VideoInfo(-1, -1) { } - VideoInfo(int32_t aWidth, int32_t aHeight) + explicit VideoInfo(int32_t aWidth, int32_t aHeight) + : VideoInfo(nsIntSize(aWidth, aHeight)) + { + } + + explicit VideoInfo(const nsIntSize& aSize) : TrackInfo(kVideoTrack, NS_LITERAL_STRING("2"), NS_LITERAL_STRING("main"), EmptyString(), EmptyString(), true, 2) - , mDisplay(nsIntSize(aWidth, aHeight)) + , mDisplay(aSize) , mStereoMode(StereoMode::MONO) - , mImage(nsIntSize(aWidth, aHeight)) + , mImage(aSize) , mCodecSpecificConfig(new MediaByteBuffer) , mExtraData(new MediaByteBuffer) , mRotation(kDegree_0) - , mImageRect(nsIntRect(0, 0, aWidth, aHeight)) + , mImageRect(nsIntRect(nsIntPoint(), aSize)) { } VideoInfo(const VideoInfo& aOther) : TrackInfo(aOther) , mDisplay(aOther.mDisplay) , mStereoMode(aOther.mStereoMode) , mImage(aOther.mImage)
--- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -1189,30 +1189,30 @@ public: nsresult rv; if (mAudioDevice) { rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio), mPrefs, mOrigin); if (NS_FAILED(rv)) { LOG(("Failed to allocate audiosource %d",rv)); - Fail(NS_LITERAL_STRING("SourceUnavailableError"), + Fail(NS_LITERAL_STRING("NotReadableError"), NS_LITERAL_STRING("Failed to allocate audiosource")); return NS_OK; } } if (mVideoDevice) { rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo), mPrefs, mOrigin); if (NS_FAILED(rv)) { LOG(("Failed to allocate videosource %d\n",rv)); if (mAudioDevice) { mAudioDevice->GetSource()->Deallocate(); } - Fail(NS_LITERAL_STRING("SourceUnavailableError"), + Fail(NS_LITERAL_STRING("NotReadableError"), NS_LITERAL_STRING("Failed to allocate videosource")); return NS_OK; } } PeerIdentity* peerIdentity = nullptr; if (!mConstraints.mPeerIdentity.IsEmpty()) { peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity); }
--- a/dom/media/MediaStreamError.cpp +++ b/dom/media/MediaStreamError.cpp @@ -20,19 +20,18 @@ BaseMediaMgrError::BaseMediaMgrError(con if (mMessage.IsEmpty()) { if (mName.EqualsLiteral("NotFoundError")) { mMessage.AssignLiteral("The object can not be found here."); } else if (mName.EqualsLiteral("NotAllowedError")) { mMessage.AssignLiteral("The request is not allowed by the user agent " "or the platform in the current context."); } 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)."); + } else if (mName.EqualsLiteral("NotReadableError")) { + mMessage.AssignLiteral("The I/O read operation failed."); } else if (mName.EqualsLiteral("InternalError")) { mMessage.AssignLiteral("Internal error."); } else if (mName.EqualsLiteral("NotSupportedError")) { mMessage.AssignLiteral("The operation is not supported."); } else if (mName.EqualsLiteral("OverconstrainedError")) { mMessage.AssignLiteral("Constraints could be not satisfied."); } }
--- a/dom/media/VideoFrameContainer.cpp +++ b/dom/media/VideoFrameContainer.cpp @@ -131,16 +131,17 @@ void VideoFrameContainer::ClearCurrentFr MutexAutoLock lock(mMutex); // See comment in SetCurrentFrame for the reasoning behind // using a kungFuDeathGrip here. nsTArray<ImageContainer::OwningImage> kungFuDeathGrip; mImageContainer->GetCurrentImages(&kungFuDeathGrip); mImageContainer->ClearAllImages(); + mImageContainer->ClearCachedResources(); } void VideoFrameContainer::ClearFutureFrames() { MutexAutoLock lock(mMutex); // See comment in SetCurrentFrame for the reasoning behind // using a kungFuDeathGrip here.
--- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -217,20 +217,17 @@ CreateTestH264Decoder(layers::LayersBack aConfig.mDuration = 40000; aConfig.mMediaTime = 0; aConfig.mImage = aConfig.mDisplay = nsIntSize(640, 360); aConfig.mExtraData = new MediaByteBuffer(); aConfig.mExtraData->AppendElements(sTestH264ExtraData, MOZ_ARRAY_LENGTH(sTestH264ExtraData)); RefPtr<PDMFactory> platform = new PDMFactory(); - RefPtr<MediaDataDecoder> decoder( - platform->CreateDecoder(aConfig, aTaskQueue, nullptr, - /* DecoderDoctorDiagnostics* */ nullptr, - aBackend, nullptr)); + RefPtr<MediaDataDecoder> decoder(platform->CreateDecoder({ aConfig, aTaskQueue, aBackend })); return decoder.forget(); } /* static */ already_AddRefed<dom::Promise> MP4Decoder::IsVideoAccelerated(layers::LayersBackend aBackend, nsIGlobalObject* aParent) { MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/platforms/PDMFactory.cpp +++ b/dom/media/platforms/PDMFactory.cpp @@ -96,126 +96,94 @@ PDMFactory::EnsureInit() const nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([]() { ClearOnShutdown(&sInstance); }); NS_DispatchToMainThread(runnable); } } } already_AddRefed<MediaDataDecoder> -PDMFactory::CreateDecoder(const TrackInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer) const +PDMFactory::CreateDecoder(const CreateDecoderParams& aParams) { - bool isEncrypted = mEMEPDM && aConfig.mCrypto.mValid; + const TrackInfo& config = aParams.mConfig; + bool isEncrypted = mEMEPDM && config.mCrypto.mValid; if (isEncrypted) { - return CreateDecoderWithPDM(mEMEPDM, - aConfig, - aTaskQueue, - aCallback, - aDiagnostics, - aLayersBackend, - aImageContainer); + return CreateDecoderWithPDM(mEMEPDM, aParams); } - if (aDiagnostics) { + DecoderDoctorDiagnostics* diagnostics = aParams.mDiagnostics; + if (diagnostics) { // If libraries failed to load, the following loop over mCurrentPDMs // will not even try to use them. So we record failures now. if (mWMFFailedToLoad) { - aDiagnostics->SetWMFFailedToLoad(); + diagnostics->SetWMFFailedToLoad(); } if (mFFmpegFailedToLoad) { - aDiagnostics->SetFFmpegFailedToLoad(); + diagnostics->SetFFmpegFailedToLoad(); } if (mGMPPDMFailedToStartup) { - aDiagnostics->SetGMPPDMFailedToStartup(); + diagnostics->SetGMPPDMFailedToStartup(); } } for (auto& current : mCurrentPDMs) { - if (!current->SupportsMimeType(aConfig.mMimeType, aDiagnostics)) { + if (!current->SupportsMimeType(config.mMimeType, diagnostics)) { continue; } - RefPtr<MediaDataDecoder> m = - CreateDecoderWithPDM(current, - aConfig, - aTaskQueue, - aCallback, - aDiagnostics, - aLayersBackend, - aImageContainer); + RefPtr<MediaDataDecoder> m = CreateDecoderWithPDM(current, aParams); if (m) { return m.forget(); } } NS_WARNING("Unable to create a decoder, no platform found."); return nullptr; } already_AddRefed<MediaDataDecoder> PDMFactory::CreateDecoderWithPDM(PlatformDecoderModule* aPDM, - const TrackInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer) const + const CreateDecoderParams& aParams) { MOZ_ASSERT(aPDM); RefPtr<MediaDataDecoder> m; - if (aConfig.GetAsAudioInfo()) { - m = aPDM->CreateAudioDecoder(*aConfig.GetAsAudioInfo(), - aTaskQueue, - aCallback, - aDiagnostics); + const TrackInfo& config = aParams.mConfig; + if (config.IsAudio()) { + m = aPDM->CreateAudioDecoder(aParams); return m.forget(); } - if (!aConfig.GetAsVideoInfo()) { + if (!config.IsVideo()) { return nullptr; } - MediaDataDecoderCallback* callback = aCallback; + MediaDataDecoderCallback* callback = aParams.mCallback; RefPtr<DecoderCallbackFuzzingWrapper> callbackWrapper; if (MediaPrefs::PDMFuzzingEnabled()) { - callbackWrapper = new DecoderCallbackFuzzingWrapper(aCallback); + callbackWrapper = new DecoderCallbackFuzzingWrapper(callback); callbackWrapper->SetVideoOutputMinimumInterval( TimeDuration::FromMilliseconds(MediaPrefs::PDMFuzzingInterval())); callbackWrapper->SetDontDelayInputExhausted(!MediaPrefs::PDMFuzzingDelayInputExhausted()); callback = callbackWrapper.get(); } - if (H264Converter::IsH264(aConfig)) { - RefPtr<H264Converter> h - = new H264Converter(aPDM, - *aConfig.GetAsVideoInfo(), - aLayersBackend, - aImageContainer, - aTaskQueue, - callback, - aDiagnostics); + CreateDecoderParams params = aParams; + params.mCallback = callback; + + if (H264Converter::IsH264(config)) { + RefPtr<H264Converter> h = new H264Converter(aPDM, params); const nsresult rv = h->GetLastError(); if (NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_INITIALIZED) { // The H264Converter either successfully created the wrapped decoder, // or there wasn't enough AVCC data to do so. Otherwise, there was some // problem, for example WMF DLLs were missing. m = h.forget(); } } else { - m = aPDM->CreateVideoDecoder(*aConfig.GetAsVideoInfo(), - aLayersBackend, - aImageContainer, - aTaskQueue, - callback, - aDiagnostics); + m = aPDM->CreateVideoDecoder(params); } if (callbackWrapper && m) { m = new DecoderFuzzingWrapper(m.forget(), callbackWrapper.forget()); } return m.forget(); }
--- a/dom/media/platforms/PDMFactory.h +++ b/dom/media/platforms/PDMFactory.h @@ -25,22 +25,17 @@ public: PDMFactory(); // Factory method that creates the appropriate PlatformDecoderModule for // the platform we're running on. Caller is responsible for deleting this // instance. It's expected that there will be multiple // PlatformDecoderModules alive at the same time. // This is called on the decode task queue. already_AddRefed<MediaDataDecoder> - CreateDecoder(const TrackInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics, - layers::LayersBackend aLayersBackend = layers::LayersBackend::LAYERS_NONE, - layers::ImageContainer* aImageContainer = nullptr) const; + CreateDecoder(const CreateDecoderParams& aParams); bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const; #ifdef MOZ_EME // Creates a PlatformDecoderModule that uses a CDMProxy to decrypt or // decrypt-and-decode EME encrypted content. If the CDM only decrypts and // does not decode, we create a PDM and use that to create MediaDataDecoders @@ -56,22 +51,17 @@ private: bool StartupPDM(PlatformDecoderModule* aPDM); // Returns the first PDM in our list supporting the mimetype. already_AddRefed<PlatformDecoderModule> GetDecoder(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const; already_AddRefed<MediaDataDecoder> CreateDecoderWithPDM(PlatformDecoderModule* aPDM, - const TrackInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer) const; + const CreateDecoderParams& aParams); nsTArray<RefPtr<PlatformDecoderModule>> mCurrentPDMs; RefPtr<PlatformDecoderModule> mEMEPDM; bool mWMFFailedToLoad = false; bool mFFmpegFailedToLoad = false; bool mGMPPDMFailedToStartup = false;
--- a/dom/media/platforms/PlatformDecoderModule.h +++ b/dom/media/platforms/PlatformDecoderModule.h @@ -27,16 +27,64 @@ class ImageContainer; class MediaDataDecoder; class MediaDataDecoderCallback; class TaskQueue; class CDMProxy; static LazyLogModule sPDMLog("PlatformDecoderModule"); +struct CreateDecoderParams { + explicit CreateDecoderParams(const TrackInfo& aConfig) + : mConfig(aConfig) + {} + + template <typename T1, typename... Ts> + CreateDecoderParams(const TrackInfo& aConfig, T1 a1, Ts... as) + : mConfig(aConfig) + { + Set(a1, as...); + } + + const VideoInfo& VideoConfig() const + { + MOZ_ASSERT(mConfig.IsVideo()); + return *mConfig.GetAsVideoInfo(); + } + + const AudioInfo& AudioConfig() const + { + MOZ_ASSERT(mConfig.IsAudio()); + return *mConfig.GetAsAudioInfo(); + } + + const TrackInfo& mConfig; + TaskQueue* mTaskQueue = nullptr; + MediaDataDecoderCallback* mCallback = nullptr; + DecoderDoctorDiagnostics* mDiagnostics = nullptr; + layers::ImageContainer* mImageContainer = nullptr; + layers::LayersBackend mLayersBackend = layers::LayersBackend::LAYERS_NONE; + +private: + void Set(TaskQueue* aTaskQueue) { mTaskQueue = aTaskQueue; } + void Set(MediaDataDecoderCallback* aCallback) { mCallback = aCallback; } + void Set(DecoderDoctorDiagnostics* aDiagnostics) { mDiagnostics = aDiagnostics; } + void Set(layers::ImageContainer* aImageContainer) { mImageContainer = aImageContainer; } + void Set(layers::LayersBackend aLayersBackend) { mLayersBackend = aLayersBackend; } + template <typename T1, typename T2, typename... Ts> + void Set(T1 a1, T2 a2, Ts... as) + { + // Parameter pack expansion trick, to call Set() on each argument. + using expander = int[]; + (void)expander { + (Set(a1), 0), (Set(a2), 0), (Set(as), 0)... + }; + } +}; + // The PlatformDecoderModule interface is used by the MediaFormatReader to // abstract access to decoders provided by various // platforms. // Each platform (Windows, MacOSX, Linux, B2G etc) must implement a // PlatformDecoderModule to provide access to its decoders in order to get // decompressed H.264/AAC from the MediaFormatReader. // // Decoding is asynchronous, and should be performed on the task queue @@ -83,38 +131,30 @@ protected: // not hold a reference to it. // Output and errors should be returned to the reader via aCallback. // On Windows the task queue's threads in have MSCOM initialized with // COINIT_MULTITHREADED. // Returns nullptr if the decoder can't be created. // It is safe to store a reference to aConfig. // This is called on the decode task queue. virtual already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) = 0; + CreateVideoDecoder(const CreateDecoderParams& aParams) = 0; // Creates an Audio decoder with the specified properties. // Asynchronous decoding of audio should be done in runnables dispatched to // aAudioTaskQueue. If the task queue isn't needed, the decoder should // not hold a reference to it. // Output and errors should be returned to the reader via aCallback. // Returns nullptr if the decoder can't be created. // On Windows the task queue's threads in have MSCOM initialized with // COINIT_MULTITHREADED. // It is safe to store a reference to aConfig. // This is called on the decode task queue. virtual already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) = 0; + CreateAudioDecoder(const CreateDecoderParams& aParams) = 0; }; enum MediaDataDecoderError { FATAL_ERROR, DECODE_ERROR }; // A callback used by MediaDataDecoder to return output/errors to the
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp +++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp @@ -18,51 +18,37 @@ AgnosticDecoderModule::SupportsMimeType( { return VPXDecoder::IsVPX(aMimeType) || OpusDataDecoder::IsOpus(aMimeType) || VorbisDataDecoder::IsVorbis(aMimeType) || WaveDataDecoder::IsWave(aMimeType); } already_AddRefed<MediaDataDecoder> -AgnosticDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { RefPtr<MediaDataDecoder> m; - if (VPXDecoder::IsVPX(aConfig.mMimeType)) { - m = new VPXDecoder(*aConfig.GetAsVideoInfo(), - aImageContainer, - aTaskQueue, - aCallback); + if (VPXDecoder::IsVPX(aParams.mConfig.mMimeType)) { + m = new VPXDecoder(aParams); } return m.forget(); } already_AddRefed<MediaDataDecoder> -AgnosticDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +AgnosticDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { RefPtr<MediaDataDecoder> m; - if (VorbisDataDecoder::IsVorbis(aConfig.mMimeType)) { - m = new VorbisDataDecoder(*aConfig.GetAsAudioInfo(), - aTaskQueue, - aCallback); - } else if (OpusDataDecoder::IsOpus(aConfig.mMimeType)) { - m = new OpusDataDecoder(*aConfig.GetAsAudioInfo(), - aTaskQueue, - aCallback); - } else if (WaveDataDecoder::IsWave(aConfig.mMimeType)) { - m = new WaveDataDecoder(*aConfig.GetAsAudioInfo(), aCallback); + const TrackInfo& config = aParams.mConfig; + if (VorbisDataDecoder::IsVorbis(config.mMimeType)) { + m = new VorbisDataDecoder(aParams); + } else if (OpusDataDecoder::IsOpus(config.mMimeType)) { + m = new OpusDataDecoder(aParams); + } else if (WaveDataDecoder::IsWave(config.mMimeType)) { + m = new WaveDataDecoder(aParams); } return m.forget(); } } // namespace mozilla
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.h +++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.h @@ -17,26 +17,18 @@ public: DecoderNeedsConversion(const TrackInfo& aConfig) const override { return ConversionRequired::kNeedNone; } protected: // Decode thread. already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateVideoDecoder(const CreateDecoderParams& aParams) override; // Decode thread. already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateAudioDecoder(const CreateDecoderParams& aParams) override; }; } // namespace mozilla #endif /* AgnosticDecoderModule_h_ */
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp +++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp @@ -21,21 +21,20 @@ namespace mozilla { // Decoder that uses a passed in object's Create function to create blank // MediaData objects. template<class BlankMediaDataCreator> class BlankMediaDataDecoder : public MediaDataDecoder { public: BlankMediaDataDecoder(BlankMediaDataCreator* aCreator, - MediaDataDecoderCallback* aCallback, - TrackInfo::TrackType aType) + const CreateDecoderParams& aParams) : mCreator(aCreator) - , mCallback(aCallback) - , mType(aType) + , mCallback(aParams.mCallback) + , mType(aParams.mConfig.GetType()) { } RefPtr<InitPromise> Init() override { return InitPromise::CreateAndResolve(mType, __func__); } nsresult Shutdown() override { @@ -194,44 +193,34 @@ private: uint32_t mSampleRate; }; class BlankDecoderModule : public PlatformDecoderModule { public: // Decode thread. already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override { + CreateVideoDecoder(const CreateDecoderParams& aParams) override { + const VideoInfo& config = aParams.VideoConfig(); BlankVideoDataCreator* creator = new BlankVideoDataCreator( - aConfig.mDisplay.width, aConfig.mDisplay.height, aImageContainer); + config.mDisplay.width, config.mDisplay.height, aParams.mImageContainer); RefPtr<MediaDataDecoder> decoder = - new BlankMediaDataDecoder<BlankVideoDataCreator>(creator, - aCallback, - TrackInfo::kVideoTrack); + new BlankMediaDataDecoder<BlankVideoDataCreator>(creator, aParams); return decoder.forget(); } // Decode thread. already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override { + CreateAudioDecoder(const CreateDecoderParams& aParams) override { + const AudioInfo& config = aParams.AudioConfig(); BlankAudioDataCreator* creator = new BlankAudioDataCreator( - aConfig.mChannels, aConfig.mRate); + config.mChannels, config.mRate); RefPtr<MediaDataDecoder> decoder = - new BlankMediaDataDecoder<BlankAudioDataCreator>(creator, - aCallback, - TrackInfo::kAudioTrack); + new BlankMediaDataDecoder<BlankAudioDataCreator>(creator, aParams); return decoder.forget(); } bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override { return true;
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp +++ b/dom/media/platforms/agnostic/OpusDecoder.cpp @@ -15,22 +15,20 @@ #include <stdint.h> #include <inttypes.h> // For PRId64 #define OPUS_DEBUG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, \ ("OpusDataDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__)) namespace mozilla { -OpusDataDecoder::OpusDataDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback) - : mInfo(aConfig) - , mTaskQueue(aTaskQueue) - , mCallback(aCallback) +OpusDataDecoder::OpusDataDecoder(const CreateDecoderParams& aParams) + : mInfo(aParams.AudioConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(aParams.mCallback) , mOpusDecoder(nullptr) , mSkip(0) , mDecodedHeader(false) , mPaddingDiscarded(false) , mFrames(0) , mIsFlushing(false) { }
--- a/dom/media/platforms/agnostic/OpusDecoder.h +++ b/dom/media/platforms/agnostic/OpusDecoder.h @@ -12,19 +12,17 @@ #include "mozilla/Maybe.h" #include "nsAutoPtr.h" namespace mozilla { class OpusDataDecoder : public MediaDataDecoder { public: - OpusDataDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback); + explicit OpusDataDecoder(const CreateDecoderParams& aParams); ~OpusDataDecoder(); RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; const char* GetDescriptionName() const override
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp +++ b/dom/media/platforms/agnostic/VPXDecoder.cpp @@ -27,26 +27,23 @@ static int MimeTypeToCodec(const nsACStr if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) { return VPXDecoder::Codec::VP8; } else if (aMimeType.EqualsLiteral("video/webm; codecs=vp9")) { return VPXDecoder::Codec::VP9; } return -1; } -VPXDecoder::VPXDecoder(const VideoInfo& aConfig, - ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback) - : mImageContainer(aImageContainer) - , mTaskQueue(aTaskQueue) - , mCallback(aCallback) +VPXDecoder::VPXDecoder(const CreateDecoderParams& aParams) + : mImageContainer(aParams.mImageContainer) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(aParams.mCallback) , mIsFlushing(false) - , mInfo(aConfig) - , mCodec(MimeTypeToCodec(aConfig.mMimeType)) + , mInfo(aParams.VideoConfig()) + , mCodec(MimeTypeToCodec(aParams.VideoConfig().mMimeType)) { MOZ_COUNT_CTOR(VPXDecoder); PodZero(&mVPX); } VPXDecoder::~VPXDecoder() { MOZ_COUNT_DTOR(VPXDecoder);
--- a/dom/media/platforms/agnostic/VPXDecoder.h +++ b/dom/media/platforms/agnostic/VPXDecoder.h @@ -16,21 +16,17 @@ namespace mozilla { using namespace layers; class VPXDecoder : public MediaDataDecoder { public: - VPXDecoder(const VideoInfo& aConfig, - ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback); - + explicit VPXDecoder(const CreateDecoderParams& aParams); ~VPXDecoder(); RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; const char* GetDescriptionName() const override
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp +++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp @@ -25,22 +25,20 @@ ogg_packet InitVorbisPacket(const unsign packet.bytes = aLength; packet.b_o_s = aBOS; packet.e_o_s = aEOS; packet.granulepos = aGranulepos; packet.packetno = aPacketNo; return packet; } -VorbisDataDecoder::VorbisDataDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback) - : mInfo(aConfig) - , mTaskQueue(aTaskQueue) - , mCallback(aCallback) +VorbisDataDecoder::VorbisDataDecoder(const CreateDecoderParams& aParams) + : mInfo(aParams.AudioConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(aParams.mCallback) , mPacketCount(0) , mFrames(0) , mIsFlushing(false) { // Zero these member vars to avoid crashes in Vorbis clear functions when // destructor is called before |Init|. PodZero(&mVorbisBlock); PodZero(&mVorbisDsp);
--- a/dom/media/platforms/agnostic/VorbisDecoder.h +++ b/dom/media/platforms/agnostic/VorbisDecoder.h @@ -16,19 +16,17 @@ #include "vorbis/codec.h" #endif namespace mozilla { class VorbisDataDecoder : public MediaDataDecoder { public: - VorbisDataDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback); + explicit VorbisDataDecoder(const CreateDecoderParams& aParams); ~VorbisDataDecoder(); RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; const char* GetDescriptionName() const override
--- a/dom/media/platforms/agnostic/WAVDecoder.cpp +++ b/dom/media/platforms/agnostic/WAVDecoder.cpp @@ -40,20 +40,19 @@ DecodeULawSample(uint8_t aValue) aValue = aValue ^ 0xFF; int8_t sign = (aValue & 0x80) ? -1 : 1; uint8_t exponent = (aValue & 0x70) >> 4; uint8_t mantissa = aValue & 0x0F; int16_t sample = (33 + 2 * mantissa) * (2 << (exponent + 1)) - 33; return sign * sample; } -WaveDataDecoder::WaveDataDecoder(const AudioInfo& aConfig, - MediaDataDecoderCallback* aCallback) - : mInfo(aConfig) - , mCallback(aCallback) +WaveDataDecoder::WaveDataDecoder(const CreateDecoderParams& aParams) + : mInfo(aParams.AudioConfig()) + , mCallback(aParams.mCallback) { } nsresult WaveDataDecoder::Shutdown() { return NS_OK; }
--- a/dom/media/platforms/agnostic/WAVDecoder.h +++ b/dom/media/platforms/agnostic/WAVDecoder.h @@ -10,18 +10,17 @@ #include "PlatformDecoderModule.h" #include "mp4_demuxer/ByteReader.h" namespace mozilla { class WaveDataDecoder : public MediaDataDecoder { public: - WaveDataDecoder(const AudioInfo& aConfig, - MediaDataDecoderCallback* aCallback); + explicit WaveDataDecoder(const CreateDecoderParams& aParams); // Return true if mimetype is Wave static bool IsWave(const nsACString& aMimeType); RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override;
--- a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp +++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp @@ -16,16 +16,23 @@ EMEAudioCallbackAdapter::Error(GMPErr aE // The GMP failed to decrypt a frame due to not having a key. This can // happen if a key expires or a session is closed during playback. NS_WARNING("GMP failed to decrypt due to lack of key"); return; } AudioCallbackAdapter::Error(aErr); } +EMEAudioDecoder::EMEAudioDecoder(CDMProxy* aProxy, + const GMPAudioDecoderParams& aParams) + : GMPAudioDecoder(GMPAudioDecoderParams(aParams).WithAdapter( + new EMEAudioCallbackAdapter(aParams.mCallback))) + , mProxy(aProxy) +{} + void EMEAudioDecoder::InitTags(nsTArray<nsCString>& aTags) { aTags.AppendElement(NS_LITERAL_CSTRING("aac")); aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem())); } nsCString
--- a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h +++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h @@ -18,24 +18,17 @@ public: : AudioCallbackAdapter(aCallback) {} void Error(GMPErr aErr) override; }; class EMEAudioDecoder : public GMPAudioDecoder { public: - EMEAudioDecoder(CDMProxy* aProxy, - const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallbackProxy* aCallback) - : GMPAudioDecoder(aConfig, aTaskQueue, aCallback, new EMEAudioCallbackAdapter(aCallback)) - , mProxy(aProxy) - { - } + EMEAudioDecoder(CDMProxy* aProxy, const GMPAudioDecoderParams& aParams); private: void InitTags(nsTArray<nsCString>& aTags) override; nsCString GetNodeId() override; RefPtr<CDMProxy> mProxy; };
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp @@ -231,85 +231,66 @@ CreateDecoderWrapper(MediaDataDecoderCal return nullptr; } RefPtr<MediaDataDecoderProxy> decoder( new EMEMediaDataDecoderProxy(thread.forget(), aCallback, aProxy, aTaskQueue)); return decoder.forget(); } already_AddRefed<MediaDataDecoder> -EMEDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { - MOZ_ASSERT(aConfig.mCrypto.mValid); + MOZ_ASSERT(aParams.mConfig.mCrypto.mValid); - if (SupportsMimeType(aConfig.mMimeType, nullptr)) { + if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) { // GMP decodes. Assume that means it can decrypt too. - RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback, mProxy, aTaskQueue); - wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, - aConfig, - aLayersBackend, - aImageContainer, - aTaskQueue, - wrapper->Callback())); + RefPtr<MediaDataDecoderProxy> wrapper = + CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue); + auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params)); return wrapper.forget(); } MOZ_ASSERT(mPDM); - RefPtr<MediaDataDecoder> decoder( - mPDM->CreateDecoder(aConfig, - aTaskQueue, - aCallback, - aDiagnostics, - aLayersBackend, - aImageContainer)); + RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams)); if (!decoder) { return nullptr; } RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder, - aCallback, - mProxy, - AbstractThread::GetCurrent()->AsTaskQueue())); + aParams.mCallback, + mProxy, + AbstractThread::GetCurrent()->AsTaskQueue())); return emeDecoder.forget(); } already_AddRefed<MediaDataDecoder> -EMEDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { - MOZ_ASSERT(aConfig.mCrypto.mValid); + MOZ_ASSERT(aParams.mConfig.mCrypto.mValid); - if (SupportsMimeType(aConfig.mMimeType, nullptr)) { + if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) { // GMP decodes. Assume that means it can decrypt too. - RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback, mProxy, aTaskQueue); - wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy, - aConfig, - aTaskQueue, - wrapper->Callback())); + RefPtr<MediaDataDecoderProxy> wrapper = + CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue); + auto gmpParams = GMPAudioDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy, gmpParams)); return wrapper.forget(); } MOZ_ASSERT(mPDM); - RefPtr<MediaDataDecoder> decoder( - mPDM->CreateDecoder(aConfig, aTaskQueue, aCallback, aDiagnostics)); + RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams)); if (!decoder) { return nullptr; } RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder, - aCallback, - mProxy, - AbstractThread::GetCurrent()->AsTaskQueue())); + aParams.mCallback, + mProxy, + AbstractThread::GetCurrent()->AsTaskQueue())); return emeDecoder.forget(); } PlatformDecoderModule::ConversionRequired EMEDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const { if (aConfig.IsVideo()) { return kNeedAVCC;
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h @@ -21,29 +21,21 @@ private: public: EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM); virtual ~EMEDecoderModule(); protected: // Decode thread. already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateVideoDecoder(const CreateDecoderParams& aParams) override; // Decode thread. already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateAudioDecoder(const CreateDecoderParams& aParams) override; ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override; bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override;
--- a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp +++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp @@ -18,16 +18,25 @@ EMEVideoCallbackAdapter::Error(GMPErr aE // The GMP failed to decrypt a frame due to not having a key. This can // happen if a key expires or a session is closed during playback. NS_WARNING("GMP failed to decrypt due to lack of key"); return; } VideoCallbackAdapter::Error(aErr); } +EMEVideoDecoder::EMEVideoDecoder(CDMProxy* aProxy, + const GMPVideoDecoderParams& aParams) + : GMPVideoDecoder(GMPVideoDecoderParams(aParams).WithAdapter( + new EMEVideoCallbackAdapter(aParams.mCallback, + VideoInfo(aParams.mConfig.mDisplay), + aParams.mImageContainer))) + , mProxy(aProxy) +{} + void EMEVideoDecoder::InitTags(nsTArray<nsCString>& aTags) { aTags.AppendElement(NS_LITERAL_CSTRING("h264")); aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem())); } nsCString
--- a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h +++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h @@ -23,34 +23,17 @@ public: : VideoCallbackAdapter(aCallback, aVideoInfo, aImageContainer) {} void Error(GMPErr aErr) override; }; class EMEVideoDecoder : public GMPVideoDecoder { public: - EMEVideoDecoder(CDMProxy* aProxy, - const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallbackProxy* aCallback) - : GMPVideoDecoder(aConfig, - aLayersBackend, - aImageContainer, - aTaskQueue, - aCallback, - new EMEVideoCallbackAdapter(aCallback, - VideoInfo(aConfig.mDisplay.width, - aConfig.mDisplay.height), - aImageContainer)) - , mProxy(aProxy) - { - } + EMEVideoDecoder(CDMProxy* aProxy, const GMPVideoDecoderParams& aParams); private: void InitTags(nsTArray<nsCString>& aTags) override; nsCString GetNodeId() override; GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample) override; RefPtr<CDMProxy> mProxy; };
--- a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp @@ -121,16 +121,55 @@ AudioCallbackAdapter::Error(GMPErr aErr) void AudioCallbackAdapter::Terminated() { NS_WARNING("AAC GMP decoder terminated."); mCallback->Error(MediaDataDecoderError::FATAL_ERROR); } +GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams) + : mConfig(aParams.AudioConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(nullptr) + , mAdapter(nullptr) +{} + +GMPAudioDecoderParams& +GMPAudioDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper) +{ + MOZ_ASSERT(aWrapper); + MOZ_ASSERT(!mCallback); // Should only be called once per instance. + mCallback = aWrapper->Callback(); + mAdapter = nullptr; + return *this; +} + +GMPAudioDecoderParams& +GMPAudioDecoderParams::WithAdapter(AudioCallbackAdapter* aAdapter) +{ + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!mAdapter); // Should only be called once per instance. + mCallback = aAdapter->Callback(); + mAdapter = aAdapter; + return *this; +} + +GMPAudioDecoder::GMPAudioDecoder(const GMPAudioDecoderParams& aParams) + : mConfig(aParams.mConfig) + , mCallback(aParams.mCallback) + , mGMP(nullptr) + , mAdapter(aParams.mAdapter) +{ + MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback()); + if (!mAdapter) { + mAdapter = new AudioCallbackAdapter(mCallback); + } +} + void GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags) { aTags.AppendElement(NS_LITERAL_CSTRING("aac")); const Maybe<nsCString> gmp( GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm"))); if (gmp.isSome()) { aTags.AppendElement(gmp.value());
--- a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h @@ -20,16 +20,18 @@ public: explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback) : mCallback(aCallback) , mLastStreamOffset(0) , mAudioFrameSum(0) , mAudioFrameOffset(0) , mMustRecaptureAudioPosition(true) {} + MediaDataDecoderCallbackProxy* Callback() const { return mCallback; } + // GMPAudioDecoderCallbackProxy void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) override; void InputDataExhausted() override; void DrainComplete() override; void ResetComplete() override; void Error(GMPErr aErr) override; void Terminated() override; @@ -41,39 +43,30 @@ private: MediaDataDecoderCallbackProxy* mCallback; int64_t mLastStreamOffset; int64_t mAudioFrameSum; int64_t mAudioFrameOffset; bool mMustRecaptureAudioPosition; }; +struct GMPAudioDecoderParams { + explicit GMPAudioDecoderParams(const CreateDecoderParams& aParams); + GMPAudioDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper); + GMPAudioDecoderParams& WithAdapter(AudioCallbackAdapter* aAdapter); + + const AudioInfo& mConfig; + TaskQueue* mTaskQueue; + MediaDataDecoderCallbackProxy* mCallback; + AudioCallbackAdapter* mAdapter; +}; + class GMPAudioDecoder : public MediaDataDecoder { -protected: - GMPAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallbackProxy* aCallback, - AudioCallbackAdapter* aAdapter) - : mConfig(aConfig) - , mCallback(aCallback) - , mGMP(nullptr) - , mAdapter(aAdapter) - { - } - public: - GMPAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallbackProxy* aCallback) - : mConfig(aConfig) - , mCallback(aCallback) - , mGMP(nullptr) - , mAdapter(new AudioCallbackAdapter(aCallback)) - { - } + explicit GMPAudioDecoder(const GMPAudioDecoderParams& aParams); RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; const char* GetDescriptionName() const override {
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp @@ -41,64 +41,52 @@ CreateDecoderWrapper(MediaDataDecoderCal if (!thread) { return nullptr; } RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback)); return decoder.forget(); } already_AddRefed<MediaDataDecoder> -GMPDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +GMPDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { - if (!aConfig.mMimeType.EqualsLiteral("video/avc")) { + if (!aParams.mConfig.mMimeType.EqualsLiteral("video/avc")) { return nullptr; } - if (aDiagnostics) { - const Maybe<nsCString> preferredGMP = PreferredGMP(aConfig.mMimeType); + if (aParams.mDiagnostics) { + const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType); if (preferredGMP.isSome()) { - aDiagnostics->SetGMP(preferredGMP.value()); + aParams.mDiagnostics->SetGMP(preferredGMP.value()); } } - RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback); - wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig, - aLayersBackend, - aImageContainer, - aTaskQueue, - wrapper->Callback())); + RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback); + auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new GMPVideoDecoder(params)); return wrapper.forget(); } already_AddRefed<MediaDataDecoder> -GMPDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +GMPDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { - if (!aConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) { + if (!aParams.mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) { return nullptr; } - if (aDiagnostics) { - const Maybe<nsCString> preferredGMP = PreferredGMP(aConfig.mMimeType); + if (aParams.mDiagnostics) { + const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType); if (preferredGMP.isSome()) { - aDiagnostics->SetGMP(preferredGMP.value()); + aParams.mDiagnostics->SetGMP(preferredGMP.value()); } } - RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback); - wrapper->SetProxyTarget(new GMPAudioDecoder(aConfig, - aTaskQueue, - wrapper->Callback())); + RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback); + auto params = GMPAudioDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new GMPAudioDecoder(params)); return wrapper.forget(); } PlatformDecoderModule::ConversionRequired GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const { // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format. if (aConfig.IsVideo()) {
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h @@ -28,29 +28,21 @@ namespace mozilla { class GMPDecoderModule : public PlatformDecoderModule { public: GMPDecoderModule(); virtual ~GMPDecoderModule(); // Decode thread. already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateVideoDecoder(const CreateDecoderParams& aParams) override; // Decode thread. already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateAudioDecoder(const CreateDecoderParams& aParams) override; ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override; bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override;
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp @@ -99,16 +99,62 @@ VideoCallbackAdapter::Error(GMPErr aErr) void VideoCallbackAdapter::Terminated() { // Note that this *may* be called from the proxy thread also. NS_WARNING("H.264 GMP decoder terminated."); mCallback->Error(MediaDataDecoderError::FATAL_ERROR); } +GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams) + : mConfig(aParams.VideoConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(nullptr) + , mAdapter(nullptr) + , mImageContainer(aParams.mImageContainer) + , mLayersBackend(aParams.mLayersBackend) +{} + +GMPVideoDecoderParams& +GMPVideoDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper) +{ + MOZ_ASSERT(aWrapper); + MOZ_ASSERT(!mCallback); // Should only be called once per instance. + mCallback = aWrapper->Callback(); + mAdapter = nullptr; + return *this; +} + +GMPVideoDecoderParams& +GMPVideoDecoderParams::WithAdapter(VideoCallbackAdapter* aAdapter) +{ + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!mAdapter); // Should only be called once per instance. + mCallback = aAdapter->Callback(); + mAdapter = aAdapter; + return *this; +} + +GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams) + : mConfig(aParams.mConfig) + , mCallback(aParams.mCallback) + , mGMP(nullptr) + , mHost(nullptr) + , mAdapter(aParams.mAdapter) + , mConvertNALUnitLengths(false) +{ + MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback()); + if (!mAdapter) { + mAdapter = new VideoCallbackAdapter(mCallback, + VideoInfo(mConfig.mDisplay.width, + mConfig.mDisplay.height), + aParams.mImageContainer); + } +} + void GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags) { aTags.AppendElement(NS_LITERAL_CSTRING("h264")); const Maybe<nsCString> gmp( GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc"))); if (gmp.isSome()) { aTags.AppendElement(gmp.value());
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h @@ -22,16 +22,18 @@ public: VideoInfo aVideoInfo, layers::ImageContainer* aImageContainer) : mCallback(aCallback) , mLastStreamOffset(0) , mVideoInfo(aVideoInfo) , mImageContainer(aImageContainer) {} + MediaDataDecoderCallbackProxy* Callback() const { return mCallback; } + // GMPVideoDecoderCallbackProxy void Decoded(GMPVideoi420Frame* aDecodedFrame) override; void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override; void ReceivedDecodedFrame(const uint64_t aPictureId) override; void InputDataExhausted() override; void DrainComplete() override; void ResetComplete() override; void Error(GMPErr aErr) override; @@ -44,50 +46,32 @@ public: private: MediaDataDecoderCallbackProxy* mCallback; int64_t mLastStreamOffset; VideoInfo mVideoInfo; RefPtr<layers::ImageContainer> mImageContainer; }; -class GMPVideoDecoder : public MediaDataDecoder { -protected: - GMPVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallbackProxy* aCallback, - VideoCallbackAdapter* aAdapter) - : mConfig(aConfig) - , mCallback(aCallback) - , mGMP(nullptr) - , mHost(nullptr) - , mAdapter(aAdapter) - , mConvertNALUnitLengths(false) - { - } +struct GMPVideoDecoderParams { + explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams); + GMPVideoDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper); + GMPVideoDecoderParams& WithAdapter(VideoCallbackAdapter* aAdapter); + const VideoInfo& mConfig; + TaskQueue* mTaskQueue; + MediaDataDecoderCallbackProxy* mCallback; + VideoCallbackAdapter* mAdapter; + layers::ImageContainer* mImageContainer; + layers::LayersBackend mLayersBackend; +}; + +class GMPVideoDecoder : public MediaDataDecoder { public: - GMPVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallbackProxy* aCallback) - : mConfig(aConfig) - , mCallback(aCallback) - , mGMP(nullptr) - , mHost(nullptr) - , mAdapter(new VideoCallbackAdapter(aCallback, - VideoInfo(aConfig.mDisplay.width, - aConfig.mDisplay.height), - aImageContainer)) - , mConvertNALUnitLengths(false) - { - } + explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams); RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; const char* GetDescriptionName() const override {
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp +++ b/dom/media/platforms/android/AndroidDecoderModule.cpp @@ -277,57 +277,55 @@ AndroidDecoderModule::SupportsMimeType(c return false; } return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType( nsCString(TranslateMimeType(aMimeType))); } already_AddRefed<MediaDataDecoder> -AndroidDecoderModule::CreateVideoDecoder( - const VideoInfo& aConfig, layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +AndroidDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { MediaFormat::LocalRef format; + const VideoInfo& config = aParams.VideoConfig(); NS_ENSURE_SUCCESS(MediaFormat::CreateVideoFormat( - TranslateMimeType(aConfig.mMimeType), - aConfig.mDisplay.width, - aConfig.mDisplay.height, + TranslateMimeType(config.mMimeType), + config.mDisplay.width, + config.mDisplay.height, &format), nullptr); RefPtr<MediaDataDecoder> decoder = - new VideoDataDecoder(aConfig, format, aCallback, aImageContainer); + new VideoDataDecoder(config, + format, + aParams.mCallback, + aParams.mImageContainer); return decoder.forget(); } already_AddRefed<MediaDataDecoder> -AndroidDecoderModule::CreateAudioDecoder( - const AudioInfo& aConfig, TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +AndroidDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { - MOZ_ASSERT(aConfig.mBitDepth == 16, "We only handle 16-bit audio!"); + const AudioInfo& config = aParams.AudioConfig(); + MOZ_ASSERT(config.mBitDepth == 16, "We only handle 16-bit audio!"); MediaFormat::LocalRef format; LOG("CreateAudioFormat with mimeType=%s, mRate=%d, channels=%d", - aConfig.mMimeType.Data(), aConfig.mRate, aConfig.mChannels); + config.mMimeType.Data(), config.mRate, config.mChannels); NS_ENSURE_SUCCESS(MediaFormat::CreateAudioFormat( - aConfig.mMimeType, - aConfig.mRate, - aConfig.mChannels, + config.mMimeType, + config.mRate, + config.mChannels, &format), nullptr); RefPtr<MediaDataDecoder> decoder = - new AudioDataDecoder(aConfig, format, aCallback); + new AudioDataDecoder(config, format, aParams.mCallback); return decoder.forget(); } PlatformDecoderModule::ConversionRequired AndroidDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const { if (aConfig.IsVideo()) {
--- a/dom/media/platforms/android/AndroidDecoderModule.h +++ b/dom/media/platforms/android/AndroidDecoderModule.h @@ -17,28 +17,20 @@ namespace mozilla { typedef std::deque<RefPtr<MediaRawData>> SampleQueue; class AndroidDecoderModule : public PlatformDecoderModule { public: already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateVideoDecoder(const CreateDecoderParams& aParams) override; already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateAudioDecoder(const CreateDecoderParams& aParams) override; AndroidDecoderModule() {} virtual ~AndroidDecoderModule() {} bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override;
--- a/dom/media/platforms/apple/AppleDecoderModule.cpp +++ b/dom/media/platforms/apple/AppleDecoderModule.cpp @@ -69,52 +69,49 @@ AppleDecoderModule::Startup() { if (!sInitialized || (!sIsVDAAvailable && !sIsVTAvailable)) { return NS_ERROR_FAILURE; } return NS_OK; } already_AddRefed<MediaDataDecoder> -AppleDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +AppleDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { RefPtr<MediaDataDecoder> decoder; if (sIsVDAAvailable && (!sIsVTHWAvailable || MediaPrefs::AppleForceVDA())) { decoder = - AppleVDADecoder::CreateVDADecoder(aConfig, - aTaskQueue, - aCallback, - aImageContainer); + AppleVDADecoder::CreateVDADecoder(aParams.VideoConfig(), + aParams.mTaskQueue, + aParams.mCallback, + aParams.mImageContainer); if (decoder) { return decoder.forget(); } } // We fallback here if VDA isn't available, or is available but isn't // supported by the current platform. if (sIsVTAvailable) { decoder = - new AppleVTDecoder(aConfig, aTaskQueue, aCallback, aImageContainer); + new AppleVTDecoder(aParams.VideoConfig(), + aParams.mTaskQueue, + aParams.mCallback, + aParams.mImageContainer); } return decoder.forget(); } already_AddRefed<MediaDataDecoder> -AppleDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +AppleDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { RefPtr<MediaDataDecoder> decoder = - new AppleATDecoder(aConfig, aTaskQueue, aCallback); + new AppleATDecoder(aParams.AudioConfig(), + aParams.mTaskQueue, + aParams.mCallback); return decoder.forget(); } bool AppleDecoderModule::SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const { return (sIsCoreMediaAvailable &&
--- a/dom/media/platforms/apple/AppleDecoderModule.h +++ b/dom/media/platforms/apple/AppleDecoderModule.h @@ -15,29 +15,21 @@ class AppleDecoderModule : public Platfo public: AppleDecoderModule(); virtual ~AppleDecoderModule(); nsresult Startup() override; // Decode thread. already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateVideoDecoder(const CreateDecoderParams& aParams) override; // Decode thread. already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateAudioDecoder(const CreateDecoderParams& aParams) override; bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override; ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override; static void Init();
--- a/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h +++ b/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h @@ -26,40 +26,38 @@ public: return pdm.forget(); } explicit FFmpegDecoderModule(FFmpegLibWrapper* aLib) : mLib(aLib) {} virtual ~FFmpegDecoderModule() {} already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override + CreateVideoDecoder(const CreateDecoderParams& aParams) override { RefPtr<MediaDataDecoder> decoder = - new FFmpegVideoDecoder<V>(mLib, aTaskQueue, aCallback, aConfig, - aImageContainer); + new FFmpegVideoDecoder<V>(mLib, + aParams.mTaskQueue, + aParams.mCallback, + aParams.VideoConfig(), + aParams.mImageContainer); return decoder.forget(); } already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override + CreateAudioDecoder(const CreateDecoderParams& aParams) override { #ifdef USING_MOZFFVPX return nullptr; #else RefPtr<MediaDataDecoder> decoder = - new FFmpegAudioDecoder<V>(mLib, aTaskQueue, aCallback, aConfig); + new FFmpegAudioDecoder<V>(mLib, + aParams.mTaskQueue, + aParams.mCallback, + aParams.AudioConfig()); return decoder.forget(); #endif } bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override { AVCodecID videoCodec = FFmpegVideoDecoder<V>::GetCodecId(aMimeType);
--- a/dom/media/platforms/gonk/GonkDecoderModule.cpp +++ b/dom/media/platforms/gonk/GonkDecoderModule.cpp @@ -14,34 +14,26 @@ GonkDecoderModule::GonkDecoderModule() { } GonkDecoderModule::~GonkDecoderModule() { } already_AddRefed<MediaDataDecoder> -GonkDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig, - mozilla::layers::LayersBackend aLayersBackend, - mozilla::layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +GonkDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { RefPtr<MediaDataDecoder> decoder = new GonkMediaDataDecoder(new GonkVideoDecoderManager(aImageContainer, aConfig), aCallback); return decoder.forget(); } already_AddRefed<MediaDataDecoder> -GonkDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +GonkDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { RefPtr<MediaDataDecoder> decoder = new GonkMediaDataDecoder(new GonkAudioDecoderManager(aConfig), aCallback); return decoder.forget(); } PlatformDecoderModule::ConversionRequired
--- a/dom/media/platforms/gonk/GonkDecoderModule.h +++ b/dom/media/platforms/gonk/GonkDecoderModule.h @@ -13,29 +13,21 @@ namespace mozilla { class GonkDecoderModule : public PlatformDecoderModule { public: GonkDecoderModule(); virtual ~GonkDecoderModule(); // Decode thread. already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - mozilla::layers::LayersBackend aLayersBackend, - mozilla::layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateVideoDecoder(const CreateDecoderParams& aParams) override; // Decode thread. already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateAudioDecoder(const CreateDecoderParams& aParams) override; ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override; bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override; };
--- a/dom/media/platforms/omx/OmxDecoderModule.cpp +++ b/dom/media/platforms/omx/OmxDecoderModule.cpp @@ -7,34 +7,30 @@ #include "OmxDecoderModule.h" #include "OmxDataDecoder.h" #include "OmxPlatformLayer.h" namespace mozilla { already_AddRefed<MediaDataDecoder> -OmxDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig, - mozilla::layers::LayersBackend aLayersBackend, - mozilla::layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +OmxDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { - RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aConfig, aCallback, aImageContainer); + RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aParams.mConfig, + aParams.mCallback, + aParams.mImageContainer); return decoder.forget(); } already_AddRefed<MediaDataDecoder> -OmxDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +OmxDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { - RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aConfig, aCallback, nullptr); + RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aParams.mConfig, + aParams.mCallback, + nullptr); return decoder.forget(); } PlatformDecoderModule::ConversionRequired OmxDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const { return kNeedNone; }
--- a/dom/media/platforms/omx/OmxDecoderModule.h +++ b/dom/media/platforms/omx/OmxDecoderModule.h @@ -9,28 +9,20 @@ #include "PlatformDecoderModule.h" namespace mozilla { class OmxDecoderModule : public PlatformDecoderModule { public: already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - mozilla::layers::LayersBackend aLayersBackend, - mozilla::layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateVideoDecoder(const CreateDecoderParams& aParams) override; already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateAudioDecoder(const CreateDecoderParams& aParams) override; bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override; ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override; }; } // namespace mozilla
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp +++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp @@ -71,53 +71,45 @@ WMFDecoderModule::GetNumDecoderThreads() nsresult WMFDecoderModule::Startup() { mWMFInitialized = SUCCEEDED(wmf::MFStartup()); return mWMFInitialized ? NS_OK : NS_ERROR_FAILURE; } already_AddRefed<MediaDataDecoder> -WMFDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +WMFDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { nsAutoPtr<WMFVideoMFTManager> manager( - new WMFVideoMFTManager(aConfig, - aLayersBackend, - aImageContainer, + new WMFVideoMFTManager(aParams.VideoConfig(), + aParams.mLayersBackend, + aParams.mImageContainer, sDXVAEnabled)); if (!manager->Init()) { return nullptr; } RefPtr<MediaDataDecoder> decoder = - new WMFMediaDataDecoder(manager.forget(), aTaskQueue, aCallback); + new WMFMediaDataDecoder(manager.forget(), aParams.mTaskQueue, aParams.mCallback); return decoder.forget(); } already_AddRefed<MediaDataDecoder> -WMFDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) +WMFDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { - nsAutoPtr<WMFAudioMFTManager> manager(new WMFAudioMFTManager(aConfig)); + nsAutoPtr<WMFAudioMFTManager> manager(new WMFAudioMFTManager(aParams.AudioConfig())); if (!manager->Init()) { return nullptr; } RefPtr<MediaDataDecoder> decoder = - new WMFMediaDataDecoder(manager.forget(), aTaskQueue, aCallback); + new WMFMediaDataDecoder(manager.forget(), aParams.mTaskQueue, aParams.mCallback); return decoder.forget(); } static bool CanCreateMFTDecoder(const GUID& aGuid) { if (FAILED(wmf::MFStartup())) { return false;
--- a/dom/media/platforms/wmf/WMFDecoderModule.h +++ b/dom/media/platforms/wmf/WMFDecoderModule.h @@ -15,28 +15,20 @@ class WMFDecoderModule : public Platform public: WMFDecoderModule(); virtual ~WMFDecoderModule(); // Initializes the module, loads required dynamic libraries, etc. nsresult Startup() override; already_AddRefed<MediaDataDecoder> - CreateVideoDecoder(const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateVideoDecoder(const CreateDecoderParams& aParams) override; already_AddRefed<MediaDataDecoder> - CreateAudioDecoder(const AudioInfo& aConfig, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) override; + CreateAudioDecoder(const CreateDecoderParams& aParams) override; bool SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const override; ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override; // Called on main thread.
--- a/dom/media/platforms/wrappers/H264Converter.cpp +++ b/dom/media/platforms/wrappers/H264Converter.cpp @@ -11,34 +11,29 @@ #include "MediaInfo.h" #include "mp4_demuxer/AnnexB.h" #include "mp4_demuxer/H264.h" namespace mozilla { H264Converter::H264Converter(PlatformDecoderModule* aPDM, - const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics) + const CreateDecoderParams& aParams) : mPDM(aPDM) - , mOriginalConfig(aConfig) - , mCurrentConfig(aConfig) - , mLayersBackend(aLayersBackend) - , mImageContainer(aImageContainer) - , mTaskQueue(aTaskQueue) - , mCallback(aCallback) + , mOriginalConfig(aParams.VideoConfig()) + , mCurrentConfig(aParams.VideoConfig()) + , mLayersBackend(aParams.mLayersBackend) + , mImageContainer(aParams.mImageContainer) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(aParams.mCallback) , mDecoder(nullptr) - , mNeedAVCC(aPDM->DecoderNeedsConversion(aConfig) == PlatformDecoderModule::kNeedAVCC) + , mNeedAVCC(aPDM->DecoderNeedsConversion(aParams.mConfig) == PlatformDecoderModule::kNeedAVCC) , mLastError(NS_OK) { - CreateDecoder(aDiagnostics); + CreateDecoder(aParams.mDiagnostics); } H264Converter::~H264Converter() { } RefPtr<MediaDataDecoder::InitPromise> H264Converter::Init() @@ -141,22 +136,25 @@ H264Converter::CreateDecoder(DecoderDoct } UpdateConfigFromExtraData(mCurrentConfig.mExtraData); if (!mNeedAVCC) { // When using a decoder handling AnnexB, we get here only once from the // constructor. We do want to get the dimensions extracted from the SPS. mOriginalConfig = mCurrentConfig; } - mDecoder = mPDM->CreateVideoDecoder(mNeedAVCC ? mCurrentConfig : mOriginalConfig, - mLayersBackend, - mImageContainer, - mTaskQueue, - mCallback, - aDiagnostics); + mDecoder = mPDM->CreateVideoDecoder({ + mNeedAVCC ? mCurrentConfig : mOriginalConfig, + mTaskQueue, + mCallback, + aDiagnostics, + mImageContainer, + mLayersBackend + }); + if (!mDecoder) { mLastError = NS_ERROR_FAILURE; return NS_ERROR_FAILURE; } return NS_OK; } nsresult
--- a/dom/media/platforms/wrappers/H264Converter.h +++ b/dom/media/platforms/wrappers/H264Converter.h @@ -17,22 +17,17 @@ namespace mozilla { // provided in the init segment (e.g. AVC3 or Annex B) // H264Converter will monitor the input data, and will delay creation of the // MediaDataDecoder until a SPS and PPS NALs have been extracted. class H264Converter : public MediaDataDecoder { public: H264Converter(PlatformDecoderModule* aPDM, - const VideoInfo& aConfig, - layers::LayersBackend aLayersBackend, - layers::ImageContainer* aImageContainer, - TaskQueue* aTaskQueue, - MediaDataDecoderCallback* aCallback, - DecoderDoctorDiagnostics* aDiagnostics); + const CreateDecoderParams& aParams); virtual ~H264Converter(); RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
--- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html +++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html @@ -42,16 +42,22 @@ var tests = [ constraint: "mediaSource" }, { message: "unknown mediaSource in audio fails", constraints: { audio: { mediaSource: 'uncle' } }, error: "OverconstrainedError", constraint: "mediaSource" }, { message: "emtpy constraint fails", constraints: { }, error: "NotSupportedError" }, + { message: "Triggering mock failure in default video device fails", + constraints: { video: { deviceId: 'bad device' }, fake: true }, + error: "NotReadableError" }, + { message: "Triggering mock failure in default audio device fails", + constraints: { audio: { deviceId: 'bad device' }, fake: true }, + error: "NotReadableError" }, { message: "Success-path: optional video facingMode + audio ignoring facingMode", constraints: { audio: { mediaSource: 'microphone', facingMode: 'left', foo: 0, advanced: [{ facingMode: 'environment' }, { facingMode: 'user' }, { bar: 0 }] }, video: { mediaSource: 'camera',
--- a/dom/media/webrtc/MediaEngineDefault.cpp +++ b/dom/media/webrtc/MediaEngineDefault.cpp @@ -90,16 +90,22 @@ MediaEngineDefaultVideoSource::Allocate( const MediaEnginePrefs &aPrefs, const nsString& aDeviceId, const nsACString& aOrigin) { if (mState != kReleased) { return NS_ERROR_FAILURE; } + // Mock failure for automated tests. + if (aConstraints.mDeviceId.IsString() && + aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) { + return NS_ERROR_FAILURE; + } + mOpts = aPrefs; mOpts.mWidth = mOpts.mWidth ? mOpts.mWidth : MediaEngine::DEFAULT_43_VIDEO_WIDTH; mOpts.mHeight = mOpts.mHeight ? mOpts.mHeight : MediaEngine::DEFAULT_43_VIDEO_HEIGHT; mState = kAllocated; return NS_OK; } nsresult @@ -403,16 +409,22 @@ MediaEngineDefaultAudioSource::Allocate( const MediaEnginePrefs &aPrefs, const nsString& aDeviceId, const nsACString& aOrigin) { if (mState != kReleased) { return NS_ERROR_FAILURE; } + // Mock failure for automated tests. + if (aConstraints.mDeviceId.IsString() && + aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) { + return NS_ERROR_FAILURE; + } + mState = kAllocated; // generate sine wave (default 1KHz) mSineGenerator = new SineWaveGenerator(AUDIO_RATE, static_cast<uint32_t>(aPrefs.mFreq ? aPrefs.mFreq : 1000)); return NS_OK; } nsresult
--- a/dom/moz.build +++ b/dom/moz.build @@ -26,17 +26,16 @@ interfaces = [ 'json', 'offline', 'geolocation', 'notification', 'permission', 'svg', 'smil', 'apps', - 'gamepad', 'push', ] DIRS += ['interfaces/' + i for i in interfaces] DIRS += [ 'animation', 'apps', @@ -109,16 +108,17 @@ DIRS += [ 'xul', 'resourcestats', 'manifest', 'vr', 'newapps', 'u2f', 'console', 'performance', + 'xhr', ] if CONFIG['OS_ARCH'] == 'WINNT': DIRS += ['plugins/ipc/hangui'] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': DIRS += [ 'speakermanager',
--- a/dom/plugins/ipc/PluginInstanceChild.cpp +++ b/dom/plugins/ipc/PluginInstanceChild.cpp @@ -4628,10 +4628,16 @@ PluginInstanceChild::AnswerNPP_Destroy(N Destroy(); return true; } void PluginInstanceChild::ActorDestroy(ActorDestroyReason why) { +#ifdef XP_WIN + // ClearAllSurfaces() should not try to send anything after ActorDestroy. + mCurrentSurfaceActor = nullptr; + mBackSurfaceActor = nullptr; +#endif + Destroy(); }
--- a/dom/push/PushDB.jsm +++ b/dom/push/PushDB.jsm @@ -120,20 +120,20 @@ this.PushDB.prototype = { */ delete: function(aKeyID) { console.debug("delete()"); return new Promise((resolve, reject) => this.newTxn( "readwrite", this._dbStoreName, - function txnCb(aTxn, aStore) { + (aTxn, aStore) => { console.debug("delete: Removing record", aKeyID); aStore.get(aKeyID).onsuccess = event => { - aTxn.result = event.target.result; + aTxn.result = this.toPushRecord(event.target.result); aStore.delete(aKeyID); }; }, resolve, reject ) ); },
--- a/dom/push/PushNotifier.cpp +++ b/dom/push/PushNotifier.cpp @@ -42,55 +42,60 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNoti NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier) NS_IMETHODIMP PushNotifier::NotifyPushWithData(const nsACString& aScope, nsIPrincipal* aPrincipal, const nsAString& aMessageId, uint32_t aDataLen, uint8_t* aData) { + NS_ENSURE_ARG(aPrincipal); nsTArray<uint8_t> data; if (!data.SetCapacity(aDataLen, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } if (!data.InsertElementsAt(0, aData, aDataLen, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Some(data)); return Dispatch(dispatcher); } NS_IMETHODIMP PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal, const nsAString& aMessageId) { + NS_ENSURE_ARG(aPrincipal); PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing()); return Dispatch(dispatcher); } NS_IMETHODIMP PushNotifier::NotifySubscriptionChange(const nsACString& aScope, nsIPrincipal* aPrincipal) { + NS_ENSURE_ARG(aPrincipal); PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal); return Dispatch(dispatcher); } NS_IMETHODIMP PushNotifier::NotifySubscriptionModified(const nsACString& aScope, nsIPrincipal* aPrincipal) { + NS_ENSURE_ARG(aPrincipal); PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal); return Dispatch(dispatcher); } NS_IMETHODIMP PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal, const nsAString& aMessage, uint32_t aFlags) { + NS_ENSURE_ARG(aPrincipal); PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags); return Dispatch(dispatcher); } nsresult PushNotifier::Dispatch(PushDispatcher& aDispatcher) { if (XRE_IsParentProcess()) { @@ -271,16 +276,19 @@ PushDispatcher::NotifyObserversAndWorker { Unused << NS_WARN_IF(NS_FAILED(NotifyObservers())); return NotifyWorkers(); } bool PushDispatcher::ShouldNotifyWorkers() { + if (NS_WARN_IF(!mPrincipal)) { + return false; + } // System subscriptions use observer notifications instead of service worker // events. The `testing.notifyWorkers` pref disables worker events for // non-system subscriptions. return !nsContentUtils::IsSystemPrincipal(mPrincipal) && Preferences::GetBool("dom.push.testing.notifyWorkers", true); } nsresult
--- a/dom/push/PushService.jsm +++ b/dom/push/PushService.jsm @@ -1018,20 +1018,18 @@ this.PushService = { getAllUnexpired: function() { return this._db.getAllUnexpired(); }, _sendRequest(action, ...params) { if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) { return Promise.reject(new Error("Push service disabled")); - } else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) { - if (this._service.serviceType() == "WebSocket" && action == "unregister") { - return Promise.resolve(); - } + } + if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) { return Promise.reject(new Error("Push service offline")); } // Ensure the backend is ready. `getByPageRecord` already checks this, but // we need to check again here in case the service was restarted in the // meantime. return this._checkActivated().then(_ => { switch (action) { case "register": @@ -1198,22 +1196,23 @@ this.PushService = { .then(record => { if (record === undefined) { return false; } let reason = Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL; return Promise.all([ this._sendUnregister(record, reason), - this._db.delete(record.keyID), - ]).then(() => { - gPushNotifier.notifySubscriptionModified(record.scope, - record.principal); - return true; - }); + this._db.delete(record.keyID).then(record => { + if (record) { + gPushNotifier.notifySubscriptionModified(record.scope, + record.principal); + } + }), + ]).then(([success]) => success); }); }, clear: function(info) { return this._checkActivated() .then(_ => { return this._dropRegistrationsIf(record => info.domain == "*" ||
--- a/dom/push/PushServiceAndroidGCM.jsm +++ b/dom/push/PushServiceAndroidGCM.jsm @@ -54,20 +54,16 @@ this.PushServiceAndroidGCM = { newPushDB: function() { return new PushDB(kPUSHANDROIDGCMDB_DB_NAME, kPUSHANDROIDGCMDB_DB_VERSION, kPUSHANDROIDGCMDB_STORE_NAME, "channelID", PushRecordAndroidGCM); }, - serviceType: function() { - return "AndroidGCM"; - }, - validServerURI: function(serverURI) { if (!serverURI) { return false; } if (serverURI.scheme == "https") { return true; }
--- a/dom/push/PushServiceHttp2.jsm +++ b/dom/push/PushServiceHttp2.jsm @@ -425,20 +425,16 @@ this.PushServiceHttp2 = { newPushDB: function() { return new PushDB(kPUSHHTTP2DB_DB_NAME, kPUSHHTTP2DB_DB_VERSION, kPUSHHTTP2DB_STORE_NAME, "subscriptionUri", PushRecordHttp2); }, - serviceType: function() { - return "http2"; - }, - hasmainPushService: function() { return this._mainPushService !== null; }, validServerURI: function(serverURI) { if (serverURI.scheme == "http") { return !!prefs.get("testing.allowInsecureServerURL"); }
--- a/dom/push/PushServiceWebSocket.jsm +++ b/dom/push/PushServiceWebSocket.jsm @@ -138,20 +138,16 @@ this.PushServiceWebSocket = { newPushDB: function() { return new PushDB(kPUSHWSDB_DB_NAME, kPUSHWSDB_DB_VERSION, kPUSHWSDB_STORE_NAME, "channelID", PushRecordWebSocket); }, - serviceType: function() { - return "WebSocket"; - }, - disconnect: function() { this._shutdownWS(); }, observe: function(aSubject, aTopic, aData) { if (aTopic == "nsPref:changed" && aData == "dom.push.userAgentID") { this._onUAIDChanged(); } else if (aTopic == "timer-callback") { @@ -229,27 +225,25 @@ this.PushServiceWebSocket = { if (this._lastPingTime > 0 && now - this._lastPingTime > this._requestTimeout) { console.debug("timeOutRequests: Did not receive pong in time"); requestTimedOut = true; } else { - for (let [channelID, request] of this._registerRequests) { + for (let [key, request] of this._pendingRequests) { let duration = now - request.ctime; // If any of the registration requests time out, all the ones after it // also made to fail, since we are going to be disconnecting the // socket. requestTimedOut |= duration > this._requestTimeout; if (requestTimedOut) { - request.reject(new Error( - "Register request timed out for channel ID " + channelID)); - - this._registerRequests.delete(channelID); + request.reject(new Error("Request timed out: " + key)); + this._pendingRequests.delete(key); } } } // The most likely reason for a pong or registration request timing out is // that the socket has disconnected. Best to reconnect. if (requestTimedOut) { this._reconnect(); @@ -273,17 +267,17 @@ this.PushServiceWebSocket = { "Not updating userAgentID"); return; } console.debug("New _UAID", newID); prefs.set("userAgentID", newID); }, _ws: null, - _registerRequests: new Map(), + _pendingRequests: new Map(), _currentState: STATE_SHUT_DOWN, _requestTimeout: 0, _requestTimeoutTimer: null, _retryFailCount: 0, /** * According to the WS spec, servers should immediately close the underlying * TCP connection after they close a WebSocket. This causes wsOnStop to be @@ -371,17 +365,17 @@ this.PushServiceWebSocket = { this._lastPingTime = 0; if (this._pingTimer) { this._pingTimer.cancel(); } if (shouldCancelPending) { - this._cancelRegisterRequests(); + this._cancelPendingRequests(); } if (this._notifyRequestQueue) { this._notifyRequestQueue(); this._notifyRequestQueue = null; } }, @@ -432,17 +426,17 @@ this.PushServiceWebSocket = { this._backoffTimer = Cc["@mozilla.org/timer;1"] .createInstance(Ci.nsITimer); } this._backoffTimer.init(this, retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT); }, /** Indicates whether we're waiting for pongs or requests. */ _hasPendingRequests() { - return this._lastPingTime > 0 || this._registerRequests.size > 0; + return this._lastPingTime > 0 || this._pendingRequests.size > 0; }, /** * Starts the request timeout timer unless we're already waiting for a pong * or register request. */ _startRequestTimeoutTimer() { if (this._hasPendingRequests()) { @@ -617,17 +611,17 @@ this.PushServiceWebSocket = { return; } let sendRequests = () => { if (this._notifyRequestQueue) { this._notifyRequestQueue(); this._notifyRequestQueue = null; } - this._sendRegisterRequests(); + this._sendPendingRequests(); }; function finishHandshake() { this._UAID = reply.uaid; this._currentState = STATE_READY; prefs.observe("userAgentID", this); this._dataEnabled = !!reply.use_webpush; @@ -664,27 +658,22 @@ this.PushServiceWebSocket = { finishHandshake.bind(this)(); }, /** * Protocol handler invoked by server message. */ _handleRegisterReply: function(reply) { console.debug("handleRegisterReply()"); - if (typeof reply.channelID !== "string" || - !this._registerRequests.has(reply.channelID)) { + + let tmp = this._takeRequestForReply(reply); + if (!tmp) { return; } - let tmp = this._registerRequests.get(reply.channelID); - this._registerRequests.delete(reply.channelID); - if (!this._hasPendingRequests()) { - this._requestTimeoutTimer.cancel(); - } - if (reply.status == 200) { try { Services.io.newURI(reply.pushEndpoint, null, null); } catch (e) { tmp.reject(new Error("Invalid push endpoint: " + reply.pushEndpoint)); return; } @@ -703,16 +692,28 @@ this.PushServiceWebSocket = { tmp.resolve(record); } else { console.error("handleRegisterReply: Unexpected server response", reply); tmp.reject(new Error("Wrong status code for register reply: " + reply.status)); } }, + _handleUnregisterReply(reply) { + console.debug("handleUnregisterReply()"); + + let request = this._takeRequestForReply(reply); + if (!request) { + return; + } + + let success = reply.status === 200; + request.resolve(success); + }, + _handleDataUpdate: function(update) { let promise; if (typeof update.channelID != "string") { console.warn("handleDataUpdate: Discarding update without channel ID", update); return; } function updateRecord(record) { @@ -840,112 +841,131 @@ this.PushServiceWebSocket = { .getService(Ci.nsIUUIDGenerator); // generateUUID() gives a UUID surrounded by {...}, slice them off. return uuidGenerator.generateUUID().toString().slice(1, -1); }, register(record) { console.debug("register() ", record); - // start the timer since we now have at least one request - this._startRequestTimeoutTimer(); - let data = {channelID: this._generateID(), messageType: "register"}; if (record.appServerKey) { data.key = ChromeUtils.base64URLEncode(record.appServerKey, { // The Push server requires padding. pad: true, }); } - return new Promise((resolve, reject) => { - this._registerRequests.set(data.channelID, { - record: record, - resolve: resolve, - reject: reject, - ctime: Date.now(), - }); - this._queueRequest(data); - }).then(record => { + return this._sendRequestForReply(record, data).then(record => { if (!this._dataEnabled) { return record; } return PushCrypto.generateKeys() .then(([publicKey, privateKey]) => { record.p256dhPublicKey = publicKey; record.p256dhPrivateKey = privateKey; record.authenticationSecret = PushCrypto.generateAuthenticationSecret(); return record; }); }); }, unregister(record, reason) { console.debug("unregister() ", record, reason); - let code = kUNREGISTER_REASON_TO_CODE[reason]; - if (!code) { - return Promise.reject(new Error('Invalid unregister reason')); - } - let data = {channelID: record.channelID, - messageType: "unregister", - code: code}; - this._queueRequest(data); - return Promise.resolve(); + return Promise.resolve().then(_ => { + let code = kUNREGISTER_REASON_TO_CODE[reason]; + if (!code) { + throw new Error('Invalid unregister reason'); + } + let data = {channelID: record.channelID, + messageType: "unregister", + code: code}; + + return this._sendRequestForReply(record, data); + }); }, _queueStart: Promise.resolve(), _notifyRequestQueue: null, _queue: null, _enqueue: function(op) { console.debug("enqueue()"); if (!this._queue) { this._queue = this._queueStart; } this._queue = this._queue .then(op) .catch(_ => {}); }, + /** Sends a request to the server. */ _send(data) { - if (this._currentState == STATE_READY) { - if (data.messageType != "register" || - this._registerRequests.has(data.channelID)) { - - // check if request has not been cancelled - this._wsSendMessage(data); - } + if (this._currentState != STATE_READY) { + console.warn("send: Unexpected state; ignoring message", + this._currentState); + return; } + if (!this._requestHasReply(data)) { + this._wsSendMessage(data); + return; + } + // If we're expecting a reply, check that we haven't cancelled the request. + let key = this._makePendingRequestKey(data); + if (!this._pendingRequests.has(key)) { + console.log("send: Request cancelled; ignoring message", key); + return; + } + this._wsSendMessage(data); }, - _sendRegisterRequests() { + /** Indicates whether a request has a corresponding reply from the server. */ + _requestHasReply(data) { + return data.messageType == "register" || data.messageType == "unregister"; + }, + + /** + * Sends all pending requests that expect replies. Called after the connection + * is established and the handshake is complete. + */ + _sendPendingRequests() { this._enqueue(_ => {