зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound, a=merge
This commit is contained in:
Коммит
6c4583e52f
|
@ -1525,6 +1525,10 @@ const DownloadsFooter = {
|
|||
} else {
|
||||
this._footerNode.removeAttribute("showingsummary");
|
||||
}
|
||||
if (!aValue && this._showingSummary) {
|
||||
// Make sure the panel's height shrinks when the summary is hidden.
|
||||
DownloadsBlockedSubview.view.setHeightToFit();
|
||||
}
|
||||
this._showingSummary = aValue;
|
||||
}
|
||||
return aValue;
|
||||
|
|
|
@ -10,8 +10,15 @@ const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
|
|||
.userAgent;
|
||||
const NOKIA_UA = "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; " +
|
||||
"Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)";
|
||||
const Types = require("devtools/client/responsive.html/types");
|
||||
|
||||
addRDMTask(TEST_URL, function* ({ ui, manager }) {
|
||||
let { store } = ui.toolWindow;
|
||||
|
||||
// Wait until the viewport has been added and the device list has been loaded
|
||||
yield waitUntilState(store, state => state.viewports.length == 1
|
||||
&& state.devices.listState == Types.deviceListState.LOADED);
|
||||
|
||||
// Test defaults
|
||||
testViewportDimensions(ui, 320, 480);
|
||||
yield testUserAgent(ui, DEFAULT_UA);
|
||||
|
|
|
@ -1056,7 +1056,11 @@ AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aA
|
|||
// Only foreground window can request audio focus, but it would still own the
|
||||
// audio focus even it goes to background. Audio focus would be abandoned
|
||||
// only when other foreground window starts audio competing.
|
||||
mOwningAudioFocus = !(aAgent->Window()->IsBackground());
|
||||
// One exception is if the pref "media.block-autoplay-until-in-foreground"
|
||||
// is on and the background page is the non-visited before. Because the media
|
||||
// in that page would be blocked until the page is going to foreground.
|
||||
mOwningAudioFocus = (!(aAgent->Window()->IsBackground()) ||
|
||||
aAgent->Window()->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK) ;
|
||||
|
||||
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
|
||||
("AudioChannelWindow, RequestAudioFocus, this = %p, "
|
||||
|
|
|
@ -1498,6 +1498,8 @@ nsDocument::nsDocument(const char* aContentType)
|
|||
// Add the base queue sentinel to the processing stack.
|
||||
sProcessingStack->AppendElement((CustomElementData*) nullptr);
|
||||
}
|
||||
|
||||
mEverInForeground = false;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -12596,6 +12598,10 @@ nsDocument::UpdateVisibilityState()
|
|||
|
||||
EnumerateActivityObservers(NotifyActivityChanged, nullptr);
|
||||
}
|
||||
|
||||
if (mVisibilityState == dom::VisibilityState::Visible) {
|
||||
MaybeActiveMediaComponents();
|
||||
}
|
||||
}
|
||||
|
||||
VisibilityState
|
||||
|
@ -12631,6 +12637,23 @@ nsDocument::PostVisibilityUpdateEvent()
|
|||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::MaybeActiveMediaComponents()
|
||||
{
|
||||
if (mEverInForeground) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEverInForeground = true;
|
||||
if (GetWindow()->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK) {
|
||||
GetWindow()->SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocument::GetMozHidden(bool* aHidden)
|
||||
{
|
||||
|
|
|
@ -1209,6 +1209,10 @@ public:
|
|||
// Posts an event to call UpdateVisibilityState
|
||||
virtual void PostVisibilityUpdateEvent() override;
|
||||
|
||||
// Since we wouldn't automatically play media from non-visited page, we need
|
||||
// to notify window when the page was first visited.
|
||||
void MaybeActiveMediaComponents();
|
||||
|
||||
virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const override;
|
||||
// DocAddSizeOfIncludingThis is inherited from nsIDocument.
|
||||
|
||||
|
|
|
@ -618,7 +618,8 @@ nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow)
|
|||
mInnerObjectsFreed(false),
|
||||
mIsModalContentWindow(false),
|
||||
mIsActive(false), mIsBackground(false),
|
||||
mMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED),
|
||||
mMediaSuspend(Preferences::GetBool("media.block-autoplay-until-in-foreground", true) ?
|
||||
nsISuspendedTypes::SUSPENDED_BLOCK : nsISuspendedTypes::NONE_SUSPENDED),
|
||||
mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false),
|
||||
mDesktopModeViewport(false), mInnerWindow(nullptr),
|
||||
mOuterWindow(aOuterWindow),
|
||||
|
|
|
@ -3069,6 +3069,9 @@ protected:
|
|||
// Do we currently have an event posted to call FlushUserFontSet?
|
||||
bool mPostedFlushUserFontSet : 1;
|
||||
|
||||
// True is document has ever been in a foreground window.
|
||||
bool mEverInForeground : 1;
|
||||
|
||||
enum Type {
|
||||
eUnknown, // should never be used
|
||||
eHTML,
|
||||
|
|
|
@ -74,7 +74,7 @@ function setupTestFrame() {
|
|||
|
||||
ac.onactivestatechanged = () => {
|
||||
ac.onactivestatechanged = null;
|
||||
error("Should not receive onactivestatechanged!");
|
||||
ok(true, "Should receive onactivestatechanged!");
|
||||
};
|
||||
|
||||
continueTest();
|
||||
|
|
|
@ -1020,6 +1020,9 @@ void HTMLMediaElement::AbortExistingLoads()
|
|||
if (mTextTrackManager) {
|
||||
mTextTrackManager->NotifyReset();
|
||||
}
|
||||
|
||||
mEventDeliveryPaused = false;
|
||||
mPendingEvents.Clear();
|
||||
}
|
||||
|
||||
void HTMLMediaElement::NoSupportedMediaSourceError()
|
||||
|
@ -1396,7 +1399,7 @@ void HTMLMediaElement::NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream)
|
|||
if (videoHasChanged) {
|
||||
// We are a video element and HasVideo() changed so update the screen
|
||||
// wakelock
|
||||
NotifyOwnerDocumentActivityChangedInternal();
|
||||
NotifyOwnerDocumentActivityChanged();
|
||||
}
|
||||
|
||||
mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
|
||||
|
@ -2882,7 +2885,6 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
|||
mPlayingThroughTheAudioChannelBeforeSeek(false),
|
||||
mPausedForInactiveDocumentOrChannel(false),
|
||||
mEventDeliveryPaused(false),
|
||||
mWaitingFired(false),
|
||||
mIsRunningLoadMethod(false),
|
||||
mIsDoingExplicitLoad(false),
|
||||
mIsLoadingFromSourceChildren(false),
|
||||
|
@ -2904,7 +2906,6 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
|||
mAudioChannelVolume(1.0),
|
||||
mPlayingThroughTheAudioChannel(false),
|
||||
mDisableVideo(false),
|
||||
mPlayBlockedBecauseHidden(false),
|
||||
mElementInTreeState(ELEMENT_NOT_INTREE),
|
||||
mHasUserInteraction(false),
|
||||
mFirstFrameLoaded(false),
|
||||
|
@ -2922,7 +2923,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
|||
mPaused.SetOuter(this);
|
||||
|
||||
RegisterActivityObserver();
|
||||
NotifyOwnerDocumentActivityChangedInternal();
|
||||
NotifyOwnerDocumentActivityChanged();
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mWatchManager.Watch(mDownloadSuspendedByCache, &HTMLMediaElement::UpdateReadyStateInternal);
|
||||
|
@ -3011,14 +3012,14 @@ HTMLMediaElement::NotifyXPCOMShutdown()
|
|||
void
|
||||
HTMLMediaElement::Play(ErrorResult& aRv)
|
||||
{
|
||||
nsresult rv = PlayInternal(nsContentUtils::IsCallerChrome());
|
||||
nsresult rv = PlayInternal();
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
HTMLMediaElement::PlayInternal(bool aCallerIsChrome)
|
||||
HTMLMediaElement::PlayInternal()
|
||||
{
|
||||
if (!IsAllowedToPlay()) {
|
||||
return NS_OK;
|
||||
|
@ -3037,14 +3038,6 @@ HTMLMediaElement::PlayInternal(bool aCallerIsChrome)
|
|||
ResumeLoad(PRELOAD_ENOUGH);
|
||||
}
|
||||
|
||||
if (Preferences::GetBool("media.block-play-until-visible", false) &&
|
||||
!aCallerIsChrome &&
|
||||
OwnerDoc()->Hidden()) {
|
||||
LOG(LogLevel::Debug, ("%p Blocked playback because owner hidden.", this));
|
||||
mPlayBlockedBecauseHidden = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Even if we just did Load() or ResumeLoad(), we could already have a decoder
|
||||
// here if we managed to clone an existing decoder.
|
||||
if (mDecoder) {
|
||||
|
@ -3063,9 +3056,28 @@ HTMLMediaElement::PlayInternal(bool aCallerIsChrome)
|
|||
mCurrentPlayRangeStart = CurrentTime();
|
||||
}
|
||||
|
||||
bool oldPaused = mPaused;
|
||||
mPaused = false;
|
||||
mAutoplaying = false;
|
||||
SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
|
||||
|
||||
// We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
|
||||
// and our preload status.
|
||||
AddRemoveSelfReference();
|
||||
UpdatePreloadAction();
|
||||
UpdateSrcMediaStreamPlaying();
|
||||
UpdateAudioChannelPlayingState();
|
||||
|
||||
// The check here is to handle the case that the media element starts playing
|
||||
// after it loaded fail. eg. preload the data before playing.
|
||||
OpenUnsupportedMediaWithExtenalAppIfNeeded();
|
||||
|
||||
// We should check audio channel playing state before dispatching any events,
|
||||
// because we don't want to dispatch events for blocked media. For blocked
|
||||
// media, the event would be pending until media is resumed.
|
||||
// TODO: If the playback has ended, then the user agent must set
|
||||
// seek to the effective start.
|
||||
if (mPaused) {
|
||||
if (oldPaused) {
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
|
||||
switch (mReadyState) {
|
||||
case nsIDOMHTMLMediaElement::HAVE_NOTHING:
|
||||
|
@ -3084,27 +3096,12 @@ HTMLMediaElement::PlayInternal(bool aCallerIsChrome)
|
|||
}
|
||||
}
|
||||
|
||||
mPaused = false;
|
||||
mAutoplaying = false;
|
||||
SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
|
||||
|
||||
// We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
|
||||
// and our preload status.
|
||||
AddRemoveSelfReference();
|
||||
UpdatePreloadAction();
|
||||
UpdateSrcMediaStreamPlaying();
|
||||
UpdateAudioChannelPlayingState();
|
||||
|
||||
// The check here is to handle the case that the media element starts playing
|
||||
// after it loaded fail. eg. preload the data before playing.
|
||||
OpenUnsupportedMediaWithExtenalAppIfNeeded();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP HTMLMediaElement::Play()
|
||||
{
|
||||
return PlayInternal(/* aCallerIsChrome = */ true);
|
||||
return PlayInternal();
|
||||
}
|
||||
|
||||
HTMLMediaElement::WakeLockBoolWrapper&
|
||||
|
@ -3796,10 +3793,6 @@ nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
|
|||
// Force a same-origin check before allowing events for this media resource.
|
||||
mMediaSecurityVerified = false;
|
||||
|
||||
// The new stream has not been suspended by us.
|
||||
mPausedForInactiveDocumentOrChannel = false;
|
||||
mEventDeliveryPaused = false;
|
||||
mPendingEvents.Clear();
|
||||
// Set mDecoder now so if methods like GetCurrentSrc get called between
|
||||
// here and Load(), they work.
|
||||
SetDecoder(aDecoder);
|
||||
|
@ -3865,7 +3858,7 @@ nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
|
|||
|
||||
// We may want to suspend the new stream now.
|
||||
// This will also do an AddRemoveSelfReference.
|
||||
NotifyOwnerDocumentActivityChangedInternal();
|
||||
NotifyOwnerDocumentActivityChanged();
|
||||
UpdateAudioChannelPlayingState();
|
||||
|
||||
if (!mPaused) {
|
||||
|
@ -4308,7 +4301,8 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
|
|||
// get dispatched.
|
||||
AutoNotifyAudioChannelAgent autoNotify(this);
|
||||
|
||||
mMediaInfo = *aInfo;
|
||||
SetMediaInfo(*aInfo);
|
||||
|
||||
mIsEncrypted = aInfo->IsEncrypted()
|
||||
#ifdef MOZ_EME
|
||||
|| mPendingEncryptedInitData.IsEncrypted()
|
||||
|
@ -4350,7 +4344,7 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
|
|||
|
||||
if (IsVideo() && aInfo->HasVideo()) {
|
||||
// We are a video element playing video so update the screen wakelock
|
||||
NotifyOwnerDocumentActivityChangedInternal();
|
||||
NotifyOwnerDocumentActivityChanged();
|
||||
}
|
||||
|
||||
if (mDefaultPlaybackStartPosition != 0.0) {
|
||||
|
@ -4775,11 +4769,6 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
|||
if (mFirstFrameLoaded) {
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
|
||||
}
|
||||
if (!mWaitingFired && nextFrameStatus == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING) {
|
||||
FireTimeUpdate(false);
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
|
||||
mWaitingFired = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4838,9 +4827,24 @@ void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
|||
UpdateAudioChannelPlayingState();
|
||||
|
||||
// Handle raising of "waiting" event during seek (see 4.8.10.9)
|
||||
// or
|
||||
// 4.8.12.7 Ready states:
|
||||
// "If the previous ready state was HAVE_FUTURE_DATA or more, and the new
|
||||
// ready state is HAVE_CURRENT_DATA or less
|
||||
// If the media element was potentially playing before its readyState
|
||||
// attribute changed to a value lower than HAVE_FUTURE_DATA, and the element
|
||||
// has not ended playback, and playback has not stopped due to errors,
|
||||
// paused for user interaction, or paused for in-band content, the user agent
|
||||
// must queue a task to fire a simple event named timeupdate at the element,
|
||||
// and queue a task to fire a simple event named waiting at the element."
|
||||
if (mPlayingBeforeSeek &&
|
||||
mReadyState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
|
||||
} else if (oldState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
mReadyState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
!Paused() && !Ended() && !mError) {
|
||||
FireTimeUpdate(false);
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
|
||||
}
|
||||
|
||||
if (oldState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
|
||||
|
@ -4850,10 +4854,6 @@ void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
|||
mLoadedDataFired = true;
|
||||
}
|
||||
|
||||
if (mReadyState == nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
|
||||
mWaitingFired = false;
|
||||
}
|
||||
|
||||
if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
|
||||
|
@ -4956,13 +4956,6 @@ void HTMLMediaElement::CheckAutoplayDataReady()
|
|||
return;
|
||||
}
|
||||
|
||||
if (Preferences::GetBool("media.block-play-until-visible", false) &&
|
||||
OwnerDoc()->Hidden()) {
|
||||
LOG(LogLevel::Debug, ("%p Blocked autoplay because owner hidden.", this));
|
||||
mPlayBlockedBecauseHidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
mPaused = false;
|
||||
// We changed mPaused which can affect AddRemoveSelfReference
|
||||
AddRemoveSelfReference();
|
||||
|
@ -4974,12 +4967,15 @@ void HTMLMediaElement::CheckAutoplayDataReady()
|
|||
if (mCurrentPlayRangeStart == -1.0) {
|
||||
mCurrentPlayRangeStart = CurrentTime();
|
||||
}
|
||||
mDecoder->Play();
|
||||
if (!ShouldElementBePaused()) {
|
||||
mDecoder->Play();
|
||||
}
|
||||
} else if (mSrcStream) {
|
||||
SetPlayedOrSeeked(true);
|
||||
}
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
|
||||
|
||||
// For blocked media, the event would be pending until it is resumed.
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
|
||||
}
|
||||
|
||||
bool HTMLMediaElement::IsActive() const
|
||||
|
@ -5303,24 +5299,6 @@ bool HTMLMediaElement::IsBeingDestroyed()
|
|||
}
|
||||
|
||||
void HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
|
||||
{
|
||||
bool pauseElement = NotifyOwnerDocumentActivityChangedInternal();
|
||||
if (pauseElement && mAudioChannelAgent) {
|
||||
// If the element is being paused since we are navigating away from the
|
||||
// document, notify the audio channel agent.
|
||||
// Be careful to ignore this event during a docshell frame swap.
|
||||
auto docShell = static_cast<nsDocShell*>(OwnerDoc()->GetDocShell());
|
||||
if (!docShell) {
|
||||
return;
|
||||
}
|
||||
if (!docShell->InFrameSwap()) {
|
||||
NotifyAudioChannelAgent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLMediaElement::NotifyOwnerDocumentActivityChangedInternal()
|
||||
{
|
||||
bool visible = !IsHidden();
|
||||
if (visible) {
|
||||
|
@ -5335,20 +5313,10 @@ HTMLMediaElement::NotifyOwnerDocumentActivityChangedInternal()
|
|||
mDecoder->NotifyOwnerActivityChanged(visible);
|
||||
}
|
||||
|
||||
bool pauseElement = !IsActive();
|
||||
bool pauseElement = ShouldElementBePaused();
|
||||
SuspendOrResumeElement(pauseElement, !IsActive());
|
||||
|
||||
if (!mPausedForInactiveDocumentOrChannel &&
|
||||
mPlayBlockedBecauseHidden &&
|
||||
!OwnerDoc()->Hidden()) {
|
||||
LOG(LogLevel::Debug, ("%p Resuming playback now that owner doc is visble.", this));
|
||||
mPlayBlockedBecauseHidden = false;
|
||||
Play();
|
||||
}
|
||||
|
||||
AddRemoveSelfReference();
|
||||
|
||||
return pauseElement;
|
||||
}
|
||||
|
||||
void HTMLMediaElement::AddRemoveSelfReference()
|
||||
|
@ -5533,7 +5501,7 @@ HTMLMediaElement::CopyInnerTo(Element* aDest)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (aDest->OwnerDoc()->IsStaticDocument()) {
|
||||
HTMLMediaElement* dest = static_cast<HTMLMediaElement*>(aDest);
|
||||
dest->mMediaInfo = mMediaInfo;
|
||||
dest->SetMediaInfo(mMediaInfo);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
@ -5789,11 +5757,6 @@ HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
|
|||
return false;
|
||||
}
|
||||
|
||||
// If this element doesn't have any audio tracks.
|
||||
if (!HasAudio()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We should consider any bfcached page or inactive document as non-playing.
|
||||
if (!IsActive()) {
|
||||
return false;
|
||||
|
@ -5804,11 +5767,6 @@ HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
|
|||
return true;
|
||||
}
|
||||
|
||||
// If we are actually playing...
|
||||
if (IsCurrentlyPlaying()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we are seeking, we consider it as playing
|
||||
if (mPlayingThroughTheAudioChannelBeforeSeek) {
|
||||
return true;
|
||||
|
@ -5819,7 +5777,7 @@ HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
|
|||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -5952,7 +5910,7 @@ HTMLMediaElement::ResumeFromAudioChannelPaused(SuspendTypes aSuspend)
|
|||
mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE);
|
||||
|
||||
SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
|
||||
nsresult rv = PlayInternal(nsContentUtils::IsCallerChrome());
|
||||
nsresult rv = PlayInternal();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
|
@ -5965,7 +5923,7 @@ HTMLMediaElement::ResumeFromAudioChannelBlocked()
|
|||
MOZ_ASSERT(mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
|
||||
|
||||
SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
|
||||
mPaused.SetCanPlay(true);
|
||||
mPaused = false;
|
||||
SuspendOrResumeElement(false /* resume */, false);
|
||||
}
|
||||
|
||||
|
@ -5989,8 +5947,8 @@ HTMLMediaElement::BlockByAudioChannel()
|
|||
}
|
||||
|
||||
SetAudioChannelSuspended(nsISuspendedTypes::SUSPENDED_BLOCK);
|
||||
SuspendOrResumeElement(true /* suspend */, false);
|
||||
mPaused.SetCanPlay(false);
|
||||
mPaused = true;
|
||||
SuspendOrResumeElement(true /* suspend */, true /* pending event */);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -6280,48 +6238,11 @@ HTMLMediaElement::CannotDecryptWaitingForKey()
|
|||
NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged(bool aCapture)
|
||||
{
|
||||
MOZ_ASSERT(mAudioChannelAgent);
|
||||
MOZ_ASSERT(HasAudio());
|
||||
|
||||
if (!OwnerDoc()->GetInnerWindow()) {
|
||||
return NS_OK;
|
||||
if (mAudioCapturedByWindow != aCapture) {
|
||||
mAudioCapturedByWindow = aCapture;
|
||||
AudioCaptureStreamChangeIfNeeded();
|
||||
}
|
||||
|
||||
if (aCapture != mAudioCapturedByWindow) {
|
||||
if (aCapture) {
|
||||
mAudioCapturedByWindow = true;
|
||||
nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
|
||||
uint64_t id = window->WindowID();
|
||||
MediaStreamGraph* msg =
|
||||
MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
|
||||
mAudioChannel);
|
||||
|
||||
if (GetSrcMediaStream()) {
|
||||
mCaptureStreamPort = msg->ConnectToCaptureStream(id, GetSrcMediaStream());
|
||||
} else {
|
||||
RefPtr<DOMMediaStream> stream = CaptureStreamInternal(false, msg);
|
||||
mCaptureStreamPort = msg->ConnectToCaptureStream(id, stream->GetPlaybackStream());
|
||||
}
|
||||
} else {
|
||||
mAudioCapturedByWindow = false;
|
||||
if (mDecoder) {
|
||||
ProcessedMediaStream* ps =
|
||||
mCaptureStreamPort->GetSource()->AsProcessedStream();
|
||||
MOZ_ASSERT(ps);
|
||||
|
||||
for (uint32_t i = 0; i < mOutputStreams.Length(); i++) {
|
||||
if (mOutputStreams[i].mStream->GetPlaybackStream() == ps) {
|
||||
mOutputStreams.RemoveElementAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mDecoder->RemoveOutputStream(ps);
|
||||
}
|
||||
mCaptureStreamPort->Destroy();
|
||||
mCaptureStreamPort = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -6588,6 +6509,69 @@ HTMLMediaElement::OnVisibilityChange(Visibility aOldVisibility,
|
|||
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLMediaElement::ShouldElementBePaused()
|
||||
{
|
||||
// The media in the non-visited page would be blocked.
|
||||
if (mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bfcached page or inactive document.
|
||||
if (!IsActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLMediaElement::SetMediaInfo(const MediaInfo aInfo)
|
||||
{
|
||||
mMediaInfo = aInfo;
|
||||
AudioCaptureStreamChangeIfNeeded();
|
||||
}
|
||||
|
||||
void
|
||||
HTMLMediaElement::AudioCaptureStreamChangeIfNeeded()
|
||||
{
|
||||
// TODO : only capture media element with audio track, see bug1298777.
|
||||
if (mAudioCapturedByWindow && !mCaptureStreamPort) {
|
||||
nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
|
||||
if (!OwnerDoc()->GetInnerWindow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t id = window->WindowID();
|
||||
MediaStreamGraph* msg =
|
||||
MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
|
||||
mAudioChannel);
|
||||
|
||||
if (GetSrcMediaStream()) {
|
||||
mCaptureStreamPort = msg->ConnectToCaptureStream(id, GetSrcMediaStream());
|
||||
} else {
|
||||
RefPtr<DOMMediaStream> stream = CaptureStreamInternal(false, msg);
|
||||
mCaptureStreamPort = msg->ConnectToCaptureStream(id, stream->GetPlaybackStream());
|
||||
}
|
||||
} else if (!mAudioCapturedByWindow && mCaptureStreamPort) {
|
||||
if (mDecoder) {
|
||||
ProcessedMediaStream* ps =
|
||||
mCaptureStreamPort->GetSource()->AsProcessedStream();
|
||||
MOZ_ASSERT(ps);
|
||||
|
||||
for (uint32_t i = 0; i < mOutputStreams.Length(); i++) {
|
||||
if (mOutputStreams[i].mStream->GetPlaybackStream() == ps) {
|
||||
mOutputStreams.RemoveElementAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mDecoder->RemoveOutputStream(ps);
|
||||
}
|
||||
mCaptureStreamPort->Destroy();
|
||||
mCaptureStreamPort = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLMediaElement::NotifyCueDisplayStatesChanged()
|
||||
{
|
||||
|
|
|
@ -161,12 +161,7 @@ public:
|
|||
* Call this to reevaluate whether we should start/stop due to our owner
|
||||
* document being active, inactive, visible or hidden.
|
||||
*/
|
||||
void NotifyOwnerDocumentActivityChanged();
|
||||
|
||||
// This method does the work necessary for the
|
||||
// NotifyOwnerDocumentActivityChanged() notification. It returns true if the
|
||||
// media element was paused as a result.
|
||||
virtual bool NotifyOwnerDocumentActivityChangedInternal();
|
||||
virtual void NotifyOwnerDocumentActivityChanged();
|
||||
|
||||
// Called by the video decoder object, on the main thread,
|
||||
// when it has read the metadata containing video dimensions,
|
||||
|
@ -744,6 +739,8 @@ public:
|
|||
bool ComputedMuted() const;
|
||||
nsSuspendedTypes ComputedSuspended() const;
|
||||
|
||||
void SetMediaInfo(const MediaInfo aInfo);
|
||||
|
||||
protected:
|
||||
virtual ~HTMLMediaElement();
|
||||
|
||||
|
@ -805,7 +802,7 @@ protected:
|
|||
nsTArray<Pair<nsString, RefPtr<MediaInputPort>>> mTrackPorts;
|
||||
};
|
||||
|
||||
nsresult PlayInternal(bool aCallerIsChrome);
|
||||
nsresult PlayInternal();
|
||||
|
||||
/** Use this method to change the mReadyState member, so required
|
||||
* events can be fired.
|
||||
|
@ -1221,6 +1218,12 @@ protected:
|
|||
// channel agent is ready to be used.
|
||||
bool MaybeCreateAudioChannelAgent();
|
||||
|
||||
// Determine if the element should be paused because of suspend conditions.
|
||||
bool ShouldElementBePaused();
|
||||
|
||||
// Create or destroy the captured stream depend on mAudioCapturedByWindow.
|
||||
void AudioCaptureStreamChangeIfNeeded();
|
||||
|
||||
/**
|
||||
* We have different kinds of suspended cases,
|
||||
* - SUSPENDED_PAUSE
|
||||
|
@ -1516,10 +1519,6 @@ protected:
|
|||
// True iff event delivery is suspended (mPausedForInactiveDocumentOrChannel must also be true).
|
||||
bool mEventDeliveryPaused;
|
||||
|
||||
// True if we've reported a "waiting" event since the last
|
||||
// readyState change to HAVE_CURRENT_DATA.
|
||||
bool mWaitingFired;
|
||||
|
||||
// True if we're running the "load()" method.
|
||||
bool mIsRunningLoadMethod;
|
||||
|
||||
|
@ -1615,11 +1614,6 @@ protected:
|
|||
// playback.
|
||||
bool mDisableVideo;
|
||||
|
||||
// True if we blocked either a play() call or autoplay because the
|
||||
// media's owner doc was not visible. Only enforced when the pref
|
||||
// media.block-play-until-visible=true.
|
||||
bool mPlayBlockedBecauseHidden;
|
||||
|
||||
// An agent used to join audio channel service.
|
||||
nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
|
||||
|
||||
|
|
|
@ -221,12 +221,11 @@ HTMLVideoElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|||
return HTMLVideoElementBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLVideoElement::NotifyOwnerDocumentActivityChangedInternal()
|
||||
void
|
||||
HTMLVideoElement::NotifyOwnerDocumentActivityChanged()
|
||||
{
|
||||
bool pauseElement = HTMLMediaElement::NotifyOwnerDocumentActivityChangedInternal();
|
||||
HTMLMediaElement::NotifyOwnerDocumentActivityChanged();
|
||||
UpdateScreenWakeLock();
|
||||
return pauseElement;
|
||||
}
|
||||
|
||||
FrameStatistics*
|
||||
|
|
|
@ -128,7 +128,7 @@ public:
|
|||
|
||||
void SetMozUseScreenWakeLock(bool aValue);
|
||||
|
||||
bool NotifyOwnerDocumentActivityChangedInternal() override;
|
||||
void NotifyOwnerDocumentActivityChanged() override;
|
||||
|
||||
// Gives access to the decoder's frame statistics, if present.
|
||||
FrameStatistics* GetFrameStatistics();
|
||||
|
|
|
@ -30,7 +30,7 @@ function load1Soon() {
|
|||
|
||||
function load1Done() {
|
||||
// Check the title
|
||||
var title = ctx.tab1Browser.contentWindow.document.title;
|
||||
var title = ctx.tab1Browser.contentTitle;
|
||||
checkTitle(title);
|
||||
|
||||
// Try loading the same image in a new tab to make sure things work in
|
||||
|
@ -49,7 +49,7 @@ function load2Soon() {
|
|||
|
||||
function load2Done() {
|
||||
// Check the title
|
||||
var title = ctx.tab2Browser.contentWindow.document.title;
|
||||
var title = ctx.tab2Browser.contentTitle;
|
||||
checkTitle(title);
|
||||
|
||||
// Clean up
|
||||
|
|
|
@ -1303,14 +1303,6 @@ StartMacOSContentSandbox()
|
|||
MOZ_CRASH("Failed to get NS_OS_TEMP_DIR path");
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIFile> profileDir;
|
||||
ContentChild::GetSingleton()->GetProfileDir(getter_AddRefs(profileDir));
|
||||
nsCString profileDirPath;
|
||||
rv = profileDir->GetNativePath(profileDirPath);
|
||||
if (NS_FAILED(rv)) {
|
||||
MOZ_CRASH("Failed to get profile path");
|
||||
}
|
||||
|
||||
MacSandboxInfo info;
|
||||
info.type = MacSandboxType_Content;
|
||||
info.level = info.level = sandboxLevel;
|
||||
|
@ -1318,7 +1310,6 @@ StartMacOSContentSandbox()
|
|||
info.appBinaryPath.assign(appBinaryPath.get());
|
||||
info.appDir.assign(appDir.get());
|
||||
info.appTempDir.assign(tempDirPath.get());
|
||||
info.profileDir.assign(profileDirPath.get());
|
||||
|
||||
std::string err;
|
||||
if (!mozilla::StartMacSandbox(info, err)) {
|
||||
|
|
|
@ -21,9 +21,6 @@
|
|||
#include "nsWeakPtr.h"
|
||||
#include "nsIWindowProvider.h"
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
#include "nsIFile.h"
|
||||
#endif
|
||||
|
||||
struct ChromePackage;
|
||||
class nsIObserver;
|
||||
|
@ -117,19 +114,6 @@ public:
|
|||
|
||||
void GetProcessName(nsACString& aName) const;
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
void GetProfileDir(nsIFile** aProfileDir) const
|
||||
{
|
||||
*aProfileDir = mProfileDir;
|
||||
NS_IF_ADDREF(*aProfileDir);
|
||||
}
|
||||
|
||||
void SetProfileDir(nsIFile* aProfileDir)
|
||||
{
|
||||
mProfileDir = aProfileDir;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool IsAlive() const;
|
||||
|
||||
static void AppendProcessId(nsACString& aName);
|
||||
|
@ -697,10 +681,6 @@ private:
|
|||
nsCOMPtr<nsIDomainPolicy> mPolicy;
|
||||
nsCOMPtr<nsITimer> mForceKillTimer;
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
nsCOMPtr<nsIFile> mProfileDir;
|
||||
#endif
|
||||
|
||||
// Hashtable to keep track of the pending GetFilesHelper objects.
|
||||
// This GetFilesHelperChild objects are removed when RecvGetFilesResponse is
|
||||
// received.
|
||||
|
|
|
@ -114,21 +114,6 @@ ContentProcess::SetAppDir(const nsACString& aPath)
|
|||
mXREEmbed.SetAppDir(aPath);
|
||||
}
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
void
|
||||
ContentProcess::SetProfile(const nsACString& aProfile)
|
||||
{
|
||||
bool flag;
|
||||
nsresult rv =
|
||||
XRE_GetFileFromPath(aProfile.BeginReading(), getter_AddRefs(mProfileDir));
|
||||
if (NS_FAILED(rv) ||
|
||||
NS_FAILED(mProfileDir->Exists(&flag)) || !flag) {
|
||||
NS_WARNING("Invalid profile directory passed to content process.");
|
||||
mProfileDir = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool
|
||||
ContentProcess::Init()
|
||||
{
|
||||
|
@ -139,10 +124,6 @@ ContentProcess::Init()
|
|||
mContent.InitXPCOM();
|
||||
mContent.InitGraphicsDeviceData();
|
||||
|
||||
#if (defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
|
||||
mContent.SetProfileDir(mProfileDir);
|
||||
#endif
|
||||
|
||||
#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
|
||||
SetUpSandboxEnvironment();
|
||||
#endif
|
||||
|
|
|
@ -39,18 +39,9 @@ public:
|
|||
|
||||
void SetAppDir(const nsACString& aPath);
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
void SetProfile(const nsACString& aProfile);
|
||||
#endif
|
||||
|
||||
private:
|
||||
ContentChild mContent;
|
||||
mozilla::ipc::ScopedXREEmbed mXREEmbed;
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
nsCOMPtr<nsIFile> mProfileDir;
|
||||
#endif
|
||||
|
||||
#if defined(XP_WIN)
|
||||
// This object initializes and configures COM.
|
||||
mozilla::mscom::MainThreadRuntime mCOMRuntime;
|
||||
|
|
|
@ -61,7 +61,6 @@ public:
|
|||
virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0;
|
||||
|
||||
virtual AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() { return nullptr; };
|
||||
virtual AbstractCanonical<Maybe<double>>* CanonicalExplicitDuration() { return nullptr; }
|
||||
|
||||
// Return an event that will be notified when data arrives in MediaResource.
|
||||
// MediaDecoderReader will register with this event to receive notifications
|
||||
|
|
|
@ -45,7 +45,7 @@ public:
|
|||
: mStart(T())
|
||||
, mEnd(T())
|
||||
, mFuzz(T())
|
||||
{}
|
||||
{ }
|
||||
|
||||
template<typename StartArg, typename EndArg>
|
||||
Interval(StartArg&& aStart, EndArg&& aEnd)
|
||||
|
@ -69,7 +69,7 @@ public:
|
|||
: mStart(aOther.mStart)
|
||||
, mEnd(aOther.mEnd)
|
||||
, mFuzz(aOther.mFuzz)
|
||||
{}
|
||||
{ }
|
||||
|
||||
Interval(SelfType&& aOther)
|
||||
: mStart(Move(aOther.mStart))
|
||||
|
@ -136,8 +136,8 @@ public:
|
|||
|
||||
bool Contains(const SelfType& aOther) const
|
||||
{
|
||||
return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz) &&
|
||||
(aOther.mEnd - aOther.mFuzz <= mEnd + mFuzz);
|
||||
return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz)
|
||||
&& (aOther.mEnd - aOther.mFuzz <= mEnd + mFuzz);
|
||||
}
|
||||
|
||||
bool ContainsStrict(const SelfType& aOther) const
|
||||
|
@ -147,14 +147,14 @@ public:
|
|||
|
||||
bool ContainsWithStrictEnd(const SelfType& aOther) const
|
||||
{
|
||||
return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz) &&
|
||||
aOther.mEnd <= mEnd;
|
||||
return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz)
|
||||
&& aOther.mEnd <= mEnd;
|
||||
}
|
||||
|
||||
bool Intersects(const SelfType& aOther) const
|
||||
{
|
||||
return (mStart - mFuzz < aOther.mEnd + aOther.mFuzz) &&
|
||||
(aOther.mStart - aOther.mFuzz < mEnd + mFuzz);
|
||||
return (mStart - mFuzz < aOther.mEnd + aOther.mFuzz)
|
||||
&& (aOther.mStart - aOther.mFuzz < mEnd + mFuzz);
|
||||
}
|
||||
|
||||
bool IntersectsStrict(const SelfType& aOther) const
|
||||
|
@ -165,8 +165,8 @@ public:
|
|||
// Same as Intersects, but including the boundaries.
|
||||
bool Touches(const SelfType& aOther) const
|
||||
{
|
||||
return (mStart - mFuzz <= aOther.mEnd + aOther.mFuzz) &&
|
||||
(aOther.mStart - aOther.mFuzz <= mEnd + mFuzz);
|
||||
return (mStart - mFuzz <= aOther.mEnd + aOther.mFuzz)
|
||||
&& (aOther.mStart - aOther.mFuzz <= mEnd + mFuzz);
|
||||
}
|
||||
|
||||
// Returns true if aOther is strictly to the right of this and contiguous.
|
||||
|
@ -235,9 +235,9 @@ public:
|
|||
// of aOther
|
||||
bool TouchesOnRight(const SelfType& aOther) const
|
||||
{
|
||||
return aOther.mStart <= mStart &&
|
||||
(mStart - mFuzz <= aOther.mEnd + aOther.mFuzz) &&
|
||||
(aOther.mStart - aOther.mFuzz <= mEnd + mFuzz);
|
||||
return aOther.mStart <= mStart
|
||||
&& (mStart - mFuzz <= aOther.mEnd + aOther.mFuzz)
|
||||
&& (aOther.mStart - aOther.mFuzz <= mEnd + mFuzz);
|
||||
}
|
||||
|
||||
T mStart;
|
||||
|
@ -572,7 +572,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
bool Contains(const ElemType& aInterval) const {
|
||||
bool Contains(const ElemType& aInterval) const
|
||||
{
|
||||
for (const auto& interval : mIntervals) {
|
||||
if (interval.Contains(aInterval)) {
|
||||
return true;
|
||||
|
@ -581,8 +582,20 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Contains(const T& aX) const {
|
||||
bool ContainsStrict(const ElemType& aInterval) const
|
||||
{
|
||||
for (const auto& interval : mIntervals) {
|
||||
if (interval.ContainsStrict(aInterval)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Contains(const T& aX) const
|
||||
{
|
||||
for (const auto& interval : mIntervals)
|
||||
{
|
||||
if (interval.Contains(aX)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -590,7 +603,8 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ContainsStrict(const T& aX) const {
|
||||
bool ContainsStrict(const T& aX) const
|
||||
{
|
||||
for (const auto& interval : mIntervals) {
|
||||
if (interval.ContainsStrict(aX)) {
|
||||
return true;
|
||||
|
@ -599,7 +613,8 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ContainsWithStrictEnd(const T& aX) const {
|
||||
bool ContainsWithStrictEnd(const T& aX) const
|
||||
{
|
||||
for (const auto& interval : mIntervals) {
|
||||
if (interval.ContainsWithStrictEnd(aX)) {
|
||||
return true;
|
||||
|
@ -618,7 +633,8 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
void SetFuzz(const T& aFuzz) {
|
||||
void SetFuzz(const T& aFuzz)
|
||||
{
|
||||
for (auto& interval : mIntervals) {
|
||||
interval.SetFuzz(aFuzz);
|
||||
}
|
||||
|
|
|
@ -826,9 +826,6 @@ protected:
|
|||
|
||||
public:
|
||||
AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
|
||||
AbstractCanonical<Maybe<double>>* CanonicalExplicitDuration() override {
|
||||
return &mExplicitDuration;
|
||||
}
|
||||
AbstractCanonical<double>* CanonicalVolume() {
|
||||
return &mVolume;
|
||||
}
|
||||
|
@ -841,6 +838,9 @@ public:
|
|||
AbstractCanonical<media::NullableTimeUnit>* CanonicalEstimatedDuration() {
|
||||
return &mEstimatedDuration;
|
||||
}
|
||||
AbstractCanonical<Maybe<double>>* CanonicalExplicitDuration() {
|
||||
return &mExplicitDuration;
|
||||
}
|
||||
AbstractCanonical<PlayState>* CanonicalPlayState() {
|
||||
return &mPlayState;
|
||||
}
|
||||
|
|
|
@ -2542,6 +2542,12 @@ void MediaDecoderStateMachine::UpdateNextFrameStatus()
|
|||
|
||||
if (status != mNextFrameStatus) {
|
||||
DECODER_LOG("Changed mNextFrameStatus to %s", statusString);
|
||||
if(status == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING ||
|
||||
status == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE) {
|
||||
// Ensure currentTime is up to date prior updating mNextFrameStatus so that
|
||||
// the MediaDecoderOwner fire events at correct currentTime.
|
||||
UpdatePlaybackPositionPeriodically();
|
||||
}
|
||||
}
|
||||
|
||||
mNextFrameStatus = status;
|
||||
|
|
|
@ -77,7 +77,6 @@ MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
|
|||
, mDemuxOnly(false)
|
||||
, mSeekScheduled(false)
|
||||
, mVideoFrameContainer(aVideoFrameContainer)
|
||||
, mExplicitDuration(mTaskQueue, Maybe<double>(), "MediaFormatReader::mExplicitDuration(Mirror)")
|
||||
{
|
||||
MOZ_ASSERT(aDemuxer);
|
||||
MOZ_COUNT_CTOR(MediaFormatReader);
|
||||
|
@ -140,8 +139,6 @@ MediaFormatReader::Shutdown()
|
|||
mPlatform = nullptr;
|
||||
mVideoFrameContainer = nullptr;
|
||||
|
||||
mExplicitDuration.DisconnectIfConnected();
|
||||
|
||||
return MediaDecoderReader::Shutdown();
|
||||
}
|
||||
|
||||
|
@ -254,10 +251,6 @@ MediaFormatReader::AsyncReadMetadata()
|
|||
return MetadataPromise::CreateAndResolve(metadata, __func__);
|
||||
}
|
||||
|
||||
if (mDecoder->CanonicalExplicitDuration()) {
|
||||
mExplicitDuration.Connect(mDecoder->CanonicalExplicitDuration());
|
||||
}
|
||||
|
||||
RefPtr<MetadataPromise> p = mMetadataPromise.Ensure(__func__);
|
||||
|
||||
mDemuxerInitRequest.Begin(mDemuxer->Init()
|
||||
|
@ -1083,20 +1076,6 @@ MediaFormatReader::InternalSeek(TrackType aTrack, const InternalSeekTarget& aTar
|
|||
[self, aTrack] (DemuxerFailureReason aResult) {
|
||||
auto& decoder = self->GetDecoderData(aTrack);
|
||||
decoder.mSeekRequest.Complete();
|
||||
|
||||
if (aResult == DemuxerFailureReason::END_OF_STREAM) {
|
||||
// We want to enter EOS when performing an
|
||||
// internal seek only if we're attempting to seek past
|
||||
// the explicit duration to avoid unwanted ended
|
||||
// event to be fired.
|
||||
if (self->mExplicitDuration.Ref().isSome() &&
|
||||
decoder.mTimeThreshold.ref().Time() <
|
||||
TimeUnit::FromSeconds(
|
||||
self->mExplicitDuration.Ref().ref())) {
|
||||
aResult = DemuxerFailureReason::WAITING_FOR_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
switch (aResult) {
|
||||
case DemuxerFailureReason::WAITING_FOR_DATA:
|
||||
self->NotifyWaitingForData(aTrack);
|
||||
|
@ -1754,14 +1733,6 @@ MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult)
|
|||
mAudio.mSeekRequest.Complete();
|
||||
}
|
||||
|
||||
// We want to enter EOS when performing a seek only if we're attempting to
|
||||
// seek past the explicit duration to avoid unwanted ended
|
||||
// event to be fired.
|
||||
if (mExplicitDuration.Ref().isSome() &&
|
||||
mPendingSeekTime.ref() < TimeUnit::FromSeconds(mExplicitDuration.Ref().ref())) {
|
||||
aResult = DemuxerFailureReason::WAITING_FOR_DATA;
|
||||
}
|
||||
|
||||
if (aResult == DemuxerFailureReason::WAITING_FOR_DATA) {
|
||||
if (HasVideo() && aTrack == TrackType::kAudioTrack &&
|
||||
mFallbackSeekTime.isSome() &&
|
||||
|
|
|
@ -9,9 +9,8 @@
|
|||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/StateMirroring.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
|
||||
#include "MediaDataDemuxer.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
|
@ -585,9 +584,6 @@ private:
|
|||
RefPtr<GMPCrashHelper> mCrashHelper;
|
||||
|
||||
void SetBlankDecode(TrackType aTrack, bool aIsBlankDecode);
|
||||
|
||||
// The duration explicitly set by JS, mirrored from the main thread.
|
||||
Mirror<Maybe<double>> mExplicitDuration;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -399,7 +399,7 @@ VideoSink::UpdateRenderedVideoFrames()
|
|||
// Skip frames up to the playback position.
|
||||
int64_t lastDisplayedFrameEndTime = 0;
|
||||
while (VideoQueue().GetSize() > mMinVideoQueueSize &&
|
||||
clockTime > VideoQueue().PeekFront()->GetEndTime()) {
|
||||
clockTime >= VideoQueue().PeekFront()->GetEndTime()) {
|
||||
RefPtr<MediaData> frame = VideoQueue().PopFront();
|
||||
if (frame->As<VideoData>()->mSentToCompositor) {
|
||||
lastDisplayedFrameEndTime = frame->GetEndTime();
|
||||
|
|
|
@ -536,16 +536,13 @@ MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv)
|
|||
return;
|
||||
}
|
||||
|
||||
// 3. Update duration to new duration.
|
||||
// 4. If a user agent is unable to partially render audio frames or text cues
|
||||
// that start before and end after the duration, then run the following steps:
|
||||
// Update new duration to the highest end time reported by the buffered
|
||||
// attribute across all SourceBuffer objects in sourceBuffers.
|
||||
// 1. Update new duration to the highest end time reported by the buffered
|
||||
// attribute across all SourceBuffer objects in sourceBuffers.
|
||||
// 2. Update duration to new duration.
|
||||
// 3. Let highest end time be the largest track buffer ranges end time across
|
||||
// all the track buffers across all SourceBuffer objects in sourceBuffers.
|
||||
double highestEndTime = mSourceBuffers->HighestEndTime();
|
||||
// 4. If new duration is less than highest end time, then
|
||||
// 4.1 Update new duration to equal highest end time.
|
||||
aNewDuration =
|
||||
std::max(aNewDuration, mSourceBuffers->GetHighestBufferedEndTime());
|
||||
std::max(aNewDuration, highestEndTime);
|
||||
|
||||
// 5. Update the media duration to new duration and run the HTMLMediaElement
|
||||
// duration change algorithm.
|
||||
|
|
|
@ -277,10 +277,13 @@ MediaSourceDecoder::NextFrameBufferedStatus()
|
|||
// Next frame hasn't been decoded yet.
|
||||
// Use the buffered range to consider if we have the next frame available.
|
||||
TimeUnit currentPosition = TimeUnit::FromMicroseconds(CurrentPosition());
|
||||
TimeInterval interval(currentPosition,
|
||||
currentPosition + media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED),
|
||||
MediaSourceDemuxer::EOS_FUZZ);
|
||||
return GetBuffered().Contains(ClampIntervalToEnd(interval))
|
||||
TimeIntervals buffered = GetBuffered();
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
|
||||
TimeInterval interval(
|
||||
currentPosition,
|
||||
currentPosition
|
||||
+ media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
|
||||
return buffered.ContainsStrict(ClampIntervalToEnd(interval))
|
||||
? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
|
||||
: MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
|
||||
}
|
||||
|
@ -308,12 +311,12 @@ MediaSourceDecoder::CanPlayThrough()
|
|||
}
|
||||
// If we have data up to the mediasource's duration or 30s ahead, we can
|
||||
// assume that we can play without interruption.
|
||||
TimeIntervals buffered = GetBuffered();
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
|
||||
TimeUnit timeAhead =
|
||||
std::min(duration, currentPosition + TimeUnit::FromSeconds(30));
|
||||
TimeInterval interval(currentPosition,
|
||||
timeAhead,
|
||||
MediaSourceDemuxer::EOS_FUZZ);
|
||||
return GetBuffered().Contains(ClampIntervalToEnd(interval));
|
||||
TimeInterval interval(currentPosition, timeAhead);
|
||||
return buffered.ContainsStrict(ClampIntervalToEnd(interval));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -336,8 +339,13 @@ MediaSourceDecoder::ClampIntervalToEnd(const TimeInterval& aInterval)
|
|||
if (!mEnded) {
|
||||
return aInterval;
|
||||
}
|
||||
TimeInterval interval(TimeUnit(), TimeUnit::FromSeconds(GetDuration()));
|
||||
return aInterval.Intersection(interval);
|
||||
TimeUnit duration = TimeUnit::FromSeconds(GetDuration());
|
||||
if (duration < aInterval.mStart) {
|
||||
return aInterval;
|
||||
}
|
||||
return TimeInterval(aInterval.mStart,
|
||||
std::min(aInterval.mEnd, duration),
|
||||
aInterval.mFuzz);
|
||||
}
|
||||
|
||||
#undef MSE_DEBUG
|
||||
|
|
|
@ -379,20 +379,28 @@ MediaSourceTrackDemuxer::BreakCycles()
|
|||
RefPtr<MediaSourceTrackDemuxer::SeekPromise>
|
||||
MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
|
||||
{
|
||||
typedef TrackBuffersManager::GetSampleResult Result;
|
||||
|
||||
TimeIntervals buffered = mManager->Buffered(mType);
|
||||
// Fuzz factor represents a +/- threshold. So when seeking it allows the gap
|
||||
// to be twice as big as the fuzz value. We only want to allow EOS_FUZZ gap.
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
|
||||
TimeUnit seekTime = std::max(aTime - mPreRoll, TimeUnit::FromMicroseconds(0));
|
||||
|
||||
if (!buffered.Contains(seekTime)) {
|
||||
if (!buffered.Contains(aTime)) {
|
||||
if (mManager->IsEnded() && seekTime >= buffered.GetEnd()) {
|
||||
// We're attempting to seek past the end time. Cap seekTime so that we seek
|
||||
// to the last sample instead.
|
||||
seekTime =
|
||||
std::max(mManager->HighestStartTime(mType) - mPreRoll,
|
||||
TimeUnit::FromMicroseconds(0));
|
||||
}
|
||||
if (!buffered.ContainsWithStrictEnd(seekTime)) {
|
||||
if (!buffered.ContainsWithStrictEnd(aTime)) {
|
||||
// We don't have the data to seek to.
|
||||
return SeekPromise::CreateAndReject(
|
||||
mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
|
||||
DemuxerFailureReason::WAITING_FOR_DATA, __func__);
|
||||
return SeekPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA,
|
||||
__func__);
|
||||
}
|
||||
// Theorically we should reject the promise with WAITING_FOR_DATA,
|
||||
// Theoretically we should reject the promise with WAITING_FOR_DATA,
|
||||
// however, to avoid unwanted regressions we assume that if at this time
|
||||
// we don't have the wanted data it won't come later.
|
||||
// Instead of using the pre-rolled time, use the earliest time available in
|
||||
|
@ -401,13 +409,13 @@ MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
|
|||
MOZ_ASSERT(index != TimeIntervals::NoIndex);
|
||||
seekTime = buffered[index].mStart;
|
||||
}
|
||||
seekTime = mManager->Seek(mType, seekTime, MediaSourceDemuxer::EOS_FUZZ / 2);
|
||||
bool error;
|
||||
seekTime = mManager->Seek(mType, seekTime, MediaSourceDemuxer::EOS_FUZZ);
|
||||
Result result;
|
||||
RefPtr<MediaRawData> sample =
|
||||
mManager->GetSample(mType,
|
||||
media::TimeUnit(),
|
||||
error);
|
||||
MOZ_ASSERT(!error && sample);
|
||||
result);
|
||||
MOZ_ASSERT(result != Result::ERROR && sample);
|
||||
mNextSample = Some(sample);
|
||||
mReset = false;
|
||||
{
|
||||
|
@ -421,33 +429,39 @@ MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
|
|||
RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
|
||||
MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
|
||||
{
|
||||
typedef TrackBuffersManager::GetSampleResult Result;
|
||||
|
||||
if (mReset) {
|
||||
// If a seek (or reset) was recently performed, we ensure that the data
|
||||
// we are about to retrieve is still available.
|
||||
TimeIntervals buffered = mManager->Buffered(mType);
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ);
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
|
||||
|
||||
if (!buffered.Contains(TimeUnit::FromMicroseconds(0))) {
|
||||
return SamplesPromise::CreateAndReject(
|
||||
mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
|
||||
DemuxerFailureReason::WAITING_FOR_DATA, __func__);
|
||||
if (!buffered.Length() && mManager->IsEnded()) {
|
||||
return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM,
|
||||
__func__);
|
||||
}
|
||||
if (!buffered.ContainsWithStrictEnd(TimeUnit::FromMicroseconds(0))) {
|
||||
return SamplesPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA,
|
||||
__func__);
|
||||
}
|
||||
mReset = false;
|
||||
}
|
||||
bool error = false;
|
||||
RefPtr<MediaRawData> sample;
|
||||
if (mNextSample) {
|
||||
sample = mNextSample.ref();
|
||||
mNextSample.reset();
|
||||
} else {
|
||||
sample = mManager->GetSample(mType, MediaSourceDemuxer::EOS_FUZZ, error);
|
||||
Result result;
|
||||
sample = mManager->GetSample(mType, MediaSourceDemuxer::EOS_FUZZ, result);
|
||||
if (!sample) {
|
||||
if (error) {
|
||||
if (result == Result::ERROR) {
|
||||
return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
|
||||
}
|
||||
return SamplesPromise::CreateAndReject(
|
||||
mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
|
||||
DemuxerFailureReason::WAITING_FOR_DATA, __func__);
|
||||
(result == Result::EOS && mManager->IsEnded())
|
||||
? DemuxerFailureReason::END_OF_STREAM
|
||||
: DemuxerFailureReason::WAITING_FOR_DATA, __func__);
|
||||
}
|
||||
}
|
||||
RefPtr<SamplesHolder> samples = new SamplesHolder;
|
||||
|
@ -466,8 +480,8 @@ MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(media::TimeUnit aTimeThre
|
|||
uint32_t parsed = 0;
|
||||
// Ensure that the data we are about to skip to is still available.
|
||||
TimeIntervals buffered = mManager->Buffered(mType);
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ);
|
||||
if (buffered.Contains(aTimeThreadshold)) {
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
|
||||
if (buffered.ContainsWithStrictEnd(aTimeThreadshold)) {
|
||||
bool found;
|
||||
parsed = mManager->SkipToNextRandomAccessPoint(mType,
|
||||
aTimeThreadshold,
|
||||
|
|
|
@ -554,6 +554,15 @@ SourceBuffer::HighestStartTime()
|
|||
: 0.0;
|
||||
}
|
||||
|
||||
double
|
||||
SourceBuffer::HighestEndTime()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mTrackBuffersManager
|
||||
? mTrackBuffersManager->HighestEndTime().ToSeconds()
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(SourceBuffer)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SourceBuffer)
|
||||
|
|
|
@ -118,6 +118,7 @@ public:
|
|||
double GetBufferedStart();
|
||||
double GetBufferedEnd();
|
||||
double HighestStartTime();
|
||||
double HighestEndTime();
|
||||
|
||||
// Runs the range removal algorithm as defined by the MSE spec.
|
||||
void RangeRemoval(double aStart, double aEnd);
|
||||
|
|
|
@ -191,6 +191,18 @@ SourceBufferList::HighestStartTime()
|
|||
return highestStartTime;
|
||||
}
|
||||
|
||||
double
|
||||
SourceBufferList::HighestEndTime()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
double highestEndTime = 0;
|
||||
for (auto& sourceBuffer : mSourceBuffers) {
|
||||
highestEndTime =
|
||||
std::max(sourceBuffer->HighestEndTime(), highestEndTime);
|
||||
}
|
||||
return highestEndTime;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
SourceBufferList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
|
|
|
@ -86,6 +86,7 @@ public:
|
|||
void ClearSimple();
|
||||
|
||||
double HighestStartTime();
|
||||
double HighestEndTime();
|
||||
|
||||
private:
|
||||
~SourceBufferList();
|
||||
|
|
|
@ -300,11 +300,11 @@ TimeIntervals
|
|||
TrackBuffersManager::Buffered()
|
||||
{
|
||||
MSE_DEBUG("");
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
// http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
|
||||
// 2. Let highest end time be the largest track buffer ranges end time across all the track buffers managed by this SourceBuffer object.
|
||||
TimeUnit highestEndTime;
|
||||
TimeUnit highestEndTime = HighestEndTime();
|
||||
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
nsTArray<TimeIntervals*> tracks;
|
||||
if (HasVideo()) {
|
||||
tracks.AppendElement(&mVideoBufferedRanges);
|
||||
|
@ -312,9 +312,6 @@ TrackBuffersManager::Buffered()
|
|||
if (HasAudio()) {
|
||||
tracks.AppendElement(&mAudioBufferedRanges);
|
||||
}
|
||||
for (auto trackRanges : tracks) {
|
||||
highestEndTime = std::max(trackRanges->GetEnd(), highestEndTime);
|
||||
}
|
||||
|
||||
// 3. Let intersection ranges equal a TimeRange object containing a single range from 0 to highest end time.
|
||||
TimeIntervals intersection{TimeInterval(TimeUnit::FromSeconds(0), highestEndTime)};
|
||||
|
@ -1896,6 +1893,13 @@ TrackBuffersManager::Buffered(TrackInfo::TrackType aTrack)
|
|||
return GetTracksData(aTrack).mBufferedRanges;
|
||||
}
|
||||
|
||||
const media::TimeUnit&
|
||||
TrackBuffersManager::HighestStartTime(TrackInfo::TrackType aTrack)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
return GetTracksData(aTrack).mHighestStartTimestamp;
|
||||
}
|
||||
|
||||
TimeIntervals
|
||||
TrackBuffersManager::SafeBuffered(TrackInfo::TrackType aTrack) const
|
||||
{
|
||||
|
@ -1917,6 +1921,25 @@ TrackBuffersManager::HighestStartTime()
|
|||
return highestStartTime;
|
||||
}
|
||||
|
||||
TimeUnit
|
||||
TrackBuffersManager::HighestEndTime()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
TimeUnit highestEndTime;
|
||||
|
||||
nsTArray<TimeIntervals*> tracks;
|
||||
if (HasVideo()) {
|
||||
tracks.AppendElement(&mVideoBufferedRanges);
|
||||
}
|
||||
if (HasAudio()) {
|
||||
tracks.AppendElement(&mAudioBufferedRanges);
|
||||
}
|
||||
for (auto trackRanges : tracks) {
|
||||
highestEndTime = std::max(trackRanges->GetEnd(), highestEndTime);
|
||||
}
|
||||
return highestEndTime;
|
||||
}
|
||||
|
||||
const TrackBuffersManager::TrackBuffer&
|
||||
TrackBuffersManager::GetTrackBuffer(TrackInfo::TrackType aTrack)
|
||||
{
|
||||
|
@ -1963,12 +1986,14 @@ TrackBuffersManager::Seek(TrackInfo::TrackType aTrack,
|
|||
if (aTime != TimeUnit()) {
|
||||
// Determine the interval of samples we're attempting to seek to.
|
||||
TimeIntervals buffered = trackBuffer.mBufferedRanges;
|
||||
// Fuzz factor is +/- aFuzz; as we want to only eliminate gaps
|
||||
// that are less than aFuzz wide, we set a fuzz factor aFuzz/2.
|
||||
buffered.SetFuzz(aFuzz / 2);
|
||||
TimeIntervals::IndexType index = buffered.Find(aTime);
|
||||
buffered.SetFuzz(aFuzz);
|
||||
index = buffered.Find(aTime);
|
||||
MOZ_ASSERT(index != TimeIntervals::NoIndex);
|
||||
|
||||
MOZ_ASSERT(index != TimeIntervals::NoIndex,
|
||||
"We shouldn't be called if aTime isn't buffered");
|
||||
TimeInterval target = buffered[index];
|
||||
target.mFuzz = aFuzz;
|
||||
i = FindSampleIndex(track, target);
|
||||
}
|
||||
|
||||
|
@ -2061,8 +2086,10 @@ TrackBuffersManager::SkipToNextRandomAccessPoint(TrackInfo::TrackType aTrack,
|
|||
// SkipToNextRandomAccessPoint will not count again the parsed sample as
|
||||
// skipped.
|
||||
if (aFound) {
|
||||
trackData.mNextSampleTimecode = nextSampleTimecode;
|
||||
trackData.mNextSampleTime = nextSampleTime;
|
||||
trackData.mNextSampleTimecode =
|
||||
TimeUnit::FromMicroseconds(track[i]->mTimecode);
|
||||
trackData.mNextSampleTime =
|
||||
TimeUnit::FromMicroseconds(track[i]->mTime);
|
||||
trackData.mNextGetSampleIndex = Some(i);
|
||||
} else if (i > 0) {
|
||||
// Go back to the previous keyframe or the original position so the next
|
||||
|
@ -2116,17 +2143,19 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack,
|
|||
already_AddRefed<MediaRawData>
|
||||
TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack,
|
||||
const TimeUnit& aFuzz,
|
||||
bool& aError)
|
||||
GetSampleResult& aResult)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
auto& trackData = GetTracksData(aTrack);
|
||||
const TrackBuffer& track = GetTrackBuffer(aTrack);
|
||||
|
||||
aError = false;
|
||||
aResult = GetSampleResult::WAITING_FOR_DATA;
|
||||
|
||||
if (!track.Length()) {
|
||||
aResult = GetSampleResult::EOS;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (trackData.mNextGetSampleIndex.isNothing() &&
|
||||
trackData.mNextSampleTimecode == TimeUnit()) {
|
||||
// First demux, get first sample.
|
||||
|
@ -2134,6 +2163,10 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack,
|
|||
}
|
||||
|
||||
if (trackData.mNextGetSampleIndex.isSome()) {
|
||||
if (trackData.mNextGetSampleIndex.ref() >= track.Length()) {
|
||||
aResult = GetSampleResult::EOS;
|
||||
return nullptr;
|
||||
}
|
||||
const MediaRawData* sample =
|
||||
GetSample(aTrack,
|
||||
trackData.mNextGetSampleIndex.ref(),
|
||||
|
@ -2146,18 +2179,44 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack,
|
|||
|
||||
RefPtr<MediaRawData> p = sample->Clone();
|
||||
if (!p) {
|
||||
aError = true;
|
||||
aResult = GetSampleResult::ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
trackData.mNextGetSampleIndex.ref()++;
|
||||
// Estimate decode timestamp of the next sample.
|
||||
trackData.mNextSampleTimecode =
|
||||
// Estimate decode timestamp and timestamp of the next sample.
|
||||
TimeUnit nextSampleTimecode =
|
||||
TimeUnit::FromMicroseconds(sample->mTimecode + sample->mDuration);
|
||||
trackData.mNextSampleTime =
|
||||
TimeUnit nextSampleTime =
|
||||
TimeUnit::FromMicroseconds(sample->GetEndTime());
|
||||
const MediaRawData* nextSample =
|
||||
GetSample(aTrack,
|
||||
trackData.mNextGetSampleIndex.ref(),
|
||||
nextSampleTimecode,
|
||||
nextSampleTime,
|
||||
aFuzz);
|
||||
if (nextSample) {
|
||||
// We have a valid next sample, can use exact values.
|
||||
trackData.mNextSampleTimecode =
|
||||
TimeUnit::FromMicroseconds(nextSample->mTimecode);
|
||||
trackData.mNextSampleTime =
|
||||
TimeUnit::FromMicroseconds(nextSample->mTime);
|
||||
} else {
|
||||
// Next sample isn't available yet. Use estimates.
|
||||
trackData.mNextSampleTimecode = nextSampleTimecode;
|
||||
trackData.mNextSampleTime = nextSampleTime;
|
||||
}
|
||||
aResult = GetSampleResult::NO_ERROR;
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
if (trackData.mNextSampleTimecode.ToMicroseconds() >
|
||||
track.LastElement()->mTimecode + track.LastElement()->mDuration) {
|
||||
// The next element is past our last sample. We're done.
|
||||
trackData.mNextGetSampleIndex = Some(uint32_t(track.Length()));
|
||||
aResult = GetSampleResult::EOS;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Our previous index has been overwritten, attempt to find the new one.
|
||||
int32_t pos = FindCurrentPosition(aTrack, aFuzz);
|
||||
if (pos < 0) {
|
||||
|
@ -2171,7 +2230,7 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack,
|
|||
RefPtr<MediaRawData> p = sample->Clone();
|
||||
if (!p) {
|
||||
// OOM
|
||||
aError = true;
|
||||
aResult = GetSampleResult::ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
trackData.mNextGetSampleIndex = Some(uint32_t(pos)+1);
|
||||
|
@ -2179,6 +2238,7 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack,
|
|||
TimeUnit::FromMicroseconds(sample->mTimecode + sample->mDuration);
|
||||
trackData.mNextSampleTime =
|
||||
TimeUnit::FromMicroseconds(sample->GetEndTime());
|
||||
aResult = GetSampleResult::NO_ERROR;
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
|
@ -2190,6 +2250,23 @@ TrackBuffersManager::FindCurrentPosition(TrackInfo::TrackType aTrack,
|
|||
auto& trackData = GetTracksData(aTrack);
|
||||
const TrackBuffer& track = GetTrackBuffer(aTrack);
|
||||
|
||||
// Perform an exact search first.
|
||||
for (uint32_t i = 0; i < track.Length(); i++) {
|
||||
const RefPtr<MediaRawData>& sample = track[i];
|
||||
TimeInterval sampleInterval{
|
||||
TimeUnit::FromMicroseconds(sample->mTimecode),
|
||||
TimeUnit::FromMicroseconds(sample->mTimecode + sample->mDuration)};
|
||||
|
||||
if (sampleInterval.ContainsStrict(trackData.mNextSampleTimecode)) {
|
||||
return i;
|
||||
}
|
||||
if (sampleInterval.mStart > trackData.mNextSampleTimecode) {
|
||||
// Samples are ordered by timecode. There's no need to search
|
||||
// any further.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < track.Length(); i++) {
|
||||
const RefPtr<MediaRawData>& sample = track[i];
|
||||
TimeInterval sampleInterval{
|
||||
|
@ -2200,6 +2277,11 @@ TrackBuffersManager::FindCurrentPosition(TrackInfo::TrackType aTrack,
|
|||
if (sampleInterval.ContainsWithStrictEnd(trackData.mNextSampleTimecode)) {
|
||||
return i;
|
||||
}
|
||||
if (sampleInterval.mStart - aFuzz > trackData.mNextSampleTimecode) {
|
||||
// Samples are ordered by timecode. There's no need to search
|
||||
// any further.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't find our sample by decode timestamp. Attempt to find it using
|
||||
|
|
|
@ -123,6 +123,7 @@ public:
|
|||
// Buffered must conform to http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
|
||||
media::TimeIntervals Buffered();
|
||||
media::TimeUnit HighestStartTime();
|
||||
media::TimeUnit HighestEndTime();
|
||||
|
||||
// Return the size of the data managed by this SourceBufferContentManager.
|
||||
int64_t GetSize() const;
|
||||
|
@ -139,6 +140,7 @@ public:
|
|||
MediaInfo GetMetadata();
|
||||
const TrackBuffer& GetTrackBuffer(TrackInfo::TrackType aTrack);
|
||||
const media::TimeIntervals& Buffered(TrackInfo::TrackType);
|
||||
const media::TimeUnit& HighestStartTime(TrackInfo::TrackType);
|
||||
media::TimeIntervals SafeBuffered(TrackInfo::TrackType) const;
|
||||
bool IsEnded() const
|
||||
{
|
||||
|
@ -151,9 +153,18 @@ public:
|
|||
const media::TimeUnit& aTimeThreadshold,
|
||||
const media::TimeUnit& aFuzz,
|
||||
bool& aFound);
|
||||
|
||||
enum class GetSampleResult
|
||||
{
|
||||
NO_ERROR,
|
||||
ERROR,
|
||||
WAITING_FOR_DATA,
|
||||
EOS
|
||||
};
|
||||
|
||||
already_AddRefed<MediaRawData> GetSample(TrackInfo::TrackType aTrack,
|
||||
const media::TimeUnit& aFuzz,
|
||||
bool& aError);
|
||||
GetSampleResult& aResult);
|
||||
int32_t FindCurrentPosition(TrackInfo::TrackType aTrack,
|
||||
const media::TimeUnit& aFuzz);
|
||||
media::TimeUnit GetNextRandomAccessPoint(TrackInfo::TrackType aTrack,
|
||||
|
|
|
@ -104,6 +104,8 @@ skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not
|
|||
skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3
|
||||
[test_SeekedEvent_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3
|
||||
[test_SeekToEnd_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3
|
||||
[test_SeekTwice_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3
|
||||
[test_Sequence_mp4.html]
|
||||
|
@ -126,6 +128,8 @@ skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not
|
|||
skip-if = true # Disabled due to bug 1124493 and friends. WebM MSE is deprioritized.
|
||||
[test_WaitingOnMissingData_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3
|
||||
[test_WaitingOnMissingDataEnded_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3
|
||||
[test_WaitingToEndedTransition_mp4.html]
|
||||
skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>MSE: seeking to end of data with data gap.</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="mediasource.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
runWithMSE(function(ms, el) {
|
||||
|
||||
once(ms, 'sourceopen').then(function() {
|
||||
ok(true, "Receive a sourceopen event");
|
||||
var videosb = ms.addSourceBuffer("video/mp4");
|
||||
var audiosb = ms.addSourceBuffer("audio/mp4");
|
||||
|
||||
fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
|
||||
.then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', range(1, 6), '.m4s'))
|
||||
.then(fetchAndLoad.bind(null, audiosb, 'bipbop/bipbop_audio', ['init'], '.mp4'))
|
||||
.then(function() {
|
||||
is(videosb.buffered.length, 1, "continuous buffered range");
|
||||
// Ensure we have at least 2s less audio than video.
|
||||
audiosb.appendWindowEnd = videosb.buffered.end(0) - 2;
|
||||
return fetchAndLoad(audiosb, 'bipbop/bipbop_audio', range(1, 6), '.m4s');
|
||||
}).then(function() {
|
||||
ms.endOfStream();
|
||||
return Promise.all([once(el, "durationchange"), once(ms, "sourceended")]);
|
||||
}).then(function() {
|
||||
ok(true, "endOfStream completed");
|
||||
// Seek to the middle of the gap where audio is missing. As we are in readyState = ended
|
||||
// seeking must complete.
|
||||
el.currentTime = videosb.buffered.end(0) / 2 + audiosb.buffered.end(0) / 2;
|
||||
ok(el.currentTime - audiosb.buffered.end(0) > 1, "gap is big enough");
|
||||
is(el.buffered.length, 1, "continuous buffered range");
|
||||
is(el.buffered.end(0), videosb.buffered.end(0), "buffered range end is aligned with longest track");
|
||||
ok(el.seeking, "element is now seeking");
|
||||
ok(el.currentTime >= el.buffered.start(0) && el.currentTime <= el.buffered.end(0), "seeking time is in buffered range");
|
||||
ok(el.currentTime > audiosb.buffered.end(0), "seeking point is not buffered in audio track");
|
||||
return once(el, 'seeked');
|
||||
}).then(function() {
|
||||
ok(true, "we have successfully seeked");
|
||||
// Now ensure that we can play to the end, even though we are missing data in one track.
|
||||
el.play();
|
||||
once(el, 'ended').then(SimpleTest.finish.bind(SimpleTest));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -15,13 +15,12 @@ SimpleTest.waitForExplicitFinish();
|
|||
runWithMSE(function(ms, el) {
|
||||
|
||||
var threshold = 0.5; // gap threshold in seconds.
|
||||
// duration of a frame. The FFmpeg decoder can't calculate properly calculate the duration of the last frame.
|
||||
var fuzz = 33322 / 1000000;
|
||||
var fuzz = 0.000001; // fuzz when comparing double.
|
||||
|
||||
once(ms, 'sourceopen').then(function() {
|
||||
ok(true, "Receive a sourceopen event");
|
||||
var videosb = ms.addSourceBuffer("video/mp4");
|
||||
var vchunks = [ {start: 0, end: 3.2033}, { start: 3.2033, end: 6.4066}];
|
||||
var vchunks = [ {start: 0, end: 3.203333}, { start: 3.203333, end: 6.406666}];
|
||||
|
||||
fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
|
||||
.then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', range(1, 5), '.m4s'))
|
||||
|
@ -40,8 +39,10 @@ var fuzz = 33322 / 1000000;
|
|||
}).then(function() {
|
||||
return once(el, 'waiting');
|
||||
}).then(function() {
|
||||
// We're waiting for data at the end of the last segment.
|
||||
isfuzzy(el.currentTime, vchunks[1].end + threshold, fuzz, "skipped the gap properly");
|
||||
// We're waiting for data after the start of the last frame.
|
||||
// 0.033333 is the duration of the last frame.
|
||||
ok(el.currentTime >= vchunks[1].end - 0.033333 + threshold - fuzz
|
||||
&& el.currentTime <= vchunks[1].end + threshold + fuzz, "skipped the gap properly: " + el.currentTime + " " + (vchunks[1].end + threshold));
|
||||
is(el.buffered.length, 2, "buffered range has right length");
|
||||
// Now we test that seeking will succeed despite the gap.
|
||||
el.currentTime = el.buffered.end(0) + (threshold / 2);
|
||||
|
@ -68,8 +69,10 @@ var fuzz = 33322 / 1000000;
|
|||
}).then(function() {
|
||||
return once(el, 'waiting');
|
||||
}).then(function() {
|
||||
// We're waiting for data at the end of the first segment as the gap is too big.
|
||||
isfuzzy(el.currentTime, vchunks[0].end, fuzz, "stopped at the gap properly");
|
||||
// We're waiting for data after the start of the last frame.
|
||||
// 0.033333 is the duration of the last frame.
|
||||
ok(el.currentTime >= vchunks[0].end - 0.033333 - fuzz
|
||||
&& el.currentTime <= vchunks[0].end + fuzz, "stopped at the gap properly: " + el.currentTime + " " + vchunks[0].end);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -71,9 +71,11 @@ runWithMSE(function(ms, el) {
|
|||
return Promise.all([audiosb.updating ? once(audiosb, 'updateend') : Promise.resolve(),
|
||||
videosb.updating ? once(videosb, 'updateend') : Promise.resolve()]);
|
||||
}).then(function() {
|
||||
ms.endOfStream();
|
||||
once(el, 'ended').then(SimpleTest.finish.bind(SimpleTest));
|
||||
info("waiting for play to complete");
|
||||
el.play();
|
||||
el.currentTime = el.buffered.start(0);
|
||||
ms.endOfStream();
|
||||
Promise.all([once(el, 'ended'), once(el, 'seeked')]).then(SimpleTest.finish.bind(SimpleTest));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
|
||||
<title>MSE: |waiting| event when source data is missing</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="mediasource.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test"><script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
runWithMSE(function(ms, el) {
|
||||
el.controls = true;
|
||||
once(ms, 'sourceopen').then(function() {
|
||||
ok(true, "Receive a sourceopen event");
|
||||
el.addEventListener("ended", function () {
|
||||
ok(false, "ended should never fire");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
var videosb = ms.addSourceBuffer("video/mp4");
|
||||
fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
|
||||
.then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', range(1, 5), '.m4s'))
|
||||
.then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', range(6, 8), '.m4s'))
|
||||
.then(function() {
|
||||
is(el.buffered.length, 2, "discontinuous buffered range");
|
||||
ms.endOfStream();
|
||||
return Promise.all([once(el, "durationchange"), once(ms, "sourceended")]);
|
||||
}).then(function() {
|
||||
// HTMLMediaElement fires 'waiting' if somebody invokes |play()| before the MDSM
|
||||
// has notified it of available data. Make sure that we get 'playing' before
|
||||
// we starting waiting for 'waiting'.
|
||||
info("Invoking play()");
|
||||
el.play();
|
||||
return once(el, 'playing');
|
||||
}).then(function() {
|
||||
ok(true, "Video playing. It should play for a bit, then fire 'waiting'");
|
||||
return once(el, 'waiting');
|
||||
}).then(function() {
|
||||
// waiting is fired when we start to play the last frame.
|
||||
// 0.033334 is the duration of the last frame, + 0.000001 of fuzz.
|
||||
// the next video frame, currentTime can be up to 1 frame's worth earlier than end of video.
|
||||
isfuzzy(el.currentTime, videosb.buffered.end(0), 0.033334, "waiting was fired on gap");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -23,10 +23,6 @@
|
|||
#include "prenv.h"
|
||||
#include "nsXPCOMPrivate.h"
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#endif
|
||||
|
||||
#include "nsExceptionHandler.h"
|
||||
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
|
@ -612,20 +608,6 @@ AddAppDirToCommandLine(std::vector<std::string>& aCmdLine)
|
|||
aCmdLine.push_back(path.get());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
// Full path to the profile dir
|
||||
nsCOMPtr<nsIFile> profileDir;
|
||||
rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR,
|
||||
NS_GET_IID(nsIFile),
|
||||
getter_AddRefs(profileDir));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsAutoCString path;
|
||||
MOZ_ALWAYS_SUCCEEDS(profileDir->GetNativePath(path));
|
||||
aCmdLine.push_back("-profile");
|
||||
aCmdLine.push_back(path.get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public class FxAccountDeviceRegistrator implements BundleEventListener {
|
|||
|
||||
// The current version of the device registration, we use this to re-register
|
||||
// devices after we update what we send on device registration.
|
||||
public static final Integer DEVICE_REGISTRATION_VERSION = 1;
|
||||
public static final Integer DEVICE_REGISTRATION_VERSION = 2;
|
||||
|
||||
private static FxAccountDeviceRegistrator instance;
|
||||
private final WeakReference<Context> context;
|
||||
|
|
|
@ -317,10 +317,6 @@ pref("media.wakelock_timeout", 2000);
|
|||
// opened as top-level documents, as opposed to inside a media element.
|
||||
pref("media.play-stand-alone", true);
|
||||
|
||||
// Whether we should delay actioning a "play()" JS function call and autoplay
|
||||
// attribute until the media element's owner document is visible.
|
||||
pref("media.block-play-until-visible", false);
|
||||
|
||||
pref("media.hardware-video-decoding.enabled", true);
|
||||
pref("media.hardware-video-decoding.force-enabled", false);
|
||||
|
||||
|
@ -5539,6 +5535,7 @@ pref("dom.webkitBlink.dirPicker.enabled", true);
|
|||
pref("dom.webkitBlink.filesystem.enabled", true);
|
||||
#endif
|
||||
|
||||
pref("media.block-autoplay-until-in-foreground", true);
|
||||
#ifdef MOZ_STYLO
|
||||
// Is the Servo-backed style system enabled?
|
||||
pref("layout.css.servo.enabled", true);
|
||||
|
|
|
@ -41,8 +41,7 @@ typedef struct _MacSandboxInfo {
|
|||
_MacSandboxInfo(const struct _MacSandboxInfo& other)
|
||||
: type(other.type), level(other.level), pluginInfo(other.pluginInfo),
|
||||
appPath(other.appPath), appBinaryPath(other.appBinaryPath),
|
||||
appDir(other.appDir), appTempDir(other.appTempDir),
|
||||
profileDir(other.profileDir) {}
|
||||
appDir(other.appDir), appTempDir(other.appTempDir) {}
|
||||
MacSandboxType type;
|
||||
int32_t level;
|
||||
MacSandboxPluginInfo pluginInfo;
|
||||
|
@ -50,7 +49,6 @@ typedef struct _MacSandboxInfo {
|
|||
std::string appBinaryPath;
|
||||
std::string appDir;
|
||||
std::string appTempDir;
|
||||
std::string profileDir;
|
||||
} MacSandboxInfo;
|
||||
|
||||
namespace mozilla {
|
||||
|
|
|
@ -157,7 +157,6 @@ static const char contentSandboxRules[] =
|
|||
"(define appBinaryPath \"%s\")\n"
|
||||
"(define appDir \"%s\")\n"
|
||||
"(define appTempDir \"%s\")\n"
|
||||
"(define profileDir \"%s\")\n"
|
||||
"(define home-path \"%s\")\n"
|
||||
"\n"
|
||||
"; Allow read access to standard system paths.\n"
|
||||
|
@ -233,9 +232,6 @@ static const char contentSandboxRules[] =
|
|||
" (define (home-literal home-relative-literal)\n"
|
||||
" (resolving-literal (string-append home-path home-relative-literal)))\n"
|
||||
"\n"
|
||||
" (define (profile-subpath profile-relative-subpath)\n"
|
||||
" (resolving-subpath (string-append profileDir profile-relative-subpath)))\n"
|
||||
"\n"
|
||||
" (define (container-regex container-relative-regex)\n"
|
||||
" (resolving-regex (string-append \"^\" (regex-quote container-path) container-relative-regex)))\n"
|
||||
" (define (container-subpath container-relative-subpath)\n"
|
||||
|
@ -375,17 +371,16 @@ static const char contentSandboxRules[] =
|
|||
" (allow file-read*\n"
|
||||
" (home-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n"
|
||||
" (resolving-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n"
|
||||
" (profile-subpath \"/extensions\")\n"
|
||||
" (profile-subpath \"/weave\"))\n"
|
||||
" (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/extensions/\")\n"
|
||||
" (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/weave/\"))\n"
|
||||
"\n"
|
||||
"; the following rules should be removed when printing and\n"
|
||||
"; the following rules should be removed when printing and \n"
|
||||
"; opening a file from disk are brokered through the main process\n"
|
||||
" (if\n"
|
||||
" (< sandbox-level 2)\n"
|
||||
" (allow file*\n"
|
||||
" (require-all\n"
|
||||
" (require-not (home-subpath \"/Library\"))\n"
|
||||
" (require-not (subpath profileDir))))\n"
|
||||
" (require-not\n"
|
||||
" (home-subpath \"/Library\")))\n"
|
||||
" (allow file*\n"
|
||||
" (require-all\n"
|
||||
" (subpath home-path)\n"
|
||||
|
@ -502,7 +497,6 @@ bool StartMacSandbox(MacSandboxInfo aInfo, std::string &aErrorMessage)
|
|||
aInfo.appBinaryPath.c_str(),
|
||||
aInfo.appDir.c_str(),
|
||||
aInfo.appTempDir.c_str(),
|
||||
aInfo.profileDir.c_str(),
|
||||
getenv("HOME"));
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
|
|
|
@ -38,6 +38,8 @@ all-tests:
|
|||
asan-tests:
|
||||
- cppunit
|
||||
- crashtest
|
||||
- firefox-ui-functional-local
|
||||
- firefox-ui-functional-remote
|
||||
- gtest
|
||||
- jittests
|
||||
- jsreftest
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from firefox_ui_harness.testcases import FirefoxTestCase
|
||||
from marionette_driver import Wait
|
||||
|
@ -11,32 +12,31 @@ from marionette_driver import Wait
|
|||
class TestSafeBrowsingInitialDownload(FirefoxTestCase):
|
||||
|
||||
test_data = [{
|
||||
'platforms': ['linux', 'windows_nt', 'darwin'],
|
||||
'files': [
|
||||
# Phishing
|
||||
"goog-badbinurl-shavar.pset",
|
||||
"goog-badbinurl-shavar.sbstore",
|
||||
"goog-malware-shavar.pset",
|
||||
"goog-malware-shavar.sbstore",
|
||||
"goog-phish-shavar.pset",
|
||||
"goog-phish-shavar.sbstore",
|
||||
"goog-unwanted-shavar.pset",
|
||||
"goog-unwanted-shavar.sbstore",
|
||||
'platforms': ['linux', 'windows_nt', 'darwin'],
|
||||
'files': [
|
||||
# Phishing
|
||||
r'goog-badbinurl-shavar.pset',
|
||||
r'goog-badbinurl-shavar.sbstore',
|
||||
r'goog-malware-shavar.pset',
|
||||
r'goog-malware-shavar.sbstore',
|
||||
r'goog(pub)?-phish-shavar.pset',
|
||||
r'goog(pub)?-phish-shavar.sbstore',
|
||||
r'goog-unwanted-shavar.pset',
|
||||
r'goog-unwanted-shavar.sbstore',
|
||||
|
||||
# Tracking Protections
|
||||
"base-track-digest256.pset",
|
||||
"base-track-digest256.sbstore",
|
||||
"mozstd-trackwhite-digest256.pset",
|
||||
"mozstd-trackwhite-digest256.sbstore"
|
||||
]
|
||||
},
|
||||
{
|
||||
'platforms': ['windows_nt'],
|
||||
'files': [
|
||||
"goog-downloadwhite-digest256.pset",
|
||||
"goog-downloadwhite-digest256.sbstore"
|
||||
]
|
||||
}
|
||||
# Tracking Protections
|
||||
r'base-track-digest256.pset',
|
||||
r'base-track-digest256.sbstore',
|
||||
r'mozstd-trackwhite-digest256.pset',
|
||||
r'mozstd-trackwhite-digest256.sbstore'
|
||||
],
|
||||
}, {
|
||||
'platforms': ['windows_nt'],
|
||||
'files': [
|
||||
r'goog-downloadwhite-digest256.pset',
|
||||
r'goog-downloadwhite-digest256.sbstore'
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
browser_prefs = {
|
||||
|
@ -72,5 +72,5 @@ class TestSafeBrowsingInitialDownload(FirefoxTestCase):
|
|||
continue
|
||||
for item in data['files']:
|
||||
wait.until(
|
||||
lambda _: os.path.exists(os.path.join(self.sb_files_path, item)),
|
||||
lambda _: [f for f in os.listdir(self.sb_files_path) if re.search(item, f)],
|
||||
message='Safe Browsing File: {} not found!'.format(item))
|
||||
|
|
|
@ -17,6 +17,8 @@ class GeckoInstance(object):
|
|||
required_prefs = {
|
||||
"browser.sessionstore.resume_from_crash": False,
|
||||
"browser.shell.checkDefaultBrowser": False,
|
||||
# Needed for branded builds to prevent opening a second tab on startup
|
||||
"browser.startup.homepage_override.mstone": "ignore",
|
||||
"browser.startup.page": 0,
|
||||
"browser.tabs.remote.autostart.1": False,
|
||||
"browser.tabs.remote.autostart.2": False,
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# 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/.
|
||||
import pytest
|
||||
|
||||
from mock import Mock, MagicMock
|
||||
|
||||
from marionette_driver.marionette import Marionette
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def logger():
|
||||
"""
|
||||
Fake logger to help with mocking out other runner-related classes.
|
||||
"""
|
||||
import mozlog
|
||||
return Mock(spec=mozlog.structuredlog.StructuredLogger)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mach_parsed_kwargs(logger):
|
||||
"""
|
||||
Parsed and verified dictionary used during simplest
|
||||
call to mach marionette-test
|
||||
"""
|
||||
return {
|
||||
'adb_path': None,
|
||||
'addon': None,
|
||||
'address': None,
|
||||
'app': None,
|
||||
'app_args': [],
|
||||
'avd': None,
|
||||
'avd_home': None,
|
||||
'binary': u'/path/to/firefox',
|
||||
'browsermob_port' : None,
|
||||
'browsermob_script' : None,
|
||||
'device_serial': None,
|
||||
'e10s': True,
|
||||
'emulator': False,
|
||||
'emulator_bin': None,
|
||||
'gecko_log': None,
|
||||
'jsdebugger': False,
|
||||
'log_errorsummary': None,
|
||||
'log_html': None,
|
||||
'log_mach': None,
|
||||
'log_mach_buffer': None,
|
||||
'log_mach_level': None,
|
||||
'log_mach_verbose': None,
|
||||
'log_raw': None,
|
||||
'log_raw_level': None,
|
||||
'log_tbpl': None,
|
||||
'log_tbpl_buffer': None,
|
||||
'log_tbpl_compact': None,
|
||||
'log_tbpl_level': None,
|
||||
'log_unittest': None,
|
||||
'log_xunit': None,
|
||||
'logger_name': 'Marionette-based Tests',
|
||||
'prefs': {
|
||||
'browser.tabs.remote.autostart': True,
|
||||
'browser.tabs.remote.force-enable': True,
|
||||
'extensions.e10sBlocksEnabling': False,
|
||||
},
|
||||
'prefs_args': None,
|
||||
'prefs_files': None,
|
||||
'profile': None,
|
||||
'pydebugger': None,
|
||||
'repeat': 0,
|
||||
'server_root': None,
|
||||
'shuffle': False,
|
||||
'shuffle_seed': 2276870381009474531,
|
||||
'socket_timeout': 360.0,
|
||||
'sources': None,
|
||||
'startup_timeout': 60,
|
||||
'symbols_path': None,
|
||||
'test_tags': None,
|
||||
'tests': [u'/path/to/unit-tests.ini'],
|
||||
'testvars': None,
|
||||
'this_chunk': None,
|
||||
'timeout': None,
|
||||
'total_chunks': None,
|
||||
'verbose': None,
|
||||
'workspace': None,
|
||||
'logger': logger,
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def mock_marionette(request):
|
||||
""" Mock marionette instance """
|
||||
marionette = MagicMock(spec=Marionette)
|
||||
if 'has_crashed' in request.funcargnames:
|
||||
marionette.check_for_crash.return_value = request.getfuncargvalue(
|
||||
'has_crashed'
|
||||
)
|
||||
return marionette
|
|
@ -0,0 +1,32 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
import pytest
|
||||
|
||||
from marionette.runtests import MarionetteArguments
|
||||
|
||||
|
||||
@pytest.mark.parametrize("socket_timeout", ['A', '10', '1B-', '1C2', '44.35'])
|
||||
def test_parse_arg_socket_timeout(socket_timeout):
|
||||
argv = ['marionette', '--socket-timeout', socket_timeout]
|
||||
parser = MarionetteArguments()
|
||||
|
||||
def _is_float_convertible(value):
|
||||
try:
|
||||
float(value)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
if not _is_float_convertible(socket_timeout):
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
parser.parse_args(args=argv)
|
||||
assert ex.value.code == 2
|
||||
else:
|
||||
args = parser.parse_args(args=argv)
|
||||
assert hasattr(args, 'socket_timeout') and args.socket_timeout == float(socket_timeout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(pytest.main(['--verbose', __file__]))
|
|
@ -0,0 +1,110 @@
|
|||
# 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/.
|
||||
import pytest
|
||||
|
||||
from mock import Mock, patch, sentinel
|
||||
|
||||
from marionette.runtests import MarionetteTestRunner, MarionetteHarness, cli
|
||||
|
||||
# avoid importing MarionetteJSTestCase to prevent pytest from
|
||||
# collecting and running it as part of this test suite
|
||||
import marionette.marionette_test as marionette_test
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def harness_class(request):
|
||||
"""
|
||||
Mock based on MarionetteHarness whose run method just returns a number of
|
||||
failures according to the supplied test parameter
|
||||
"""
|
||||
if 'num_fails_crashed' in request.funcargnames:
|
||||
num_fails_crashed = request.getfuncargvalue('num_fails_crashed')
|
||||
else:
|
||||
num_fails_crashed = (0, 0)
|
||||
harness_cls = Mock(spec=MarionetteHarness)
|
||||
harness = harness_cls.return_value
|
||||
if num_fails_crashed is None:
|
||||
harness.run.side_effect = Exception
|
||||
else:
|
||||
harness.run.return_value = sum(num_fails_crashed)
|
||||
return harness_cls
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner_class(request):
|
||||
"""
|
||||
Mock based on MarionetteTestRunner, wherein the runner.failed,
|
||||
runner.crashed attributes are provided by a test parameter
|
||||
"""
|
||||
if 'num_fails_crashed' in request.funcargnames:
|
||||
failures, crashed = request.getfuncargvalue('num_fails_crashed')
|
||||
else:
|
||||
failures = 0
|
||||
crashed = 0
|
||||
mock_runner_class = Mock(spec=MarionetteTestRunner)
|
||||
runner = mock_runner_class.return_value
|
||||
runner.failed = failures
|
||||
runner.crashed = crashed
|
||||
return mock_runner_class
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_fails_crashed,exit_code",
|
||||
[((0, 0), 0), ((1, 0), 10), ((0, 1), 10), (None, 1)],
|
||||
)
|
||||
def test_cli_exit_code(num_fails_crashed, exit_code, harness_class):
|
||||
with pytest.raises(SystemExit) as err:
|
||||
cli(harness_class=harness_class)
|
||||
assert err.value.code == exit_code
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_fails_crashed", [(0, 0), (1, 0), (1, 1)])
|
||||
def test_call_harness_with_parsed_args_yields_num_failures(mach_parsed_kwargs,
|
||||
runner_class,
|
||||
num_fails_crashed):
|
||||
with patch(
|
||||
'marionette.runtests.MarionetteHarness.parse_args'
|
||||
) as parse_args:
|
||||
failed_or_crashed = MarionetteHarness(runner_class,
|
||||
args=mach_parsed_kwargs).run()
|
||||
parse_args.assert_not_called()
|
||||
assert failed_or_crashed == sum(num_fails_crashed)
|
||||
|
||||
|
||||
def test_call_harness_with_no_args_yields_num_failures(runner_class):
|
||||
with patch(
|
||||
'marionette.runtests.MarionetteHarness.parse_args',
|
||||
return_value={'tests': []}
|
||||
) as parse_args:
|
||||
failed_or_crashed = MarionetteHarness(runner_class).run()
|
||||
assert parse_args.call_count == 1
|
||||
assert failed_or_crashed == 0
|
||||
|
||||
|
||||
def test_args_passed_to_runner_class(mach_parsed_kwargs, runner_class):
|
||||
arg_list = mach_parsed_kwargs.keys()
|
||||
arg_list.remove('tests')
|
||||
mach_parsed_kwargs.update([(a, getattr(sentinel, a)) for a in arg_list])
|
||||
harness = MarionetteHarness(runner_class, args=mach_parsed_kwargs)
|
||||
harness.process_args = Mock()
|
||||
harness.run()
|
||||
for arg in arg_list:
|
||||
assert harness._runner_class.call_args[1][arg] is getattr(sentinel, arg)
|
||||
|
||||
|
||||
def test_harness_sets_up_default_test_handlers(mach_parsed_kwargs):
|
||||
"""
|
||||
If the necessary TestCase is not in test_handlers,
|
||||
tests are omitted silently
|
||||
"""
|
||||
harness = MarionetteHarness(args=mach_parsed_kwargs)
|
||||
mach_parsed_kwargs.pop('tests')
|
||||
runner = harness._runner_class(**mach_parsed_kwargs)
|
||||
assert marionette_test.MarionetteTestCase in runner.test_handlers
|
||||
assert marionette_test.MarionetteJSTestCase in runner.test_handlers
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(pytest.main(['--verbose', __file__]))
|
|
@ -2,132 +2,11 @@
|
|||
# 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/.
|
||||
import pytest
|
||||
from mock import patch, Mock, DEFAULT, mock_open, MagicMock, sentinel
|
||||
|
||||
from marionette.runtests import (
|
||||
MarionetteTestRunner,
|
||||
MarionetteHarness,
|
||||
MarionetteArguments,
|
||||
cli
|
||||
)
|
||||
from marionette.runner import MarionetteTestResult
|
||||
from marionette_driver.marionette import Marionette
|
||||
from manifestparser import TestManifest
|
||||
from mock import Mock, patch, mock_open, sentinel, DEFAULT
|
||||
|
||||
# avoid importing MarionetteJSTestCase to prevent pytest from
|
||||
# collecting and running it as part of this test suite
|
||||
import marionette.marionette_test as marionette_test
|
||||
|
||||
|
||||
def _check_crash_counts(has_crashed, runner, mock_marionette):
|
||||
if has_crashed:
|
||||
assert mock_marionette.check_for_crash.call_count == 1
|
||||
assert runner.crashed == 1
|
||||
else:
|
||||
assert runner.crashed == 0
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_marionette(request):
|
||||
""" Mock marionette instance """
|
||||
marionette = MagicMock(spec=Marionette)
|
||||
if 'has_crashed' in request.funcargnames:
|
||||
marionette.check_for_crash.return_value = request.getfuncargvalue(
|
||||
'has_crashed'
|
||||
)
|
||||
return marionette
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def empty_marionette_testcase():
|
||||
""" Testable MarionetteTestCase class """
|
||||
class EmptyTestCase(marionette_test.MarionetteTestCase):
|
||||
def test_nothing(self):
|
||||
pass
|
||||
|
||||
return EmptyTestCase
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def empty_marionette_test(mock_marionette, empty_marionette_testcase):
|
||||
return empty_marionette_testcase(lambda: mock_marionette, 'test_nothing')
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def logger():
|
||||
"""
|
||||
Fake logger to help with mocking out other runner-related classes.
|
||||
"""
|
||||
import mozlog
|
||||
return Mock(spec=mozlog.structuredlog.StructuredLogger)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mach_parsed_kwargs(logger):
|
||||
"""
|
||||
Parsed and verified dictionary used during simplest
|
||||
call to mach marionette-test
|
||||
"""
|
||||
return {
|
||||
'adb_path': None,
|
||||
'addon': None,
|
||||
'address': None,
|
||||
'app': None,
|
||||
'app_args': [],
|
||||
'avd': None,
|
||||
'avd_home': None,
|
||||
'binary': u'/path/to/firefox',
|
||||
'browsermob_port' : None,
|
||||
'browsermob_script' : None,
|
||||
'device_serial': None,
|
||||
'e10s': True,
|
||||
'emulator': False,
|
||||
'emulator_bin': None,
|
||||
'gecko_log': None,
|
||||
'jsdebugger': False,
|
||||
'log_errorsummary': None,
|
||||
'log_html': None,
|
||||
'log_mach': None,
|
||||
'log_mach_buffer': None,
|
||||
'log_mach_level': None,
|
||||
'log_mach_verbose': None,
|
||||
'log_raw': None,
|
||||
'log_raw_level': None,
|
||||
'log_tbpl': None,
|
||||
'log_tbpl_buffer': None,
|
||||
'log_tbpl_compact': None,
|
||||
'log_tbpl_level': None,
|
||||
'log_unittest': None,
|
||||
'log_xunit': None,
|
||||
'logger_name': 'Marionette-based Tests',
|
||||
'package_name': None,
|
||||
'prefs': {
|
||||
'browser.tabs.remote.autostart': True,
|
||||
'browser.tabs.remote.force-enable': True,
|
||||
'extensions.e10sBlocksEnabling': False,
|
||||
},
|
||||
'prefs_args': None,
|
||||
'prefs_files': None,
|
||||
'profile': None,
|
||||
'pydebugger': None,
|
||||
'repeat': 0,
|
||||
'server_root': None,
|
||||
'shuffle': False,
|
||||
'shuffle_seed': 2276870381009474531,
|
||||
'socket_timeout': 360.0,
|
||||
'sources': None,
|
||||
'startup_timeout': 60,
|
||||
'symbols_path': None,
|
||||
'test_tags': None,
|
||||
'tests': [u'/path/to/unit-tests.ini'],
|
||||
'testvars': None,
|
||||
'this_chunk': None,
|
||||
'timeout': None,
|
||||
'total_chunks': None,
|
||||
'verbose': None,
|
||||
'workspace': None,
|
||||
'logger': logger,
|
||||
}
|
||||
from marionette.runtests import MarionetteTestRunner
|
||||
import manifestparser
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -153,118 +32,6 @@ def mock_runner(runner, mock_marionette, monkeypatch):
|
|||
return runner
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def harness_class(request):
|
||||
"""
|
||||
Mock based on MarionetteHarness whose run method just returns a number of
|
||||
failures according to the supplied test parameter
|
||||
"""
|
||||
if 'num_fails_crashed' in request.funcargnames:
|
||||
num_fails_crashed = request.getfuncargvalue('num_fails_crashed')
|
||||
else:
|
||||
num_fails_crashed = (0, 0)
|
||||
harness_cls = Mock(spec=MarionetteHarness)
|
||||
harness = harness_cls.return_value
|
||||
if num_fails_crashed is None:
|
||||
harness.run.side_effect = Exception
|
||||
else:
|
||||
harness.run.return_value = sum(num_fails_crashed)
|
||||
return harness_cls
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner_class(request):
|
||||
"""
|
||||
Mock based on MarionetteTestRunner, wherein the runner.failed,
|
||||
runner.crashed attributes are provided by a test parameter
|
||||
"""
|
||||
if 'num_fails_crashed' in request.funcargnames:
|
||||
failures, crashed = request.getfuncargvalue('num_fails_crashed')
|
||||
else:
|
||||
failures = 0
|
||||
crashed = 0
|
||||
mock_runner_class = Mock(spec=MarionetteTestRunner)
|
||||
runner = mock_runner_class.return_value
|
||||
runner.failed = failures
|
||||
runner.crashed = crashed
|
||||
return mock_runner_class
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_fails_crashed,exit_code",
|
||||
[((0, 0), 0), ((1, 0), 10), ((0, 1), 10), (None, 1)],
|
||||
)
|
||||
def test_cli_exit_code(num_fails_crashed, exit_code, harness_class):
|
||||
with pytest.raises(SystemExit) as err:
|
||||
cli(harness_class=harness_class)
|
||||
assert err.value.code == exit_code
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_fails_crashed", [(0, 0), (1, 0), (1, 1)])
|
||||
def test_call_harness_with_parsed_args_yields_num_failures(mach_parsed_kwargs,
|
||||
runner_class,
|
||||
num_fails_crashed):
|
||||
with patch(
|
||||
'marionette.runtests.MarionetteHarness.parse_args'
|
||||
) as parse_args:
|
||||
failed_or_crashed = MarionetteHarness(runner_class,
|
||||
args=mach_parsed_kwargs).run()
|
||||
parse_args.assert_not_called()
|
||||
assert failed_or_crashed == sum(num_fails_crashed)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sock_timeout_value", ['A', '10', '1B-', '1C2', '44.35'])
|
||||
def test_parse_arg_socket_timeout_with_multiple_values(sock_timeout_value):
|
||||
argv = ['marionette', '--socket-timeout', sock_timeout_value]
|
||||
parser = MarionetteArguments()
|
||||
|
||||
def _is_float_convertible(value):
|
||||
try:
|
||||
float(value)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
if not _is_float_convertible(sock_timeout_value):
|
||||
# should raising exception, since sock_timeout must be convertible to float.
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
parser.parse_args(args=argv)
|
||||
assert ex.value.code == 2
|
||||
else:
|
||||
# should pass without raising exception.
|
||||
args = parser.parse_args(args=argv)
|
||||
assert hasattr(args, 'socket_timeout') and args.socket_timeout == float(sock_timeout_value)
|
||||
|
||||
|
||||
def test_call_harness_with_no_args_yields_num_failures(runner_class):
|
||||
with patch(
|
||||
'marionette.runtests.MarionetteHarness.parse_args',
|
||||
return_value={'tests': []}
|
||||
) as parse_args:
|
||||
failed_or_crashed = MarionetteHarness(runner_class).run()
|
||||
assert parse_args.call_count == 1
|
||||
assert failed_or_crashed == 0
|
||||
|
||||
|
||||
def test_args_passed_to_runner_class(mach_parsed_kwargs, runner_class):
|
||||
arg_list = mach_parsed_kwargs.keys()
|
||||
arg_list.remove('tests')
|
||||
mach_parsed_kwargs.update([(a, getattr(sentinel, a)) for a in arg_list])
|
||||
harness = MarionetteHarness(runner_class, args=mach_parsed_kwargs)
|
||||
harness.process_args = Mock()
|
||||
harness.run()
|
||||
for arg in arg_list:
|
||||
assert harness._runner_class.call_args[1][arg] is getattr(sentinel, arg)
|
||||
|
||||
|
||||
def test_args_passed_to_driverclass(mock_runner):
|
||||
built_kwargs = {'arg1': 'value1', 'arg2': 'value2'}
|
||||
mock_runner._build_kwargs = Mock(return_value=built_kwargs)
|
||||
with pytest.raises(IOError):
|
||||
mock_runner.run_tests(['fake_tests.ini'])
|
||||
assert mock_runner.driverclass.call_args[1] == built_kwargs
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def build_kwargs_using(mach_parsed_kwargs):
|
||||
'''Helper function for test_build_kwargs_* functions'''
|
||||
|
@ -307,6 +74,43 @@ def expected_driver_args(runner):
|
|||
return expected
|
||||
|
||||
|
||||
@pytest.fixture(params=['enabled', 'disabled', 'enabled_disabled', 'empty'])
|
||||
def manifest_fixture(request):
|
||||
'''
|
||||
Fixture for the contents of mock_manifest, where a manifest
|
||||
can include enabled tests, disabled tests, both, or neither (empty)
|
||||
'''
|
||||
included = []
|
||||
if 'enabled' in request.param:
|
||||
included += [(u'test_expected_pass.py', 'pass'),
|
||||
(u'test_expected_fail.py', 'fail')]
|
||||
if 'disabled' in request.param:
|
||||
included += [(u'test_pass_disabled.py', 'pass', 'skip-if: true'),
|
||||
(u'test_fail_disabled.py', 'fail', 'skip-if: true')]
|
||||
keys = ('path', 'expected', 'disabled')
|
||||
active_tests = [dict(zip(keys, values)) for values in included]
|
||||
|
||||
class ManifestFixture:
|
||||
def __init__(self, name, tests):
|
||||
self.filepath = "/path/to/fake/manifest.ini"
|
||||
self.n_disabled = len([t for t in tests if 'disabled' in t])
|
||||
self.n_enabled = len(tests) - self.n_disabled
|
||||
mock_manifest = Mock(spec=manifestparser.TestManifest,
|
||||
active_tests=Mock(return_value=tests))
|
||||
self.mock_manifest = Mock(return_value=mock_manifest)
|
||||
self.__repr__ = lambda: "<ManifestFixture {}>".format(name)
|
||||
|
||||
return ManifestFixture(request.param, active_tests)
|
||||
|
||||
|
||||
def test_args_passed_to_driverclass(mock_runner):
|
||||
built_kwargs = {'arg1': 'value1', 'arg2': 'value2'}
|
||||
mock_runner._build_kwargs = Mock(return_value=built_kwargs)
|
||||
with pytest.raises(IOError):
|
||||
mock_runner.run_tests(['fake_tests.ini'])
|
||||
assert mock_runner.driverclass.call_args[1] == built_kwargs
|
||||
|
||||
|
||||
def test_build_kwargs_basic_args(build_kwargs_using):
|
||||
'''Test the functionality of runner._build_kwargs:
|
||||
make sure that basic arguments (those which should
|
||||
|
@ -386,18 +190,6 @@ def test_build_kwargs_with_emulator_or_address(expected_driver_args, build_kwarg
|
|||
expected_driver_args.assert_keys_not_in(built_kwargs)
|
||||
|
||||
|
||||
def test_harness_sets_up_default_test_handlers(mach_parsed_kwargs):
|
||||
"""
|
||||
If the necessary TestCase is not in test_handlers,
|
||||
tests are omitted silently
|
||||
"""
|
||||
harness = MarionetteHarness(args=mach_parsed_kwargs)
|
||||
mach_parsed_kwargs.pop('tests')
|
||||
runner = harness._runner_class(**mach_parsed_kwargs)
|
||||
assert marionette_test.MarionetteTestCase in runner.test_handlers
|
||||
assert marionette_test.MarionetteJSTestCase in runner.test_handlers
|
||||
|
||||
|
||||
def test_parsing_testvars(mach_parsed_kwargs):
|
||||
mach_parsed_kwargs.pop('tests')
|
||||
testvars_json_loads = [
|
||||
|
@ -435,28 +227,12 @@ def test_load_testvars_throws_expected_errors(mach_parsed_kwargs):
|
|||
assert 'not properly formatted' in json_exc.value.message
|
||||
|
||||
|
||||
@pytest.mark.parametrize("has_crashed", [True, False])
|
||||
def test_crash_is_recorded_as_error(empty_marionette_test,
|
||||
logger,
|
||||
has_crashed):
|
||||
""" Number of errors is incremented by stopTest iff has_crashed is true """
|
||||
# collect results from the empty test
|
||||
result = MarionetteTestResult(
|
||||
marionette=empty_marionette_test._marionette_weakref(),
|
||||
logger=logger, verbosity=None,
|
||||
stream=None, descriptions=None,
|
||||
)
|
||||
result.startTest(empty_marionette_test)
|
||||
assert len(result.errors) == 0
|
||||
assert len(result.failures) == 0
|
||||
assert result.testsRun == 1
|
||||
assert result.shouldStop is False
|
||||
result.stopTest(empty_marionette_test)
|
||||
assert result.shouldStop == has_crashed
|
||||
def _check_crash_counts(has_crashed, runner, mock_marionette):
|
||||
if has_crashed:
|
||||
assert len(result.errors) == 1
|
||||
assert mock_marionette.check_for_crash.call_count == 1
|
||||
assert runner.crashed == 1
|
||||
else:
|
||||
assert len(result.errors) == 0
|
||||
assert runner.crashed == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("has_crashed", [True, False])
|
||||
|
@ -511,36 +287,6 @@ def test_add_test_directory(runner):
|
|||
assert len(runner.tests) == 4
|
||||
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(params=['enabled', 'disabled', 'enabled_disabled', 'empty'])
|
||||
def manifest_fixture(request):
|
||||
'''
|
||||
Fixture for the contents of mock_manifest, where a manifest
|
||||
can include enabled tests, disabled tests, both, or neither (empty)
|
||||
'''
|
||||
included = []
|
||||
if 'enabled' in request.param:
|
||||
included += [(u'test_expected_pass.py', 'pass'),
|
||||
(u'test_expected_fail.py', 'fail')]
|
||||
if 'disabled' in request.param:
|
||||
included += [(u'test_pass_disabled.py', 'pass', 'skip-if: true'),
|
||||
(u'test_fail_disabled.py', 'fail', 'skip-if: true')]
|
||||
keys = ('path', 'expected', 'disabled')
|
||||
active_tests = [dict(zip(keys, values)) for values in included]
|
||||
|
||||
class ManifestFixture:
|
||||
def __init__(self, name, tests):
|
||||
self.filepath = "/path/to/fake/manifest.ini"
|
||||
self.n_disabled = len([t for t in tests if 'disabled' in t])
|
||||
self.n_enabled = len(tests) - self.n_disabled
|
||||
mock_manifest = Mock(spec=TestManifest, active_tests=Mock(return_value=tests))
|
||||
self.mock_manifest = Mock(return_value=mock_manifest)
|
||||
self.__repr__ = lambda: "<ManifestFixture {}>".format(name)
|
||||
|
||||
return ManifestFixture(request.param, active_tests)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_files_exist", [True, False])
|
||||
def test_add_test_manifest(mock_runner, manifest_fixture, monkeypatch, test_files_exist):
|
||||
monkeypatch.setattr('marionette.runner.base.TestManifest', manifest_fixture.mock_manifest)
|
||||
|
@ -625,4 +371,4 @@ def test_catch_invalid_test_names(runner):
|
|||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(pytest.main(['--verbose', __file__]))
|
||||
sys.exit(pytest.main(['--verbose', __file__]))
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# 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/.
|
||||
import pytest
|
||||
|
||||
from marionette.runner import MarionetteTestResult
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def empty_marionette_testcase():
|
||||
""" Testable MarionetteTestCase class """
|
||||
from marionette.marionette_test import MarionetteTestCase
|
||||
|
||||
class EmptyTestCase(MarionetteTestCase):
|
||||
def test_nothing(self):
|
||||
pass
|
||||
|
||||
return EmptyTestCase
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def empty_marionette_test(mock_marionette, empty_marionette_testcase):
|
||||
return empty_marionette_testcase(lambda: mock_marionette, 'test_nothing')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("has_crashed", [True, False])
|
||||
def test_crash_is_recorded_as_error(empty_marionette_test,
|
||||
logger,
|
||||
has_crashed):
|
||||
""" Number of errors is incremented by stopTest iff has_crashed is true """
|
||||
# collect results from the empty test
|
||||
result = MarionetteTestResult(
|
||||
marionette=empty_marionette_test._marionette_weakref(),
|
||||
logger=logger, verbosity=None,
|
||||
stream=None, descriptions=None,
|
||||
)
|
||||
result.startTest(empty_marionette_test)
|
||||
assert len(result.errors) == 0
|
||||
assert len(result.failures) == 0
|
||||
assert result.testsRun == 1
|
||||
assert result.shouldStop is False
|
||||
result.stopTest(empty_marionette_test)
|
||||
assert result.shouldStop == has_crashed
|
||||
if has_crashed:
|
||||
assert len(result.errors) == 1
|
||||
else:
|
||||
assert len(result.errors) == 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(pytest.main(['--verbose', __file__]))
|
|
@ -7,8 +7,5 @@ config = {
|
|||
|
||||
"pip_index": False,
|
||||
|
||||
"download_symbols": "ondemand",
|
||||
"download_minidump_stackwalk": True,
|
||||
|
||||
"tooltool_cache": "/builds/tooltool_cache",
|
||||
}
|
||||
|
|
|
@ -22,6 +22,12 @@ from mozharness.mozilla.vcstools import VCSToolsScript
|
|||
|
||||
# General command line arguments for Firefox ui tests
|
||||
firefox_ui_tests_config_options = [
|
||||
[["--allow-software-gl-layers"], {
|
||||
"action": "store_true",
|
||||
"dest": "allow_software_gl_layers",
|
||||
"default": False,
|
||||
"help": "Permits a software GL implementation (such as LLVMPipe) to use the GL compositor.",
|
||||
}],
|
||||
[['--dry-run'], {
|
||||
'dest': 'dry_run',
|
||||
'default': False,
|
||||
|
@ -33,15 +39,6 @@ firefox_ui_tests_config_options = [
|
|||
'default': False,
|
||||
'help': 'Enable multi-process (e10s) mode when running tests.',
|
||||
}],
|
||||
[['--firefox-ui-branch'], {
|
||||
'dest': 'firefox_ui_branch',
|
||||
'help': 'which branch to use for firefox_ui_tests',
|
||||
}],
|
||||
[['--firefox-ui-repo'], {
|
||||
'dest': 'firefox_ui_repo',
|
||||
'default': 'https://github.com/mozilla/firefox-ui-tests.git',
|
||||
'help': 'which firefox_ui_tests repo to use',
|
||||
}],
|
||||
[['--symbols-path=SYMBOLS_PATH'], {
|
||||
'dest': 'symbols_path',
|
||||
'help': 'absolute path to directory containing breakpad '
|
||||
|
@ -51,12 +48,6 @@ firefox_ui_tests_config_options = [
|
|||
'dest': 'tag',
|
||||
'help': 'Subset of tests to run (local, remote).',
|
||||
}],
|
||||
[["--allow-software-gl-layers"], {
|
||||
"action": "store_true",
|
||||
"dest": "allow_software_gl_layers",
|
||||
"default": False,
|
||||
"help": "Permits a software GL implementation (such as LLVMPipe) to use the GL compositor.",
|
||||
}],
|
||||
] + copy.deepcopy(testing_config_options)
|
||||
|
||||
# Command line arguments for update tests
|
||||
|
|
|
@ -4182,7 +4182,7 @@ PERFORMANCE OF THIS SOFTWARE.
|
|||
<h1><a id="node-properties"></a>node-properties License</h1>
|
||||
|
||||
<p>This license applies to
|
||||
<span class="path">devtools/client/shared/vendor/node-properties.js</span>.</p>
|
||||
<span class="path">devtools/shared/node-properties/node-properties.js</span>.</p>
|
||||
|
||||
<pre>
|
||||
The MIT License (MIT)
|
||||
|
@ -4878,7 +4878,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<h1><a id="sprintf.js"></a>sprintf.js License</h1>
|
||||
|
||||
<p>This license applies to
|
||||
<span class="path">devtools/client/shared/vendor/sprintf.js</span>.</p>
|
||||
<span class="path">devtools/shared/sprintfjs/sprintf.js</span>.</p>
|
||||
|
||||
<pre>
|
||||
Copyright (c) 2007-2016, Alexandru Marasteanu <hello [at) alexei (dot] ro>
|
||||
|
|
|
@ -9,6 +9,10 @@ tags = audiochannel
|
|||
support-files =
|
||||
file_multipleAudio.html
|
||||
[browser_autoscroll_disabled.js]
|
||||
[browser_block_autoplay_media.js]
|
||||
tags = audiochannel
|
||||
support-files =
|
||||
file_multipleAudio.html
|
||||
[browser_bug295977_autoscroll_overflow.js]
|
||||
[browser_bug451286.js]
|
||||
skip-if = !e10s
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_multipleAudio.html";
|
||||
|
||||
var SuspendedType = {
|
||||
NONE_SUSPENDED : 0,
|
||||
SUSPENDED_PAUSE : 1,
|
||||
SUSPENDED_BLOCK : 2,
|
||||
SUSPENDED_PAUSE_DISPOSABLE : 3
|
||||
};
|
||||
|
||||
function* wait_for_tab_playing_event(tab, expectPlaying) {
|
||||
if (tab.soundPlaying == expectPlaying) {
|
||||
ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
|
||||
} else {
|
||||
yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
|
||||
if (event.detail.changed.indexOf("soundplaying") >= 0) {
|
||||
is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function check_audio_suspended(suspendedType) {
|
||||
var autoPlay = content.document.getElementById('autoplay');
|
||||
if (!autoPlay) {
|
||||
ok(false, "Can't get the audio element!");
|
||||
}
|
||||
|
||||
is(autoPlay.computedSuspended, suspendedType,
|
||||
"The suspeded state of autoplay audio is correct.");
|
||||
}
|
||||
|
||||
add_task(function* setup_test_preference() {
|
||||
yield new Promise(resolve => {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["media.useAudioChannelService.testing", true],
|
||||
["media.block-autoplay-until-in-foreground", true]
|
||||
]}, resolve);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* block_autoplay_media() {
|
||||
info("- open new background tab1 -");
|
||||
let tab1 = window.gBrowser.addTab("about:blank");
|
||||
tab1.linkedBrowser.loadURI(PAGE);
|
||||
yield BrowserTestUtils.browserLoaded(tab1.linkedBrowser);
|
||||
|
||||
info("- should block autoplay media for non-visited tab1 -");
|
||||
yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.SUSPENDED_BLOCK,
|
||||
check_audio_suspended);
|
||||
|
||||
info("- open new background tab2 -");
|
||||
let tab2 = window.gBrowser.addTab("about:blank");
|
||||
tab2.linkedBrowser.loadURI(PAGE);
|
||||
yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
|
||||
|
||||
info("- should block autoplay for non-visited tab2 -");
|
||||
yield ContentTask.spawn(tab2.linkedBrowser, SuspendedType.SUSPENDED_BLOCK,
|
||||
check_audio_suspended);
|
||||
|
||||
info("- select tab1 as foreground tab -");
|
||||
yield BrowserTestUtils.switchTab(window.gBrowser, tab1);
|
||||
|
||||
info("- media should be unblocked because the tab was visited -");
|
||||
yield wait_for_tab_playing_event(tab1, true);
|
||||
yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.NONE_SUSPENDED,
|
||||
check_audio_suspended);
|
||||
|
||||
info("- open another new foreground tab3 -");
|
||||
let tab3 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser,
|
||||
"about:blank");
|
||||
info("- should still play media from tab1 -");
|
||||
yield wait_for_tab_playing_event(tab1, true);
|
||||
yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.NONE_SUSPENDED,
|
||||
check_audio_suspended);
|
||||
|
||||
info("- should still block media from tab2 -");
|
||||
yield wait_for_tab_playing_event(tab2, false);
|
||||
yield ContentTask.spawn(tab2.linkedBrowser, SuspendedType.SUSPENDED_BLOCK,
|
||||
check_audio_suspended);
|
||||
|
||||
info("- remove tabs -");
|
||||
yield BrowserTestUtils.removeTab(tab1);
|
||||
yield BrowserTestUtils.removeTab(tab2);
|
||||
yield BrowserTestUtils.removeTab(tab3);
|
||||
});
|
|
@ -143,14 +143,7 @@ function play_nonautoplay_audio_should_be_blocked(suspendedType) {
|
|||
}
|
||||
|
||||
nonAutoPlay.play();
|
||||
return new Promise(resolve => {
|
||||
nonAutoPlay.onplay = function () {
|
||||
nonAutoPlay.onplay = null;
|
||||
is(nonAutoPlay.computedSuspended, suspendedType,
|
||||
"The suspeded state of non-autoplay audio is correct.");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
ok(nonAutoPlay.paused, "The blocked audio can't be playback.");
|
||||
}
|
||||
|
||||
function* suspended_pause(url, browser) {
|
||||
|
|
|
@ -383,9 +383,13 @@
|
|||
}
|
||||
setTimeout(selectText, 0);
|
||||
|
||||
// Clear the text because we don't want the text appearing underneath the input.
|
||||
this.view.setCellText(row, column, "");
|
||||
// Save the original text so we can restore it after stoping editing.
|
||||
input.setAttribute("data-original-text", input.value);
|
||||
|
||||
this._editingRow = row;
|
||||
this._editingColumn = column;
|
||||
|
||||
this.setAttribute("editing", "true");
|
||||
return true;
|
||||
]]>
|
||||
|
@ -402,16 +406,15 @@
|
|||
var input = this.inputField;
|
||||
var editingRow = this._editingRow;
|
||||
var editingColumn = this._editingColumn;
|
||||
var value = accept ? input.value : input.getAttribute("data-original-text");
|
||||
this._editingRow = -1;
|
||||
this._editingColumn = null;
|
||||
if (accept) {
|
||||
var value = input.value;
|
||||
this.view.setCellText(editingRow, editingColumn, value);
|
||||
}
|
||||
this.view.setCellText(editingRow, editingColumn, value);
|
||||
|
||||
input.hidden = true;
|
||||
input.value = "";
|
||||
this.removeAttribute("editing");
|
||||
input.removeAttribute("data-original-text");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
|
|
@ -606,44 +606,13 @@ XRE_InitChildProcess(int aArgc,
|
|||
case GeckoProcessType_Content: {
|
||||
process = new ContentProcess(parentPID);
|
||||
// If passed in grab the application path for xpcom init
|
||||
bool foundAppdir = false;
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
// If passed in grab the profile path for sandboxing
|
||||
bool foundProfile = false;
|
||||
#endif
|
||||
|
||||
nsCString appDir;
|
||||
for (int idx = aArgc; idx > 0; idx--) {
|
||||
if (aArgv[idx] && !strcmp(aArgv[idx], "-appdir")) {
|
||||
MOZ_ASSERT(!foundAppdir);
|
||||
if (foundAppdir) {
|
||||
continue;
|
||||
}
|
||||
nsCString appDir;
|
||||
appDir.Assign(nsDependentCString(aArgv[idx+1]));
|
||||
static_cast<ContentProcess*>(process.get())->SetAppDir(appDir);
|
||||
foundAppdir = true;
|
||||
}
|
||||
|
||||
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
|
||||
if (aArgv[idx] && !strcmp(aArgv[idx], "-profile")) {
|
||||
MOZ_ASSERT(!foundProfile);
|
||||
if (foundProfile) {
|
||||
continue;
|
||||
}
|
||||
nsCString profile;
|
||||
profile.Assign(nsDependentCString(aArgv[idx+1]));
|
||||
static_cast<ContentProcess*>(process.get())->SetProfile(profile);
|
||||
foundProfile = true;
|
||||
}
|
||||
if (foundProfile && foundAppdir) {
|
||||
break;
|
||||
}
|
||||
#else
|
||||
if (foundAppdir) {
|
||||
break;
|
||||
}
|
||||
#endif /* XP_MACOSX && MOZ_CONTENT_SANDBOX */
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
Загрузка…
Ссылка в новой задаче