diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 4ed8d872b7..616cc3aabb 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -191,6 +191,7 @@ #include "mozilla/dom/VRDevice.h" #include "nsRefreshDriver.h" +#include "Layers.h" #include "mozilla/AddonPathService.h" #include "mozilla/BasePrincipal.h" diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 0f321079d3..c8751a86e2 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -60,6 +60,7 @@ #include "nsBidi.h" #include "nsBidiPresUtils.h" #include "Layers.h" +#include "LayerUserData.h" #include "CanvasUtils.h" #include "nsIMemoryReporter.h" #include "nsStyleUtil.h" diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index 20f7f89ac9..fe44075946 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -21,6 +21,7 @@ #include "ImageContainer.h" #include "ImageEncoder.h" #include "Layers.h" +#include "LayerUserData.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/HTMLVideoElement.h" diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp index 843fe47d65..8352e9c84c 100644 --- a/dom/events/ContentEventHandler.cpp +++ b/dom/events/ContentEventHandler.cpp @@ -203,13 +203,13 @@ ContentEventHandler::QueryContentRect(nsIContent* aContent, // get rect for first frame nsRect resultRect(nsPoint(0, 0), frame->GetRect().Size()); - nsresult rv = ConvertToRootViewRelativeOffset(frame, resultRect); + nsresult rv = ConvertToRootRelativeOffset(frame, resultRect); NS_ENSURE_SUCCESS(rv, rv); // account for any additional frames while ((frame = frame->GetNextContinuation()) != nullptr) { nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size()); - rv = ConvertToRootViewRelativeOffset(frame, frameRect); + rv = ConvertToRootRelativeOffset(frame, frameRect); NS_ENSURE_SUCCESS(rv, rv); resultRect.UnionRect(resultRect, frameRect); } @@ -1017,7 +1017,7 @@ ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent) // get the starting frame rect nsRect rect(nsPoint(0, 0), firstFrame->GetRect().Size()); - rv = ConvertToRootViewRelativeOffset(firstFrame, rect); + rv = ConvertToRootRelativeOffset(firstFrame, rect); NS_ENSURE_SUCCESS(rv, rv); nsRect frameRect = rect; nsPoint ptOffset; @@ -1059,7 +1059,7 @@ ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent) } } frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size()); - rv = ConvertToRootViewRelativeOffset(frame, frameRect); + rv = ConvertToRootRelativeOffset(frame, frameRect); NS_ENSURE_SUCCESS(rv, rv); if (frame != lastFrame) { // not last frame, so just add rect to previous result @@ -1125,7 +1125,7 @@ ContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent) lineBreakType); NS_ENSURE_SUCCESS(rv, rv); if (offset == aEvent->mInput.mOffset) { - rv = ConvertToRootViewRelativeOffset(caretFrame, caretRect); + rv = ConvertToRootRelativeOffset(caretFrame, caretRect); NS_ENSURE_SUCCESS(rv, rv); nscoord appUnitsPerDevPixel = caretFrame->PresContext()->AppUnitsPerDevPixel(); @@ -1186,7 +1186,7 @@ ContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent) rect.height = fontHeight; } - rv = ConvertToRootViewRelativeOffset(frame, rect); + rv = ConvertToRootRelativeOffset(frame, rect); NS_ENSURE_SUCCESS(rv, rv); aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped( @@ -1547,18 +1547,21 @@ ContentEventHandler::GetStartFrameAndOffset(const nsRange* aRange, } nsresult -ContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame* aFrame, - nsRect& aRect) +ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame* aFrame, + nsRect& aRect) { NS_ASSERTION(aFrame, "aFrame must not be null"); - nsView* view = nullptr; - nsPoint posInView; - aFrame->GetOffsetFromView(posInView, &view); - if (!view) { + nsPresContext* rootPresContext = aFrame->PresContext()->GetRootPresContext(); + if (NS_WARN_IF(!rootPresContext)) { return NS_ERROR_FAILURE; } - aRect += posInView + view->GetOffsetTo(nullptr); + nsIFrame* rootFrame = rootPresContext->PresShell()->GetRootFrame(); + if (NS_WARN_IF(!rootFrame)) { + return NS_ERROR_FAILURE; + } + + aRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, aRect, rootFrame); return NS_OK; } diff --git a/dom/events/ContentEventHandler.h b/dom/events/ContentEventHandler.h index ecda97123e..6883ea14af 100644 --- a/dom/events/ContentEventHandler.h +++ b/dom/events/ContentEventHandler.h @@ -136,9 +136,10 @@ protected: nsresult GetStartFrameAndOffset(const nsRange* aRange, nsIFrame*& aFrame, int32_t& aOffsetInFrame); - // Convert the frame relative offset to the root view relative offset. - nsresult ConvertToRootViewRelativeOffset(nsIFrame* aFrame, - nsRect& aRect); + // Convert the frame relative offset to the root frame of the root presContext + // relative offset. + nsresult ConvertToRootRelativeOffset(nsIFrame* aFrame, + nsRect& aRect); // Expand aXPOffset to the nearest offset in cluster boundary. aForward is // true, it is expanded to forward. nsresult ExpandToClusterBoundary(nsIContent* aContent, bool aForward, diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 52ce9be123..210127c99f 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -986,7 +986,7 @@ HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) co nsAutoString value; GetValueInternal(value); // SetValueInternal handles setting the VALUE_CHANGED bit for us - rv = it->SetValueInternal(value, false, true); + rv = it->SetValueInternal(value, nsTextEditorState::eSetValue_Notify); NS_ENSURE_SUCCESS(rv, rv); } break; @@ -1173,7 +1173,8 @@ HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, // if @max in the example above were to change from 1 to -1. nsAutoString value; GetValue(value); - nsresult rv = SetValueInternal(value, false, false); + nsresult rv = + SetValueInternal(value, nsTextEditorState::eSetValue_Internal); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW), "HTML5 spec does not allow this"); @@ -1186,7 +1187,8 @@ HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, // See @max comment nsAutoString value; GetValue(value); - nsresult rv = SetValueInternal(value, false, false); + nsresult rv = + SetValueInternal(value, nsTextEditorState::eSetValue_Internal); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW), "HTML5 spec does not allow this"); @@ -1197,7 +1199,8 @@ HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, // See @max comment nsAutoString value; GetValue(value); - nsresult rv = SetValueInternal(value, false, false); + nsresult rv = + SetValueInternal(value, nsTextEditorState::eSetValue_Internal); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW), "HTML5 spec does not allow this"); @@ -1585,7 +1588,9 @@ HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv) nsAutoString currentValue; GetValue(currentValue); - nsresult rv = SetValueInternal(aValue, false, true); + nsresult rv = + SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent | + nsTextEditorState::eSetValue_Notify); if (NS_FAILED(rv)) { aRv.Throw(rv); return; @@ -1595,7 +1600,9 @@ HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv) GetValue(mFocusedValue); } } else { - nsresult rv = SetValueInternal(aValue, false, true); + nsresult rv = + SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent | + nsTextEditorState::eSetValue_Notify); if (NS_FAILED(rv)) { aRv.Throw(rv); return; @@ -2212,7 +2219,9 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) MozSetFileNameArray(list, rv); return rv.StealNSResult(); } else { - nsresult rv = SetValueInternal(aValue, true, true); + nsresult rv = + SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput | + nsTextEditorState::eSetValue_Notify); NS_ENSURE_SUCCESS(rv, rv); } @@ -2520,9 +2529,7 @@ HTMLInputElement::UpdateFileList() } nsresult -HTMLInputElement::SetValueInternal(const nsAString& aValue, - bool aUserInput, - bool aSetValueChanged) +HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags) { NS_PRECONDITION(GetValueMode() != VALUE_MODE_FILENAME, "Don't call SetValueInternal for file inputs"); @@ -2540,12 +2547,13 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, } // else DoneCreatingElement calls us again once mParserCreating is false - if (aSetValueChanged) { + bool setValueChanged = !!(aFlags & nsTextEditorState::eSetValue_Notify); + if (setValueChanged) { SetValueChanged(true); } if (IsSingleLineTextControl(false)) { - if (!mInputData.mState->SetValue(value, aUserInput, aSetValueChanged)) { + if (!mInputData.mState->SetValue(value, aFlags)) { return NS_ERROR_OUT_OF_MEMORY; } if (mType == NS_FORM_INPUT_EMAIL) { @@ -2554,7 +2562,7 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, } else { free(mInputData.mValue); mInputData.mValue = ToNewUnicode(value); - if (aSetValueChanged) { + if (setValueChanged) { SetValueChanged(true); } if (mType == NS_FORM_INPUT_NUMBER) { @@ -3164,7 +3172,8 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor) if (IsExperimentalMobileType(mType)) { nsAutoString aValue; GetValueInternal(aValue); - nsresult rv = SetValueInternal(aValue, false, false); + nsresult rv = + SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal); NS_ENSURE_SUCCESS(rv, rv); } FireChangeEventIfNeeded(); @@ -3285,7 +3294,9 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor) numberControlFrame->GetValueOfAnonTextControl(value); numberControlFrame->HandlingInputEvent(true); nsWeakFrame weakNumberControlFrame(numberControlFrame); - rv = SetValueInternal(value, true, true); + rv = SetValueInternal(value, + nsTextEditorState::eSetValue_BySetUserInput | + nsTextEditorState::eSetValue_Notify); NS_ENSURE_SUCCESS(rv, rv); if (weakNumberControlFrame.IsAlive()) { numberControlFrame->HandlingInputEvent(false); @@ -3362,7 +3373,8 @@ HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent) ConvertNumberToString(mRangeThumbDragStartValue, val); // TODO: What should we do if SetValueInternal fails? (The allocation // is small, so we should be fine here.) - SetValueInternal(val, true, true); + SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput | + nsTextEditorState::eSetValue_Notify); nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame()); if (frame) { frame->UpdateForValueChange(); @@ -3382,7 +3394,8 @@ HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue) ConvertNumberToString(aValue, val); // TODO: What should we do if SetValueInternal fails? (The allocation // is small, so we should be fine here.) - SetValueInternal(val, true, true); + SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput | + nsTextEditorState::eSetValue_Notify); nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame()); if (frame) { frame->UpdateForValueChange(); @@ -3477,7 +3490,8 @@ HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) ConvertNumberToString(newValue, newVal); // TODO: What should we do if SetValueInternal fails? (The allocation // is small, so we should be fine here.) - SetValueInternal(newVal, true, true); + SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput | + nsTextEditorState::eSetValue_Notify); nsContentUtils::DispatchTrustedEvent(OwnerDoc(), static_cast(this), @@ -4284,7 +4298,7 @@ HTMLInputElement::HandleTypeChange(uint8_t aNewType) // TODO: What should we do if SetValueInternal fails? (The allocation // may potentially be big, but most likely we've failed to allocate // before the type change.) - SetValueInternal(value, false, false); + SetValueInternal(value, nsTextEditorState::eSetValue_Internal); } break; case VALUE_MODE_FILENAME: @@ -5074,7 +5088,8 @@ HTMLInputElement::SetRangeText(const nsAString& aReplacement, uint32_t aStart, if (aStart <= aEnd) { value.Replace(aStart, aEnd - aStart, aReplacement); - nsresult rv = SetValueInternal(value, false, false); + nsresult rv = + SetValueInternal(value, nsTextEditorState::eSetValue_ByContent); if (NS_FAILED(rv)) { aRv.Throw(rv); return; @@ -5395,7 +5410,7 @@ HTMLInputElement::SetDefaultValueAsValue() GetDefaultValue(resetVal); // SetValueInternal is going to sanitize the value. - return SetValueInternal(resetVal, false, false); + return SetValueInternal(resetVal, nsTextEditorState::eSetValue_Internal); } void @@ -5636,7 +5651,7 @@ HTMLInputElement::DoneCreatingElement() // TODO: What should we do if SetValueInternal fails? (The allocation // may potentially be big, but most likely we've failed to allocate // before the type change.) - SetValueInternal(aValue, false, false); + SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal); } mShouldInitChecked = false; @@ -5795,7 +5810,8 @@ HTMLInputElement::RestoreState(nsPresState* aState) // TODO: What should we do if SetValueInternal fails? (The allocation // may potentially be big, but most likely we've failed to allocate // before the type change.) - SetValueInternal(inputState->GetValue(), false, true); + SetValueInternal(inputState->GetValue(), + nsTextEditorState::eSetValue_Notify); break; } } diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index c99b908b26..f9370b0529 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -846,9 +846,14 @@ protected: uint32_t aLen, uint32_t* aResult); // Helper method - nsresult SetValueInternal(const nsAString& aValue, - bool aUserInput, - bool aSetValueChanged); + + /** + * Setting the value. + * + * @param aValue String to set. + * @param aFlags See nsTextEditorState::SetValueFlags. + */ + nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags); nsresult GetValueInternal(nsAString& aValue) const; diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index 1ef7768c99..47e36187fa 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -297,13 +297,14 @@ HTMLTextAreaElement::GetPlaceholderVisibility() nsresult HTMLTextAreaElement::SetValueInternal(const nsAString& aValue, - bool aUserInput) + uint32_t aFlags) { // Need to set the value changed flag here, so that // nsTextControlFrame::UpdateValueDisplay retrieves the correct value // if needed. SetValueChanged(true); - if (!mState.SetValue(aValue, aUserInput, true)) { + aFlags |= nsTextEditorState::eSetValue_Notify; + if (!mState.SetValue(aValue, aFlags)) { return NS_ERROR_OUT_OF_MEMORY; } @@ -323,7 +324,8 @@ HTMLTextAreaElement::SetValue(const nsAString& aValue) nsAutoString currentValue; GetValueInternal(currentValue, true); - nsresult rv = SetValueInternal(aValue, false); + nsresult rv = + SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent); NS_ENSURE_SUCCESS(rv, rv); if (mFocusedValue.Equals(currentValue)) { @@ -339,7 +341,7 @@ HTMLTextAreaElement::SetUserInput(const nsAString& aValue) if (!nsContentUtils::IsCallerChrome()) { return NS_ERROR_DOM_SECURITY_ERR; } - return SetValueInternal(aValue, true); + return SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput); } NS_IMETHODIMP @@ -968,7 +970,8 @@ HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement, if (aStart <= aEnd) { value.Replace(aStart, aEnd - aStart, aReplacement); - nsresult rv = SetValueInternal(value, false); + nsresult rv = + SetValueInternal(value, nsTextEditorState::eSetValue_ByContent); if (NS_FAILED(rv)) { aRv.Throw(rv); return; diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h index 343fba93b6..3189997137 100644 --- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -322,8 +322,14 @@ protected: */ void GetValueInternal(nsAString& aValue, bool aIgnoreWrap) const; - nsresult SetValueInternal(const nsAString& aValue, - bool aUserInput); + /** + * Setting the value. + * + * @param aValue String to set. + * @param aFlags See nsTextEditorState::SetValueFlags. + */ + nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags); + nsresult GetSelectionRange(int32_t* aSelectionStart, int32_t* aSelectionEnd); /** diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp index a06d3b687c..19115292b7 100644 --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -27,6 +27,7 @@ #include "nsAttrValueInlines.h" #include "nsGenericHTMLElement.h" #include "nsIDOMEventListener.h" +#include "nsIEditorIMESupport.h" #include "nsIEditorObserver.h" #include "nsIWidget.h" #include "nsIDocumentEncoder.h" @@ -1019,16 +1020,16 @@ nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate, // nsTextEditorState nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement) - : mTextCtrlElement(aOwningElement), - mRestoringSelection(nullptr), - mBoundFrame(nullptr), - mEverInited(false), - mEditorInitialized(false), - mInitializing(false), - mValueTransferInProgress(false), - mSelectionCached(true), - mSelectionRestoreEagerInit(false), - mPlaceholderVisibility(false) + : mTextCtrlElement(aOwningElement) + , mBoundFrame(nullptr) + , mEverInited(false) + , mEditorInitialized(false) + , mInitializing(false) + , mValueTransferInProgress(false) + , mSelectionCached(true) + , mSelectionRestoreEagerInit(false) + , mPlaceholderVisibility(false) + , mIsCommittingComposition(false) { MOZ_COUNT_CTOR(nsTextEditorState); } @@ -1305,6 +1306,13 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue) } newEditor = mEditor; // just pretend that we have a new editor! + + // Don't lose application flags in the process. + uint32_t originalFlags = 0; + newEditor->GetFlags(&originalFlags); + if (originalFlags & nsIPlaintextEditor::eEditorMailMask) { + editorFlags |= nsIPlaintextEditor::eEditorMailMask; + } } // Get the current value of the textfield from the content. @@ -1438,7 +1446,7 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue) rv = newEditor->EnableUndo(false); NS_ENSURE_SUCCESS(rv, rv); - bool success = SetValue(defaultValue, false, false); + bool success = SetValue(defaultValue, eSetValue_Internal); NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); rv = newEditor->EnableUndo(true); @@ -1494,6 +1502,12 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue) return rv; } +void +nsTextEditorState::FinishedRestoringSelection() +{ + mRestoringSelection = nullptr; +} + bool nsTextEditorState::IsSelectionCached() const { @@ -1698,7 +1712,7 @@ nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame) // Now that we don't have a frame any more, store the value in the text buffer. // The only case where we don't do this is if a value transfer is in progress. if (!mValueTransferInProgress) { - bool success = SetValue(value, false, false); + bool success = SetValue(value, eSetValue_Internal); // TODO Find something better to do if this fails... NS_ENSURE_TRUE_VOID(success); } @@ -1853,6 +1867,15 @@ nsTextEditorState::GetMaxLength() void nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const { + // While SetValue() is being called and requesting to commit composition to + // IME, GetValue() may be called for appending text or something. Then, we + // need to return the latest aValue of SetValue() since the value hasn't + // been set to the editor yet. + if (mIsCommittingComposition) { + aValue = mValueBeingSet; + return; + } + if (mEditor && mBoundFrame && (mEditorInitialized || !IsSingleLineTextControl())) { bool canCache = aIgnoreWrap && !IsSingleLineTextControl(); if (canCache && !mCachedValue.IsEmpty()) { @@ -1914,9 +1937,73 @@ nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const } bool -nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput, - bool aSetValueChanged) +nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags) { + nsAutoString newValue(aValue); + + // While mIsCommittingComposition is true (that means that some event + // handlers which are fired during committing composition are the caller of + // this method), GetValue() uses mValueBeingSet for its result because the + // first calls of this methods hasn't set the value yet. So, when it's true, + // we need to modify mValueBeingSet. In this case, we will back to the first + // call of this method, then, mValueBeingSet will be truncated when + // mIsCommittingComposition is set false. See below. + if (mIsCommittingComposition) { + mValueBeingSet = aValue; + } + + // Note that if this may be called during reframe of the editor. In such + // case, we shouldn't commit composition. Therefore, when this is called + // for internal processing, we shouldn't commit the composition. + if (aFlags & (eSetValue_BySetUserInput | eSetValue_ByContent)) { + if (EditorHasComposition()) { + // When this is called recursively, there shouldn't be composition. + if (NS_WARN_IF(mIsCommittingComposition)) { + // Don't request to commit composition again. But if it occurs, + // we should skip to set the new value to the editor here. It should + // be set later with the updated mValueBeingSet. + return true; + } + // If there is composition, need to commit composition first because + // other browsers do that. + // NOTE: We don't need to block nested calls of this because input nor + // other events won't be fired by setting values and script blocker + // is used during setting the value to the editor. IE also allows + // to set the editor value on the input event which is caused by + // forcibly committing composition. + if (nsContentUtils::IsSafeToRunScript()) { + WeakPtr self(this); + // WARNING: During this call, compositionupdate, compositionend, input + // events will be fired. Therefore, everything can occur. E.g., the + // document may be unloaded. + mValueBeingSet = aValue; + mIsCommittingComposition = true; + nsCOMPtr editorIMESupport = + do_QueryInterface(mEditor); + MOZ_RELEASE_ASSERT(editorIMESupport); + nsresult rv = editorIMESupport->ForceCompositionEnd(); + if (!self.get()) { + return true; + } + mIsCommittingComposition = false; + // If this is called recursively during committing composition and + // some of them may be skipped above. Therefore, we need to set + // value to the editor with the aValue of the latest call. + newValue = mValueBeingSet; + // When mIsCommittingComposition is false, mValueBeingSet won't be + // used. Therefore, let's clear it. + mValueBeingSet.Truncate(); + if (NS_FAILED(rv)) { + NS_WARNING("nsTextEditorState failed to commit composition"); + return true; + } + } else { + NS_WARNING("SetValue() is called when there is composition but " + "it's not safe to request to commit the composition"); + } + } + } + if (mEditor && mBoundFrame) { // The InsertText call below might flush pending notifications, which // could lead into a scheduled PrepareEditor to be called. That will @@ -1938,19 +2025,13 @@ nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput, nsWeakFrame weakFrame(mBoundFrame); // this is necessary to avoid infinite recursion - if (!currentValue.Equals(aValue)) + if (!currentValue.Equals(newValue)) { ValueSetter valueSetter(mEditor); // \r is an illegal character in the dom, but people use them, // so convert windows and mac platform linebreaks to \n: - // Unfortunately aValue is declared const, so we have to copy - // in order to do this substitution. - nsString newValue; - if (!newValue.Assign(aValue, fallible)) { - return false; - } - if (aValue.FindChar(char16_t('\r')) != -1) { + if (newValue.FindChar(char16_t('\r')) != -1) { if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) { return false; } @@ -2013,7 +2094,8 @@ nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput, mEditor->SetFlags(flags); mTextListener->SettingValue(true); - mTextListener->SetValueChanged(aSetValueChanged); + bool notifyValueChanged = !!(aFlags & eSetValue_Notify); + mTextListener->SetValueChanged(notifyValueChanged); // Also don't enforce max-length here int32_t savedMaxLength; @@ -2036,7 +2118,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput, // the existing selection -- see bug 574558), in which case we don't // need to reset the value here. if (!mBoundFrame) { - return SetValue(newValue, false, aSetValueChanged); + return SetValue(newValue, aFlags & eSetValue_Notify); } return true; } @@ -2058,7 +2140,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput, mValue = new nsCString; } nsString value; - if (!value.Assign(aValue, fallible)) { + if (!value.Assign(newValue, fallible)) { return false; } if (!nsContentUtils::PlatformToDOMLineBreaks(value, fallible)) { @@ -2158,6 +2240,16 @@ nsTextEditorState::HideSelectionIfBlurred() } } +bool +nsTextEditorState::EditorHasComposition() +{ + bool isComposing = false; + nsCOMPtr editorIMESupport = do_QueryInterface(mEditor); + return editorIMESupport && + NS_SUCCEEDED(editorIMESupport->GetComposing(&isComposing)) && + isComposing; +} + NS_IMPL_ISUPPORTS(nsAnonDivObserver, nsIMutationObserver) void diff --git a/dom/html/nsTextEditorState.h b/dom/html/nsTextEditorState.h index 09fe7157c3..a64caee183 100644 --- a/dom/html/nsTextEditorState.h +++ b/dom/html/nsTextEditorState.h @@ -142,9 +142,20 @@ public: nsresult PrepareEditor(const nsAString *aValue = nullptr); void InitializeKeyboardEventListeners(); + enum SetValueFlags + { + // The call is for internal processing. + eSetValue_Internal = 0, + // The value is changed by a call of setUserInput() from chrome. + eSetValue_BySetUserInput = 1 << 0, + // The value is changed by changing value attribute of the element or + // something like setRangeText(). + eSetValue_ByContent = 1 << 1, + // Whether the value change should be notified to the frame/contet nor not. + eSetValue_Notify = 1 << 2 + }; MOZ_WARN_UNUSED_RESULT bool SetValue(const nsAString& aValue, - bool aUserInput, - bool aSetValueAsChanged); + uint32_t aFlags); void GetValue(nsAString& aValue, bool aIgnoreWrap) const; void EmptyValue() { if (mValue) mValue->Truncate(); } bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; } @@ -239,10 +250,12 @@ private: nsresult InitializeRootNode(); - void FinishedRestoringSelection() { mRestoringSelection = nullptr; } + void FinishedRestoringSelection(); mozilla::dom::HTMLInputElement* GetParentNumberControl(nsFrame* aFrame) const; + bool EditorHasComposition(); + class InitializationGuard { public: explicit InitializationGuard(nsTextEditorState& aState) : @@ -271,7 +284,7 @@ private: nsITextControlElement* const mTextCtrlElement; RefPtr mSelCon; - RestoreSelectionState* mRestoringSelection; + RefPtr mRestoringSelection; nsCOMPtr mEditor; nsCOMPtr mRootNode; nsCOMPtr mPlaceholderDiv; @@ -280,14 +293,20 @@ private: nsAutoPtr mValue; RefPtr mMutationObserver; mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control. + // mValueBeingSet is available only while SetValue() is requesting to commit + // composition. I.e., this is valid only while mIsCommittingComposition is + // true. While active composition is being committed, GetValue() needs + // the latest value which is set by SetValue(). So, this is cache for that. + nsString mValueBeingSet; + SelectionProperties mSelectionProperties; bool mEverInited; // Have we ever been initialized? bool mEditorInitialized; bool mInitializing; // Whether we're in the process of initialization bool mValueTransferInProgress; // Whether a value is being transferred to the frame bool mSelectionCached; // Whether mSelectionProperties is valid mutable bool mSelectionRestoreEagerInit; // Whether we're eager initing because of selection restore - SelectionProperties mSelectionProperties; bool mPlaceholderVisibility; + bool mIsCommittingComposition; }; inline void diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h index aef245577e..0f0fad2853 100644 --- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -96,9 +96,6 @@ public: // on failure. virtual nsresult Init() { return NS_OK; } - // True if this reader is waiting for a Content Decryption Module to become - // available. - virtual bool IsWaitingOnCDMResource() { return false; } // Release media resources they should be released in dormant state // The reader can be made usable again by calling ReadMetadata(). virtual void ReleaseMediaResources() {} diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 359e326daa..b3b86a9d82 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -1,3 +1,4 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* 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 @@ -16,6 +17,7 @@ #include "mediasink/DecodedAudioDataSink.h" #include "mediasink/AudioSinkWrapper.h" +#include "mediasink/VideoSink.h" #include "mediasink/DecodedStream.h" #include "mozilla/DebugOnly.h" #include "mozilla/Logging.h" @@ -136,7 +138,7 @@ static_assert(LOW_DATA_THRESHOLD_USECS > AMPLE_AUDIO_USECS, } // namespace detail // Amount of excess usecs of data to add in to the "should we buffer" calculation. -static const uint32_t EXHAUSTED_DATA_MARGIN_USECS = 60000; +static const uint32_t EXHAUSTED_DATA_MARGIN_USECS = 100000; // If we enter buffering within QUICK_BUFFER_THRESHOLD_USECS seconds of starting // decoding, we'll enter "quick buffering" mode, which exits a lot sooner than @@ -180,6 +182,20 @@ static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE; static uint32_t sVideoQueueHWAccelSize = MIN_VIDEO_QUEUE_SIZE; static uint32_t sVideoQueueSendToCompositorSize = VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE; +static void InitVideoQueuePrefs() { + MOZ_ASSERT(NS_IsMainThread()); + static bool sPrefInit = false; + if (!sPrefInit) { + sPrefInit = true; + sVideoQueueDefaultSize = Preferences::GetUint( + "media.video-queue.default-size", MAX_VIDEO_QUEUE_SIZE); + sVideoQueueHWAccelSize = Preferences::GetUint( + "media.video-queue.hw-accel-size", MIN_VIDEO_QUEUE_SIZE); + sVideoQueueSendToCompositorSize = Preferences::GetUint( + "media.video-queue.send-to-compositor-size", VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE); + } +} + MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, MediaDecoderReader* aReader, bool aRealTime) : @@ -187,7 +203,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK), /* aSupportsTailDispatch = */ true)), mWatchManager(this, mTaskQueue), - mProducerID(ImageContainer::AllocateProducerID()), mRealTime(aRealTime), mDispatchedStateMachine(false), mDelayedScheduler(mTaskQueue), @@ -197,7 +212,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mFragmentEndTime(-1), mReader(aReader), mDecodedAudioEndTime(-1), - mVideoFrameEndTime(-1), mDecodedVideoEndTime(-1), mPlaybackRate(1.0), mLowAudioThresholdUsecs(detail::LOW_AUDIO_USECS), @@ -269,19 +283,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, nsCOMPtr r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::InitializationTask); mTaskQueue->Dispatch(r.forget()); - static bool sPrefCacheInit = false; - if (!sPrefCacheInit) { - sPrefCacheInit = true; - Preferences::AddUintVarCache(&sVideoQueueDefaultSize, - "media.video-queue.default-size", - MAX_VIDEO_QUEUE_SIZE); - Preferences::AddUintVarCache(&sVideoQueueHWAccelSize, - "media.video-queue.hw-accel-size", - MIN_VIDEO_QUEUE_SIZE); - Preferences::AddUintVarCache(&sVideoQueueSendToCompositorSize, - "media.video-queue.send-to-compositor-size", - VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE); - } + InitVideoQueuePrefs(); mBufferingWait = IsRealTime() ? 0 : 15; mLowDataThresholdUsecs = IsRealTime() ? 0 : detail::LOW_DATA_THRESHOLD_USECS; @@ -302,7 +304,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mMetadataManager.Connect(mReader->TimedMetadataEvent(), OwnerThread()); - mMediaSink = CreateAudioSink(); + mMediaSink = CreateMediaSink(mAudioCaptured); } MediaDecoderStateMachine::~MediaDecoderStateMachine() @@ -371,6 +373,23 @@ MediaDecoderStateMachine::CreateAudioSink() return new AudioSinkWrapper(mTaskQueue, audioSinkCreator); } +already_AddRefed +MediaDecoderStateMachine::CreateMediaSink(bool aAudioCaptured) +{ + // TODO: We can't really create a new DecodedStream until OutputStreamManager + // is extracted. It is tricky that the implementation of DecodedStream + // happens to allow reuse after shutdown without creating a new one. + RefPtr audioSink = aAudioCaptured ? + mStreamSink : CreateAudioSink(); + + RefPtr mediaSink = + new VideoSink(mTaskQueue, audioSink, mVideoQueue, + mDecoder->GetVideoFrameContainer(), mRealTime, + mDecoder->GetFrameStatistics(), AUDIO_DURATION_USECS, + sVideoQueueSendToCompositorSize); + return mediaSink.forget(); +} + bool MediaDecoderStateMachine::HasFutureAudio() { MOZ_ASSERT(OnTaskQueue()); @@ -1037,7 +1056,6 @@ void MediaDecoderStateMachine::StopPlayback() mDecoder->DispatchPlaybackStopped(); if (IsPlaying()) { - RenderVideoFrames(1); mMediaSink->SetPlaying(false); MOZ_ASSERT(!IsPlaying()); } @@ -1218,6 +1236,18 @@ MediaDecoderStateMachine::SetDormant(bool aDormant) return; } + if (mMetadataRequest.Exists()) { + if (mPendingDormant && mPendingDormant.ref() != aDormant && !aDormant) { + // We already have a dormant request pending; the new request would have + // resumed from dormant, we can just cancel any pending dormant requests. + mPendingDormant.reset(); + } else { + mPendingDormant = Some(aDormant); + } + return; + } + mPendingDormant.reset(); + DECODER_LOG("SetDormant=%d", aDormant); if (aDormant) { @@ -1247,6 +1277,9 @@ MediaDecoderStateMachine::SetDormant(bool aDormant) mPendingSeek.RejectIfExists(__func__); mCurrentSeek.RejectIfExists(__func__); SetState(DECODER_STATE_DORMANT); + if (IsPlaying()) { + StopPlayback(); + } Reset(); @@ -1453,7 +1486,7 @@ MediaDecoderStateMachine::Seek(SeekTarget aTarget) DECODER_LOG("Changed state to SEEKING (to %lld)", mPendingSeek.mTarget.mTime); SetState(DECODER_STATE_SEEKING); ScheduleStateMachine(); - + return mPendingSeek.mPromise.Ensure(__func__); } @@ -1470,7 +1503,8 @@ void MediaDecoderStateMachine::StopMediaSink() if (mMediaSink->IsStarted()) { DECODER_LOG("Stop MediaSink"); mMediaSink->Stop(); - mMediaSinkPromise.DisconnectIfExists(); + mMediaSinkAudioPromise.DisconnectIfExists(); + mMediaSinkVideoPromise.DisconnectIfExists(); } } @@ -1745,12 +1779,20 @@ MediaDecoderStateMachine::StartMediaSink() mAudioCompleted = false; mMediaSink->Start(GetMediaTime(), mInfo); - auto promise = mMediaSink->OnEnded(TrackInfo::kAudioTrack); - if (promise) { - mMediaSinkPromise.Begin(promise->Then( + auto videoPromise = mMediaSink->OnEnded(TrackInfo::kVideoTrack); + auto audioPromise = mMediaSink->OnEnded(TrackInfo::kAudioTrack); + + if (audioPromise) { + mMediaSinkAudioPromise.Begin(audioPromise->Then( OwnerThread(), __func__, this, - &MediaDecoderStateMachine::OnMediaSinkComplete, - &MediaDecoderStateMachine::OnMediaSinkError)); + &MediaDecoderStateMachine::OnMediaSinkAudioComplete, + &MediaDecoderStateMachine::OnMediaSinkAudioError)); + } + if (videoPromise) { + mMediaSinkVideoPromise.Begin(videoPromise->Then( + OwnerThread(), __func__, this, + &MediaDecoderStateMachine::OnMediaSinkVideoComplete, + &MediaDecoderStateMachine::OnMediaSinkVideoError)); } } } @@ -1819,7 +1861,7 @@ bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs) int64_t endOfDecodedVideoData = INT64_MAX; if (HasVideo() && !VideoQueue().AtEndOfStream()) { - endOfDecodedVideoData = VideoQueue().Peek() ? VideoQueue().Peek()->GetEndTime() : mVideoFrameEndTime; + endOfDecodedVideoData = VideoQueue().Peek() ? VideoQueue().Peek()->GetEndTime() : VideoEndTime(); } int64_t endOfDecodedAudioData = INT64_MAX; if (HasAudio() && !AudioQueue().AtEndOfStream()) { @@ -1868,6 +1910,11 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata) MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA); mMetadataRequest.Complete(); + if (mPendingDormant) { + SetDormant(mPendingDormant.ref()); + return; + } + // Set mode to PLAYBACK after reading metadata. mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK); mDecoder->DispatchSetMediaSeekable(mReader->IsMediaSeekable()); @@ -1917,12 +1964,14 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata) // feeding in the CDM, which we need to decode the first frame (and // thus get the metadata). We could fix this if we could compute the start // time by demuxing without necessaring decoding. - mNotifyMetadataBeforeFirstFrame = mDuration.Ref().isSome() || mReader->IsWaitingOnCDMResource(); + bool waitingForCDM = + false; + mNotifyMetadataBeforeFirstFrame = mDuration.Ref().isSome() || waitingForCDM; if (mNotifyMetadataBeforeFirstFrame) { EnqueueLoadedMetadataEvent(); } - if (mReader->IsWaitingOnCDMResource()) { + if (waitingForCDM) { // Metadata parsing was successful but we're still waiting for CDM caps // to become available so that we can build the correct decryptor/decoder. SetState(DECODER_STATE_WAIT_FOR_CDM); @@ -2013,7 +2062,7 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame() DECODER_LOG("FinishDecodeFirstFrame"); if (!IsRealTime() && !mSentFirstFrameLoadedEvent) { - RenderVideoFrames(1); + mMediaSink->Redraw(); } // If we don't know the duration by this point, we assume infinity, per spec. @@ -2101,7 +2150,6 @@ MediaDecoderStateMachine::SeekCompleted() // Try to decode another frame to detect if we're at the end... DECODER_LOG("Seek completed, mCurrentPosition=%lld", mCurrentPosition.Ref()); - // Reset quick buffering status. This ensures that if we began the // seek while quick-buffering, we won't bypass quick buffering mode // if we need to buffer after the seek. @@ -2111,7 +2159,7 @@ MediaDecoderStateMachine::SeekCompleted() ScheduleStateMachine(); if (video) { - RenderVideoFrames(1); + mMediaSink->Redraw(); nsCOMPtr event = NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate); AbstractThread::MainThread()->Dispatch(event.forget()); @@ -2124,6 +2172,7 @@ public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecoderDisposer) DecoderDisposer(MediaDecoder* aDecoder, MediaDecoderStateMachine* aStateMachine) : mDecoder(aDecoder), mStateMachine(aStateMachine) {} + void OnTaskQueueShutdown() { MOZ_ASSERT(NS_IsMainThread()); @@ -2266,7 +2315,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine() // Start playback if necessary so that the clock can be properly queried. MaybeStartPlayback(); - UpdateRenderedVideoFrames(); + UpdatePlaybackPositionPeriodically(); NS_ASSERTION(!IsPlaying() || mLogicallySeeking || IsStateMachineScheduled(), @@ -2337,7 +2386,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine() { // Start playback if necessary to play the remaining media. MaybeStartPlayback(); - UpdateRenderedVideoFrames(); + UpdatePlaybackPositionPeriodically(); NS_ASSERTION(!IsPlaying() || mLogicallySeeking || IsStateMachineScheduled(), @@ -2359,7 +2408,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine() if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING && !mSentPlaybackEndedEvent) { - int64_t clockTime = std::max(AudioEndTime(), mVideoFrameEndTime); + int64_t clockTime = std::max(AudioEndTime(), VideoEndTime()); clockTime = std::max(int64_t(0), std::max(clockTime, Duration().ToMicroseconds())); UpdatePlaybackPosition(clockTime); @@ -2390,8 +2439,8 @@ MediaDecoderStateMachine::Reset() // dormant state. We could also be in the process of going dormant, and have // just switched to exiting dormant before we finished entering dormant, // hence the DECODING_NONE case below. - MOZ_ASSERT(mState == DECODER_STATE_SEEKING || - mState == DECODER_STATE_SHUTDOWN || + MOZ_ASSERT(IsShutdown() || + mState == DECODER_STATE_SEEKING || mState == DECODER_STATE_DORMANT || mState == DECODER_STATE_DECODING_NONE); @@ -2400,7 +2449,6 @@ MediaDecoderStateMachine::Reset() // crash for no samples to be popped. StopMediaSink(); - mVideoFrameEndTime = -1; mDecodedVideoEndTime = -1; mDecodedAudioEndTime = -1; mAudioCompleted = false; @@ -2452,70 +2500,6 @@ MediaDecoderStateMachine::CheckFrameValidity(VideoData* aData) } } -void MediaDecoderStateMachine::RenderVideoFrames(int32_t aMaxFrames, - int64_t aClockTime, - const TimeStamp& aClockTimeStamp) -{ - MOZ_ASSERT(OnTaskQueue()); - - VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); - nsAutoTArray,16> frames; - VideoQueue().GetFirstElements(aMaxFrames, &frames); - if (frames.IsEmpty() || !container) { - return; - } - - nsAutoTArray images; - TimeStamp lastFrameTime; - for (uint32_t i = 0; i < frames.Length(); ++i) { - VideoData* frame = frames[i]->As(); - - bool valid = !frame->mImage || frame->mImage->IsValid(); - frame->mSentToCompositor = true; - - if (!valid) { - continue; - } - - int64_t frameTime = frame->mTime; - if (frameTime < 0) { - // Frame times before the start time are invalid; drop such frames - continue; - } - - - TimeStamp t; - if (aMaxFrames > 1) { - MOZ_ASSERT(!aClockTimeStamp.IsNull()); - int64_t delta = frame->mTime - aClockTime; - t = aClockTimeStamp + - TimeDuration::FromMicroseconds(delta / mPlaybackRate); - if (!lastFrameTime.IsNull() && t <= lastFrameTime) { - // Timestamps out of order; drop the new frame. In theory we should - // probably replace the previous frame with the new frame if the - // timestamps are equal, but this is a corrupt video file already so - // never mind. - continue; - } - lastFrameTime = t; - } - - ImageContainer::NonOwningImage* img = images.AppendElement(); - img->mTimeStamp = t; - img->mImage = frame->mImage; - img->mFrameID = frame->mFrameID; - img->mProducerID = mProducerID; - - VERBOSE_LOG("playing video frame %lld (id=%x) (queued=%i, state-machine=%i, decoder-queued=%i)", - frame->mTime, frame->mFrameID, - VideoQueue().GetSize() + mReader->SizeOfVideoQueueInFrames(), - VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames()); - } - - - container->SetCurrentFrames(frames[0]->As()->mDisplay, images); -} - int64_t MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const { @@ -2525,7 +2509,8 @@ MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const return clockTime; } -void MediaDecoderStateMachine::UpdateRenderedVideoFrames() +void +MediaDecoderStateMachine::UpdatePlaybackPositionPeriodically() { MOZ_ASSERT(OnTaskQueue()); @@ -2537,47 +2522,21 @@ void MediaDecoderStateMachine::UpdateRenderedVideoFrames() DiscardStreamData(); } - TimeStamp nowTime; - const int64_t clockTime = GetClock(&nowTime); - // Skip frames up to the frame at the playback position, and figure out - // the time remaining until it's time to display the next frame and drop - // the current frame. - NS_ASSERTION(clockTime >= 0, "Should have positive clock time."); - int64_t remainingTime = AUDIO_DURATION_USECS; - if (VideoQueue().GetSize() > 0) { - RefPtr currentFrame = VideoQueue().PopFront(); - int32_t framesRemoved = 0; - while (VideoQueue().GetSize() > 0) { - MediaData* nextFrame = VideoQueue().PeekFront(); - if (!IsRealTime() && nextFrame->mTime > clockTime) { - remainingTime = nextFrame->mTime - clockTime; - break; - } - ++framesRemoved; - if (!currentFrame->As()->mSentToCompositor) { - mDecoder->NotifyDecodedFrames(0, 0, 1); - VERBOSE_LOG("discarding video frame mTime=%lld clock_time=%lld", - currentFrame->mTime, clockTime); - } - currentFrame = VideoQueue().PopFront(); - - } - VideoQueue().PushFront(currentFrame); - if (framesRemoved > 0) { - mVideoFrameEndTime = currentFrame->GetEndTime(); - FrameStatistics& frameStats = mDecoder->GetFrameStatistics(); - frameStats.NotifyPresentedFrame(); - } - } - - RenderVideoFrames(sVideoQueueSendToCompositorSize, clockTime, nowTime); - // Cap the current time to the larger of the audio and video end time. // This ensures that if we're running off the system clock, we don't // advance the clock to after the media end time. - if (mVideoFrameEndTime != -1 || AudioEndTime() != -1) { + if (VideoEndTime() != -1 || AudioEndTime() != -1) { + + const int64_t clockTime = GetClock(); + // Skip frames up to the frame at the playback position, and figure out + // the time remaining until it's time to display the next frame and drop + // the current frame. + NS_ASSERTION(clockTime >= 0, "Should have positive clock time."); + // These will be non -1 if we've displayed a video frame, or played an audio frame. - int64_t t = std::min(clockTime, std::max(mVideoFrameEndTime, AudioEndTime())); + int64_t t = std::min(clockTime, std::max(VideoEndTime(), AudioEndTime())); + // FIXME: Bug 1091422 - chained ogg files hit this assertion. + //MOZ_ASSERT(t >= GetMediaTime()); if (t > GetMediaTime()) { UpdatePlaybackPosition(t); } @@ -2587,7 +2546,7 @@ void MediaDecoderStateMachine::UpdateRenderedVideoFrames() // the monitor and get a staled value from GetCurrentTimeUs() which hits the // assertion in GetClock(). - int64_t delay = std::max(1, remainingTime / mPlaybackRate); + int64_t delay = std::max(1, AUDIO_DURATION_USECS / mPlaybackRate); ScheduleStateMachineIn(delay); } @@ -2669,7 +2628,7 @@ MediaDecoderStateMachine::DropAudioUpToSeekTarget(MediaData* aSample) NS_ASSERTION(mCurrentSeek.mTarget.mTime < audio->mTime + sampleDuration.value(), "Data must end after target."); - CheckedInt64 framesToPrune = + CheckedInt64 framesToPrune = UsecsToFrames(mCurrentSeek.mTarget.mTime - audio->mTime, mInfo.mAudio.mRate); if (!framesToPrune.isValid()) { return NS_ERROR_FAILURE; @@ -2890,22 +2849,57 @@ MediaDecoderStateMachine::AudioEndTime() const return -1; } -void MediaDecoderStateMachine::OnMediaSinkComplete() +int64_t +MediaDecoderStateMachine::VideoEndTime() const { MOZ_ASSERT(OnTaskQueue()); + if (mMediaSink->IsStarted()) { + return mMediaSink->GetEndTime(TrackInfo::kVideoTrack); + } + return -1; +} - mMediaSinkPromise.Complete(); +void +MediaDecoderStateMachine::OnMediaSinkVideoComplete() +{ + MOZ_ASSERT(OnTaskQueue()); + VERBOSE_LOG("[%s]", __func__); + + mMediaSinkVideoPromise.Complete(); + ScheduleStateMachine(); +} + +void +MediaDecoderStateMachine::OnMediaSinkVideoError() +{ + MOZ_ASSERT(OnTaskQueue()); + VERBOSE_LOG("[%s]", __func__); + + mMediaSinkVideoPromise.Complete(); + if (HasAudio()) { + return; + } + DecodeError(); +} + +void MediaDecoderStateMachine::OnMediaSinkAudioComplete() +{ + MOZ_ASSERT(OnTaskQueue()); + VERBOSE_LOG("[%s]", __func__); + + mMediaSinkAudioPromise.Complete(); // Set true only when we have audio. mAudioCompleted = mInfo.HasAudio(); // To notify PlaybackEnded as soon as possible. ScheduleStateMachine(); } -void MediaDecoderStateMachine::OnMediaSinkError() +void MediaDecoderStateMachine::OnMediaSinkAudioError() { MOZ_ASSERT(OnTaskQueue()); + VERBOSE_LOG("[%s]", __func__); - mMediaSinkPromise.Complete(); + mMediaSinkAudioPromise.Complete(); // Set true only when we have audio. mAudioCompleted = mInfo.HasAudio(); @@ -2936,10 +2930,7 @@ MediaDecoderStateMachine::SetAudioCaptured(bool aCaptured) mMediaSink->Shutdown(); // Create a new sink according to whether audio is captured. - // TODO: We can't really create a new DecodedStream until OutputStreamManager - // is extracted. It is tricky that the implementation of DecodedStream - // happens to allow reuse after shutdown without creating a new one. - mMediaSink = aCaptured ? mStreamSink : CreateAudioSink(); + mMediaSink = CreateMediaSink(aCaptured); // Restore playback parameters. mMediaSink->SetPlaybackParams(params); diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index abb081aeb6..67c04a58b7 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -128,7 +128,6 @@ public: typedef MediaDecoderReader::AudioDataPromise AudioDataPromise; typedef MediaDecoderReader::VideoDataPromise VideoDataPromise; typedef MediaDecoderOwner::NextFrameStatus NextFrameStatus; - typedef mozilla::layers::ImageContainer::ProducerID ProducerID; typedef mozilla::layers::ImageContainer::FrameID FrameID; MediaDecoderStateMachine(MediaDecoder* aDecoder, MediaDecoderReader* aReader, @@ -467,25 +466,15 @@ protected: // acceleration. void CheckFrameValidity(VideoData* aData); - // Sets VideoQueue images into the VideoFrameContainer. Called on the shared - // state machine thread. Decode monitor must be held. The first aMaxFrames - // (at most) are set. - // aClockTime and aClockTimeStamp are used as the baseline for deriving - // timestamps for the frames; when omitted, aMaxFrames must be 1 and - // a null timestamp is passed to the VideoFrameContainer. - // If the VideoQueue is empty, this does nothing. - void RenderVideoFrames(int32_t aMaxFrames, int64_t aClockTime = 0, - const TimeStamp& aClickTimeStamp = TimeStamp()); - - // If we have video, display a video frame if it's time for display has - // arrived, otherwise sleep until it's time for the next frame. Update the - // current frame time as appropriate, and trigger ready state update. The - // decoder monitor must be held with exactly one lock count. Called on the - // state machine thread. - void UpdateRenderedVideoFrames(); + // Update playback position and trigger next update by default time period. + // Called on the state machine thread. + void UpdatePlaybackPositionPeriodically(); media::MediaSink* CreateAudioSink(); + // Always create mediasink which contains an AudioSink or StreamSink inside. + already_AddRefed CreateMediaSink(bool aAudioCaptured); + // Stops the media sink and shut it down. // The decoder monitor must be held with exactly one lock count. // Called on the state machine thread. @@ -640,12 +629,14 @@ protected: bool IsVideoDecoding(); private: - // Resolved by the MediaSink to signal that all outstanding work is complete - // and the sink is shutting down. - void OnMediaSinkComplete(); + // Resolved by the MediaSink to signal that all audio/video outstanding + // work is complete and identify which part(a/v) of the sink is shutting down. + void OnMediaSinkAudioComplete(); + void OnMediaSinkVideoComplete(); - // Rejected by the MediaSink to signal errors. - void OnMediaSinkError(); + // Rejected by the MediaSink to signal errors for audio/video. + void OnMediaSinkAudioError(); + void OnMediaSinkVideoError(); // Return true if the video decoder's decode speed can not catch up the // play time. @@ -670,10 +661,6 @@ private: // State-watching manager. WatchManager mWatchManager; - // Producer ID to help ImageContainer distinguish different streams of - // FrameIDs. A unique and immutable value per MDSM. - const ProducerID mProducerID; - // True is we are decoding a realtime stream, like a camera stream. const bool mRealTime; @@ -871,30 +858,30 @@ private: mPromise = Move(aOther.mPromise); } - bool Exists() - { - MOZ_ASSERT(mTarget.IsValid() == !mPromise.IsEmpty()); - return mTarget.IsValid(); - } + bool Exists() + { + MOZ_ASSERT(mTarget.IsValid() == !mPromise.IsEmpty()); + return mTarget.IsValid(); + } - void Resolve(bool aAtEnd, const char* aCallSite) - { - mTarget.Reset(); - MediaDecoder::SeekResolveValue val(aAtEnd, mTarget.mEventVisibility); - mPromise.Resolve(val, aCallSite); - } + void Resolve(bool aAtEnd, const char* aCallSite) + { + mTarget.Reset(); + MediaDecoder::SeekResolveValue val(aAtEnd, mTarget.mEventVisibility); + mPromise.Resolve(val, aCallSite); + } - void RejectIfExists(const char* aCallSite) - { - mTarget.Reset(); - mPromise.RejectIfExists(true, aCallSite); - } + void RejectIfExists(const char* aCallSite) + { + mTarget.Reset(); + mPromise.RejectIfExists(true, aCallSite); + } - ~SeekJob() - { - MOZ_DIAGNOSTIC_ASSERT(!mTarget.IsValid()); - MOZ_DIAGNOSTIC_ASSERT(mPromise.IsEmpty()); - } + ~SeekJob() + { + MOZ_DIAGNOSTIC_ASSERT(!mTarget.IsValid()); + MOZ_DIAGNOSTIC_ASSERT(mPromise.IsEmpty()); + } SeekTarget mTarget; MozPromiseHolder mPromise; @@ -924,14 +911,14 @@ private: // of the audio stream, unless another frame is pushed to the hardware. int64_t AudioEndTime() const; + // The end time of the last rendered video frame that's been sent to + // compositor. + int64_t VideoEndTime() const; + // The end time of the last decoded audio frame. This signifies the end of // decoded audio data. Used to check if we are low in decoded data. int64_t mDecodedAudioEndTime; - // The presentation end time of the last video frame which has been displayed - // in microseconds. Accessed from the state machine thread. - int64_t mVideoFrameEndTime; - // The end time of the last decoded video frame. Used to check if we are low // on decoded video data. int64_t mDecodedVideoEndTime; @@ -991,7 +978,8 @@ private: MOZ_ASSERT(result <= mAmpleAudioThresholdUsecs, "Prerolling will never finish"); return result; } - uint32_t VideoPrerollFrames() const + + uint32_t VideoPrerollFrames() const { MOZ_ASSERT(OnTaskQueue()); return IsRealTime() ? 0 : GetAmpleVideoFrames() / 2; @@ -1098,6 +1086,9 @@ private: // been written to the MediaStream. Watchable mAudioCompleted; + // Set if MDSM receives dormant request during reading metadata. + Maybe mPendingDormant; + // Flag whether we notify metadata before decoding the first frame or after. // // Note that the odd semantics here are designed to replicate the current @@ -1193,7 +1184,9 @@ private: // Media data resource from the decoder. RefPtr mResource; - MozPromiseRequestHolder mMediaSinkPromise; + // Track the complete & error for audio/video separately + MozPromiseRequestHolder mMediaSinkAudioPromise; + MozPromiseRequestHolder mMediaSinkVideoPromise; MediaEventListener mAudioQueueListener; MediaEventListener mVideoQueueListener; diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index 7b7dac892b..5f87f6bb6b 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -95,7 +95,6 @@ MediaFormatReader::Shutdown() MOZ_ASSERT(OnTaskQueue()); mDemuxerInitRequest.DisconnectIfExists(); - mDecodersInitRequest.DisconnectIfExists(); mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__); mSeekPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); mSkipRequest.DisconnectIfExists(); @@ -105,6 +104,7 @@ MediaFormatReader::Shutdown() if (mAudio.HasPromise()) { mAudio.RejectPromise(CANCELED, __func__); } + mAudio.mInitPromise.DisconnectIfExists(); mAudio.mDecoder->Shutdown(); mAudio.mDecoder = nullptr; } @@ -125,6 +125,7 @@ MediaFormatReader::Shutdown() if (mVideo.HasPromise()) { mVideo.RejectPromise(CANCELED, __func__); } + mVideo.mInitPromise.DisconnectIfExists(); mVideo.mDecoder->Shutdown(); mVideo.mDecoder = nullptr; } @@ -196,7 +197,9 @@ MediaFormatReader::Init() return NS_OK; } -bool MediaFormatReader::IsWaitingOnCDMResource() { +bool +MediaFormatReader::IsWaitingOnCDMResource() { + MOZ_ASSERT(OnTaskQueue()); // EME Stub return false; } @@ -206,19 +209,18 @@ MediaFormatReader::AsyncReadMetadata() { MOZ_ASSERT(OnTaskQueue()); - RefPtr p = mMetadataPromise.Ensure(__func__); + MOZ_DIAGNOSTIC_ASSERT(mMetadataPromise.IsEmpty()); if (mInitDone) { // We are returning from dormant. - if (!EnsureDecodersCreated()) { - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); - return p; - } - MOZ_ASSERT(!mDecodersInitRequest.Exists()); - EnsureDecodersInitialized(); - return p; + RefPtr metadata = new MetadataHolder(); + metadata->mInfo = mInfo; + metadata->mTags = nullptr; + return MetadataPromise::CreateAndResolve(metadata, __func__); } + RefPtr p = mMetadataPromise.Ensure(__func__); + mDemuxerInitRequest.Begin(mDemuxer->Init() ->Then(OwnerThread(), __func__, this, &MediaFormatReader::OnDemuxerInitDone, @@ -241,7 +243,10 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) if (videoActive) { // We currently only handle the first video track. mVideo.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0); - MOZ_ASSERT(mVideo.mTrackDemuxer); + if (!mVideo.mTrackDemuxer) { + mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + return; + } mInfo.mVideo = *mVideo.mTrackDemuxer->GetInfo()->GetAsVideoInfo(); mVideo.mCallback = new DecoderCallback(this, TrackInfo::kVideoTrack); mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered(); @@ -251,7 +256,10 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) bool audioActive = !!mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack); if (audioActive) { mAudio.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0); - MOZ_ASSERT(mAudio.mTrackDemuxer); + if (!mAudio.mTrackDemuxer) { + mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + return; + } mInfo.mAudio = *mAudio.mTrackDemuxer->GetInfo()->GetAsAudioInfo(); mAudio.mCallback = new DecoderCallback(this, TrackInfo::kAudioTrack); mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered(); @@ -262,7 +270,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) mIsEncrypted = crypto && crypto->IsEncrypted(); - if (mDecoder && crypto && crypto->IsEncrypted()) { + if (mDecoder && crypto && crypto->IsEncrypted()) { mInfo.mCrypto = *crypto; } @@ -281,24 +289,11 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) return; } - if (IsWaitingOnCDMResource()) { - // Decoder can't be allocated before CDM resource is ready, so resolving the - // mMetadataPromise here to wait for CDM on MDSM. - mInitDone = true; - RefPtr metadata = new MetadataHolder(); - metadata->mInfo = mInfo; - metadata->mTags = nullptr; - mMetadataPromise.Resolve(metadata, __func__); - return; - } - - if (!EnsureDecodersCreated()) { - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); - return; - } - - MOZ_ASSERT(!mDecodersInitRequest.Exists()); - EnsureDecodersInitialized(); + mInitDone = true; + RefPtr metadata = new MetadataHolder(); + metadata->mInfo = mInfo; + metadata->mTags = nullptr; + mMetadataPromise.Resolve(metadata, __func__); } void @@ -353,78 +348,33 @@ MediaFormatReader::EnsureDecodersCreated() } bool -MediaFormatReader::EnsureDecodersInitialized() +MediaFormatReader::EnsureDecoderInitialized(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); - MOZ_ASSERT(mVideo.mDecoder || mAudio.mDecoder); + auto& decoder = GetDecoderData(aTrack); - // DecodeDemuxedSample() could call this function before mDecodersInitRequest - // is completed. And it is ok to return false here because DecodeDemuxedSample - // will call ScheduleUpdate() again. - // It also avoids calling decoder->Init() multiple times. - if (mDecodersInitRequest.Exists()) { + if (!decoder.mDecoder || decoder.mInitPromise.Exists()) { + MOZ_ASSERT(decoder.mDecoder); return false; } - - nsTArray> promises; - - if (mVideo.mDecoder && !mVideo.mDecoderInitialized) { - promises.AppendElement(mVideo.mDecoder->Init()); + if (decoder.mDecoderInitialized) { + return true; } - - if (mAudio.mDecoder && !mAudio.mDecoderInitialized) { - promises.AppendElement(mAudio.mDecoder->Init()); - } - - if (promises.Length()) { - mDecodersInitRequest.Begin(MediaDataDecoder::InitPromise::All(OwnerThread(), promises) - ->Then(OwnerThread(), __func__, this, - &MediaFormatReader::OnDecoderInitDone, - &MediaFormatReader::OnDecoderInitFailed)); - } - - LOG("Init decoders: audio: %p, audio init: %d, video: %p, video init: %d", - mAudio.mDecoder.get(), mAudio.mDecoderInitialized, - mVideo.mDecoder.get(), mVideo.mDecoderInitialized); - - // Return false if any decoder is under initialization. - return !promises.Length(); -} - -void -MediaFormatReader::OnDecoderInitDone(const nsTArray& aTrackTypes) -{ - MOZ_ASSERT(OnTaskQueue()); - mDecodersInitRequest.Complete(); - - for (const auto& track : aTrackTypes) { - auto& decoder = GetDecoderData(track); - decoder.mDecoderInitialized = true; - - ScheduleUpdate(track); - } - - if (!mMetadataPromise.IsEmpty()) { - mInitDone = true; - RefPtr metadata = new MetadataHolder(); - metadata->mInfo = mInfo; - metadata->mTags = nullptr; - mMetadataPromise.Resolve(metadata, __func__); - } -} - -void -MediaFormatReader::OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason) -{ - MOZ_ASSERT(OnTaskQueue()); - mDecodersInitRequest.Complete(); - - NS_WARNING("Failed to init decoder"); - - mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__); - - NotifyError(TrackType::kAudioTrack); - NotifyError(TrackType::kVideoTrack); + RefPtr self = this; + decoder.mInitPromise.Begin(decoder.mDecoder->Init() + ->Then(OwnerThread(), __func__, + [self] (TrackType aTrack) { + auto& decoder = self->GetDecoderData(aTrack); + decoder.mInitPromise.Complete(); + decoder.mDecoderInitialized = true; + self->ScheduleUpdate(aTrack); + }, + [self, aTrack] (MediaDataDecoder::DecoderFailureReason aResult) { + auto& decoder = self->GetDecoderData(aTrack); + decoder.mInitPromise.Complete(); + self->NotifyError(aTrack); + })); + return false; } void @@ -835,8 +785,7 @@ MediaFormatReader::HandleDemuxedSamples(TrackType aTrack, return; } - if (!EnsureDecodersInitialized()) { - ScheduleUpdate(aTrack); + if (!EnsureDecoderInitialized(aTrack)) { return; } @@ -970,7 +919,7 @@ MediaFormatReader::Update(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); - if (mShutdown) { + if (mShutdown || !mInitDone) { return; } @@ -1475,6 +1424,7 @@ void MediaFormatReader::ReleaseMediaResources() mVideoFrameContainer->ClearCurrentFrame(); } if (mVideo.mDecoder) { + mVideo.mInitPromise.DisconnectIfExists(); mVideo.mDecoder->Shutdown(); mVideo.mDecoder = nullptr; } diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h index 052bd4862e..054ec57b92 100644 --- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -76,8 +76,6 @@ public: bool IsWaitForDataSupported() override { return true; } RefPtr WaitForData(MediaData::Type aType) override; - bool IsWaitingOnCDMResource() override; - // MediaFormatReader supports demuxed-only mode. bool IsDemuxOnlySupported() const override { return true; } @@ -101,6 +99,8 @@ private: bool HasVideo() { return mVideo.mTrackDemuxer; } bool HasAudio() { return mAudio.mTrackDemuxer; } + bool IsWaitingOnCDMResource(); + bool InitDemuxer(); // Notify the demuxer that new data has been received. // The next queued task calling GetBuffered() is guaranteed to have up to date @@ -109,9 +109,7 @@ private: void ReturnOutput(MediaData* aData, TrackType aTrack); bool EnsureDecodersCreated(); - // It returns true when all decoders are initialized. False when there is pending - // initialization. - bool EnsureDecodersInitialized(); + bool EnsureDecoderInitialized(TrackType aTrack); // Enqueues a task to call Update(aTrack) on the decoder task queue. // Lock for corresponding track must be held. @@ -249,6 +247,8 @@ private: } // MediaDataDecoder handler's variables. + // Decoder initialization promise holder. + MozPromiseRequestHolder mInitPromise; // False when decoder is created. True when decoder Init() promise is resolved. bool mDecoderInitialized; bool mDecodingRequested; @@ -347,9 +347,6 @@ private: DecoderData& GetDecoderData(TrackType aTrack); - void OnDecoderInitDone(const nsTArray& aTrackTypes); - void OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason); - // Demuxer objects. RefPtr mDemuxer; bool mDemuxerInitDone; @@ -396,10 +393,6 @@ private: { return mIsEncrypted; } - // Accessed from multiple thread, in particular the MediaDecoderStateMachine, - // however the value doesn't currently change after reading the metadata. - // TODO handle change of encryption half-way. The above assumption will then - // become incorrect. bool mIsEncrypted; // Set to true if any of our track buffers may be blocking. @@ -431,9 +424,6 @@ private: Maybe mPendingSeekTime; MozPromiseHolder mSeekPromise; - // Pending decoders initialization. - MozPromiseRequestHolder mDecodersInitRequest; - RefPtr mVideoFrameContainer; layers::ImageContainer* GetImageContainer(); diff --git a/dom/media/VideoUtils.h b/dom/media/VideoUtils.h index f622a03899..e17127f04e 100644 --- a/dom/media/VideoUtils.h +++ b/dom/media/VideoUtils.h @@ -153,9 +153,9 @@ nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs); // The maximum height and width of the video. Used for // sanitizing the memory allocation of the RGB buffer. // The maximum resolution we anticipate encountering in the -// wild is 2160p - 3840x2160 pixels. -static const int32_t MAX_VIDEO_WIDTH = 4000; -static const int32_t MAX_VIDEO_HEIGHT = 3000; +// wild is 2160p (UHD "4K") or 4320p - 7680x4320 pixels for VR. +static const int32_t MAX_VIDEO_WIDTH = 8192; +static const int32_t MAX_VIDEO_HEIGHT = 4608; // Scales the display rect aDisplay by aspect ratio aAspectRatio. // Note that aDisplay must be validated by IsValidVideoRegion() @@ -285,14 +285,14 @@ RefPtr InvokeUntil(Work aWork, Condition aCondition) { } struct Helper { - static void Iteration(RefPtr aPromise, Work aWork, Condition aCondition) { - if (!aWork()) { + static void Iteration(RefPtr aPromise, Work aLocalWork, Condition aLocalCondition) { + if (!aLocalWork()) { aPromise->Reject(NS_ERROR_FAILURE, __func__); - } else if (aCondition()) { + } else if (aLocalCondition()) { aPromise->Resolve(true, __func__); } else { nsCOMPtr r = - NS_NewRunnableFunction([aPromise, aWork, aCondition] () { Iteration(aPromise, aWork, aCondition); }); + NS_NewRunnableFunction([aPromise, aLocalWork, aLocalCondition] () { Iteration(aPromise, aLocalWork, aLocalCondition); }); AbstractThread::GetCurrent()->Dispatch(r.forget()); } } diff --git a/dom/media/mediasink/AudioSinkWrapper.cpp b/dom/media/mediasink/AudioSinkWrapper.cpp index 1c75a09ca6..e41d07b1b7 100644 --- a/dom/media/mediasink/AudioSinkWrapper.cpp +++ b/dom/media/mediasink/AudioSinkWrapper.cpp @@ -34,9 +34,9 @@ AudioSinkWrapper::SetPlaybackParams(const PlaybackParams& aParams) { AssertOwnerThread(); if (mAudioSink) { - mAudioSink->SetVolume(aParams.volume); - mAudioSink->SetPlaybackRate(aParams.playbackRate); - mAudioSink->SetPreservesPitch(aParams.preservesPitch); + mAudioSink->SetVolume(aParams.mVolume); + mAudioSink->SetPlaybackRate(aParams.mPlaybackRate); + mAudioSink->SetPreservesPitch(aParams.mPreservesPitch); } mParams = aParams; } @@ -71,7 +71,7 @@ AudioSinkWrapper::GetVideoPosition(TimeStamp aNow) const // Time elapsed since we started playing. int64_t delta = (aNow - mPlayStartTime).ToMicroseconds(); // Take playback rate into account. - return mPlayDuration + delta * mParams.playbackRate; + return mPlayDuration + delta * mParams.mPlaybackRate; } int64_t @@ -112,7 +112,7 @@ void AudioSinkWrapper::SetVolume(double aVolume) { AssertOwnerThread(); - mParams.volume = aVolume; + mParams.mVolume = aVolume; if (mAudioSink) { mAudioSink->SetVolume(aVolume); } @@ -122,7 +122,7 @@ void AudioSinkWrapper::SetPlaybackRate(double aPlaybackRate) { AssertOwnerThread(); - mParams.playbackRate = aPlaybackRate; + mParams.mPlaybackRate = aPlaybackRate; if (!mAudioEnded) { // Pass the playback rate to the audio sink. The underlying AudioStream // will handle playback rate changes and report correct audio position. @@ -141,7 +141,7 @@ void AudioSinkWrapper::SetPreservesPitch(bool aPreservesPitch) { AssertOwnerThread(); - mParams.preservesPitch = aPreservesPitch; + mParams.mPreservesPitch = aPreservesPitch; if (mAudioSink) { mAudioSink->SetPreservesPitch(aPreservesPitch); } diff --git a/dom/media/mediasink/DecodedStream.cpp b/dom/media/mediasink/DecodedStream.cpp index 208ab7b24d..9041b80de7 100644 --- a/dom/media/mediasink/DecodedStream.cpp +++ b/dom/media/mediasink/DecodedStream.cpp @@ -589,21 +589,21 @@ void DecodedStream::SetVolume(double aVolume) { AssertOwnerThread(); - mParams.volume = aVolume; + mParams.mVolume = aVolume; } void DecodedStream::SetPlaybackRate(double aPlaybackRate) { AssertOwnerThread(); - mParams.playbackRate = aPlaybackRate; + mParams.mPlaybackRate = aPlaybackRate; } void DecodedStream::SetPreservesPitch(bool aPreservesPitch) { AssertOwnerThread(); - mParams.preservesPitch = aPreservesPitch; + mParams.mPreservesPitch = aPreservesPitch; } void @@ -877,7 +877,7 @@ DecodedStream::SendData() } InitTracks(); - SendAudio(mParams.volume, mSameOrigin); + SendAudio(mParams.mVolume, mSameOrigin); SendVideo(mSameOrigin); AdvanceTracks(); diff --git a/dom/media/mediasink/MediaSink.h b/dom/media/mediasink/MediaSink.h index 34a825f169..e753d32466 100644 --- a/dom/media/mediasink/MediaSink.h +++ b/dom/media/mediasink/MediaSink.h @@ -40,10 +40,10 @@ public: struct PlaybackParams { PlaybackParams() - : volume(1.0) , playbackRate(1.0) , preservesPitch(true) {} - double volume; - double playbackRate; - bool preservesPitch; + : mVolume(1.0) , mPlaybackRate(1.0) , mPreservesPitch(true) {} + double mVolume; + double mPlaybackRate; + bool mPreservesPitch; }; // Return the playback parameters of this sink. @@ -93,6 +93,11 @@ public: // Pause/resume the playback. Only work after playback starts. virtual void SetPlaying(bool aPlaying) = 0; + // Single frame rendering operation may need to be done before playback + // started (1st frame) or right after seek completed or playback stopped. + // Do nothing if this sink has no video track. Can be called in any state. + virtual void Redraw() {}; + // Begin a playback session with the provided start time and media info. // Must be called when playback is stopped. virtual void Start(int64_t aStartTime, const MediaInfo& aInfo) = 0; diff --git a/dom/media/mediasink/VideoSink.cpp b/dom/media/mediasink/VideoSink.cpp new file mode 100644 index 0000000000..6e67139d23 --- /dev/null +++ b/dom/media/mediasink/VideoSink.cpp @@ -0,0 +1,379 @@ +/* -*- 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 "VideoSink.h" + +namespace mozilla { + +extern PRLogModuleInfo* gMediaDecoderLog; +#define VSINK_LOG(msg, ...) \ + MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, \ + ("VideoSink=%p " msg, this, ##__VA_ARGS__)) +#define VSINK_LOG_V(msg, ...) \ + MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, \ + ("VideoSink=%p " msg, this, ##__VA_ARGS__)) + +using namespace mozilla::layers; + +namespace media { + +VideoSink::VideoSink(AbstractThread* aThread, + MediaSink* aAudioSink, + MediaQueue& aVideoQueue, + VideoFrameContainer* aContainer, + bool aRealTime, + FrameStatistics& aFrameStats, + int aDelayDuration, + uint32_t aVQueueSentToCompositerSize) + : mOwnerThread(aThread) + , mAudioSink(aAudioSink) + , mVideoQueue(aVideoQueue) + , mContainer(aContainer) + , mProducerID(ImageContainer::AllocateProducerID()) + , mRealTime(aRealTime) + , mFrameStats(aFrameStats) + , mVideoFrameEndTime(-1) + , mHasVideo(false) + , mUpdateScheduler(aThread) + , mDelayDuration(aDelayDuration) + , mVideoQueueSendToCompositorSize(aVQueueSentToCompositerSize) +{ + MOZ_ASSERT(mAudioSink, "AudioSink should exist."); +} + +VideoSink::~VideoSink() +{ +} + +const MediaSink::PlaybackParams& +VideoSink::GetPlaybackParams() const +{ + AssertOwnerThread(); + + return mAudioSink->GetPlaybackParams(); +} + +void +VideoSink::SetPlaybackParams(const PlaybackParams& aParams) +{ + AssertOwnerThread(); + + mAudioSink->SetPlaybackParams(aParams); +} + +RefPtr +VideoSink::OnEnded(TrackType aType) +{ + AssertOwnerThread(); + MOZ_ASSERT(mAudioSink->IsStarted(), "Must be called after playback starts."); + + if (aType == TrackInfo::kAudioTrack) { + return mAudioSink->OnEnded(aType); + } else if (aType == TrackInfo::kVideoTrack) { + return mEndPromise; + } + return nullptr; +} + +int64_t +VideoSink::GetEndTime(TrackType aType) const +{ + AssertOwnerThread(); + MOZ_ASSERT(mAudioSink->IsStarted(), "Must be called after playback starts."); + + if (aType == TrackInfo::kVideoTrack) { + return mVideoFrameEndTime; + } else if (aType == TrackInfo::kAudioTrack) { + return mAudioSink->GetEndTime(aType); + } + return -1; +} + +int64_t +VideoSink::GetPosition(TimeStamp* aTimeStamp) const +{ + AssertOwnerThread(); + + return mAudioSink->GetPosition(aTimeStamp); +} + +bool +VideoSink::HasUnplayedFrames(TrackType aType) const +{ + AssertOwnerThread(); + MOZ_ASSERT(aType == TrackInfo::kAudioTrack, "Not implemented for non audio tracks."); + + return mAudioSink->HasUnplayedFrames(aType); +} + +void +VideoSink::SetPlaybackRate(double aPlaybackRate) +{ + AssertOwnerThread(); + + mAudioSink->SetPlaybackRate(aPlaybackRate); +} + +void +VideoSink::SetVolume(double aVolume) +{ + AssertOwnerThread(); + + mAudioSink->SetVolume(aVolume); +} + +void +VideoSink::SetPreservesPitch(bool aPreservesPitch) +{ + AssertOwnerThread(); + + mAudioSink->SetPreservesPitch(aPreservesPitch); +} + +void +VideoSink::SetPlaying(bool aPlaying) +{ + AssertOwnerThread(); + VSINK_LOG_V(" playing (%d) -> (%d)", mAudioSink->IsPlaying(), aPlaying); + + if (!aPlaying) { + // Reset any update timer if paused. + mUpdateScheduler.Reset(); + // Since playback is paused, tell compositor to render only current frame. + RenderVideoFrames(1); + } + + mAudioSink->SetPlaying(aPlaying); + + if (mHasVideo && aPlaying) { + // There's no thread in VideoSink for pulling video frames, need to trigger + // rendering while becoming playing status. because the VideoQueue may be + // full already. + TryUpdateRenderedVideoFrames(); + } +} + +void +VideoSink::Start(int64_t aStartTime, const MediaInfo& aInfo) +{ + AssertOwnerThread(); + VSINK_LOG("[%s]", __func__); + + mAudioSink->Start(aStartTime, aInfo); + + mHasVideo = aInfo.HasVideo(); + + if (mHasVideo) { + mEndPromise = mEndPromiseHolder.Ensure(__func__); + ConnectListener(); + TryUpdateRenderedVideoFrames(); + } +} + +void +VideoSink::Stop() +{ + AssertOwnerThread(); + MOZ_ASSERT(mAudioSink->IsStarted(), "playback not started."); + VSINK_LOG("[%s]", __func__); + + mAudioSink->Stop(); + + mUpdateScheduler.Reset(); + if (mHasVideo) { + DisconnectListener(); + mEndPromiseHolder.Resolve(true, __func__); + mEndPromise = nullptr; + } + mVideoFrameEndTime = -1; +} + +bool +VideoSink::IsStarted() const +{ + AssertOwnerThread(); + + return mAudioSink->IsStarted(); +} + +bool +VideoSink::IsPlaying() const +{ + AssertOwnerThread(); + + return mAudioSink->IsPlaying(); +} + +void +VideoSink::Shutdown() +{ + AssertOwnerThread(); + MOZ_ASSERT(!mAudioSink->IsStarted(), "must be called after playback stops."); + VSINK_LOG("[%s]", __func__); + + mAudioSink->Shutdown(); +} + +void +VideoSink::OnVideoQueueEvent() +{ + AssertOwnerThread(); + // Listen to push event, VideoSink should try rendering ASAP if first frame + // arrives but update scheduler is not triggered yet. + TryUpdateRenderedVideoFrames(); +} + +void +VideoSink::Redraw() +{ + AssertOwnerThread(); + RenderVideoFrames(1); +} + +void +VideoSink::TryUpdateRenderedVideoFrames() +{ + AssertOwnerThread(); + if (!mUpdateScheduler.IsScheduled() && VideoQueue().GetSize() >= 1 && + mAudioSink->IsPlaying()) { + UpdateRenderedVideoFrames(); + } +} + +void +VideoSink::UpdateRenderedVideoFramesByTimer() +{ + AssertOwnerThread(); + mUpdateScheduler.CompleteRequest(); + UpdateRenderedVideoFrames(); +} + +void +VideoSink::ConnectListener() +{ + AssertOwnerThread(); + mPushListener = VideoQueue().PushEvent().Connect( + mOwnerThread, this, &VideoSink::OnVideoQueueEvent); +} + +void +VideoSink::DisconnectListener() +{ + AssertOwnerThread(); + mPushListener.Disconnect(); +} + +void +VideoSink::RenderVideoFrames(int32_t aMaxFrames, + int64_t aClockTime, + const TimeStamp& aClockTimeStamp) +{ + AssertOwnerThread(); + + nsAutoTArray,16> frames; + VideoQueue().GetFirstElements(aMaxFrames, &frames); + if (frames.IsEmpty() || !mContainer) { + return; + } + + nsAutoTArray images; + TimeStamp lastFrameTime; + MediaSink::PlaybackParams params = mAudioSink->GetPlaybackParams(); + for (uint32_t i = 0; i < frames.Length(); ++i) { + VideoData* frame = frames[i]->As(); + + frame->mSentToCompositor = true; + + if (!frame->mImage || !frame->mImage->IsValid()) { + continue; + } + + int64_t frameTime = frame->mTime; + if (frameTime < 0) { + // Frame times before the start time are invalid; drop such frames + continue; + } + + TimeStamp t; + if (aMaxFrames > 1) { + MOZ_ASSERT(!aClockTimeStamp.IsNull()); + int64_t delta = frame->mTime - aClockTime; + t = aClockTimeStamp + + TimeDuration::FromMicroseconds(delta / params.mPlaybackRate); + if (!lastFrameTime.IsNull() && t <= lastFrameTime) { + // Timestamps out of order; drop the new frame. In theory we should + // probably replace the previous frame with the new frame if the + // timestamps are equal, but this is a corrupt video file already so + // never mind. + continue; + } + lastFrameTime = t; + } + + ImageContainer::NonOwningImage* img = images.AppendElement(); + img->mTimeStamp = t; + img->mImage = frame->mImage; + img->mFrameID = frame->mFrameID; + img->mProducerID = mProducerID; + + VSINK_LOG_V("playing video frame %lld (id=%x) (vq-queued=%i)", + frame->mTime, frame->mFrameID, VideoQueue().GetSize()); + } + mContainer->SetCurrentFrames(frames[0]->As()->mDisplay, images); +} + +void +VideoSink::UpdateRenderedVideoFrames() +{ + AssertOwnerThread(); + MOZ_ASSERT(mAudioSink->IsPlaying(), "should be called while playing."); + + TimeStamp nowTime; + const int64_t clockTime = mAudioSink->GetPosition(&nowTime); + // Skip frames up to the frame at the playback position, and figure out + // the time remaining until it's time to display the next frame and drop + // the current frame. + NS_ASSERTION(clockTime >= 0, "Should have positive clock time."); + + int64_t remainingTime = mDelayDuration; + if (VideoQueue().GetSize() > 0) { + RefPtr currentFrame = VideoQueue().PopFront(); + int32_t framesRemoved = 0; + while (VideoQueue().GetSize() > 0) { + MediaData* nextFrame = VideoQueue().PeekFront(); + if (!mRealTime && nextFrame->mTime > clockTime) { + remainingTime = nextFrame->mTime - clockTime; + break; + } + ++framesRemoved; + if (!currentFrame->As()->mSentToCompositor) { + mFrameStats.NotifyDecodedFrames(0, 0, 1); + VSINK_LOG_V("discarding video frame mTime=%lld clock_time=%lld", + currentFrame->mTime, clockTime); + } + currentFrame = VideoQueue().PopFront(); + } + VideoQueue().PushFront(currentFrame); + if (framesRemoved > 0) { + mVideoFrameEndTime = currentFrame->GetEndTime(); + mFrameStats.NotifyPresentedFrame(); + } + } + + RenderVideoFrames(mVideoQueueSendToCompositorSize, clockTime, nowTime); + + TimeStamp target = nowTime + TimeDuration::FromMicroseconds(remainingTime); + + RefPtr self = this; + mUpdateScheduler.Ensure(target, [self] () { + self->UpdateRenderedVideoFramesByTimer(); + }, [self] () { + self->UpdateRenderedVideoFramesByTimer(); + }); +} + +} // namespace media +} // namespace mozilla diff --git a/dom/media/mediasink/VideoSink.h b/dom/media/mediasink/VideoSink.h new file mode 100644 index 0000000000..f57560b232 --- /dev/null +++ b/dom/media/mediasink/VideoSink.h @@ -0,0 +1,154 @@ +/* -*- 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 VideoSink_h_ +#define VideoSink_h_ + +#include "FrameStatistics.h" +#include "ImageContainer.h" +#include "MediaEventSource.h" +#include "MediaSink.h" +#include "MediaTimer.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "VideoFrameContainer.h" + +namespace mozilla { + +class VideoFrameContainer; +template class MediaQueue; + +namespace media { + +class VideoSink : public MediaSink +{ + typedef mozilla::layers::ImageContainer::ProducerID ProducerID; +public: + VideoSink(AbstractThread* aThread, + MediaSink* aAudioSink, + MediaQueue& aVideoQueue, + VideoFrameContainer* aContainer, + bool aRealTime, + FrameStatistics& aFrameStats, + int aDelayDuration, + uint32_t aVQueueSentToCompositerSize); + + const PlaybackParams& GetPlaybackParams() const override; + + void SetPlaybackParams(const PlaybackParams& aParams) override; + + RefPtr OnEnded(TrackType aType) override; + + int64_t GetEndTime(TrackType aType) const override; + + int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override; + + bool HasUnplayedFrames(TrackType aType) const override; + + void SetPlaybackRate(double aPlaybackRate) override; + + void SetVolume(double aVolume) override; + + void SetPreservesPitch(bool aPreservesPitch) override; + + void SetPlaying(bool aPlaying) override; + + void Redraw() override; + + void Start(int64_t aStartTime, const MediaInfo& aInfo) override; + + void Stop() override; + + bool IsStarted() const override; + + bool IsPlaying() const override; + + void Shutdown() override; + +private: + virtual ~VideoSink(); + + // VideoQueue listener related. + void OnVideoQueueEvent(); + void ConnectListener(); + void DisconnectListener(); + + // Sets VideoQueue images into the VideoFrameContainer. Called on the shared + // state machine thread. The first aMaxFrames (at most) are set. + // aClockTime and aClockTimeStamp are used as the baseline for deriving + // timestamps for the frames; when omitted, aMaxFrames must be 1 and + // a null timestamp is passed to the VideoFrameContainer. + // If the VideoQueue is empty, this does nothing. + void RenderVideoFrames(int32_t aMaxFrames, int64_t aClockTime = 0, + const TimeStamp& aClickTimeStamp = TimeStamp()); + + // Triggered while videosink is started, videosink becomes "playing" status, + // or VideoQueue event arrived. + void TryUpdateRenderedVideoFrames(); + + // If we have video, display a video frame if it's time for display has + // arrived, otherwise sleep until it's time for the next frame. Update the + // current frame time as appropriate, and trigger ready state update. + // Called on the shared state machine thread. + void UpdateRenderedVideoFrames(); + void UpdateRenderedVideoFramesByTimer(); + + void AssertOwnerThread() const + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + } + + MediaQueue& VideoQueue() const { + return mVideoQueue; + } + + const RefPtr mOwnerThread; + RefPtr mAudioSink; + MediaQueue& mVideoQueue; + VideoFrameContainer* mContainer; + + // Producer ID to help ImageContainer distinguish different streams of + // FrameIDs. A unique and immutable value per VideoSink. + const ProducerID mProducerID; + + // True if we are decoding a real-time stream. + const bool mRealTime; + + // Used to notify MediaDecoder's frame statistics + FrameStatistics& mFrameStats; + + RefPtr mEndPromise; + MozPromiseHolder mEndPromiseHolder; + MozPromiseRequestHolder mVideoSinkEndRequest; + + // The presentation end time of the last video frame which has been displayed + // in microseconds. + int64_t mVideoFrameEndTime; + + // Event listeners for VideoQueue + MediaEventListener mPushListener; + + // True if this sink is going to handle video track. + bool mHasVideo; + + // Used to trigger another update of rendered frames in next round. + DelayedScheduler mUpdateScheduler; + + // A delay duration to trigger next time UpdateRenderedVideoFrames(). + // Based on the default value in MDSM. + const int mDelayDuration; + + // Max frame number sent to compositor at a time. + // Based on the pref value obtained in MDSM. + const uint32_t mVideoQueueSendToCompositorSize; +}; + +} // namespace media +} // namespace mozilla + +#endif diff --git a/dom/media/mediasink/moz.build b/dom/media/mediasink/moz.build index 931bf0fd59..228bd2e281 100644 --- a/dom/media/mediasink/moz.build +++ b/dom/media/mediasink/moz.build @@ -8,6 +8,7 @@ UNIFIED_SOURCES += [ 'AudioSinkWrapper.cpp', 'DecodedAudioDataSink.cpp', 'DecodedStream.cpp', + 'VideoSink.cpp', ] FINAL_LIBRARY = 'xul' diff --git a/dom/tv/TVServiceFactory.cpp b/dom/tv/TVServiceFactory.cpp index ab7e876e6c..49cb4157d8 100644 --- a/dom/tv/TVServiceFactory.cpp +++ b/dom/tv/TVServiceFactory.cpp @@ -8,6 +8,7 @@ #include "mozilla/dom/TVListeners.h" #include "mozilla/Preferences.h" #include "nsITVService.h" +#include "nsITVSimulatorService.h" #include "nsServiceManagerUtils.h" #include "TVServiceFactory.h" @@ -27,8 +28,14 @@ TVServiceFactory::AutoCreateTVService() nsresult rv; nsCOMPtr service = do_CreateInstance(TV_SERVICE_CONTRACTID); if (!service) { - // Fallback to the fake service. - service = do_CreateInstance(FAKE_TV_SERVICE_CONTRACTID, &rv); + if (Preferences::GetBool("dom.testing.tv_enabled_for_hosted_apps", false)) { + // Fallback to the fake service. + service = do_CreateInstance(FAKE_TV_SERVICE_CONTRACTID, &rv); + } else { + // Fallback to the TV Simulator Service + service = do_CreateInstance(TV_SIMULATOR_SERVICE_CONTRACTID, &rv); + } + if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } diff --git a/dom/tv/TVSimulatorService.js b/dom/tv/TVSimulatorService.js new file mode 100644 index 0000000000..a9fd2fe56f --- /dev/null +++ b/dom/tv/TVSimulatorService.js @@ -0,0 +1,438 @@ +/* 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/. */ +"use strict"; + +function debug(aMsg) { + //dump("[TVSimulatorService] " + aMsg + "\n"); +} + +const Cc = Components.classes; +const Cu = Components.utils; +const Ci = Components.interfaces; +const Cr = Components.returnCode; +const TV_SIMULATOR_DUMMY_DIRECTORY = "dummy"; +const TV_SIMULATOR_DUMMY_FILE = "settings.json"; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +function TVSimulatorService() { + this._internalTuners = null; + this._scanCompleteTimer = null; + this._scanningWrapTunerData = null; + this._init(); +} + +TVSimulatorService.prototype = { + classID: Components.ID("{94b065ad-d45a-436a-b394-6dabc3cf110f}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsITVSimulatorService, + Ci.nsITVService, + Ci.nsITimerCallback]), + + _init: function TVSimInit() { + if (this._internalTuners) { + return; + } + + // Load the setting file from local JSON file. + // Synchrhronous File Reading. + let file = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); + + file.initWithPath(this._getFilePath(TV_SIMULATOR_DUMMY_FILE)); + + let fstream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"] + .createInstance(Ci.nsIConverterInputStream); + + let settingStr = ""; + + try { + fstream.init(file, -1, 0, 0); + cstream.init(fstream, + "UTF-8", + 1024, + Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + let str = {}; + while (cstream.readString(0xffffffff, str) != 0) { + settingStr += str.value; + } + } catch(e) { + debug("Error occurred : " + e ); + return; + } finally { + cstream.close(); + } + + let settingObj; + try { + /* + * + * Setting JSON file format: + * + * Note: This setting JSON is not allow empty array. + * If set the empty array, _init() will fail. + * e.g. + * - "tuners": [] + * - "channels":[] + * Format: + * { + * "tuners": [{ + * "id": "The ID of the tuner", + * "supportedType": ["The array of source type to be used."], + * "sources": [{ + * "type": "The source type to be used", + * "channels" : [{ + * "networkId": "The ID of the channel network", + * "transportStreamId": "The ID of channel transport stream", + * "serviceId": "The ID of channel service", + * "type": "The type of channel", + * "name": "The channel name", + * "number" : The LCN (Logical Channel Number) of the channel, + * "isEmergency" : Whether this channel is emergency status, + * "isFree": Whether this channel is free or not, + * "videoFilePath": "The path of the fake video file", + * "programs":[{ + * "eventId": "The ID of this program event", + * "title" : "This program's title", + * "startTime": "The start time of this program", + * "duration": "The duration of this program", + * "description": "The description of this program", + * "rating": "The rating of this program", + * "audioLanugages": ["The array of audio language"], + * "subtitleLanguages":["The array of subtitle language"], + * },] + * },] + * },] + * },] + * } + */ + settingObj = JSON.parse(settingStr); + } catch(e) { + debug("File load error: " + e); + return; + } + + // Key is as follow + // {'tunerId':tunerId, 'sourceType':sourceType} + this._internalTuners = new Map(); + + // TVTunerData + for each (let tunerData in settingObj.tuners) { + let tuner = Cc["@mozilla.org/tv/tvtunerdata;1"] + .createInstance(Ci.nsITVTunerData); + tuner.id = tunerData.id; + tuner.streamType = tuner.TV_STREAM_TYPE_SIMULATOR; + tuner.setSupportedSourceTypes(tunerData.supportedType.length, + tunerData.supportedType); + + let wrapTunerData = { + 'tuner': tuner, + 'channels': new Map(), + 'sourceType': undefined, + }; + + // TVSource + for each (let sourceData in tunerData.sources) { + wrapTunerData.sourceType = sourceData.type; + + // TVChannel + for each (let channelData in sourceData.channels) { + let channel = Cc["@mozilla.org/tv/tvchanneldata;1"] + .createInstance(Ci.nsITVChannelData); + channel.networkId = channelData.networkId; + channel.transportStreamId = channelData.transportStreamId; + channel.serviceId = channelData.serviceId; + channel.type = channelData.type; + channel.name = channelData.name; + channel.number = channelData.number; + channel.isEmergency = channelData.isEmergency; + channel.isFree = channelData.isFree; + + let wrapChannelData = { + 'channel': channel, + 'programs': new Array(), + 'videoFilePath': channelData.videoFilePath, + }; + + // TVProgram + for each (let programData in channelData.programs) { + let program = Cc["@mozilla.org/tv/tvprogramdata;1"] + .createInstance(Ci.nsITVProgramData); + program.eventId = programData.eventId; + program.title = programData.title; + program.startTime = programData.startTime; + program.duration = programData.duration; + program.description = programData.description; + program.rating = programData.rating; + program.setAudioLanguages(programData.audioLanguages.length, + programData.audioLanguages); + program.setSubtitleLanguages(programData.subtitleLanguages.length, + programData.subtitleLanguages); + wrapChannelData.programs.push(program); + } + + // Sort the program according to the startTime + wrapChannelData.programs.sort(function(a, b) { + return a.startTime - b.startTime; + }); + wrapTunerData.channels.set(channel.number, wrapChannelData); + } + + // Sort the channel according to the channel number + wrapTunerData.channels = new Map([...wrapTunerData.channels.entries()].sort(function(a, b) { + return a[0] - b[0]; + })); + this._internalTuners.set( + this._getTunerMapKey(tuner.id, sourceData.type), + wrapTunerData); + } + } + }, + + getTuners: function TVSimGetTuners(aCallback) { + if (!aCallback) { + debug("aCallback is null\n"); + return Cr.NS_ERROR_INVALID_ARG; + } + + let tuners = Cc["@mozilla.org/array;1"] + .createInstance(Ci.nsIMutableArray); + + for (let [k,wrapTunerData] of this._internalTuners) { + tuners.appendElement(wrapTunerData.tuner, false); + } + + return aCallback.notifySuccess(tuners); + }, + + setSource: function TVSimSetSource(aTunerId, aSourceType, aCallback) { + if (!aCallback) { + debug("aCallback is null\n"); + return NS_ERROR_INVALID_ARG; + } + + let wrapTunerData = this._getWrapTunerData(aTunerId, aSourceType); + if (!wrapTunerData) { + return aCallback.notifyError(Ci.nsITVServiceCallback.TV_ERROR_FAILURE); + } + return aCallback.notifySuccess(null); + }, + + startScanningChannels: function TVSimStartScanningChannels(aTunerId, aSourceType, aCallback) { + if (!aCallback) { + debug("aCallback is null\n"); + return Cr.NS_ERROR_INVALID_ARG; + } + + let wrapTunerData = this._getWrapTunerData(aTunerId, aSourceType); + if (!wrapTunerData || !wrapTunerData.channels) { + return aCallback.notifyError(Ci.nsITVServiceCallback.TV_ERROR_FAILURE); + } + + if (this._scanningWrapTunerData) { + return aCallback.notifyError(Cr.NS_ERROR_DOM_INVALID_STATE_ERR); + } + + this._scanningWrapTunerData = wrapTunerData; + + aCallback.notifySuccess(null); + + for (let [key, wrapChannelData] of wrapTunerData.channels) { + this._sourceListener.notifyChannelScanned( + wrapTunerData.tuner.id, + wrapTunerData.sourceType, + wrapChannelData.channel); + } + + this._scanCompleteTimer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + rv = this._scanCompleteTimer.initWithCallback(this, 10, + Ci.nsITimer.TYPE_ONE_SHOT); + return Cr.NS_OK; + }, + + notify: function TVSimTimerCallback(aTimer) { + if (!this._scanningWrapTunerData) { + return; + } + + this._scanCompleteTimer = null; + this._scanningWrapTunerData = null; + return this._sourceListener.notifyChannelScanComplete( + this._scanningWrapTunerData.tuner.id, + this._scanningWrapTunerData.sourceType); + }, + + stopScanningChannels: function TVSimStopScanningChannels(aTunerId, aSourceType, aCallback) { + if (!aCallback) { + debug("aCallback is null\n"); + return Cr.NS_ERROR_INVALID_ARG; + } + + if (!this._scanningWrapTunerData) { + return aCallback.notifyError(Cr.NS_ERROR_DOM_INVALID_STATE_ERR); + } + + let wrapTunerData = this._getWrapTunerData(aTunerId, aSourceType); + if (!wrapTunerData) { + return aCallback.notifyError(Ci.nsITVServiceCallback.TV_ERROR_FAILURE); + } + + if (this._scanCompleteTimer) { + this._scanCompleteTimer.cancel(); + this._scanCompleteTimer = null; + } + + if (wrapTunerData.tuner.id === this._scanningWrapTunerData.tuner.id && + wrapTunerData.sourceType === this._scanningWrapTunerData.sourceType) { + this._scanningWrapTunerData = null; + this._sourceListener.notifyChannelScanStopped( + wrapTunerData.tuner.id, + wrapTunerData.sourceType); + } + + return aCallback.notifySuccess(null); + }, + + clearScannedChannelsCache: function TVSimClearScannedChannelsCache(aTunerId, aSourceType, aCallback) { + // Doesn't support for this method. + return Cr.NS_OK; + }, + + setChannel: function TVSimSetChannel(aTunerId, aSourceType, aChannelNumber, aCallback) { + if (!aCallback) { + debug("aCallback is null\n"); + return Cr.NS_ERROR_INVALID_ARG; + } + + let channel = Cc["@mozilla.org/array;1"] + .createInstance(Ci.nsIMutableArray); + + let wrapTunerData = this._getWrapTunerData(aTunerId, aSourceType); + if (!wrapTunerData || !wrapTunerData.channels) { + return aCallback.notifyError(Ci.nsITVServiceCallback.TV_ERROR_FAILURE); + } + + let wrapChannelData = wrapTunerData.channels.get(aChannelNumber); + if (!wrapChannelData) { + return aCallback.notifyError(Ci.nsITVServiceCallback.TV_ERROR_FAILURE); + } + + channel.appendElement(wrapChannelData.channel, false); + return aCallback.notifySuccess(channel); + + }, + + getChannels: function TVSimGetChannels(aTunerId, aSourceType, aCallback) { + if (!aCallback) { + debug("aCallback is null\n"); + return Cr.NS_ERROR_INVALID_ARG; + } + + let channelArray = Cc["@mozilla.org/array;1"] + .createInstance(Ci.nsIMutableArray); + + let wrapTunerData = this._getWrapTunerData(aTunerId, aSourceType); + if (!wrapTunerData || !wrapTunerData.channels) { + return aCallback.notifyError(Ci.nsITVServiceCallback.TV_ERROR_FAILURE); + } + + for (let [key, wrapChannelData] of wrapTunerData.channels) { + channelArray.appendElement(wrapChannelData.channel, false); + } + + return aCallback.notifySuccess(channelArray); + }, + + getPrograms: function TVSimGetPrograms(aTunerId, aSourceType, aChannelNumber, aStartTime, aEndTime, aCallback) { + if (!aCallback) { + debug("aCallback is null\n"); + return Cr.NS_ERROR_INVALID_ARG; + } + let programArray = Cc["@mozilla.org/array;1"] + .createInstance(Ci.nsIMutableArray); + + let wrapTunerData = this._getWrapTunerData(aTunerId, aSourceType); + if (!wrapTunerData || !wrapTunerData.channels) { + return aCallback.notifyError(Ci.nsITVServiceCallback.TV_ERROR_FAILURE); + } + + let wrapChannelData = wrapTunerData.channels.get(aChannelNumber); + if (!wrapChannelData || !wrapChannelData.programs) { + return Cr.NS_ERROR_INVALID_ARG; + } + + for each (let program in wrapChannelData.programs) { + programArray.appendElement(program, false); + } + + return aCallback.notifySuccess(programArray); + + }, + + getOverlayId: function TVSimGetOverlayId(aTunerId, aCallback) { + if (!aCallback) { + debug("aCallback is null\n"); + return Cr.NS_ERROR_INVALID_ARG; + } + + // TVSimulatorService does not use this parameter. + overlayIds = Cc["@mozilla.org/array;1"] + .createInstance(Ci.nsIMutableArray); + return aCallback.notifySuccess(overlayIds); + }, + + set sourceListener(aListener) { + this._sourceListener = aListener; + }, + + get sourceListener() { + return this._sourceListener; + }, + + getSimulatorVideoBlobURL: function TVSimGetSimulatorVideoBlob(aTunerId, + aSourceType, + aChannelNumber, + aWin) { + let wrapTunerData = this._getWrapTunerData(aTunerId, aSourceType); + if (!wrapTunerData || !wrapTunerData.channels) { + return ""; + } + + let wrapChannelData = wrapTunerData.channels.get(aChannelNumber); + if (!wrapChannelData) { + return ""; + } + + let videoFile = new File(this._getFilePath(wrapChannelData.videoFilePath)); + let videoBlobURL = aWin.URL.createObjectURL(videoFile); + + return videoBlobURL; + }, + + _getTunerMapKey: function TVSimGetTunerMapKey(aTunerId, aSourceType) { + return JSON.stringify({'tunerId': aTunerId, 'sourceType': aSourceType}); + }, + + _getWrapTunerData: function TVSimGetWrapTunerData(aTunerId, aSourceType) { + if (!this._internalTuners || this._internalTuners.size <= 0) { + return null; + } + return this._internalTuners.get(this._getTunerMapKey(aTunerId, aSourceType)); + }, + + _getFilePath: function TVSimGetFilePathFromDummyDirectory(fileName) { + let dsFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + dsFile.append(TV_SIMULATOR_DUMMY_DIRECTORY); + dsFile.append(fileName); + + return dsFile.path; + }, +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TVSimulatorService]); diff --git a/dom/tv/TVSimulatorService.manifest b/dom/tv/TVSimulatorService.manifest new file mode 100644 index 0000000000..fc2418a0a2 --- /dev/null +++ b/dom/tv/TVSimulatorService.manifest @@ -0,0 +1,3 @@ +component {94b065ad-d45a-436a-b394-6dabc3cf110f} TVSimulatorService.js +contract @mozilla.org/tv/simulatorservice;1 {94b065ad-d45a-436a-b394-6dabc3cf110f} +TVSimulatorService @mozilla.org/tv/simulatorservice;1 diff --git a/dom/tv/TVSource.cpp b/dom/tv/TVSource.cpp index 58eac84001..0b4e4fb691 100644 --- a/dom/tv/TVSource.cpp +++ b/dom/tv/TVSource.cpp @@ -133,6 +133,14 @@ TVSource::SetCurrentChannel(nsITVChannelData* aChannelData) mCurrentChannel = TVChannel::Create(GetOwner(), this, aChannelData); NS_ENSURE_TRUE(mCurrentChannel, NS_ERROR_DOM_ABORT_ERR); + RefPtr currentSource = mTuner->GetCurrentSource(); + if (currentSource && mType == currentSource->Type()) { + rv = mTuner->ReloadMediaStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + return DispatchCurrentChannelChangedEvent(mCurrentChannel); } diff --git a/dom/tv/TVTuner.cpp b/dom/tv/TVTuner.cpp index 55c2125f26..6493211a19 100644 --- a/dom/tv/TVTuner.cpp +++ b/dom/tv/TVTuner.cpp @@ -13,8 +13,10 @@ #include "mozilla/dom/TVUtils.h" #include "nsISupportsPrimitives.h" #include "nsITVService.h" +#include "nsITVSimulatorService.h" #include "nsServiceManagerUtils.h" #include "TVTuner.h" +#include "mozilla/dom/HTMLVideoElement.h" namespace mozilla { namespace dom { @@ -81,6 +83,9 @@ TVTuner::Init(nsITVTunerData* aData) mTVService = TVServiceFactory::AutoCreateTVService(); NS_ENSURE_TRUE(mTVService, false); + rv = aData->GetStreamType(&mStreamType); + NS_ENSURE_SUCCESS(rv, false); + return true; } @@ -194,16 +199,111 @@ TVTuner::GetStream() const return stream.forget(); } +nsresult +TVTuner::ReloadMediaStream() +{ + return InitMediaStream(); +} + nsresult TVTuner::InitMediaStream() { nsCOMPtr window = do_QueryInterface(GetOwner()); - RefPtr stream = DOMHwMediaStream::CreateHwStream(window); + RefPtr stream = nullptr; + if (mStreamType == nsITVTunerData::TV_STREAM_TYPE_HW) { + stream = DOMHwMediaStream::CreateHwStream(window); + } else if (mStreamType == nsITVTunerData::TV_STREAM_TYPE_SIMULATOR) { + stream = CreateSimulatedMediaStream(); + } mStream = stream.forget(); return NS_OK; } +already_AddRefed +TVTuner::CreateSimulatedMediaStream() +{ + ErrorResult error; + + nsIDocument* doc = GetOwner()->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + return nullptr; + } + RefPtr element = doc->CreateElement(VIDEO_TAG, error); + if (NS_WARN_IF(error.Failed())) { + return nullptr; + } + + nsCOMPtr content(do_QueryInterface(element)); + if (NS_WARN_IF(!content)) { + return nullptr; + } + + HTMLMediaElement* mediaElement = static_cast(content.get()); + if (NS_WARN_IF(!mediaElement)) { + return nullptr; + } + + mediaElement->SetAutoplay(true, error); + if (NS_WARN_IF(error.Failed())) { + return nullptr; + } + + mediaElement->SetLoop(true, error); + if (NS_WARN_IF(error.Failed())) { + return nullptr; + } + + nsCOMPtr domWin(do_QueryInterface(GetOwner())); + if (NS_WARN_IF(!domWin)) { + return nullptr; + } + + nsCOMPtr simService(do_QueryInterface(mTVService)); + if (NS_WARN_IF(!simService)) { + return nullptr; + } + + if (NS_WARN_IF(!mCurrentSource)) { + return nullptr; + } + + RefPtr currentChannel = mCurrentSource->GetCurrentChannel(); + if (NS_WARN_IF(!currentChannel)) { + return nullptr; + } + + nsString currentChannelNumber; + currentChannel->GetNumber(currentChannelNumber); + if (currentChannelNumber.IsEmpty()) { + return nullptr; + } + + nsString currentVideoBlobUrl; + nsresult rv = simService->GetSimulatorVideoBlobURL(mId, + ToTVSourceTypeStr(mCurrentSource->Type()), + currentChannelNumber, + domWin, + currentVideoBlobUrl); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + mediaElement->SetSrc(currentVideoBlobUrl, error); + if (NS_WARN_IF(error.Failed())) { + return nullptr; + } + + // See Media Capture from DOM Elements spec. + // http://www.w3.org/TR/mediacapture-fromelement/ + RefPtr stream = mediaElement->MozCaptureStream(error); + if (NS_WARN_IF(error.Failed())) { + return nullptr; + } + + return stream.forget(); +} + nsresult TVTuner::DispatchCurrentSourceChangedEvent(TVSource* aSource) { diff --git a/dom/tv/TVTuner.h b/dom/tv/TVTuner.h index af1e23418f..513d4889ab 100644 --- a/dom/tv/TVTuner.h +++ b/dom/tv/TVTuner.h @@ -11,6 +11,8 @@ // Include TVTunerBinding.h since enum TVSourceType can't be forward declared. #include "mozilla/dom/TVTunerBinding.h" +#define VIDEO_TAG NS_LITERAL_STRING("video") + class nsITVService; class nsITVTunerData; @@ -59,6 +61,8 @@ public: IMPL_EVENT_HANDLER(currentsourcechanged); + nsresult ReloadMediaStream(); + private: explicit TVTuner(nsPIDOMWindow* aWindow); @@ -68,10 +72,13 @@ private: nsresult InitMediaStream(); + already_AddRefed CreateSimulatedMediaStream(); + nsresult DispatchCurrentSourceChangedEvent(TVSource* aSource); nsCOMPtr mTVService; RefPtr mStream; + uint16_t mStreamType; RefPtr mCurrentSource; nsTArray> mSources; nsString mId; diff --git a/dom/tv/TVTypes.cpp b/dom/tv/TVTypes.cpp index bd5c103fab..399feda773 100644 --- a/dom/tv/TVTypes.cpp +++ b/dom/tv/TVTypes.cpp @@ -48,6 +48,20 @@ TVTunerData::SetId(const nsAString& aId) return NS_OK; } +/* virtual */ NS_IMETHODIMP +TVTunerData::GetStreamType(uint16_t* aStreamType) +{ + *aStreamType = mStreamType; + return NS_OK; +} + +/* virtual */ NS_IMETHODIMP +TVTunerData::SetStreamType(const uint16_t aStreamType) +{ + mStreamType = aStreamType; + return NS_OK; +} + /* virtual */ NS_IMETHODIMP TVTunerData::GetSupportedSourceTypes(uint32_t* aCount, char*** aSourceTypes) diff --git a/dom/tv/TVTypes.h b/dom/tv/TVTypes.h index 264efbc982..b89e6ea674 100644 --- a/dom/tv/TVTypes.h +++ b/dom/tv/TVTypes.h @@ -26,6 +26,7 @@ private: nsString mId; char** mSupportedSourceTypes; uint32_t mCount; + uint16_t mStreamType; }; class TVChannelData final : public nsITVChannelData diff --git a/dom/tv/moz.build b/dom/tv/moz.build index 9025a86b40..61a4417312 100644 --- a/dom/tv/moz.build +++ b/dom/tv/moz.build @@ -34,6 +34,12 @@ UNIFIED_SOURCES += [ XPIDL_SOURCES += [ 'nsITVService.idl', + 'nsITVSimulatorService.idl', +] + +EXTRA_COMPONENTS += [ + 'TVSimulatorService.js', + 'TVSimulatorService.manifest', ] XPIDL_MODULE = 'dom_tv' diff --git a/dom/tv/nsITVService.idl b/dom/tv/nsITVService.idl index f379969917..e91bafeba6 100644 --- a/dom/tv/nsITVService.idl +++ b/dom/tv/nsITVService.idl @@ -20,9 +20,19 @@ interface nsIArray; * and then uses the setter functions to adjust the properties of the object * before passing it. */ -[scriptable, builtinclass, uuid(608d3f7e-f9f1-4b3c-82c2-3eb60b1d3de8)] +[scriptable, builtinclass, uuid(c6d39e86-022b-4db5-b0df-602abfbeac69)] interface nsITVTunerData : nsISupports { + /** + * Switch TVTuner.stream type. + * TV_STREAM_TYPE_SIMULATOR : Simulate the MediaStream. This MediaStream load from Profile Directory. + * TV_STREAM_TYPE_HW : Get from real device + */ + const unsigned short TV_STREAM_TYPE_SIMULATOR = 0; + const unsigned short TV_STREAM_TYPE_HW = 1; + + attribute unsigned short streamType; + attribute DOMString id; /** @@ -138,7 +148,7 @@ interface nsITVProgramData : nsISupports [array, size_is(count)] in string languages); }; -[builtinclass, uuid(c3fd7a8c-21e4-11e4-97e8-74d02b97e723)] +[scriptable, builtinclass, uuid(47746633-1b77-4df4-9424-d315bde3d455)] interface nsITVSourceListener : nsISupports { /** @@ -198,7 +208,7 @@ interface nsITVSourceListener : nsISupports in unsigned long count); }; -[builtinclass, uuid(a19e6e7e-2293-11e4-b335-74d02b97e723)] +[scriptable, builtinclass, uuid(01582a11-4707-455d-8d2a-2c8de8227dad)] interface nsITVServiceCallback : nsISupports { const unsigned short TV_ERROR_OK = 0; @@ -258,7 +268,7 @@ interface nsITVServiceCallback : nsISupports * new TVServiceNotifyRunnable(callback, dataList, optional errorCode); * return NS_DispatchToCurrentThread(runnable); */ -[uuid(1b17e3cc-1c84-11e4-a4d4-74d02b97e723)] +[scriptable, uuid(e52f93f1-6071-468b-a198-d8e6bc5ca348)] interface nsITVService : nsISupports { attribute nsITVSourceListener sourceListener; @@ -380,3 +390,4 @@ interface nsITVService : nsISupports void getOverlayId(in DOMString tunerId, in nsITVServiceCallback callback); }; + diff --git a/dom/tv/nsITVSimulatorService.idl b/dom/tv/nsITVSimulatorService.idl new file mode 100644 index 0000000000..4debd785f1 --- /dev/null +++ b/dom/tv/nsITVSimulatorService.idl @@ -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 "nsISupports.idl" +#include "nsITVService.idl" +#include "nsIDOMWindow.idl" + +%{C++ +#define TV_SIMULATOR_SERVICE_CONTRACTID\ + "@mozilla.org/tv/simulatorservice;1" +%} + +[scriptable, uuid(8ecae67d-a959-4f8a-a786-14dc12bd8d3c)] +interface nsITVSimulatorService : nsITVService +{ + /* + * Get the URL of simulated video blob. + * + * @param tunerId The ID of the tuner. + * @param sourceType The source type to be used. + * @param channelNumber The LCN (Logical Channel Number) of the channel. + * @param window The window object of content. + * @return blobUrl The URL of created blob from local video file. + */ + void getSimulatorVideoBlobURL(in DOMString tunerId, + in DOMString sourceType, + in DOMString channelNumber, + in nsIDOMWindow window, + [retval] out DOMString blobUrl); +}; diff --git a/gfx/layers/LayerUserData.h b/gfx/layers/LayerUserData.h new file mode 100644 index 0000000000..a988b4b381 --- /dev/null +++ b/gfx/layers/LayerUserData.h @@ -0,0 +1,28 @@ +/* -*- 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 GFX_LAYERUSERDATA_H +#define GFX_LAYERUSERDATA_H + +namespace mozilla { +namespace layers { + +/** + * Base class for userdata objects attached to layers and layer managers. + * + * We define it here in a separate header so clients only need to include + * this header for their class definitions, rather than pulling in Layers.h. + * Everything else in Layers.h should be forward-declarable. + */ +class LayerUserData { +public: + virtual ~LayerUserData() {} +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYERUSERDATA_H */ diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 19f5aa1ae7..7b6c26a03a 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -205,6 +205,19 @@ LayerManager::AreComponentAlphaLayersEnabled() return gfxPrefs::ComponentAlphaEnabled(); } +/*static*/ void +LayerManager::LayerUserDataDestroy(void* data) +{ + delete static_cast(data); +} + +nsAutoPtr +LayerManager::RemoveUserData(void* aKey) +{ + nsAutoPtr d(static_cast(mUserData.Remove(static_cast(aKey)))); + return d; +} + //-------------------------------------------------- // Layer @@ -1038,6 +1051,12 @@ Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, nsIntRegion siblingVisibleRegion(sibling->GetEffectiveVisibleRegion()); // Translate the siblings region to |layer|'s origin. siblingVisibleRegion.MoveBy(-siblingOffset.x, -siblingOffset.y); + // Apply the sibling's clip. + // Layer clip rects are not affected by the layer's transform. + Maybe clipRect = sibling->GetEffectiveClipRect(); + if (clipRect) { + siblingVisibleRegion.AndWith(clipRect->ToUnknownRect()); + } // Subtract the sibling visible region from the visible region of |this|. aResult.SubOut(siblingVisibleRegion); } @@ -1316,6 +1335,12 @@ ContainerLayer::SortChildrenBy3DZOrder(nsTArray& aArray) if (toSort.Length() > 0) { SortLayersBy3DZOrder(toSort); aArray.AppendElements(Move(toSort)); + // XXX The move analysis gets confused here, because toSort gets moved + // here, and then gets used again outside of the loop. To clarify that + // we realize that the array is going to be empty to the move checker, + // we clear it again here. (This method renews toSort for the move + // analysis) + toSort.ClearAndRetainStorage(); } aArray.AppendElement(l); } @@ -2058,6 +2083,13 @@ Layer::IsBackfaceHidden() return false; } +nsAutoPtr +Layer::RemoveUserData(void* aKey) +{ + nsAutoPtr d(static_cast(mUserData.Remove(static_cast(aKey)))); + return d; +} + void PaintedLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index e0e072c481..838860084c 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -103,13 +103,8 @@ class LayersPacket; virtual const char* Name() const override { return n; } \ virtual LayerType GetType() const override { return e; } -/** - * Base class for userdata objects attached to layers and layer managers. - */ -class LayerUserData { -public: - virtual ~LayerUserData() {} -}; +// Defined in LayerUserData.h; please include that file instead. +class LayerUserData; /* * Motivation: For truly smooth animation and video playback, we need to @@ -137,11 +132,6 @@ public: * BasicLayerManager for such an implementation. */ -static void LayerManagerUserDataDestroy(void *data) -{ - delete static_cast(data); -} - /** * A LayerManager controls a tree of layers. All layers in the tree * must use the same LayerManager. @@ -509,16 +499,13 @@ public: */ void SetUserData(void* aKey, LayerUserData* aData) { - mUserData.Add(static_cast(aKey), aData, LayerManagerUserDataDestroy); + mUserData.Add(static_cast(aKey), aData, LayerUserDataDestroy); } /** * This can be used anytime. Ownership passes to the caller! */ - nsAutoPtr RemoveUserData(void* aKey) - { - nsAutoPtr d(static_cast(mUserData.Remove(static_cast(aKey)))); - return d; - } + nsAutoPtr RemoveUserData(void* aKey); + /** * This getter can be used anytime. */ @@ -676,6 +663,8 @@ public: return false; } + static void LayerUserDataDestroy(void* data); + protected: RefPtr mRoot; gfx::UserData mUserData; @@ -1398,16 +1387,12 @@ public: */ void SetUserData(void* aKey, LayerUserData* aData) { - mUserData.Add(static_cast(aKey), aData, LayerManagerUserDataDestroy); + mUserData.Add(static_cast(aKey), aData, LayerManager::LayerUserDataDestroy); } /** * This can be used anytime. Ownership passes to the caller! */ - nsAutoPtr RemoveUserData(void* aKey) - { - nsAutoPtr d(static_cast(mUserData.Remove(static_cast(aKey)))); - return d; - } + nsAutoPtr RemoveUserData(void* aKey); /** * This getter can be used anytime. */ diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index 4c140544ff..427013dde9 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -34,6 +34,7 @@ EXPORTS += [ 'LayerSorter.h', 'LayersTypes.h', 'LayerTreeInvalidation.h', + 'LayerUserData.h', 'opengl/Composer2D.h', 'opengl/OGLShaderProgram.h', 'opengl/TexturePoolOGL.h', diff --git a/gfx/tests/gtest/TestLayers.cpp b/gfx/tests/gtest/TestLayers.cpp index 9ffa4528fb..d3cd968346 100644 --- a/gfx/tests/gtest/TestLayers.cpp +++ b/gfx/tests/gtest/TestLayers.cpp @@ -6,6 +6,7 @@ #include "TestLayers.h" #include "gtest/gtest.h" #include "gmock/gmock.h" +#include "LayerUserData.h" #include "mozilla/layers/LayerMetricsWrapper.h" #include "mozilla/layers/CompositorParent.h" diff --git a/layout/base/ActiveLayerTracker.cpp b/layout/base/ActiveLayerTracker.cpp index 9a77f2ac35..4b27802d5d 100644 --- a/layout/base/ActiveLayerTracker.cpp +++ b/layout/base/ActiveLayerTracker.cpp @@ -6,6 +6,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/gfx/Matrix.h" +#include "mozilla/PodOperations.h" #include "nsExpirationTracker.h" #include "nsContainerFrame.h" #include "nsIContent.h" @@ -33,38 +34,54 @@ using namespace gfx; */ class LayerActivity { public: + enum ActivityIndex { + ACTIVITY_OPACITY, + ACTIVITY_TRANSFORM, + ACTIVITY_LEFT, + ACTIVITY_TOP, + ACTIVITY_RIGHT, + ACTIVITY_BOTTOM, + ACTIVITY_MARGIN_LEFT, + ACTIVITY_MARGIN_TOP, + ACTIVITY_MARGIN_RIGHT, + ACTIVITY_MARGIN_BOTTOM, + ACTIVITY_BACKGROUND_POSITION, + + ACTIVITY_SCALE, + + // keep as last item + ACTIVITY_COUNT + }; + explicit LayerActivity(nsIFrame* aFrame) : mFrame(aFrame) , mContent(nullptr) - , mOpacityRestyleCount(0) - , mTransformRestyleCount(0) - , mScaleRestyleCount(0) - , mLeftRestyleCount(0) - , mTopRestyleCount(0) - , mRightRestyleCount(0) - , mBottomRestyleCount(0) - , mMarginLeftRestyleCount(0) - , mMarginTopRestyleCount(0) - , mMarginRightRestyleCount(0) - , mMarginBottomRestyleCount(0) , mContentActive(false) - {} + { + PodArrayZero(mRestyleCounts); + } ~LayerActivity(); nsExpirationState* GetExpirationState() { return &mState; } uint8_t& RestyleCountForProperty(nsCSSProperty aProperty) + { + return mRestyleCounts[GetActivityIndexForProperty(aProperty)]; + } + + static ActivityIndex GetActivityIndexForProperty(nsCSSProperty aProperty) { switch (aProperty) { - case eCSSProperty_opacity: return mOpacityRestyleCount; - case eCSSProperty_transform: return mTransformRestyleCount; - case eCSSProperty_left: return mLeftRestyleCount; - case eCSSProperty_top: return mTopRestyleCount; - case eCSSProperty_right: return mRightRestyleCount; - case eCSSProperty_bottom: return mBottomRestyleCount; - case eCSSProperty_margin_left: return mMarginLeftRestyleCount; - case eCSSProperty_margin_top: return mMarginTopRestyleCount; - case eCSSProperty_margin_right: return mMarginRightRestyleCount; - case eCSSProperty_margin_bottom: return mMarginBottomRestyleCount; - default: MOZ_ASSERT(false); return mOpacityRestyleCount; + case eCSSProperty_opacity: return ACTIVITY_OPACITY; + case eCSSProperty_transform: return ACTIVITY_TRANSFORM; + case eCSSProperty_left: return ACTIVITY_LEFT; + case eCSSProperty_top: return ACTIVITY_TOP; + case eCSSProperty_right: return ACTIVITY_RIGHT; + case eCSSProperty_bottom: return ACTIVITY_BOTTOM; + case eCSSProperty_margin_left: return ACTIVITY_MARGIN_LEFT; + case eCSSProperty_margin_top: return ACTIVITY_MARGIN_TOP; + case eCSSProperty_margin_right: return ACTIVITY_MARGIN_RIGHT; + case eCSSProperty_margin_bottom: return ACTIVITY_MARGIN_BOTTOM; + case eCSSProperty_background_position: return ACTIVITY_BACKGROUND_POSITION; + default: MOZ_ASSERT(false); return ACTIVITY_OPACITY; } } @@ -80,18 +97,15 @@ public: // Previous scale due to the CSS transform property. Maybe mPreviousTransformScale; + // The scroll frame during for which we most recently received a call to + // NotifyAnimatedFromScrollHandler. + nsWeakFrame mAnimatingScrollHandlerFrame; + // The set of activities that were triggered during + // mAnimatingScrollHandlerFrame's scroll event handler. + EnumSet mScrollHandlerInducedActivity; + // Number of restyle operations detected - uint8_t mOpacityRestyleCount; - uint8_t mTransformRestyleCount; - uint8_t mScaleRestyleCount; - uint8_t mLeftRestyleCount; - uint8_t mTopRestyleCount; - uint8_t mRightRestyleCount; - uint8_t mBottomRestyleCount; - uint8_t mMarginLeftRestyleCount; - uint8_t mMarginTopRestyleCount; - uint8_t mMarginRightRestyleCount; - uint8_t mMarginBottomRestyleCount; + uint8_t mRestyleCounts[ACTIVITY_COUNT]; bool mContentActive; }; @@ -102,12 +116,20 @@ public: LayerActivityTracker() : nsExpirationTracker(GENERATION_MS, "LayerActivityTracker") + , mDestroying(false) {} ~LayerActivityTracker() { + mDestroying = true; AgeAllGenerations(); } virtual void NotifyExpired(LayerActivity* aObject); + +public: + nsWeakFrame mCurrentScrollHandlerFrame; + +private: + bool mDestroying; }; static LayerActivityTracker* gLayerActivityTracker = nullptr; @@ -126,6 +148,13 @@ NS_DECLARE_FRAME_PROPERTY(LayerActivityProperty, DeleteValue) void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) { + if (!mDestroying && aObject->mAnimatingScrollHandlerFrame.IsAlive()) { + // Reset the restyle counts, but let the layer activity survive. + PodArrayZero(aObject->mRestyleCounts); + MarkUsed(aObject); + return; + } + RemoveObject(aObject); nsIFrame* f = aObject->mFrame; @@ -225,7 +254,7 @@ IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity) if (!display->mSpecifiedTransform) { // The transform was removed. aActivity->mPreviousTransformScale = Nothing(); - IncrementMutationCount(&aActivity->mScaleRestyleCount); + IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); return; } @@ -243,7 +272,7 @@ IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity) if (!transform.Is2D(&transform2D)) { // We don't attempt to handle 3D transforms; just assume the scale changed. aActivity->mPreviousTransformScale = Nothing(); - IncrementMutationCount(&aActivity->mScaleRestyleCount); + IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); return; } @@ -253,7 +282,7 @@ IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity) } aActivity->mPreviousTransformScale = Some(scale); - IncrementMutationCount(&aActivity->mScaleRestyleCount); + IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); } /* static */ void @@ -272,10 +301,10 @@ ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, nsCSSProperty aProperty) ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame) { LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); - IncrementMutationCount(&layerActivity->mLeftRestyleCount); - IncrementMutationCount(&layerActivity->mTopRestyleCount); - IncrementMutationCount(&layerActivity->mRightRestyleCount); - IncrementMutationCount(&layerActivity->mBottomRestyleCount); + IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT]); + IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP]); + IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT]); + IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM]); } /* static */ void @@ -287,6 +316,27 @@ ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty) mutationCount = 0xFF; } +/* static */ void +ActiveLayerTracker::NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSProperty aProperty, + nsIFrame* aScrollFrame) +{ + if (aFrame->PresContext() != aScrollFrame->PresContext()) { + // Don't allow cross-document dependencies. + return; + } + LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); + LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty); + + if (layerActivity->mAnimatingScrollHandlerFrame.GetFrame() != aScrollFrame) { + // Discard any activity of a different scroll frame. We only track the + // most recent scroll handler induced activity. + layerActivity->mScrollHandlerInducedActivity.clear(); + layerActivity->mAnimatingScrollHandlerFrame = aScrollFrame; + } + + layerActivity->mScrollHandlerInducedActivity += activityIndex; +} + static bool IsPresContextInScriptAnimationCallback(nsPresContext* aPresContext) { @@ -303,10 +353,14 @@ IsPresContextInScriptAnimationCallback(nsPresContext* aPresContext) ActiveLayerTracker::NotifyInlineStyleRuleModified(nsIFrame* aFrame, nsCSSProperty aProperty) { - if (!IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { - return; + if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { + NotifyAnimated(aFrame, aProperty); + } + if (gLayerActivityTracker && + gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) { + NotifyAnimatedFromScrollHandler(aFrame, aProperty, + gLayerActivityTracker->mCurrentScrollHandlerFrame.GetFrame()); } - NotifyAnimated(aFrame, aProperty); } /* static */ bool @@ -315,6 +369,29 @@ ActiveLayerTracker::IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSProperty aProper return IsStyleAnimated(nullptr, aFrame, aProperty); } +static bool +CheckScrollInducedActivity(LayerActivity* aLayerActivity, + LayerActivity::ActivityIndex aActivityIndex, + nsDisplayListBuilder* aBuilder) +{ + if (!aLayerActivity->mScrollHandlerInducedActivity.contains(aActivityIndex) || + !aLayerActivity->mAnimatingScrollHandlerFrame.IsAlive()) { + return false; + } + + nsIScrollableFrame* scrollFrame = + do_QueryFrame(aLayerActivity->mAnimatingScrollHandlerFrame.GetFrame()); + if (scrollFrame && (!aBuilder || scrollFrame->IsScrollingActive(aBuilder))) { + return true; + } + + // The scroll frame has been destroyed or has become inactive. Clear it from + // the layer activity so that it can expire. + aLayerActivity->mAnimatingScrollHandlerFrame = nullptr; + aLayerActivity->mScrollHandlerInducedActivity.clear(); + return false; +} + /* static */ bool ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsCSSProperty aProperty) @@ -333,7 +410,11 @@ ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder, LayerActivity* layerActivity = GetLayerActivity(aFrame); if (layerActivity) { - if (layerActivity->RestyleCountForProperty(aProperty) >= 2) { + LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty); + if (layerActivity->mRestyleCounts[activityIndex] >= 2) { + return true; + } + if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) { return true; } } @@ -348,14 +429,14 @@ ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(nsIFrame* aFrame) { LayerActivity* layerActivity = GetLayerActivity(aFrame); if (layerActivity) { - if (layerActivity->mLeftRestyleCount >= 2 || - layerActivity->mTopRestyleCount >= 2 || - layerActivity->mRightRestyleCount >= 2 || - layerActivity->mBottomRestyleCount >= 2 || - layerActivity->mMarginLeftRestyleCount >= 2 || - layerActivity->mMarginTopRestyleCount >= 2 || - layerActivity->mMarginRightRestyleCount >= 2 || - layerActivity->mMarginBottomRestyleCount >= 2) { + if (layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_LEFT] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_TOP] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_RIGHT] >= 2 || + layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_BOTTOM] >= 2) { return true; } } @@ -406,7 +487,7 @@ ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) { // Check whether JavaScript is animating this frame's scale. LayerActivity* layerActivity = GetLayerActivity(aFrame); - if (layerActivity && layerActivity->mScaleRestyleCount >= 2) { + if (layerActivity && layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) { return true; } @@ -451,6 +532,15 @@ ActiveLayerTracker::IsContentActive(nsIFrame* aFrame) return layerActivity && layerActivity->mContentActive; } +/* static */ void +ActiveLayerTracker::SetCurrentScrollHandlerFrame(nsIFrame* aFrame) +{ + if (!gLayerActivityTracker) { + gLayerActivityTracker = new LayerActivityTracker(); + } + gLayerActivityTracker->mCurrentScrollHandlerFrame = aFrame; +} + /* static */ void ActiveLayerTracker::Shutdown() { diff --git a/layout/base/ActiveLayerTracker.h b/layout/base/ActiveLayerTracker.h index 92d2736611..302b0ba13d 100644 --- a/layout/base/ActiveLayerTracker.h +++ b/layout/base/ActiveLayerTracker.h @@ -49,6 +49,12 @@ public: * Any such marking will time out after a short period. */ static void NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty); + /** + * Notify aFrame as being known to have an animation of aProperty through an + * inline style modification during aScrollFrame's scroll event handler. + */ + static void NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSProperty aProperty, + nsIFrame* aScrollFrame); /** * Notify that a property in the inline style rule of aFrame's element * has been modified. @@ -107,6 +113,13 @@ public: * Return true if this frame's content is still marked as active. */ static bool IsContentActive(nsIFrame* aFrame); + + /** + * Called before and after a scroll event handler is executed, with the + * scrollframe or nullptr, respectively. This acts as a hint to treat + * inline style changes during the handler differently. + */ + static void SetCurrentScrollHandlerFrame(nsIFrame* aFrame); }; } // namespace mozilla diff --git a/layout/base/DisplayItemClip.cpp b/layout/base/DisplayItemClip.cpp index 4ee003c4fe..d3a5e1aa83 100644 --- a/layout/base/DisplayItemClip.cpp +++ b/layout/base/DisplayItemClip.cpp @@ -327,7 +327,7 @@ DisplayItemClip::ApplyNonRoundedIntersection(const nsRect& aRect) const nsRect result = aRect.Intersect(mClipRect); for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { - result.Intersect(mRoundedClipRects[i].mRect); + result = result.Intersect(mRoundedClipRects[i].mRect); } return result; } diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 2947835641..44e0103180 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -14,8 +14,10 @@ #include "ActiveLayerTracker.h" #include "BasicLayers.h" #include "ImageContainer.h" +#include "ImageLayers.h" #include "LayerTreeInvalidation.h" #include "Layers.h" +#include "LayerUserData.h" #include "MaskLayerImageCache.h" #include "UnitTransforms.h" #include "Units.h" @@ -87,6 +89,22 @@ uint8_t gLayerManagerUserData; */ uint8_t gMaskLayerUserData; +FrameLayerBuilder::FrameLayerBuilder() + : mRetainingManager(nullptr) + , mDetectedDOMModification(false) + , mInvalidateAllLayers(false) + , mInLayerTreeCompressionMode(false) + , mContainerLayerGeneration(0) + , mMaxContainerLayerGeneration(0) +{ + MOZ_COUNT_CTOR(FrameLayerBuilder); +} + +FrameLayerBuilder::~FrameLayerBuilder() +{ + MOZ_COUNT_DTOR(FrameLayerBuilder); +} + FrameLayerBuilder::DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey, Layer* aLayer, nsIFrame* aFrame) @@ -4571,6 +4589,13 @@ FrameLayerBuilder::StoreDataForFrame(nsIFrame* aFrame, lmd->mDisplayItems.PutEntry(data); } +FrameLayerBuilder::ClippedDisplayItem::ClippedDisplayItem(nsDisplayItem* aItem, + uint32_t aGeneration) + : mItem(aItem) + , mContainerLayerGeneration(aGeneration) +{ +} + FrameLayerBuilder::ClippedDisplayItem::~ClippedDisplayItem() { if (mInactiveLayerManager) { @@ -4578,6 +4603,27 @@ FrameLayerBuilder::ClippedDisplayItem::~ClippedDisplayItem() } } +FrameLayerBuilder::PaintedLayerItemsEntry::PaintedLayerItemsEntry(const PaintedLayer *aKey) + : nsPtrHashKey(aKey) + , mContainerLayerFrame(nullptr) + , mLastCommonClipCount(0) + , mContainerLayerGeneration(0) + , mHasExplicitLastPaintOffset(false) + , mCommonClipCount(0) +{ +} + +FrameLayerBuilder::PaintedLayerItemsEntry::PaintedLayerItemsEntry(const PaintedLayerItemsEntry& aOther) + : nsPtrHashKey(aOther.mKey) + , mItems(aOther.mItems) +{ + NS_ERROR("Should never be called, since we ALLOW_MEMMOVE"); +} + +FrameLayerBuilder::PaintedLayerItemsEntry::~PaintedLayerItemsEntry() +{ +} + void FrameLayerBuilder::AddLayerDisplayItem(Layer* aLayer, nsDisplayItem* aItem, diff --git a/layout/base/FrameLayerBuilder.h b/layout/base/FrameLayerBuilder.h index b256468c75..5e263fc836 100644 --- a/layout/base/FrameLayerBuilder.h +++ b/layout/base/FrameLayerBuilder.h @@ -11,10 +11,10 @@ #include "nsTArray.h" #include "nsRegion.h" #include "nsIFrame.h" -#include "ImageLayers.h" #include "DisplayItemClip.h" #include "mozilla/layers/LayersTypes.h" #include "LayerState.h" +#include "LayerUserData.h" class nsDisplayListBuilder; class nsDisplayList; @@ -28,6 +28,7 @@ class ContainerLayer; class LayerManager; class BasicLayerManager; class PaintedLayer; +class ImageLayer; } // namespace layers namespace gfx { @@ -60,6 +61,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(false) , mDisableSubpixelAntialiasingInDescendants(false) , mInLowPrecisionDisplayPort(false) + , mForEventsOnly(false) {} ContainerLayerParameters(float aXScale, float aYScale) : mXScale(aXScale) @@ -70,6 +72,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(false) , mDisableSubpixelAntialiasingInDescendants(false) , mInLowPrecisionDisplayPort(false) + , mForEventsOnly(false) {} ContainerLayerParameters(float aXScale, float aYScale, const nsIntPoint& aOffset, @@ -83,6 +86,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree) , mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants) , mInLowPrecisionDisplayPort(aParent.mInLowPrecisionDisplayPort) + , mForEventsOnly(aParent.mForEventsOnly) {} float mXScale, mYScale; @@ -111,6 +115,7 @@ struct ContainerLayerParameters { bool mInActiveTransformedSubtree; bool mDisableSubpixelAntialiasingInDescendants; bool mInLowPrecisionDisplayPort; + bool mForEventsOnly; /** * When this is false, PaintedLayer coordinates are drawn to with an integer * translation and the scale in mXScale/mYScale. @@ -172,20 +177,8 @@ public: typedef layers::BasicLayerManager BasicLayerManager; typedef layers::EventRegions EventRegions; - FrameLayerBuilder() : - mRetainingManager(nullptr), - mDetectedDOMModification(false), - mInvalidateAllLayers(false), - mInLayerTreeCompressionMode(false), - mContainerLayerGeneration(0), - mMaxContainerLayerGeneration(0) - { - MOZ_COUNT_CTOR(FrameLayerBuilder); - } - ~FrameLayerBuilder() - { - MOZ_COUNT_DTOR(FrameLayerBuilder); - } + FrameLayerBuilder(); + ~FrameLayerBuilder(); static void Shutdown(); @@ -578,11 +571,7 @@ protected: * PaintedLayer. */ struct ClippedDisplayItem { - ClippedDisplayItem(nsDisplayItem* aItem, uint32_t aGeneration) - : mItem(aItem), mContainerLayerGeneration(aGeneration) - { - } - + ClippedDisplayItem(nsDisplayItem* aItem, uint32_t aGeneration); ~ClippedDisplayItem(); nsDisplayItem* mItem; @@ -623,19 +612,9 @@ protected: public: class PaintedLayerItemsEntry : public nsPtrHashKey { public: - explicit PaintedLayerItemsEntry(const PaintedLayer *key) - : nsPtrHashKey(key) - , mContainerLayerFrame(nullptr) - , mLastCommonClipCount(0) - , mContainerLayerGeneration(0) - , mHasExplicitLastPaintOffset(false) - , mCommonClipCount(0) - {} - PaintedLayerItemsEntry(const PaintedLayerItemsEntry &toCopy) : - nsPtrHashKey(toCopy.mKey), mItems(toCopy.mItems) - { - NS_ERROR("Should never be called, since we ALLOW_MEMMOVE"); - } + explicit PaintedLayerItemsEntry(const PaintedLayer *key); + PaintedLayerItemsEntry(const PaintedLayerItemsEntry&); + ~PaintedLayerItemsEntry(); nsTArray mItems; nsIFrame* mContainerLayerFrame; diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index c56b7378be..fbcce47986 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -3868,9 +3868,12 @@ nsresult nsDisplayWrapper::WrapListsInPlace(nsDisplayListBuilder* aBuilder, } nsDisplayOpacity::nsDisplayOpacity(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, nsDisplayList* aList) + nsIFrame* aFrame, nsDisplayList* aList, + bool aForEventsOnly) : nsDisplayWrapList(aBuilder, aFrame, aList) - , mOpacity(aFrame->StyleDisplay()->mOpacity) { + , mOpacity(aFrame->StyleDisplay()->mOpacity) + , mForEventsOnly(aForEventsOnly) +{ MOZ_COUNT_CTOR(nsDisplayOpacity); } @@ -3894,9 +3897,11 @@ already_AddRefed nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters params = aContainerParameters; + params.mForEventsOnly = mForEventsOnly; RefPtr container = aManager->GetLayerBuilder()-> BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList, - aContainerParameters, nullptr, + params, nullptr, FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); if (!container) return nullptr; @@ -4001,6 +4006,14 @@ nsDisplayItem::LayerState nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) { + // If we only created this item so that we'd get correct nsDisplayEventRegions for child + // frames, then force us to inactive to avoid unnecessary layerization changes for content + // that won't ever be painted. + if (mForEventsOnly) { + MOZ_ASSERT(mOpacity == 0); + return LAYER_INACTIVE; + } + if (NeedsActiveLayer(aBuilder)) return LAYER_ACTIVE; @@ -4167,6 +4180,17 @@ nsDisplayBlendContainer::BuildLayer(nsDisplayListBuilder* aBuilder, return container.forget(); } +LayerState +nsDisplayBlendContainer::GetLayerState(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + const ContainerLayerParameters& aParameters) +{ + if (mCanBeActive && aManager->SupportsMixBlendModes(mContainedBlendModes)) { + return mozilla::LAYER_ACTIVE; + } + return mozilla::LAYER_INACTIVE; +} + bool nsDisplayBlendContainer::TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) { if (aItem->GetType() != TYPE_BLEND_CONTAINER) return false; diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 0439d4bfa7..39ae407a8b 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -15,12 +15,12 @@ #include "mozilla/Attributes.h" #include "mozilla/DebugOnly.h" +#include "mozilla/EnumSet.h" #include "nsCOMPtr.h" #include "nsContainerFrame.h" #include "nsPoint.h" #include "nsRect.h" #include "plarena.h" -#include "Layers.h" #include "nsRegion.h" #include "nsDisplayListInvalidation.h" #include "DisplayListClipState.h" @@ -3178,7 +3178,7 @@ protected: class nsDisplayOpacity : public nsDisplayWrapList { public: nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, - nsDisplayList* aList); + nsDisplayList* aList, bool aForEventsOnly); #ifdef NS_BUILD_REFCNT_LOGGING virtual ~nsDisplayOpacity(); #endif @@ -3213,6 +3213,7 @@ public: private: float mOpacity; + bool mForEventsOnly; }; class nsDisplayMixBlendMode : public nsDisplayWrapList { @@ -3263,13 +3264,7 @@ public: const ContainerLayerParameters& aContainerParameters) override; virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, - const ContainerLayerParameters& aParameters) override - { - if (mCanBeActive && aManager->SupportsMixBlendModes(mContainedBlendModes)) { - return mozilla::LAYER_ACTIVE; - } - return mozilla::LAYER_INACTIVE; - } + const ContainerLayerParameters& aParameters) override; virtual bool TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) override; virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { return false; diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 57e76535a0..913a067c9e 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -56,6 +56,7 @@ #include "mozilla/dom/TabParent.h" #include "nsRefreshDriver.h" #include "Layers.h" +#include "LayerUserData.h" #include "ClientLayerManager.h" #include "mozilla/dom/NotifyPaintEvent.h" #include "gfxPrefs.h" diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index f15347f014..fdf804e8d0 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -50,6 +50,7 @@ #include "nsFocusManager.h" #include "ThirdPartyUtil.h" #include "nsStructuredCloneContainer.h" +#include "gfxPlatform.h" #include "nsIEventListenerService.h" #include "nsIMessageManager.h" diff --git a/layout/forms/nsComboboxControlFrame.cpp b/layout/forms/nsComboboxControlFrame.cpp index 28ea90d417..4271338e2d 100644 --- a/layout/forms/nsComboboxControlFrame.cpp +++ b/layout/forms/nsComboboxControlFrame.cpp @@ -47,6 +47,7 @@ #include "mozilla/LookAndFeel.h" #include "mozilla/MouseEvents.h" #include "mozilla/unused.h" +#include "gfx2DGlue.h" #ifdef XP_WIN #define COMBOBOX_ROLLUP_CONSUME_EVENT 0 diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 9efa9538b7..c11beb13f0 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -57,6 +57,7 @@ #include "nsIPercentBSizeObserver.h" #include "nsStyleStructInlines.h" #include "FrameLayerBuilder.h" +#include "ImageLayers.h" #include "nsBidiPresUtils.h" #include "RubyUtils.h" @@ -1931,13 +1932,17 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, // need to have display items built for them. bool needEventRegions = aBuilder->IsBuildingLayerEventRegions() && StyleVisibility()->GetEffectivePointerEvents(this) != NS_STYLE_POINTER_EVENTS_NONE; + bool opacityItemForEventsOnly = false; if (disp->mOpacity == 0.0 && aBuilder->IsForPainting() && !aBuilder->WillComputePluginGeometry() && !(disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) && !nsLayoutUtils::HasCurrentAnimationOfProperty(this, - eCSSProperty_opacity) && - !needEventRegions) { - return; + eCSSProperty_opacity)) { + if (needEventRegions) { + opacityItemForEventsOnly = true; + } else { + return; + } } if (disp->mWillChangeBitField != 0) { @@ -2146,7 +2151,7 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder); opacityClipState.Clear(); resultList.AppendNewToTop( - new (aBuilder) nsDisplayOpacity(aBuilder, this, &resultList)); + new (aBuilder) nsDisplayOpacity(aBuilder, this, &resultList, opacityItemForEventsOnly)); } /* If we have sticky positioning, wrap it in a sticky position item. */ diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index cfb85893d4..39be8d9d5c 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -4199,6 +4199,7 @@ ScrollFrameHelper::FireScrollEvent() { mScrollEvent.Forget(); + ActiveLayerTracker::SetCurrentScrollHandlerFrame(mOuter); WidgetGUIEvent event(true, eScroll, nullptr); nsEventStatus status = nsEventStatus_eIgnore; nsIContent* content = mOuter->GetContent(); @@ -4216,6 +4217,7 @@ ScrollFrameHelper::FireScrollEvent() event.mFlags.mBubbles = false; EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status); } + ActiveLayerTracker::SetCurrentScrollHandlerFrame(nullptr); } void diff --git a/layout/generic/nsImageMap.cpp b/layout/generic/nsImageMap.cpp index b912973712..88612ca8d7 100644 --- a/layout/generic/nsImageMap.cpp +++ b/layout/generic/nsImageMap.cpp @@ -21,6 +21,7 @@ #include "nsIStringBundle.h" #include "nsContentUtils.h" #include "nsIContentInlines.h" +#include "ImageLayers.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" diff --git a/layout/ipc/RenderFrameParent.cpp b/layout/ipc/RenderFrameParent.cpp index b8829eba01..712c2a1955 100644 --- a/layout/ipc/RenderFrameParent.cpp +++ b/layout/ipc/RenderFrameParent.cpp @@ -380,7 +380,9 @@ RenderFrameParent::BuildLayer(nsDisplayListBuilder* aBuilder, // draw a manager's subtree. The latter is bad bad bad, but the the // MOZ_ASSERT() above will flag it. Returning nullptr here will just // cause the shadow subtree not to be rendered. - NS_WARNING("Remote iframe not rendered"); + if (!aContainerParameters.mForEventsOnly) { + NS_WARNING("Remote iframe not rendered"); + } return nullptr; } diff --git a/layout/style/nsDOMCSSAttrDeclaration.cpp b/layout/style/nsDOMCSSAttrDeclaration.cpp index 1f8ae7a953..9462f23448 100644 --- a/layout/style/nsDOMCSSAttrDeclaration.cpp +++ b/layout/style/nsDOMCSSAttrDeclaration.cpp @@ -182,7 +182,8 @@ nsDOMCSSAttributeDeclaration::SetPropertyValue(const nsCSSProperty aPropID, aPropID == eCSSProperty_left || aPropID == eCSSProperty_top || aPropID == eCSSProperty_right || aPropID == eCSSProperty_bottom || aPropID == eCSSProperty_margin_left || aPropID == eCSSProperty_margin_top || - aPropID == eCSSProperty_margin_right || aPropID == eCSSProperty_margin_bottom) { + aPropID == eCSSProperty_margin_right || aPropID == eCSSProperty_margin_bottom || + aPropID == eCSSProperty_background_position) { nsIFrame* frame = mElement->GetPrimaryFrame(); if (frame) { ActiveLayerTracker::NotifyInlineStyleRuleModified(frame, aPropID); diff --git a/mfbt/Atomics.h b/mfbt/Atomics.h index 3d168eda2c..1dcc20f935 100644 --- a/mfbt/Atomics.h +++ b/mfbt/Atomics.h @@ -336,6 +336,12 @@ struct AtomicIntrinsics { }; +template +struct ToStorageTypeArgument +{ + static T convert (T aT) { return aT; } +}; + } // namespace detail } // namespace mozilla @@ -400,60 +406,81 @@ struct Barrier static void afterStore() { __sync_synchronize(); } }; +template::value> +struct AtomicStorageType +{ + // For non-enums, just use the type directly. + typedef T Type; +}; + +template +struct AtomicStorageType + : Conditional +{ + static_assert(sizeof(T) == 4 || sizeof(T) == 8, + "wrong type computed in conditional above"); +}; + template struct IntrinsicMemoryOps { - static T load(const T& aPtr) + typedef typename AtomicStorageType::Type ValueType; + + static T load(const ValueType& aPtr) { Barrier::beforeLoad(); - T val = aPtr; + T val = T(aPtr); Barrier::afterLoad(); return val; } - static void store(T& aPtr, T aVal) + static void store(ValueType& aPtr, T aVal) { Barrier::beforeStore(); - aPtr = aVal; + aPtr = ValueType(aVal); Barrier::afterStore(); } - static T exchange(T& aPtr, T aVal) + static T exchange(ValueType& aPtr, T aVal) { // __sync_lock_test_and_set is only an acquire barrier; loads and stores // can't be moved up from after to before it, but they can be moved down // from before to after it. We may want a stricter ordering, so we need // an explicit barrier. Barrier::beforeStore(); - return __sync_lock_test_and_set(&aPtr, aVal); + return T(__sync_lock_test_and_set(&aPtr, ValueType(aVal))); } - static bool compareExchange(T& aPtr, T aOldVal, T aNewVal) + static bool compareExchange(ValueType& aPtr, T aOldVal, T aNewVal) { - return __sync_bool_compare_and_swap(&aPtr, aOldVal, aNewVal); + return __sync_bool_compare_and_swap(&aPtr, ValueType(aOldVal), ValueType(aNewVal)); } }; -template +template struct IntrinsicAddSub + : public IntrinsicMemoryOps { - typedef T ValueType; + typedef IntrinsicMemoryOps Base; + typedef typename Base::ValueType ValueType; - static T add(T& aPtr, T aVal) + static T add(ValueType& aPtr, T aVal) { - return __sync_fetch_and_add(&aPtr, aVal); + return T(__sync_fetch_and_add(&aPtr, ValueType(aVal))); } - static T sub(T& aPtr, T aVal) + static T sub(ValueType& aPtr, T aVal) { - return __sync_fetch_and_sub(&aPtr, aVal); + return T(__sync_fetch_and_sub(&aPtr, ValueType(aVal))); } }; -template -struct IntrinsicAddSub +template +struct IntrinsicAddSub + : public IntrinsicMemoryOps { - typedef T* ValueType; + typedef IntrinsicMemoryOps Base; + typedef typename Base::ValueType ValueType; /* * The reinterpret_casts are needed so that @@ -475,16 +502,18 @@ struct IntrinsicAddSub } }; -template -struct IntrinsicIncDec : public IntrinsicAddSub +template +struct IntrinsicIncDec : public IntrinsicAddSub { - static T inc(T& aPtr) { return IntrinsicAddSub::add(aPtr, 1); } - static T dec(T& aPtr) { return IntrinsicAddSub::sub(aPtr, 1); } + typedef IntrinsicAddSub Base; + typedef typename Base::ValueType ValueType; + + static T inc(ValueType& aPtr) { return Base::add(aPtr, 1); } + static T dec(ValueType& aPtr) { return Base::sub(aPtr, 1); } }; template -struct AtomicIntrinsics : public IntrinsicMemoryOps, - public IntrinsicIncDec +struct AtomicIntrinsics : public IntrinsicIncDec { static T or_( T& aPtr, T aVal) { return __sync_fetch_and_or(&aPtr, aVal); } static T xor_(T& aPtr, T aVal) { return __sync_fetch_and_xor(&aPtr, aVal); } @@ -492,11 +521,24 @@ struct AtomicIntrinsics : public IntrinsicMemoryOps, }; template -struct AtomicIntrinsics : public IntrinsicMemoryOps, - public IntrinsicIncDec +struct AtomicIntrinsics : public IntrinsicIncDec { }; +template::value> +struct ToStorageTypeArgument +{ + typedef typename AtomicStorageType::Type ResultType; + + static ResultType convert (T aT) { return ResultType(aT); } +}; + +template +struct ToStorageTypeArgument +{ + static T convert (T aT) { return aT; } +}; + } // namespace detail } // namespace mozilla @@ -900,11 +942,14 @@ class AtomicBase protected: typedef typename detail::AtomicIntrinsics Intrinsics; - typename Intrinsics::ValueType mValue; + typedef typename Intrinsics::ValueType ValueType; + ValueType mValue; public: MOZ_CONSTEXPR AtomicBase() : mValue() {} - explicit MOZ_CONSTEXPR AtomicBase(T aInit) : mValue(aInit) {} + explicit MOZ_CONSTEXPR AtomicBase(T aInit) + : mValue(ToStorageTypeArgument::convert(aInit)) + {} // Note: we can't provide operator T() here because Atomic inherits // from AtomcBase with T=uint32_t and not T=bool. If we implemented @@ -1091,7 +1136,7 @@ public: MOZ_CONSTEXPR Atomic() : Base() {} explicit MOZ_CONSTEXPR Atomic(T aInit) : Base(aInit) {} - operator T() const { return Base::Intrinsics::load(Base::mValue); } + operator T() const { return T(Base::Intrinsics::load(Base::mValue)); } using Base::operator=; diff --git a/mfbt/Attributes.h b/mfbt/Attributes.h index 9c17e1ede2..cb4bef94c6 100644 --- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -336,6 +336,35 @@ # define MOZ_WARN_UNUSED_RESULT #endif +/** + * MOZ_FALLTHROUGH is an annotation to suppress compiler warnings about switch + * cases that fall through without a break or return statement. MOZ_FALLTHROUGH + * is only needed on cases that have code: + * + * switch (foo) { + * case 1: // These cases have no code. No fallthrough annotations are needed. + * case 2: + * case 3: + * foo = 4; // This case has code, so a fallthrough annotation is needed: + * MOZ_FALLTHROUGH; + * default: + * return foo; + * } + */ +#if defined(__clang__) && __cplusplus >= 201103L + /* clang's fallthrough annotations are only available starting in C++11. */ +# define MOZ_FALLTHROUGH [[clang::fallthrough]] +#elif defined(_MSC_VER) + /* + * MSVC's __fallthrough annotations are checked by /analyze (Code Analysis): + * https://msdn.microsoft.com/en-us/library/ms235402%28VS.80%29.aspx + */ +# include +# define MOZ_FALLTHROUGH __fallthrough +#else +# define MOZ_FALLTHROUGH /* FALLTHROUGH */ +#endif + #ifdef __cplusplus /* diff --git a/mfbt/EnumSet.h b/mfbt/EnumSet.h index 8c78b2b442..a445f976c8 100644 --- a/mfbt/EnumSet.h +++ b/mfbt/EnumSet.h @@ -127,6 +127,14 @@ public: return result; } + /** + * Clear + */ + void clear() + { + mBitField = 0; + } + /** * Intersection */ diff --git a/mfbt/tests/TestAtomics.cpp b/mfbt/tests/TestAtomics.cpp index 07696dbd93..e23b3d5171 100644 --- a/mfbt/tests/TestAtomics.cpp +++ b/mfbt/tests/TestAtomics.cpp @@ -168,6 +168,44 @@ TestEnumWithOrdering() A(atomic == EnumType_3, "CAS should have changed atomic's value."); } +enum class EnumClass : uint32_t +{ + Value0 = 0, + Value1 = 1, + Value2 = 2, + Value3 = 3 +}; + +template +static void +TestEnumClassWithOrdering() +{ + Atomic atomic(EnumClass::Value2); + A(atomic == EnumClass::Value2, "Atomic variable did not initialize"); + + // Test assignment + EnumClass result; + result = (atomic = EnumClass::Value3); + A(atomic == EnumClass::Value3, "Atomic assignment failed"); + A(result == EnumClass::Value3, "Atomic assignment returned the wrong value"); + + // Test exchange. + atomic = EnumClass::Value1; + result = atomic.exchange(EnumClass::Value2); + A(atomic == EnumClass::Value2, "Atomic exchange did not work"); + A(result == EnumClass::Value1, "Atomic exchange returned the wrong value"); + + // Test CAS. + atomic = EnumClass::Value1; + bool boolResult = atomic.compareExchange(EnumClass::Value0, EnumClass::Value2); + A(!boolResult, "CAS should have returned false."); + A(atomic == EnumClass::Value1, "CAS shouldn't have done anything."); + + boolResult = atomic.compareExchange(EnumClass::Value1, EnumClass::Value3); + A(boolResult, "CAS should have succeeded."); + A(atomic == EnumClass::Value3, "CAS should have changed atomic's value."); +} + template static void TestBoolWithOrdering() @@ -222,6 +260,10 @@ TestEnum() TestEnumWithOrdering(); TestEnumWithOrdering(); TestEnumWithOrdering(); + + TestEnumClassWithOrdering(); + TestEnumClassWithOrdering(); + TestEnumClassWithOrdering(); } static void diff --git a/widget/TextEvents.h b/widget/TextEvents.h index 2f296fb543..ef8375e07f 100644 --- a/widget/TextEvents.h +++ b/widget/TextEvents.h @@ -581,7 +581,11 @@ public: // This is the offset where caret would be if user clicked at the refPoint. uint32_t mTentativeCaretOffset; nsString mString; - // Finally, the coordinates is system coordinates. + // mRect is used by eQueryTextRect, eQueryCaretRect, eQueryCharacterAtPoint + // and eQueryEditorRect. The coordinates is system coordinates relative to + // the top level widget of mFocusedWidget. E.g., if a which + // is owned by a window has focused editor, the offset of mRect is relative + // to the owner window, not the . mozilla::LayoutDeviceIntRect mRect; // The return widget has the caret. This is set at all query events. nsIWidget* mFocusedWidget; diff --git a/widget/cocoa/nsMenuBarX.mm b/widget/cocoa/nsMenuBarX.mm index 0c254660d2..c92009d063 100644 --- a/widget/cocoa/nsMenuBarX.mm +++ b/widget/cocoa/nsMenuBarX.mm @@ -507,10 +507,8 @@ char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID) /* static */ void nsMenuBarX::ResetNativeApplicationMenu() { - NSInteger ni; - ni = [sApplicationMenu numberOfItems]; - for (NSInteger c = 0; c < ni; ni++) - [sApplicationMenu removeItemAtIndex:c]; + while ([sApplicationMenu numberOfItems]) + [sApplicationMenu removeItemAtIndex:0]; [sApplicationMenu release]; sApplicationMenu = nil; sApplicationMenuIsFallback = NO; diff --git a/widget/tests/window_composition_text_querycontent.xul b/widget/tests/window_composition_text_querycontent.xul index 6aaaf90ac7..2b61e8cfc5 100644 --- a/widget/tests/window_composition_text_querycontent.xul +++ b/widget/tests/window_composition_text_querycontent.xul @@ -2309,6 +2309,86 @@ function runCharAtPointAtOutsideTest() } } +function runCSSTransformTest() +{ + textarea.focus(); + textarea.value = "some text"; + textarea.selectionStart = textarea.selectionEnd = textarea.value.length; + var editorRect = synthesizeQueryEditorRect(); + if (!checkQueryContentResult(editorRect, + "runCSSTransformTest: editorRect")) { + return; + } + var firstCharRect = synthesizeQueryTextRect(0, 1); + if (!checkQueryContentResult(firstCharRect, + "runCSSTransformTest: firstCharRect")) { + return; + } + var lastCharRect = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length); + if (!checkQueryContentResult(lastCharRect, + "runCSSTransformTest: lastCharRect")) { + return; + } + var caretRect = synthesizeQueryCaretRect(textarea.selectionStart); + if (!checkQueryContentResult(caretRect, + "runCSSTransformTest: caretRect")) { + return; + } + var caretRectBeforeFirstChar = synthesizeQueryCaretRect(0); + if (!checkQueryContentResult(caretRectBeforeFirstChar, + "runCSSTransformTest: caretRectBeforeFirstChar")) { + return; + } + + try { + textarea.style.transform = "translate(10px, 15px)"; + function movedRect(aRect, aX, aY) + { + return { left: aRect.left + aX, top: aRect.top + aY, width: aRect.width, height: aRect.height } + } + + var editorRectTranslated = synthesizeQueryEditorRect(); + if (!checkQueryContentResult(editorRectTranslated, + "runCSSTransformTest: editorRectTranslated") || + !checkRect(editorRectTranslated, movedRect(editorRect, 10, 15), + "runCSSTransformTest: editorRectTranslated")) { + return; + } + var firstCharRectTranslated = synthesizeQueryTextRect(0, 1); + if (!checkQueryContentResult(firstCharRectTranslated, + "runCSSTransformTest: firstCharRectTranslated") || + !checkRect(firstCharRectTranslated, movedRect(firstCharRect, 10, 15), + "runCSSTransformTest: firstCharRectTranslated")) { + return; + } + var lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length); + if (!checkQueryContentResult(lastCharRectTranslated, + "runCSSTransformTest: lastCharRectTranslated") || + !checkRect(lastCharRectTranslated, movedRect(lastCharRect, 10, 15), + "runCSSTransformTest: lastCharRectTranslated")) { + return; + } + var caretRectTranslated = synthesizeQueryCaretRect(textarea.selectionStart); + if (!checkQueryContentResult(caretRectTranslated, + "runCSSTransformTest: caretRectTranslated") || + !checkRect(caretRectTranslated, movedRect(caretRect, 10, 15), + "runCSSTransformTest: caretRectTranslated")) { + return; + } + var caretRectBeforeFirstCharTranslated = synthesizeQueryCaretRect(0); + if (!checkQueryContentResult(caretRectBeforeFirstCharTranslated, + "runCSSTransformTest: caretRectBeforeFirstCharTranslated") || + !checkRect(caretRectBeforeFirstCharTranslated, movedRect(caretRectBeforeFirstChar, 10, 15), + "runCSSTransformTest: caretRectBeforeFirstCharTranslated")) { + return; + } + + // XXX It's too difficult to check the result with scale and rotate... + } finally { + textarea.style.transform = ""; + } +} + function runBug722639Test() { textarea.focus(); @@ -2810,6 +2890,120 @@ function runForceCommitTest() is(iframe3.contentDocument.body.innerHTML, iframe3BodyInnerHTML, "runForceCommitTest: the other editable document has the committed text? #11"); + input.focus(); + input.value = ""; + + synthesizeCompositionChange( + { "composition": + { "string": "\u306E", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + }); + + events = []; + input.value = "set value"; + + is(events.length, 3, + "runForceCommitTest: wrong event count #12"); + is(events[0].type, "text", + "runForceCommitTest: the 1st event must be text #12"); + is(events[1].type, "compositionend", + "runForceCommitTest: the 2nd event must be compositionend #12"); + is(events[2].type, "input", + "runForceCommitTest: the 3rd event must be input #12"); + is(events[1].data, "\u306E", + "runForceCommitTest: compositionend has wrong data #12"); + is(events[0].target, input, + "runForceCommitTest: The 1st event was fired on wrong event target #12"); + is(events[1].target, input, + "runForceCommitTest: The 2nd event was fired on wrong event target #12"); + is(events[2].target, input, + "runForceCommitTest: The 3rd event was fired on wrong event target #12"); + ok(!getEditorIMESupport(input).isComposing, + "runForceCommitTest: the input still has composition #12"); + is(input.value, "set value", + "runForceCommitTest: the input doesn't have the set text #12"); + + textarea.focus(); + textarea.value = ""; + + synthesizeCompositionChange( + { "composition": + { "string": "\u306E", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + }); + + events = []; + textarea.value = "set value"; + + is(events.length, 3, + "runForceCommitTest: wrong event count #13"); + is(events[0].type, "text", + "runForceCommitTest: the 1st event must be text #13"); + is(events[1].type, "compositionend", + "runForceCommitTest: the 2nd event must be compositionend #13"); + is(events[2].type, "input", + "runForceCommitTest: the 3rd event must be input #13"); + is(events[1].data, "\u306E", + "runForceCommitTest: compositionend has wrong data #13"); + is(events[0].target, textarea, + "runForceCommitTest: The 1st event was fired on wrong event target #13"); + is(events[1].target, textarea, + "runForceCommitTest: The 2nd event was fired on wrong event target #13"); + is(events[2].target, textarea, + "runForceCommitTest: The 3rd event was fired on wrong event target #13"); + ok(!getEditorIMESupport(textarea).isComposing, + "runForceCommitTest: the textarea still has composition #13"); + is(textarea.value, "set value", + "runForceCommitTest: the textarea doesn't have the set text #13"); + + input.focus(); + input.value = ""; + + synthesizeCompositionChange( + { "composition": + { "string": "\u306E", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + }); + + events = []; + input.value += " appended value"; + + is(events.length, 3, + "runForceCommitTest: wrong event count #14"); + is(events[0].type, "text", + "runForceCommitTest: the 1st event must be text #14"); + is(events[1].type, "compositionend", + "runForceCommitTest: the 2nd event must be compositionend #14"); + is(events[2].type, "input", + "runForceCommitTest: the 3rd event must be input #14"); + is(events[1].data, "\u306E", + "runForceCommitTest: compositionend has wrong data #14"); + is(events[0].target, input, + "runForceCommitTest: The 1st event was fired on wrong event target #14"); + is(events[1].target, input, + "runForceCommitTest: The 2nd event was fired on wrong event target #14"); + is(events[2].target, input, + "runForceCommitTest: The 3rd event was fired on wrong event target #14"); + ok(!getEditorIMESupport(input).isComposing, + "runForceCommitTest: the input still has composition #14"); + is(input.value, "\u306E appended value", + "runForceCommitTest: the input should have both composed text and appended text #14"); + window.removeEventListener("compositionstart", eventHandler, true); window.removeEventListener("compositionupdate", eventHandler, true); window.removeEventListener("compositionend", eventHandler, true); @@ -2817,6 +3011,191 @@ function runForceCommitTest() window.removeEventListener("text", eventHandler, true); } +function runNestedSettingValue() +{ + var isTesting = false; + var events = []; + function eventHandler(aEvent) + { + events.push(aEvent); + if (isTesting) { + aEvent.target.value += aEvent.type + ", "; + } + } + window.addEventListener("compositionstart", eventHandler, true); + window.addEventListener("compositionupdate", eventHandler, true); + window.addEventListener("compositionend", eventHandler, true); + window.addEventListener("input", eventHandler, true); + window.addEventListener("text", eventHandler, true); + + textarea.focus(); + textarea.value = ""; + + synthesizeCompositionChange( + { "composition": + { "string": "\u306E", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + }); + + events = []; + isTesting = true; + textarea.value = "first setting value, "; + isTesting = false; + + is(events.length, 3, + "runNestedSettingValue: wrong event count #1"); + is(events[0].type, "text", + "runNestedSettingValue: the 1st event must be text #1"); + is(events[1].type, "compositionend", + "runNestedSettingValue: the 2nd event must be compositionend #1"); + is(events[2].type, "input", + "runNestedSettingValue: the 3rd event must be input #1"); + is(events[1].data, "\u306E", + "runNestedSettingValue: compositionend has wrong data #1"); + is(events[0].target, textarea, + "runNestedSettingValue: The 1st event was fired on wrong event target #1"); + is(events[1].target, textarea, + "runNestedSettingValue: The 2nd event was fired on wrong event target #1"); + is(events[2].target, textarea, + "runNestedSettingValue: The 3rd event was fired on wrong event target #1"); + ok(!getEditorIMESupport(textarea).isComposing, + "runNestedSettingValue: the textarea still has composition #1"); + is(textarea.value, "first setting value, text, compositionend, input, ", + "runNestedSettingValue: the textarea should have all string set to value attribute"); + + input.focus(); + input.value = ""; + + synthesizeCompositionChange( + { "composition": + { "string": "\u306E", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + }); + + events = []; + isTesting = true; + input.value = "first setting value, "; + isTesting = false; + + is(events.length, 3, + "runNestedSettingValue: wrong event count #2"); + is(events[0].type, "text", + "runNestedSettingValue: the 1st event must be text #2"); + is(events[1].type, "compositionend", + "runNestedSettingValue: the 2nd event must be compositionend #2"); + is(events[2].type, "input", + "runNestedSettingValue: the 3rd event must be input #2"); + is(events[1].data, "\u306E", + "runNestedSettingValue: compositionend has wrong data #2"); + is(events[0].target, input, + "runNestedSettingValue: The 1st event was fired on wrong event target #2"); + is(events[1].target, input, + "runNestedSettingValue: The 2nd event was fired on wrong event target #2"); + is(events[2].target, input, + "runNestedSettingValue: The 3rd event was fired on wrong event target #2"); + ok(!getEditorIMESupport(input).isComposing, + "runNestedSettingValue: the input still has composition #2"); + is(textarea.value, "first setting value, text, compositionend, input, ", + "runNestedSettingValue: the input should have all string set to value attribute #2"); + + textarea.focus(); + textarea.value = ""; + + synthesizeCompositionChange( + { "composition": + { "string": "\u306E", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + }); + + events = []; + isTesting = true; + textarea.setRangeText("first setting value, "); + isTesting = false; + + is(events.length, 3, + "runNestedSettingValue: wrong event count #3"); + is(events[0].type, "text", + "runNestedSettingValue: the 1st event must be text #3"); + is(events[1].type, "compositionend", + "runNestedSettingValue: the 2nd event must be compositionend #3"); + is(events[2].type, "input", + "runNestedSettingValue: the 3rd event must be input #3"); + is(events[1].data, "\u306E", + "runNestedSettingValue: compositionend has wrong data #3"); + is(events[0].target, textarea, + "runNestedSettingValue: The 1st event was fired on wrong event target #3"); + is(events[1].target, textarea, + "runNestedSettingValue: The 2nd event was fired on wrong event target #3"); + is(events[2].target, textarea, + "runNestedSettingValue: The 3rd event was fired on wrong event target #3"); + ok(!getEditorIMESupport(textarea).isComposing, + "runNestedSettingValue: the textarea still has composition #3"); + is(textarea.value, "\u306Efirst setting value, text, compositionend, input, ", + "runNestedSettingValue: the textarea should have appended by setRangeText() and all string set to value attribute #3"); + + input.focus(); + input.value = ""; + + synthesizeCompositionChange( + { "composition": + { "string": "\u306E", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 } + }); + + events = []; + isTesting = true; + input.setRangeText("first setting value, "); + isTesting = false; + + is(events.length, 3, + "runNestedSettingValue: wrong event count #4"); + is(events[0].type, "text", + "runNestedSettingValue: the 1st event must be text #4"); + is(events[1].type, "compositionend", + "runNestedSettingValue: the 2nd event must be compositionend #4"); + is(events[2].type, "input", + "runNestedSettingValue: the 3rd event must be input #4"); + is(events[1].data, "\u306E", + "runNestedSettingValue: compositionend has wrong data #4"); + is(events[0].target, input, + "runNestedSettingValue: The 1st event was fired on wrong event target #4"); + is(events[1].target, input, + "runNestedSettingValue: The 2nd event was fired on wrong event target #4"); + is(events[2].target, input, + "runNestedSettingValue: The 3rd event was fired on wrong event target #4"); + ok(!getEditorIMESupport(input).isComposing, + "runNestedSettingValue: the input still has composition #4"); + is(textarea.value, "\u306Efirst setting value, text, compositionend, input, ", + "runNestedSettingValue: the input should have all string appended by setRangeText() and set to value attribute #4"); + + window.removeEventListener("compositionstart", eventHandler, true); + window.removeEventListener("compositionupdate", eventHandler, true); + window.removeEventListener("compositionend", eventHandler, true); + window.removeEventListener("input", eventHandler, true); + window.removeEventListener("text", eventHandler, true); + +} + function runAsyncForceCommitTest(aNextTest) { var events; @@ -4223,8 +4602,10 @@ function runTest() runCompositionEventTest(); runCharAtPointTest(textarea, "textarea in the document"); runCharAtPointAtOutsideTest(); + runCSSTransformTest(); runBug722639Test(); runForceCommitTest(); + runNestedSettingValue(); runBug811755Test(); runIsComposingTest(); runRedundantChangeTest();