зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to central, a=merge
This commit is contained in:
Коммит
c625073a37
|
@ -29,12 +29,16 @@ class ProxyAccessibleWrap : public AccessibleWrap
|
|||
}
|
||||
};
|
||||
|
||||
class HyperTextProxyAccessibleWrap : public ProxyAccessibleWrap,
|
||||
public ia2AccessibleEditableText,
|
||||
public ia2AccessibleHypertext
|
||||
class HyperTextProxyAccessibleWrap : public HyperTextAccessibleWrap
|
||||
{
|
||||
HyperTextProxyAccessibleWrap(ProxyAccessible* aProxy) :
|
||||
ProxyAccessibleWrap(aProxy) {}
|
||||
HyperTextAccessibleWrap(nullptr, nullptr)
|
||||
{
|
||||
mType = eProxyType;
|
||||
mBits.proxy = aProxy;
|
||||
}
|
||||
|
||||
virtual void Shutdown() override { mBits.proxy = nullptr; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -13,9 +13,16 @@
|
|||
#include "HyperTextAccessibleWrap.h"
|
||||
#include "HyperTextAccessible-inl.h"
|
||||
#include "ProxyWrappers.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
StaticRefPtr<HyperTextAccessibleWrap> ia2AccessibleText::sLastTextChangeAcc;
|
||||
StaticAutoPtr<nsString> ia2AccessibleText::sLastTextChangeString;
|
||||
uint32_t ia2AccessibleText::sLastTextChangeStart = 0;
|
||||
uint32_t ia2AccessibleText::sLastTextChangeEnd = 0;
|
||||
bool ia2AccessibleText::sLastTextChangeWasInsert = false;
|
||||
|
||||
// IAccessibleText
|
||||
|
||||
STDMETHODIMP
|
||||
|
@ -572,21 +579,22 @@ ia2AccessibleText::GetModifiedText(bool aGetInsertedText,
|
|||
if (!aText)
|
||||
return E_INVALIDARG;
|
||||
|
||||
uint32_t startOffset = 0, endOffset = 0;
|
||||
nsAutoString text;
|
||||
if (!sLastTextChangeAcc)
|
||||
return S_OK;
|
||||
|
||||
nsresult rv = GetModifiedText(aGetInsertedText, text,
|
||||
&startOffset, &endOffset);
|
||||
if (NS_FAILED(rv))
|
||||
return GetHRESULT(rv);
|
||||
if (aGetInsertedText != sLastTextChangeWasInsert)
|
||||
return S_OK;
|
||||
|
||||
aText->start = startOffset;
|
||||
aText->end = endOffset;
|
||||
if (sLastTextChangeAcc != this)
|
||||
return S_OK;
|
||||
|
||||
if (text.IsEmpty())
|
||||
aText->start = sLastTextChangeStart;
|
||||
aText->end = sLastTextChangeEnd;
|
||||
|
||||
if (sLastTextChangeString->IsEmpty())
|
||||
return S_FALSE;
|
||||
|
||||
aText->text = ::SysAllocStringLen(text.get(), text.Length());
|
||||
aText->text = ::SysAllocStringLen(sLastTextChangeString->get(), sLastTextChangeString->Length());
|
||||
return aText->text ? S_OK : E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
|
@ -608,3 +616,24 @@ ia2AccessibleText::GetGeckoTextBoundary(enum IA2TextBoundaryType aBoundaryType)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
ia2AccessibleText::InitTextChangeData()
|
||||
{
|
||||
ClearOnShutdown(&sLastTextChangeAcc);
|
||||
ClearOnShutdown(&sLastTextChangeString);
|
||||
}
|
||||
|
||||
void
|
||||
ia2AccessibleText::UpdateTextChangeData(HyperTextAccessibleWrap* aAcc,
|
||||
bool aInsert, const nsString& aStr,
|
||||
int32_t aStart, uint32_t aLen)
|
||||
{
|
||||
if (!sLastTextChangeString)
|
||||
sLastTextChangeString = new nsString();
|
||||
|
||||
sLastTextChangeAcc = aAcc;
|
||||
sLastTextChangeStart = aStart;
|
||||
sLastTextChangeEnd = aStart + aLen;
|
||||
sLastTextChangeWasInsert = aInsert;
|
||||
*sLastTextChangeString = aStr;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
class HyperTextAccessibleWrap;
|
||||
|
||||
class ia2AccessibleText: public IAccessibleText
|
||||
{
|
||||
|
@ -113,10 +114,17 @@ public:
|
|||
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_oldText(
|
||||
/* [retval][out] */ IA2TextSegment *oldText);
|
||||
|
||||
static void InitTextChangeData();
|
||||
static void UpdateTextChangeData(HyperTextAccessibleWrap* aAcc, bool aInsert,
|
||||
const nsString& aStr, int32_t aStart,
|
||||
uint32_t aLen);
|
||||
|
||||
protected:
|
||||
virtual nsresult GetModifiedText(bool aGetInsertedText, nsAString& aText,
|
||||
uint32_t *aStartOffset,
|
||||
uint32_t *aEndOffset) = 0;
|
||||
static StaticRefPtr<HyperTextAccessibleWrap> sLastTextChangeAcc;
|
||||
static StaticAutoPtr<nsString> sLastTextChangeString;
|
||||
static bool sLastTextChangeWasInsert;
|
||||
static uint32_t sLastTextChangeStart;
|
||||
static uint32_t sLastTextChangeEnd;
|
||||
|
||||
private:
|
||||
HRESULT GetModifiedText(bool aGetInsertedText, IA2TextSegment *aNewText);
|
||||
|
|
|
@ -15,12 +15,6 @@
|
|||
using namespace mozilla;
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
StaticRefPtr<Accessible> HyperTextAccessibleWrap::sLastTextChangeAcc;
|
||||
StaticAutoPtr<nsString> HyperTextAccessibleWrap::sLastTextChangeString;
|
||||
uint32_t HyperTextAccessibleWrap::sLastTextChangeStart = 0;
|
||||
uint32_t HyperTextAccessibleWrap::sLastTextChangeEnd = 0;
|
||||
bool HyperTextAccessibleWrap::sLastTextChangeWasInsert = false;
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessibleWrap,
|
||||
HyperTextAccessible)
|
||||
|
||||
|
@ -59,44 +53,15 @@ HyperTextAccessibleWrap::HandleAccEvent(AccEvent* aEvent)
|
|||
eventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED) {
|
||||
Accessible* accessible = aEvent->GetAccessible();
|
||||
if (accessible && accessible->IsHyperText()) {
|
||||
sLastTextChangeAcc = accessible;
|
||||
if (!sLastTextChangeString)
|
||||
sLastTextChangeString = new nsString();
|
||||
|
||||
AccTextChangeEvent* event = downcast_accEvent(aEvent);
|
||||
event->GetModifiedText(*sLastTextChangeString);
|
||||
sLastTextChangeStart = event->GetStartOffset();
|
||||
sLastTextChangeEnd = sLastTextChangeStart + event->GetLength();
|
||||
sLastTextChangeWasInsert = event->IsTextInserted();
|
||||
HyperTextAccessibleWrap* text =
|
||||
static_cast<HyperTextAccessibleWrap*>(accessible->AsHyperText());
|
||||
ia2AccessibleText::UpdateTextChangeData(text, event->IsTextInserted(),
|
||||
event->ModifiedText(),
|
||||
event->GetStartOffset(),
|
||||
event->GetLength());
|
||||
}
|
||||
}
|
||||
|
||||
return HyperTextAccessible::HandleAccEvent(aEvent);
|
||||
}
|
||||
|
||||
nsresult
|
||||
HyperTextAccessibleWrap::GetModifiedText(bool aGetInsertedText,
|
||||
nsAString& aText,
|
||||
uint32_t* aStartOffset,
|
||||
uint32_t* aEndOffset)
|
||||
{
|
||||
aText.Truncate();
|
||||
*aStartOffset = 0;
|
||||
*aEndOffset = 0;
|
||||
|
||||
if (!sLastTextChangeAcc)
|
||||
return NS_OK;
|
||||
|
||||
if (aGetInsertedText != sLastTextChangeWasInsert)
|
||||
return NS_OK;
|
||||
|
||||
if (sLastTextChangeAcc != this)
|
||||
return NS_OK;
|
||||
|
||||
*aStartOffset = sLastTextChangeStart;
|
||||
*aEndOffset = sLastTextChangeEnd;
|
||||
aText.Append(*sLastTextChangeString);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,18 +38,6 @@ public:
|
|||
|
||||
protected:
|
||||
~HyperTextAccessibleWrap() {}
|
||||
|
||||
virtual nsresult GetModifiedText(bool aGetInsertedText, nsAString& aText,
|
||||
uint32_t *aStartOffset,
|
||||
uint32_t *aEndOffset);
|
||||
|
||||
static StaticRefPtr<Accessible> sLastTextChangeAcc;
|
||||
static StaticAutoPtr<nsString> sLastTextChangeString;
|
||||
static bool sLastTextChangeWasInsert;
|
||||
static uint32_t sLastTextChangeStart;
|
||||
static uint32_t sLastTextChangeEnd;
|
||||
|
||||
friend void PlatformInit();
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
|
|
@ -9,12 +9,11 @@
|
|||
#include "AccEvent.h"
|
||||
#include "Compatibility.h"
|
||||
#include "HyperTextAccessibleWrap.h"
|
||||
#include "ia2AccessibleText.h"
|
||||
#include "nsWinUtils.h"
|
||||
#include "mozilla/a11y/ProxyAccessible.h"
|
||||
#include "ProxyWrappers.h"
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
|
@ -24,8 +23,7 @@ a11y::PlatformInit()
|
|||
Compatibility::Init();
|
||||
|
||||
nsWinUtils::MaybeStartWindowEmulation();
|
||||
ClearOnShutdown(&HyperTextAccessibleWrap::sLastTextChangeAcc);
|
||||
ClearOnShutdown(&HyperTextAccessibleWrap::sLastTextChangeString);
|
||||
ia2AccessibleText::InitTextChangeData();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -74,7 +72,12 @@ a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
|
|||
}
|
||||
|
||||
void
|
||||
a11y::ProxyTextChangeEvent(ProxyAccessible*, const nsString&, int32_t, uint32_t,
|
||||
bool, bool)
|
||||
a11y::ProxyTextChangeEvent(ProxyAccessible* aText, const nsString& aStr,
|
||||
int32_t aStart, uint32_t aLen, bool aInsert, bool)
|
||||
{
|
||||
AccessibleWrap* wrapper = WrapperFor(aText);
|
||||
auto text = static_cast<HyperTextAccessibleWrap*>(wrapper->AsHyperText());
|
||||
if (text) {
|
||||
ia2AccessibleText::UpdateTextChangeData(text, aInsert, aStr, aStart, aLen);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2567,6 +2567,9 @@
|
|||
remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
|
||||
}
|
||||
|
||||
aOtherBrowser.docShellIsActive = (ourBrowser == this.selectedBrowser &&
|
||||
window.windowState != window.STATE_MINIMIZED);
|
||||
|
||||
// Swap the docshells
|
||||
ourBrowser.swapDocShells(aOtherBrowser);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[DEFAULT]
|
||||
head = head.js
|
||||
tail =
|
||||
tags = addons
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
support-files =
|
||||
|
|
|
@ -13,6 +13,7 @@ support-files = data/**
|
|||
[test_bug415367.js]
|
||||
[test_bug519468.js]
|
||||
[test_bug564667.js]
|
||||
tags = addons
|
||||
[test_bug848297.js]
|
||||
[test_crlf.js]
|
||||
[test_data_protocol_registration.js]
|
||||
|
|
|
@ -51,9 +51,9 @@ Animation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|||
void
|
||||
Animation::SetEffect(KeyframeEffectReadOnly* aEffect)
|
||||
{
|
||||
// FIXME: We should perform an early return if aEffect == mEffect but
|
||||
// current nsAnimationManager::CheckAnimationRule is relying on this
|
||||
// method updating timing even in that case.
|
||||
if (mEffect == aEffect) {
|
||||
return;
|
||||
}
|
||||
if (mEffect) {
|
||||
mEffect->SetParentTime(Nullable<TimeDuration>());
|
||||
}
|
||||
|
@ -650,6 +650,16 @@ Animation::ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
Animation::NotifyEffectTimingUpdated()
|
||||
{
|
||||
MOZ_ASSERT(mEffect,
|
||||
"We should only update timing effect when we have a target "
|
||||
"effect");
|
||||
UpdateTiming(Animation::SeekFlag::NoSeek,
|
||||
Animation::SyncNotifyFlag::Async);
|
||||
}
|
||||
|
||||
// http://w3c.github.io/web-animations/#play-an-animation
|
||||
void
|
||||
Animation::DoPlay(ErrorResult& aRv, LimitBehavior aLimitBehavior)
|
||||
|
|
|
@ -297,6 +297,9 @@ public:
|
|||
void ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
|
||||
nsCSSPropertySet& aSetProperties,
|
||||
bool& aNeedsRefreshes);
|
||||
|
||||
void NotifyEffectTimingUpdated();
|
||||
|
||||
protected:
|
||||
void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
|
||||
void SilentlySetPlaybackRate(double aPlaybackRate);
|
||||
|
|
|
@ -87,6 +87,17 @@ KeyframeEffectReadOnly::SetParentTime(Nullable<TimeDuration> aParentTime)
|
|||
mParentTime = aParentTime;
|
||||
}
|
||||
|
||||
void
|
||||
KeyframeEffectReadOnly::SetTiming(const AnimationTiming& aTiming,
|
||||
Animation& aOwningAnimation)
|
||||
{
|
||||
if (mTiming == aTiming) {
|
||||
return;
|
||||
}
|
||||
mTiming = aTiming;
|
||||
aOwningAnimation.NotifyEffectTimingUpdated();
|
||||
}
|
||||
|
||||
ComputedTiming
|
||||
KeyframeEffectReadOnly::GetComputedTimingAt(
|
||||
const Nullable<TimeDuration>& aLocalTime,
|
||||
|
|
|
@ -242,6 +242,10 @@ public:
|
|||
return mTiming;
|
||||
}
|
||||
|
||||
// FIXME: Drop |aOwningAnimation| once we make AnimationEffects track their
|
||||
// owning animation.
|
||||
void SetTiming(const AnimationTiming& aTiming, Animation& aOwningAnimtion);
|
||||
|
||||
// Return the duration from the start the active interval to the point where
|
||||
// the animation begins playback. This is zero unless the animation has
|
||||
// a negative delay in which case it is the absolute value of the delay.
|
||||
|
|
|
@ -1900,7 +1900,7 @@ GK_ATOM(ondevicelight, "ondevicelight")
|
|||
|
||||
// Audio channel events
|
||||
GK_ATOM(onmozinterruptbegin, "onmozinterruptbegin")
|
||||
GK_ATOM(onmozinterruptend, "onmozinterruptbegin")
|
||||
GK_ATOM(onmozinterruptend, "onmozinterruptend")
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Special atoms
|
||||
|
|
|
@ -438,6 +438,8 @@ TabParent::Destroy()
|
|||
return;
|
||||
}
|
||||
|
||||
IMEStateManager::OnTabParentDestroying(this);
|
||||
|
||||
RemoveWindowListeners();
|
||||
|
||||
// If this fails, it's most likely due to a content-process crash,
|
||||
|
@ -484,6 +486,8 @@ TabParent::Recv__delete__()
|
|||
void
|
||||
TabParent::ActorDestroy(ActorDestroyReason why)
|
||||
{
|
||||
// Even though TabParent::Destroy calls this, we need to do it here too in
|
||||
// case of a crash.
|
||||
IMEStateManager::OnTabParentDestroying(this);
|
||||
|
||||
nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "MediaTimer.h"
|
||||
#include "AudioSink.h"
|
||||
#include "mediasink/DecodedAudioDataSink.h"
|
||||
#include "nsTArray.h"
|
||||
#include "MediaDecoder.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
|
@ -1072,7 +1072,7 @@ void MediaDecoderStateMachine::MaybeStartPlayback()
|
|||
SetPlayStartTime(TimeStamp::Now());
|
||||
MOZ_ASSERT(IsPlaying());
|
||||
|
||||
StartAudioThread();
|
||||
StartAudioSink();
|
||||
StartDecodedStream();
|
||||
|
||||
DispatchDecodeTasksIfNeeded();
|
||||
|
@ -1461,7 +1461,7 @@ MediaDecoderStateMachine::Seek(SeekTarget aTarget)
|
|||
return mPendingSeek.mPromise.Ensure(__func__);
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::StopAudioThread()
|
||||
void MediaDecoderStateMachine::StopAudioSink()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
@ -1754,7 +1754,7 @@ MediaDecoderStateMachine::RequestVideoData()
|
|||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::StartAudioThread()
|
||||
MediaDecoderStateMachine::StartAudioSink()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
@ -1765,9 +1765,9 @@ MediaDecoderStateMachine::StartAudioThread()
|
|||
|
||||
if (HasAudio() && !mAudioSink) {
|
||||
mAudioCompleted = false;
|
||||
mAudioSink = new AudioSink(mAudioQueue,
|
||||
GetMediaTime(), mInfo.mAudio,
|
||||
mDecoder->GetAudioChannel());
|
||||
mAudioSink = new DecodedAudioDataSink(mAudioQueue,
|
||||
GetMediaTime(), mInfo.mAudio,
|
||||
mDecoder->GetAudioChannel());
|
||||
|
||||
mAudioSinkPromise.Begin(
|
||||
mAudioSink->Init()->Then(
|
||||
|
@ -2369,12 +2369,12 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
|||
// Play the remaining media. We want to run AdvanceFrame() at least
|
||||
// once to ensure the current playback position is advanced to the
|
||||
// end of the media, and so that we update the readyState.
|
||||
MaybeStartPlayback();
|
||||
if (VideoQueue().GetSize() > 1 ||
|
||||
(HasAudio() && !mAudioCompleted) ||
|
||||
(mAudioCaptured && !mDecodedStream->IsFinished()))
|
||||
{
|
||||
// Start playback if necessary to play the remaining media.
|
||||
MaybeStartPlayback();
|
||||
UpdateRenderedVideoFrames();
|
||||
NS_ASSERTION(!IsPlaying() ||
|
||||
mLogicallySeeking ||
|
||||
|
@ -2409,7 +2409,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
|||
|
||||
// Stop audio sink after call to AudioEndTime() above, otherwise it will
|
||||
// return an incorrect value due to a null mAudioSink.
|
||||
StopAudioThread();
|
||||
StopAudioSink();
|
||||
StopDecodedStream();
|
||||
}
|
||||
|
||||
|
@ -2439,7 +2439,7 @@ MediaDecoderStateMachine::Reset()
|
|||
// Stop the audio thread. Otherwise, AudioSink might be accessing AudioQueue
|
||||
// outside of the decoder monitor while we are clearing the queue and causes
|
||||
// crash for no samples to be popped.
|
||||
StopAudioThread();
|
||||
StopAudioSink();
|
||||
StopDecodedStream();
|
||||
|
||||
mVideoFrameEndTime = -1;
|
||||
|
@ -3054,7 +3054,7 @@ MediaDecoderStateMachine::AudioEndTime() const
|
|||
return mDecodedStream->AudioEndTime();
|
||||
}
|
||||
// Don't call this after mAudioSink becomes null since we can't distinguish
|
||||
// "before StartAudioThread" and "after StopAudioThread" where mAudioSink
|
||||
// "before StartAudioSink" and "after StopAudioSink" where mAudioSink
|
||||
// is null in both cases.
|
||||
MOZ_ASSERT(!mAudioCompleted);
|
||||
return -1;
|
||||
|
@ -3135,7 +3135,7 @@ void MediaDecoderStateMachine::DispatchAudioCaptured()
|
|||
ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
|
||||
if (!self->mAudioCaptured) {
|
||||
// Stop the audio sink if it's running.
|
||||
self->StopAudioThread();
|
||||
self->StopAudioSink();
|
||||
self->mAudioCaptured = true;
|
||||
// Start DecodedStream if we are already playing. Otherwise it will be
|
||||
// handled in MaybeStartPlayback().
|
||||
|
@ -3160,7 +3160,7 @@ void MediaDecoderStateMachine::DispatchAudioUncaptured()
|
|||
// Start again the audio sink.
|
||||
self->mAudioCaptured = false;
|
||||
if (self->IsPlaying()) {
|
||||
self->StartAudioThread();
|
||||
self->StartAudioSink();
|
||||
}
|
||||
self->ScheduleStateMachine();
|
||||
}
|
||||
|
|
|
@ -99,8 +99,11 @@ hardware (via AudioStream).
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
class AudioSegment;
|
||||
namespace media {
|
||||
class AudioSink;
|
||||
}
|
||||
|
||||
class AudioSegment;
|
||||
class TaskQueue;
|
||||
|
||||
extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
|
@ -510,13 +513,15 @@ protected:
|
|||
// state machine thread.
|
||||
void UpdateRenderedVideoFrames();
|
||||
|
||||
// Stops the audio thread. The decoder monitor must be held with exactly
|
||||
// one lock count. Called on the state machine thread.
|
||||
void StopAudioThread();
|
||||
// Stops the audio sink and shut it down.
|
||||
// The decoder monitor must be held with exactly one lock count.
|
||||
// Called on the state machine thread.
|
||||
void StopAudioSink();
|
||||
|
||||
// Starts the audio thread. The decoder monitor must be held with exactly
|
||||
// one lock count. Called on the state machine thread.
|
||||
void StartAudioThread();
|
||||
// Create and start the audio sink.
|
||||
// The decoder monitor must be held with exactly one lock count.
|
||||
// Called on the state machine thread.
|
||||
void StartAudioSink();
|
||||
|
||||
void StopDecodedStream();
|
||||
|
||||
|
@ -997,7 +1002,7 @@ private:
|
|||
int64_t mFragmentEndTime;
|
||||
|
||||
// The audio sink resource. Used on state machine and audio threads.
|
||||
RefPtr<AudioSink> mAudioSink;
|
||||
RefPtr<media::AudioSink> mAudioSink;
|
||||
|
||||
// The reader, don't call its methods with the decoder monitor held.
|
||||
// This is created in the state machine's constructor.
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#if !defined(AudioSink_h__)
|
||||
#define AudioSink_h__
|
||||
|
||||
#include "mozilla/nsRefPtr.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MediaData;
|
||||
template <class T> class MediaQueue;
|
||||
|
||||
namespace media {
|
||||
|
||||
/*
|
||||
* Define basic APIs for derived class instance to operate or obtain
|
||||
* information from it.
|
||||
*/
|
||||
class AudioSink {
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioSink)
|
||||
AudioSink(MediaQueue<MediaData>& aAudioQueue)
|
||||
: mAudioQueue(aAudioQueue)
|
||||
{}
|
||||
|
||||
// Return a promise which will be resolved when AudioSink finishes playing,
|
||||
// or rejected if any error.
|
||||
virtual nsRefPtr<GenericPromise> Init() = 0;
|
||||
|
||||
virtual int64_t GetEndTime() const = 0;
|
||||
virtual int64_t GetPosition() = 0;
|
||||
|
||||
// Check whether we've pushed more frames to the audio
|
||||
// hardware than it has played.
|
||||
virtual bool HasUnplayedFrames() = 0;
|
||||
|
||||
// Shut down the AudioSink's resources.
|
||||
virtual void Shutdown() = 0;
|
||||
|
||||
// Change audio playback setting.
|
||||
virtual void SetVolume(double aVolume) = 0;
|
||||
virtual void SetPlaybackRate(double aPlaybackRate) = 0;
|
||||
virtual void SetPreservesPitch(bool aPreservesPitch) = 0;
|
||||
|
||||
// Change audio playback status pause/resume.
|
||||
virtual void SetPlaying(bool aPlaying) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~AudioSink() {}
|
||||
|
||||
virtual MediaQueue<MediaData>& AudioQueue() const {
|
||||
return mAudioQueue;
|
||||
}
|
||||
|
||||
// To queue audio data (no matter it's plain or encoded or encrypted, depends
|
||||
// on the subclass)
|
||||
MediaQueue<MediaData>& mAudioQueue;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
|
@ -4,9 +4,9 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "AudioSink.h"
|
||||
#include "AudioStream.h"
|
||||
#include "MediaQueue.h"
|
||||
#include "DecodedAudioDataSink.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
#include "mozilla/CheckedInt.h"
|
||||
|
@ -16,19 +16,23 @@ namespace mozilla {
|
|||
|
||||
extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
#define SINK_LOG(msg, ...) \
|
||||
MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("AudioSink=%p " msg, this, ##__VA_ARGS__))
|
||||
MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, \
|
||||
("DecodedAudioDataSink=%p " msg, this, ##__VA_ARGS__))
|
||||
#define SINK_LOG_V(msg, ...) \
|
||||
MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, ("AudioSink=%p " msg, this, ##__VA_ARGS__))
|
||||
MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, \
|
||||
("DecodedAudioDataSink=%p " msg, this, ##__VA_ARGS__))
|
||||
|
||||
namespace media {
|
||||
|
||||
// The amount of audio frames that is used to fuzz rounding errors.
|
||||
static const int64_t AUDIO_FUZZ_FRAMES = 1;
|
||||
|
||||
AudioSink::AudioSink(MediaQueue<MediaData>& aAudioQueue,
|
||||
int64_t aStartTime,
|
||||
const AudioInfo& aInfo,
|
||||
dom::AudioChannel aChannel)
|
||||
: mAudioQueue(aAudioQueue)
|
||||
, mMonitor("AudioSink::mMonitor")
|
||||
DecodedAudioDataSink::DecodedAudioDataSink(MediaQueue<MediaData>& aAudioQueue,
|
||||
int64_t aStartTime,
|
||||
const AudioInfo& aInfo,
|
||||
dom::AudioChannel aChannel)
|
||||
: AudioSink(aAudioQueue)
|
||||
, mMonitor("DecodedAudioDataSink::mMonitor")
|
||||
, mState(AUDIOSINK_STATE_INIT)
|
||||
, mAudioLoopScheduled(false)
|
||||
, mStartTime(aStartTime)
|
||||
|
@ -42,14 +46,14 @@ AudioSink::AudioSink(MediaQueue<MediaData>& aAudioQueue,
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::SetState(State aState)
|
||||
DecodedAudioDataSink::SetState(State aState)
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
mPendingState = Some(aState);
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::DispatchTask(already_AddRefed<nsIRunnable>&& event)
|
||||
DecodedAudioDataSink::DispatchTask(already_AddRefed<nsIRunnable>&& event)
|
||||
{
|
||||
DebugOnly<nsresult> rv = mThread->Dispatch(Move(event), NS_DISPATCH_NORMAL);
|
||||
// There isn't much we can do if Dispatch() fails.
|
||||
|
@ -58,7 +62,7 @@ AudioSink::DispatchTask(already_AddRefed<nsIRunnable>&& event)
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::OnAudioQueueEvent()
|
||||
DecodedAudioDataSink::OnAudioQueueEvent()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
if (!mAudioLoopScheduled) {
|
||||
|
@ -67,17 +71,17 @@ AudioSink::OnAudioQueueEvent()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::ConnectListener()
|
||||
DecodedAudioDataSink::ConnectListener()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
mPushListener = AudioQueue().PushEvent().Connect(
|
||||
mThread, this, &AudioSink::OnAudioQueueEvent);
|
||||
mThread, this, &DecodedAudioDataSink::OnAudioQueueEvent);
|
||||
mFinishListener = AudioQueue().FinishEvent().Connect(
|
||||
mThread, this, &AudioSink::OnAudioQueueEvent);
|
||||
mThread, this, &DecodedAudioDataSink::OnAudioQueueEvent);
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::DisconnectListener()
|
||||
DecodedAudioDataSink::DisconnectListener()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
mPushListener.Disconnect();
|
||||
|
@ -85,22 +89,22 @@ AudioSink::DisconnectListener()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::ScheduleNextLoop()
|
||||
DecodedAudioDataSink::ScheduleNextLoop()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
if (mAudioLoopScheduled) {
|
||||
return;
|
||||
}
|
||||
mAudioLoopScheduled = true;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &AudioSink::AudioLoop);
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &DecodedAudioDataSink::AudioLoop);
|
||||
DispatchTask(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::ScheduleNextLoopCrossThread()
|
||||
DecodedAudioDataSink::ScheduleNextLoopCrossThread()
|
||||
{
|
||||
AssertNotOnAudioThread();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsRefPtr<DecodedAudioDataSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () {
|
||||
// Do nothing if there is already a pending task waiting for its turn.
|
||||
if (!self->mAudioLoopScheduled) {
|
||||
|
@ -111,7 +115,7 @@ AudioSink::ScheduleNextLoopCrossThread()
|
|||
}
|
||||
|
||||
nsRefPtr<GenericPromise>
|
||||
AudioSink::Init()
|
||||
DecodedAudioDataSink::Init()
|
||||
{
|
||||
nsRefPtr<GenericPromise> p = mEndPromise.Ensure(__func__);
|
||||
nsresult rv = NS_NewNamedThread("Media Audio",
|
||||
|
@ -128,7 +132,7 @@ AudioSink::Init()
|
|||
}
|
||||
|
||||
int64_t
|
||||
AudioSink::GetPosition()
|
||||
DecodedAudioDataSink::GetPosition()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
|
||||
|
@ -143,7 +147,7 @@ AudioSink::GetPosition()
|
|||
}
|
||||
|
||||
bool
|
||||
AudioSink::HasUnplayedFrames()
|
||||
DecodedAudioDataSink::HasUnplayedFrames()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
// Experimentation suggests that GetPositionInFrames() is zero-indexed,
|
||||
|
@ -152,7 +156,7 @@ AudioSink::HasUnplayedFrames()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::Shutdown()
|
||||
DecodedAudioDataSink::Shutdown()
|
||||
{
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
|
@ -160,7 +164,7 @@ AudioSink::Shutdown()
|
|||
mAudioStream->Cancel();
|
||||
}
|
||||
}
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsRefPtr<DecodedAudioDataSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
self->mStopAudioThread = true;
|
||||
if (!self->mAudioLoopScheduled) {
|
||||
|
@ -184,10 +188,10 @@ AudioSink::Shutdown()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::SetVolume(double aVolume)
|
||||
DecodedAudioDataSink::SetVolume(double aVolume)
|
||||
{
|
||||
AssertNotOnAudioThread();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsRefPtr<DecodedAudioDataSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
if (self->mState == AUDIOSINK_STATE_PLAYING) {
|
||||
self->mAudioStream->SetVolume(aVolume);
|
||||
|
@ -197,11 +201,11 @@ AudioSink::SetVolume(double aVolume)
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::SetPlaybackRate(double aPlaybackRate)
|
||||
DecodedAudioDataSink::SetPlaybackRate(double aPlaybackRate)
|
||||
{
|
||||
AssertNotOnAudioThread();
|
||||
MOZ_ASSERT(aPlaybackRate != 0, "Don't set the playbackRate to 0 on AudioStream");
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsRefPtr<DecodedAudioDataSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
if (self->mState == AUDIOSINK_STATE_PLAYING) {
|
||||
self->mAudioStream->SetPlaybackRate(aPlaybackRate);
|
||||
|
@ -211,10 +215,10 @@ AudioSink::SetPlaybackRate(double aPlaybackRate)
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::SetPreservesPitch(bool aPreservesPitch)
|
||||
DecodedAudioDataSink::SetPreservesPitch(bool aPreservesPitch)
|
||||
{
|
||||
AssertNotOnAudioThread();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsRefPtr<DecodedAudioDataSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
if (self->mState == AUDIOSINK_STATE_PLAYING) {
|
||||
self->mAudioStream->SetPreservesPitch(aPreservesPitch);
|
||||
|
@ -224,10 +228,10 @@ AudioSink::SetPreservesPitch(bool aPreservesPitch)
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::SetPlaying(bool aPlaying)
|
||||
DecodedAudioDataSink::SetPlaying(bool aPlaying)
|
||||
{
|
||||
AssertNotOnAudioThread();
|
||||
nsRefPtr<AudioSink> self = this;
|
||||
nsRefPtr<DecodedAudioDataSink> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
if (self->mState != AUDIOSINK_STATE_PLAYING ||
|
||||
self->mPlaying == aPlaying) {
|
||||
|
@ -249,7 +253,7 @@ AudioSink::SetPlaying(bool aPlaying)
|
|||
}
|
||||
|
||||
nsresult
|
||||
AudioSink::InitializeAudioStream()
|
||||
DecodedAudioDataSink::InitializeAudioStream()
|
||||
{
|
||||
// AudioStream initialization can block for extended periods in unusual
|
||||
// circumstances, so we take care to drop the decoder monitor while
|
||||
|
@ -269,7 +273,7 @@ AudioSink::InitializeAudioStream()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::Drain()
|
||||
DecodedAudioDataSink::Drain()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
MOZ_ASSERT(mPlaying && !mAudioStream->IsPaused());
|
||||
|
@ -280,7 +284,7 @@ AudioSink::Drain()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::Cleanup()
|
||||
DecodedAudioDataSink::Cleanup()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
mEndPromise.Resolve(true, __func__);
|
||||
|
@ -290,13 +294,13 @@ AudioSink::Cleanup()
|
|||
}
|
||||
|
||||
bool
|
||||
AudioSink::ExpectMoreAudioData()
|
||||
DecodedAudioDataSink::ExpectMoreAudioData()
|
||||
{
|
||||
return AudioQueue().GetSize() == 0 && !AudioQueue().IsFinished();
|
||||
}
|
||||
|
||||
bool
|
||||
AudioSink::WaitingForAudioToPlay()
|
||||
DecodedAudioDataSink::WaitingForAudioToPlay()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
// Return true if we're not playing, and we're not shutting down, or we're
|
||||
|
@ -308,7 +312,7 @@ AudioSink::WaitingForAudioToPlay()
|
|||
}
|
||||
|
||||
bool
|
||||
AudioSink::IsPlaybackContinuing()
|
||||
DecodedAudioDataSink::IsPlaybackContinuing()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
// If we're shutting down, captured, or at EOS, break out and exit the audio
|
||||
|
@ -321,7 +325,7 @@ AudioSink::IsPlaybackContinuing()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::AudioLoop()
|
||||
DecodedAudioDataSink::AudioLoop()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
mAudioLoopScheduled = false;
|
||||
|
@ -386,7 +390,7 @@ AudioSink::AudioLoop()
|
|||
}
|
||||
|
||||
bool
|
||||
AudioSink::PlayAudio()
|
||||
DecodedAudioDataSink::PlayAudio()
|
||||
{
|
||||
// See if there's a gap in the audio. If there is, push silence into the
|
||||
// audio hardware, so we can play across the gap.
|
||||
|
@ -420,7 +424,7 @@ AudioSink::PlayAudio()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::FinishAudioLoop()
|
||||
DecodedAudioDataSink::FinishAudioLoop()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
MOZ_ASSERT(mStopAudioThread || AudioQueue().AtEndOfStream());
|
||||
|
@ -433,7 +437,7 @@ AudioSink::FinishAudioLoop()
|
|||
}
|
||||
|
||||
uint32_t
|
||||
AudioSink::PlaySilence(uint32_t aFrames)
|
||||
DecodedAudioDataSink::PlaySilence(uint32_t aFrames)
|
||||
{
|
||||
// Maximum number of bytes we'll allocate and write at once to the audio
|
||||
// hardware when the audio stream contains missing frames and we're
|
||||
|
@ -452,7 +456,7 @@ AudioSink::PlaySilence(uint32_t aFrames)
|
|||
}
|
||||
|
||||
uint32_t
|
||||
AudioSink::PlayFromAudioQueue()
|
||||
DecodedAudioDataSink::PlayFromAudioQueue()
|
||||
{
|
||||
AssertOnAudioThread();
|
||||
NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
|
||||
|
@ -475,7 +479,7 @@ AudioSink::PlayFromAudioQueue()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::StartAudioStreamPlaybackIfNeeded()
|
||||
DecodedAudioDataSink::StartAudioStreamPlaybackIfNeeded()
|
||||
{
|
||||
// This value has been chosen empirically.
|
||||
const uint32_t MIN_WRITE_BEFORE_START_USECS = 200000;
|
||||
|
@ -488,7 +492,7 @@ AudioSink::StartAudioStreamPlaybackIfNeeded()
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::WriteSilence(uint32_t aFrames)
|
||||
DecodedAudioDataSink::WriteSilence(uint32_t aFrames)
|
||||
{
|
||||
uint32_t numSamples = aFrames * mInfo.mChannels;
|
||||
nsAutoTArray<AudioDataValue, 1000> buf;
|
||||
|
@ -500,7 +504,7 @@ AudioSink::WriteSilence(uint32_t aFrames)
|
|||
}
|
||||
|
||||
int64_t
|
||||
AudioSink::GetEndTime() const
|
||||
DecodedAudioDataSink::GetEndTime() const
|
||||
{
|
||||
CheckedInt64 playedUsecs = FramesToUsecs(mWritten, mInfo.mRate) + mStartTime;
|
||||
if (!playedUsecs.isValid()) {
|
||||
|
@ -511,15 +515,16 @@ AudioSink::GetEndTime() const
|
|||
}
|
||||
|
||||
void
|
||||
AudioSink::AssertOnAudioThread()
|
||||
DecodedAudioDataSink::AssertOnAudioThread()
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mThread);
|
||||
}
|
||||
|
||||
void
|
||||
AudioSink::AssertNotOnAudioThread()
|
||||
DecodedAudioDataSink::AssertNotOnAudioThread()
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() != mThread);
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace mozilla
|
|
@ -3,9 +3,10 @@
|
|||
/* 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/. */
|
||||
#if !defined(AudioSink_h__)
|
||||
#define AudioSink_h__
|
||||
#if !defined(DecodedAudioDataSink_h__)
|
||||
#define DecodedAudioDataSink_h__
|
||||
|
||||
#include "AudioSink.h"
|
||||
#include "MediaInfo.h"
|
||||
#include "mozilla/nsRefPtr.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
@ -17,41 +18,37 @@
|
|||
#include "mozilla/ReentrantMonitor.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace media {
|
||||
|
||||
class AudioData;
|
||||
class AudioStream;
|
||||
template <class T> class MediaQueue;
|
||||
|
||||
class AudioSink {
|
||||
class DecodedAudioDataSink : public AudioSink {
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioSink)
|
||||
|
||||
AudioSink(MediaQueue<MediaData>& aAudioQueue,
|
||||
int64_t aStartTime,
|
||||
const AudioInfo& aInfo,
|
||||
dom::AudioChannel aChannel);
|
||||
DecodedAudioDataSink(MediaQueue<MediaData>& aAudioQueue,
|
||||
int64_t aStartTime,
|
||||
const AudioInfo& aInfo,
|
||||
dom::AudioChannel aChannel);
|
||||
|
||||
// Return a promise which will be resolved when AudioSink finishes playing,
|
||||
// or rejected if any error.
|
||||
nsRefPtr<GenericPromise> Init();
|
||||
// Return a promise which will be resolved when DecodedAudioDataSink
|
||||
// finishes playing, or rejected if any error.
|
||||
nsRefPtr<GenericPromise> Init() override;
|
||||
|
||||
/*
|
||||
* All public functions below are thread-safe.
|
||||
*/
|
||||
int64_t GetPosition();
|
||||
int64_t GetEndTime() const;
|
||||
int64_t GetPosition() override;
|
||||
int64_t GetEndTime() const override;
|
||||
|
||||
// Check whether we've pushed more frames to the audio hardware than it has
|
||||
// played.
|
||||
bool HasUnplayedFrames();
|
||||
bool HasUnplayedFrames() override;
|
||||
|
||||
// Shut down the AudioSink's resources.
|
||||
void Shutdown();
|
||||
// Shut down the DecodedAudioDataSink's resources.
|
||||
void Shutdown() override;
|
||||
|
||||
void SetVolume(double aVolume);
|
||||
void SetPlaybackRate(double aPlaybackRate);
|
||||
void SetPreservesPitch(bool aPreservesPitch);
|
||||
void SetPlaying(bool aPlaying);
|
||||
void SetVolume(double aVolume) override;
|
||||
void SetPlaybackRate(double aPlaybackRate) override;
|
||||
void SetPreservesPitch(bool aPreservesPitch) override;
|
||||
void SetPlaying(bool aPlaying) override;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
|
@ -62,7 +59,7 @@ private:
|
|||
AUDIOSINK_STATE_ERROR
|
||||
};
|
||||
|
||||
~AudioSink() {}
|
||||
virtual ~DecodedAudioDataSink() {}
|
||||
|
||||
void DispatchTask(already_AddRefed<nsIRunnable>&& event);
|
||||
void SetState(State aState);
|
||||
|
@ -119,10 +116,6 @@ private:
|
|||
void StartAudioStreamPlaybackIfNeeded();
|
||||
void WriteSilence(uint32_t aFrames);
|
||||
|
||||
MediaQueue<MediaData>& AudioQueue() const {
|
||||
return mAudioQueue;
|
||||
}
|
||||
|
||||
ReentrantMonitor& GetReentrantMonitor() const {
|
||||
return mMonitor;
|
||||
}
|
||||
|
@ -134,7 +127,6 @@ private:
|
|||
void AssertOnAudioThread();
|
||||
void AssertNotOnAudioThread();
|
||||
|
||||
MediaQueue<MediaData>& mAudioQueue;
|
||||
mutable ReentrantMonitor mMonitor;
|
||||
|
||||
// There members are accessed on the audio thread only.
|
||||
|
@ -168,7 +160,7 @@ private:
|
|||
|
||||
const AudioInfo mInfo;
|
||||
|
||||
dom::AudioChannel mChannel;
|
||||
const dom::AudioChannel mChannel;
|
||||
|
||||
bool mStopAudioThread;
|
||||
|
||||
|
@ -180,6 +172,7 @@ private:
|
|||
MediaEventListener mFinishListener;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
|
@ -0,0 +1,13 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'DecodedAudioDataSink.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
|
@ -25,6 +25,7 @@ DIRS += [
|
|||
'gmp-plugin',
|
||||
'gmp-plugin-openh264',
|
||||
'imagecapture',
|
||||
'mediasink',
|
||||
'mediasource',
|
||||
'ogg',
|
||||
'platforms',
|
||||
|
@ -193,7 +194,6 @@ UNIFIED_SOURCES += [
|
|||
'AudioChannelFormat.cpp',
|
||||
'AudioCompactor.cpp',
|
||||
'AudioSegment.cpp',
|
||||
'AudioSink.cpp',
|
||||
'AudioStream.cpp',
|
||||
'AudioStreamTrack.cpp',
|
||||
'AudioTrack.cpp',
|
||||
|
|
|
@ -23,16 +23,18 @@ MessagePortChild::RecvStopSendingDataConfirmed()
|
|||
bool
|
||||
MessagePortChild::RecvEntangled(nsTArray<MessagePortMessage>&& aMessages)
|
||||
{
|
||||
MOZ_ASSERT(mPort);
|
||||
mPort->Entangled(aMessages);
|
||||
if (mPort) {
|
||||
mPort->Entangled(aMessages);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MessagePortChild::RecvReceiveData(nsTArray<MessagePortMessage>&& aMessages)
|
||||
{
|
||||
MOZ_ASSERT(mPort);
|
||||
mPort->MessagesReceived(aMessages);
|
||||
if (mPort) {
|
||||
mPort->MessagesReceived(aMessages);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
skip-if = buildapp == 'mulet' || toolkit == 'android' || toolkit == 'gonk'
|
||||
head = head_plugins.js
|
||||
tail =
|
||||
tags = addons
|
||||
|
||||
[test_allowed_types.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
|
|
|
@ -76,6 +76,10 @@ this.RequestSyncService = {
|
|||
_timers: {},
|
||||
_pendingRequests: {},
|
||||
|
||||
// This array contains functions to be executed after the scheduling of the
|
||||
// current task or immediately if there are not scheduling in progress.
|
||||
_afterSchedulingTasks: [],
|
||||
|
||||
// Initialization of the RequestSyncService.
|
||||
init: function() {
|
||||
debug("init");
|
||||
|
@ -94,16 +98,14 @@ this.RequestSyncService = {
|
|||
// Any incoming message will be stored and processed when the async
|
||||
// operation is completed.
|
||||
|
||||
let self = this;
|
||||
this.dbTxn("readonly", function(aStore) {
|
||||
aStore.openCursor().onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (cursor) {
|
||||
self.addRegistration(cursor.value);
|
||||
cursor.continue();
|
||||
this.addRegistration(cursor.value, cursor.continue);
|
||||
}
|
||||
}
|
||||
},
|
||||
}.bind(this),
|
||||
function() {
|
||||
debug("initialization done");
|
||||
},
|
||||
|
@ -127,11 +129,10 @@ this.RequestSyncService = {
|
|||
this.close();
|
||||
|
||||
// Removing all the registrations will delete the pending timers.
|
||||
let self = this;
|
||||
this.forEachRegistration(function(aObj) {
|
||||
let key = self.principalToKey(aObj.principal);
|
||||
self.removeRegistrationInternal(aObj.data.task, key);
|
||||
});
|
||||
let key = this.principalToKey(aObj.principal);
|
||||
this.removeRegistrationInternal(aObj.data.task, key);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
|
@ -139,15 +140,21 @@ this.RequestSyncService = {
|
|||
|
||||
switch (aTopic) {
|
||||
case 'xpcom-shutdown':
|
||||
this.shutdown();
|
||||
this.executeAfterScheduling(function() {
|
||||
this.shutdown();
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
case 'clear-origin-data':
|
||||
this.clearData(aData);
|
||||
this.executeAfterScheduling(function() {
|
||||
this.clearData(aData);
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
case 'wifi-state-changed':
|
||||
this.wifiStateChanged(aSubject == 'enabled');
|
||||
this.executeAfterScheduling(function() {
|
||||
this.wifiStateChanged(aSubject == 'enabled');
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -208,7 +215,7 @@ this.RequestSyncService = {
|
|||
},
|
||||
|
||||
// Add a task to the _registrations map and create the timer if it's needed.
|
||||
addRegistration: function(aObj) {
|
||||
addRegistration: function(aObj, aCb) {
|
||||
debug('addRegistration');
|
||||
|
||||
let key = this.principalToKey(aObj.principal);
|
||||
|
@ -216,8 +223,12 @@ this.RequestSyncService = {
|
|||
this._registrations[key] = {};
|
||||
}
|
||||
|
||||
this.scheduleTimer(aObj);
|
||||
this._registrations[key][aObj.data.task] = aObj;
|
||||
this.scheduleTimer(aObj, function() {
|
||||
this._registrations[key][aObj.data.task] = aObj;
|
||||
if (aCb) {
|
||||
aCb();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Remove a task from the _registrations map and delete the timer if it's
|
||||
|
@ -289,31 +300,45 @@ this.RequestSyncService = {
|
|||
|
||||
switch (aMessage.name) {
|
||||
case "RequestSync:Register":
|
||||
this.register(aMessage.target, aMessage.data, principal);
|
||||
this.executeAfterScheduling(function() {
|
||||
this.register(aMessage.target, aMessage.data, principal);
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
case "RequestSync:Unregister":
|
||||
this.unregister(aMessage.target, aMessage.data, principal);
|
||||
this.executeAfterScheduling(function() {
|
||||
this.unregister(aMessage.target, aMessage.data, principal);
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
case "RequestSync:Registrations":
|
||||
this.registrations(aMessage.target, aMessage.data, principal);
|
||||
this.executeAfterScheduling(function() {
|
||||
this.registrations(aMessage.target, aMessage.data, principal);
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
case "RequestSync:Registration":
|
||||
this.registration(aMessage.target, aMessage.data, principal);
|
||||
this.executeAfterScheduling(function() {
|
||||
this.registration(aMessage.target, aMessage.data, principal);
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
case "RequestSyncManager:Registrations":
|
||||
this.managerRegistrations(aMessage.target, aMessage.data, principal);
|
||||
this.executeAfterScheduling(function() {
|
||||
this.managerRegistrations(aMessage.target, aMessage.data, principal);
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
case "RequestSyncManager:SetPolicy":
|
||||
this.managerSetPolicy(aMessage.target, aMessage.data, principal);
|
||||
this.executeAfterScheduling(function() {
|
||||
this.managerSetPolicy(aMessage.target, aMessage.data, principal);
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
case "RequestSyncManager:RunTask":
|
||||
this.managerRunTask(aMessage.target, aMessage.data, principal);
|
||||
this.executeAfterScheduling(function() {
|
||||
this.managerRunTask(aMessage.target, aMessage.data, principal);
|
||||
}.bind(this));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -389,9 +414,10 @@ this.RequestSyncService = {
|
|||
aStore.put(data, data.dbKey);
|
||||
},
|
||||
function() {
|
||||
self.addRegistration(data);
|
||||
aTarget.sendAsyncMessage("RequestSync:Register:Return",
|
||||
{ requestID: aData.requestID });
|
||||
self.addRegistration(data, function() {
|
||||
aTarget.sendAsyncMessage("RequestSync:Register:Return",
|
||||
{ requestID: aData.requestID });
|
||||
});
|
||||
},
|
||||
function() {
|
||||
aTarget.sendAsyncMessage("RequestSync:Register:Return",
|
||||
|
@ -524,9 +550,10 @@ this.RequestSyncService = {
|
|||
}
|
||||
|
||||
this.updateObjectInDB(toSave, function() {
|
||||
self.scheduleTimer(toSave);
|
||||
aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return",
|
||||
{ requestID: aData.requestID });
|
||||
self.scheduleTimer(toSave, function() {
|
||||
aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return",
|
||||
{ requestID: aData.requestID });
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -600,26 +627,57 @@ this.RequestSyncService = {
|
|||
},
|
||||
|
||||
// Creation of the timer for a particular task object.
|
||||
scheduleTimer: function(aObj) {
|
||||
scheduleTimer: function(aObj, aCb) {
|
||||
debug("scheduleTimer");
|
||||
|
||||
aCb = aCb || function() {};
|
||||
|
||||
this.removeTimer(aObj);
|
||||
|
||||
// A registration can be already inactive if it was 1 shot.
|
||||
if (!aObj.active) {
|
||||
aCb();
|
||||
return;
|
||||
}
|
||||
|
||||
if (aObj.data.state == RSYNC_STATE_DISABLED) {
|
||||
aCb();
|
||||
return;
|
||||
}
|
||||
|
||||
// WifiOnly check.
|
||||
if (aObj.data.state == RSYNC_STATE_WIFIONLY && !this._wifi) {
|
||||
aCb();
|
||||
return;
|
||||
}
|
||||
|
||||
this.createTimer(aObj);
|
||||
if (this.scheduling) {
|
||||
dump("ERROR!! RequestSyncService - ScheduleTimer called into ScheduleTimer.\n");
|
||||
aCb();
|
||||
return;
|
||||
}
|
||||
|
||||
this.scheduling = true;
|
||||
|
||||
this.createTimer(aObj, function() {
|
||||
this.scheduling = false;
|
||||
|
||||
while (this._afterSchedulingTasks.length) {
|
||||
var cb = this._afterSchedulingTasks.shift();
|
||||
cb();
|
||||
}
|
||||
|
||||
aCb();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
executeAfterScheduling: function(aCb) {
|
||||
if (!this.scheduling) {
|
||||
aCb();
|
||||
return;
|
||||
}
|
||||
|
||||
this._afterSchedulingTasks.push(aCb);
|
||||
},
|
||||
|
||||
timeout: function(aObj) {
|
||||
|
@ -728,17 +786,15 @@ this.RequestSyncService = {
|
|||
{ requestID: pendingRequests[i].requestID });
|
||||
}
|
||||
|
||||
let self = this;
|
||||
this.updateObjectInDB(this._activeTask, function() {
|
||||
// SchedulerTimer creates a timer and a nsITimer cannot be cloned. This
|
||||
// is the reason why this operation has to be done after storing the task
|
||||
// into IDB.
|
||||
if (!self._activeTask.data.oneShot) {
|
||||
self.scheduleTimer(self._activeTask);
|
||||
if (!this._activeTask.data.oneShot) {
|
||||
this.scheduleTimer(this._activeTask, function() {
|
||||
this.processNextTask();
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.processNextTask();
|
||||
}
|
||||
|
||||
self.processNextTask();
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
processNextTask: function() {
|
||||
|
@ -836,36 +892,37 @@ this.RequestSyncService = {
|
|||
|
||||
wifiStateChanged: function(aEnabled) {
|
||||
debug("onWifiStateChanged");
|
||||
|
||||
this._wifi = aEnabled;
|
||||
|
||||
if (!this._wifi) {
|
||||
// Disable all the wifiOnly tasks.
|
||||
let self = this;
|
||||
this.forEachRegistration(function(aObj) {
|
||||
if (aObj.data.state == RSYNC_STATE_WIFIONLY && self.hasTimer(aObj)) {
|
||||
self.removeTimer(aObj);
|
||||
if (aObj.data.state == RSYNC_STATE_WIFIONLY && this.hasTimer(aObj)) {
|
||||
this.removeTimer(aObj);
|
||||
|
||||
// It can be that this task has been already schedulated.
|
||||
self.removeTaskFromQueue(aObj);
|
||||
this.removeTaskFromQueue(aObj);
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable all the tasks.
|
||||
let self = this;
|
||||
this.forEachRegistration(function(aObj) {
|
||||
if (aObj.active && !self.hasTimer(aObj)) {
|
||||
if (aObj.active && !this.hasTimer(aObj)) {
|
||||
if (!aObj.data.wifiOnly) {
|
||||
dump("ERROR - Found a disabled task that is not wifiOnly.");
|
||||
}
|
||||
|
||||
self.scheduleTimer(aObj);
|
||||
this.scheduleTimer(aObj);
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
createTimer: function(aObj) {
|
||||
createTimer: function(aObj, aCb) {
|
||||
aCb = aCb || function() {};
|
||||
|
||||
let interval = aObj.data.minInterval;
|
||||
if (aObj.data.overwrittenMinInterval > 0) {
|
||||
interval = aObj.data.overwrittenMinInterval;
|
||||
|
@ -875,7 +932,11 @@ this.RequestSyncService = {
|
|||
{ date: new Date(Date.now() + interval * 1000),
|
||||
ignoreTimezone: false },
|
||||
() => this.timeout(aObj),
|
||||
aTimerId => this._timers[aObj.dbKey] = aTimerId);
|
||||
function(aTimerId) {
|
||||
this._timers[aObj.dbKey] = aTimerId;
|
||||
aCb();
|
||||
}.bind(this),
|
||||
() => aCb());
|
||||
},
|
||||
|
||||
hasTimer: function(aObj) {
|
||||
|
|
|
@ -17,11 +17,9 @@ skip-if = os == "android" || toolkit == "gonk"
|
|||
[test_basic_app.html]
|
||||
skip-if = os == "android" || buildapp == 'b2g'
|
||||
[test_wakeUp.html]
|
||||
#run-if = buildapp == 'b2g' && toolkit == 'gonk'
|
||||
skip-if = true
|
||||
run-if = buildapp == 'b2g' && toolkit == 'gonk'
|
||||
[test_runNow.html]
|
||||
#run-if = buildapp == 'b2g' && toolkit == 'gonk'
|
||||
skip-if = true
|
||||
run-if = buildapp == 'b2g' && toolkit == 'gonk'
|
||||
[test_promise.html]
|
||||
skip-if = os == "android" || toolkit == "gonk"
|
||||
[test_bug1151082.html]
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
#include "mozilla/dom/PromiseDebugging.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/StructuredClone.h"
|
||||
#include "mozilla/dom/TabChild.h"
|
||||
#include "mozilla/dom/WebCryptoCommon.h"
|
||||
#include "mozilla/dom/WorkerBinding.h"
|
||||
#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
|
||||
|
@ -2395,8 +2396,9 @@ InterfaceRequestor::GetAnyLiveTabChild()
|
|||
nsCOMPtr<nsITabChild> tabChild =
|
||||
do_QueryReferent(mTabChildList.LastElement());
|
||||
|
||||
// Does this tab child still exist? If so, return it. We are done.
|
||||
if (tabChild) {
|
||||
// Does this tab child still exist? If so, return it. We are done. If the
|
||||
// PBrowser actor is no longer useful, don't bother returning this tab.
|
||||
if (tabChild && !static_cast<TabChild*>(tabChild.get())->IsDestroyed()) {
|
||||
return tabChild.forget();
|
||||
}
|
||||
|
||||
|
|
|
@ -1958,6 +1958,12 @@ XMLHttpRequest::Open(const nsACString& aMethod, const nsAString& aUrl,
|
|||
return;
|
||||
}
|
||||
|
||||
// We have been released in one of the nested Open() calls.
|
||||
if (!mProxy) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
--mProxy->mOpenCount;
|
||||
mProxy->mIsSyncXHR = !aAsync;
|
||||
}
|
||||
|
|
|
@ -1793,6 +1793,7 @@ void CheckIfRenderTargetViewNeedsRecreating(ID3D11Device *device)
|
|||
// match the clear
|
||||
if (resultColor != 0xffffff00) {
|
||||
gfxCriticalNote << "RenderTargetViewNeedsRecreating";
|
||||
gANGLESupportsD3D11 = false;
|
||||
}
|
||||
|
||||
keyedMutex->ReleaseSync(0);
|
||||
|
|
|
@ -315,20 +315,18 @@ FatalError(const char* aProtocolName, const char* aMsg,
|
|||
formattedMessage.AppendLiteral("]: \"");
|
||||
formattedMessage.AppendASCII(aMsg);
|
||||
if (aIsParent) {
|
||||
formattedMessage.AppendLiteral("\". Killing child side as a result.");
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
// We're going to crash the parent process because at this time
|
||||
// there's no other really nice way of getting a minidump out of
|
||||
// this process if we're off the main thread.
|
||||
formattedMessage.AppendLiteral("\". Intentionally crashing.");
|
||||
NS_ERROR(formattedMessage.get());
|
||||
|
||||
if (aOtherPid != kInvalidProcessId && aOtherPid != base::GetCurrentProcId()) {
|
||||
ScopedProcessHandle otherProcessHandle;
|
||||
if (base::OpenProcessHandle(aOtherPid, &otherProcessHandle.rwget())) {
|
||||
if (!base::KillProcess(otherProcessHandle,
|
||||
base::PROCESS_END_KILLED_BY_USER, false)) {
|
||||
NS_ERROR("May have failed to kill child!");
|
||||
}
|
||||
} else {
|
||||
NS_ERROR("Failed to open child process when attempting kill.");
|
||||
}
|
||||
}
|
||||
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCFatalErrorProtocol"),
|
||||
nsDependentCString(aProtocolName));
|
||||
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCFatalErrorMsg"),
|
||||
nsDependentCString(aMsg));
|
||||
#endif
|
||||
MOZ_CRASH("IPC FatalError in the parent process!");
|
||||
} else {
|
||||
formattedMessage.AppendLiteral("\". abort()ing as a result.");
|
||||
NS_RUNTIMEABORT(formattedMessage.get());
|
||||
|
|
|
@ -41,6 +41,5 @@ CPOWTimer::~CPOWTimer()
|
|||
return;
|
||||
}
|
||||
|
||||
js::PerformanceData* performance = js::GetPerformanceData(runtime);
|
||||
performance->totalCPOWTime += endInterval - startInterval_;
|
||||
js::AddCPOWPerformanceDelta(runtime, endInterval - startInterval_);
|
||||
}
|
||||
|
|
|
@ -5331,6 +5331,7 @@ class AutoStopwatch;
|
|||
|
||||
// Container for performance data
|
||||
// All values are monotonic.
|
||||
// All values are updated after running to completion.
|
||||
struct PerformanceData {
|
||||
// Number of times we have spent at least 2^n consecutive
|
||||
// milliseconds executing code in this group.
|
||||
|
@ -5397,30 +5398,68 @@ struct PerformanceGroup {
|
|||
// An id unique to this runtime.
|
||||
const uint64_t uid;
|
||||
|
||||
// The number of cycles spent in this group during this iteration
|
||||
// of the event loop. Note that cycles are not a reliable measure,
|
||||
// especially over short intervals. See Runtime.cpp for a more
|
||||
// complete discussion on the imprecision of cycle measurement.
|
||||
uint64_t recentCycles;
|
||||
|
||||
// The number of times this group has been activated during this
|
||||
// iteration of the event loop.
|
||||
uint64_t recentTicks;
|
||||
|
||||
// The number of milliseconds spent doing CPOW during this
|
||||
// iteration of the event loop.
|
||||
uint64_t recentCPOW;
|
||||
|
||||
// The current iteration of the event loop.
|
||||
uint64_t iteration() const {
|
||||
return iteration_;
|
||||
}
|
||||
|
||||
// `true` if an instance of `AutoStopwatch` is already monitoring
|
||||
// the performance of this performance group for this iteration
|
||||
// of the event loop, `false` otherwise.
|
||||
bool hasStopwatch(uint64_t iteration) const {
|
||||
return stopwatch_ != nullptr && iteration_ == iteration;
|
||||
bool hasStopwatch(uint64_t it) const {
|
||||
return stopwatch_ != nullptr && iteration_ == it;
|
||||
}
|
||||
|
||||
// `true` if a specific instance of `AutoStopwatch` is already monitoring
|
||||
// the performance of this performance group for this iteration
|
||||
// of the event loop, `false` otherwise.
|
||||
bool hasStopwatch(uint64_t it, const AutoStopwatch* stopwatch) const {
|
||||
return stopwatch_ == stopwatch && iteration_ == it;
|
||||
}
|
||||
|
||||
// Mark that an instance of `AutoStopwatch` is monitoring
|
||||
// the performance of this group for a given iteration.
|
||||
void acquireStopwatch(uint64_t iteration, const AutoStopwatch* stopwatch) {
|
||||
iteration_ = iteration;
|
||||
void acquireStopwatch(uint64_t it, const AutoStopwatch* stopwatch) {
|
||||
if (iteration_ != it) {
|
||||
// Any data that pretends to be recent is actually bound
|
||||
// to an older iteration and therefore stale.
|
||||
resetRecentData();
|
||||
}
|
||||
iteration_ = it;
|
||||
stopwatch_ = stopwatch;
|
||||
}
|
||||
|
||||
// Mark that no `AutoStopwatch` is monitoring the
|
||||
// performance of this group for the iteration.
|
||||
void releaseStopwatch(uint64_t iteration, const AutoStopwatch* stopwatch) {
|
||||
if (iteration_ != iteration)
|
||||
void releaseStopwatch(uint64_t it, const AutoStopwatch* stopwatch) {
|
||||
if (iteration_ != it)
|
||||
return;
|
||||
|
||||
MOZ_ASSERT(stopwatch == stopwatch_ || stopwatch_ == nullptr);
|
||||
stopwatch_ = nullptr;
|
||||
}
|
||||
|
||||
// Get rid of any data that pretends to be recent.
|
||||
void resetRecentData() {
|
||||
recentCycles = 0;
|
||||
recentTicks = 0;
|
||||
recentCPOW = 0;
|
||||
}
|
||||
|
||||
// Refcounting. For use with mozilla::RefPtr.
|
||||
void AddRef();
|
||||
void Release();
|
||||
|
@ -5448,10 +5487,9 @@ private:
|
|||
// The hash key for this PerformanceGroup.
|
||||
void* const key_;
|
||||
|
||||
// A reference counter.
|
||||
// Refcounter.
|
||||
uint64_t refCount_;
|
||||
|
||||
|
||||
// `true` if this PerformanceGroup may be shared by several
|
||||
// compartments, `false` if it is dedicated to a single
|
||||
// compartment.
|
||||
|
@ -5511,12 +5549,19 @@ struct PerformanceGroupHolder {
|
|||
};
|
||||
|
||||
/**
|
||||
* Reset any stopwatch currently measuring.
|
||||
* Commit any Performance Monitoring data.
|
||||
*
|
||||
* This function is designed to be called when we process a new event.
|
||||
* Until `FlushMonitoring` has been called, all PerformanceMonitoring data is invisible
|
||||
* to the outside world and can cancelled with a call to `ResetMonitoring`.
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
ResetStopwatches(JSRuntime*);
|
||||
FlushPerformanceMonitoring(JSRuntime*);
|
||||
|
||||
/**
|
||||
* Cancel any measurement that hasn't been committed.
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
ResetPerformanceMonitoring(JSRuntime*);
|
||||
|
||||
/**
|
||||
* Turn on/off stopwatch-based CPU monitoring.
|
||||
|
@ -5541,11 +5586,17 @@ GetStopwatchIsMonitoringPerCompartment(JSRuntime*);
|
|||
extern JS_PUBLIC_API(bool)
|
||||
IsStopwatchActive(JSRuntime*);
|
||||
|
||||
// Extract the CPU rescheduling data.
|
||||
extern JS_PUBLIC_API(void)
|
||||
GetPerfMonitoringTestCpuRescheduling(JSRuntime*, uint64_t* stayed, uint64_t* moved);
|
||||
|
||||
|
||||
/**
|
||||
* Access the performance information stored in a compartment.
|
||||
* Add a number of microseconds to the time spent waiting on CPOWs
|
||||
* since process start.
|
||||
*/
|
||||
extern JS_PUBLIC_API(PerformanceData*)
|
||||
GetPerformanceData(JSRuntime*);
|
||||
extern JS_PUBLIC_API(void)
|
||||
AddCPOWPerformanceDelta(JSRuntime*, uint64_t delta);
|
||||
|
||||
typedef bool
|
||||
(PerformanceStatsWalker)(JSContext* cx,
|
||||
|
|
|
@ -56,14 +56,10 @@
|
|||
#include "vm/ScopeObject-inl.h"
|
||||
#include "vm/Stack-inl.h"
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
#include <mach/mach.h>
|
||||
#elif defined(XP_UNIX)
|
||||
#include <sys/resource.h>
|
||||
#elif defined(XP_WIN)
|
||||
#if defined(XP_WIN)
|
||||
#include <processthreadsapi.h>
|
||||
#include <windows.h>
|
||||
#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
|
||||
#endif // defined(XP_WIN)
|
||||
|
||||
using namespace js;
|
||||
using namespace js::gc;
|
||||
|
@ -395,25 +391,47 @@ class AutoStopwatch final
|
|||
bool isMonitoringCPOW_;
|
||||
|
||||
// Timestamps captured while starting the stopwatch.
|
||||
uint64_t userTimeStart_;
|
||||
uint64_t systemTimeStart_;
|
||||
uint64_t cyclesStart_;
|
||||
uint64_t CPOWTimeStart_;
|
||||
|
||||
// The performance group shared by this compartment and possibly
|
||||
// others, or `nullptr` if another AutoStopwatch is already in
|
||||
// charge of monitoring that group.
|
||||
mozilla::RefPtr<js::PerformanceGroup> sharedGroup_;
|
||||
// The CPU on which we started the measure. Defined only
|
||||
// if `isMonitoringJank_` is `true`.
|
||||
#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA
|
||||
struct cpuid_t {
|
||||
WORD group_;
|
||||
BYTE number_;
|
||||
cpuid_t(WORD group, BYTE number)
|
||||
: group_(group),
|
||||
number_(number)
|
||||
{ }
|
||||
cpuid_t()
|
||||
: group_(0),
|
||||
number_(0)
|
||||
{ }
|
||||
};
|
||||
#elif defined(XP_LINUX)
|
||||
typedef int cpuid_t;
|
||||
#else
|
||||
typedef struct {} cpuid_t;
|
||||
#endif // defined(XP_WIN) || defined(XP_LINUX)
|
||||
|
||||
// The toplevel group, representing the entire process, or `nullptr`
|
||||
// if another AutoStopwatch is already in charge of monitoring that group.
|
||||
mozilla::RefPtr<js::PerformanceGroup> topGroup_;
|
||||
cpuid_t cpuStart_;
|
||||
|
||||
// The performance group specific to this compartment, or
|
||||
// `nullptr` if another AutoStopwatch is already in charge of
|
||||
// monitoring that group.
|
||||
mozilla::RefPtr<js::PerformanceGroup> ownGroup_;
|
||||
// The performance group shared by this compartment and possibly
|
||||
// others, or `nullptr` if another AutoStopwatch is already in
|
||||
// charge of monitoring that group.
|
||||
mozilla::RefPtr<js::PerformanceGroup> sharedGroup_;
|
||||
|
||||
public:
|
||||
// The toplevel group, representing the entire process, or `nullptr`
|
||||
// if another AutoStopwatch is already in charge of monitoring that group.
|
||||
mozilla::RefPtr<js::PerformanceGroup> topGroup_;
|
||||
|
||||
// The performance group specific to this compartment, or
|
||||
// `nullptr` if another AutoStopwatch is already in charge of
|
||||
// monitoring that group.
|
||||
mozilla::RefPtr<js::PerformanceGroup> ownGroup_;
|
||||
|
||||
public:
|
||||
// If the stopwatch is active, constructing an instance of
|
||||
// AutoStopwatch causes it to become the current owner of the
|
||||
// stopwatch.
|
||||
|
@ -424,8 +442,7 @@ class AutoStopwatch final
|
|||
, iteration_(0)
|
||||
, isMonitoringJank_(false)
|
||||
, isMonitoringCPOW_(false)
|
||||
, userTimeStart_(0)
|
||||
, systemTimeStart_(0)
|
||||
, cyclesStart_(0)
|
||||
, CPOWTimeStart_(0)
|
||||
{
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
|
@ -435,7 +452,7 @@ class AutoStopwatch final
|
|||
return;
|
||||
|
||||
JSRuntime* runtime = cx_->runtime();
|
||||
iteration_ = runtime->stopwatch.iteration;
|
||||
iteration_ = runtime->stopwatch.iteration();
|
||||
|
||||
sharedGroup_ = acquireGroup(compartment->performanceMonitoring.getSharedGroup(cx));
|
||||
if (sharedGroup_)
|
||||
|
@ -449,14 +466,15 @@ class AutoStopwatch final
|
|||
return;
|
||||
}
|
||||
|
||||
// Now that we are sure that JS code is being executed,
|
||||
// initialize the stopwatch for this iteration, lazily.
|
||||
runtime->stopwatch.start();
|
||||
enter();
|
||||
}
|
||||
~AutoStopwatch()
|
||||
{
|
||||
if (!sharedGroup_ && !ownGroup_) {
|
||||
// We are not in charge of monitoring anything.
|
||||
// (isMonitoringForTop_ implies isMonitoringForGroup_,
|
||||
// so we do not need to check it)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -465,32 +483,32 @@ class AutoStopwatch final
|
|||
return;
|
||||
|
||||
JSRuntime* runtime = cx_->runtime();
|
||||
if (iteration_ != runtime->stopwatch.iteration) {
|
||||
if (iteration_ != runtime->stopwatch.iteration()) {
|
||||
// We have entered a nested event loop at some point.
|
||||
// Any information we may have is obsolete.
|
||||
return;
|
||||
}
|
||||
|
||||
// Finish and commit measures
|
||||
exit();
|
||||
|
||||
releaseGroup(sharedGroup_);
|
||||
releaseGroup(topGroup_);
|
||||
releaseGroup(ownGroup_);
|
||||
|
||||
// Finish and commit measures
|
||||
exit();
|
||||
}
|
||||
private:
|
||||
void enter() {
|
||||
JSRuntime* runtime = cx_->runtime();
|
||||
|
||||
if (runtime->stopwatch.isMonitoringCPOW()) {
|
||||
CPOWTimeStart_ = runtime->stopwatch.performance.getOwnGroup()->data.totalCPOWTime;
|
||||
CPOWTimeStart_ = runtime->stopwatch.totalCPOWTime;
|
||||
isMonitoringCPOW_ = true;
|
||||
}
|
||||
|
||||
if (runtime->stopwatch.isMonitoringJank()) {
|
||||
if (this->getTimes(runtime, &userTimeStart_, &systemTimeStart_)) {
|
||||
isMonitoringJank_ = true;
|
||||
}
|
||||
cyclesStart_ = this->getCycles();
|
||||
cpuStart_ = this->getCPU();
|
||||
isMonitoringJank_ = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -498,29 +516,37 @@ class AutoStopwatch final
|
|||
void exit() {
|
||||
JSRuntime* runtime = cx_->runtime();
|
||||
|
||||
uint64_t userTimeDelta = 0;
|
||||
uint64_t systemTimeDelta = 0;
|
||||
uint64_t cyclesDelta = 0;
|
||||
if (isMonitoringJank_ && runtime->stopwatch.isMonitoringJank()) {
|
||||
// We were monitoring jank when we entered and we still are.
|
||||
uint64_t userTimeEnd, systemTimeEnd;
|
||||
if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd)) {
|
||||
// We make no attempt to recover from this error. If
|
||||
// we bail out here, we lose nothing of value, plus
|
||||
// I'm nearly sure that this error cannot happen in
|
||||
// practice.
|
||||
return;
|
||||
|
||||
// If possible, discard results when we don't end on the
|
||||
// same CPU as we started. Note that we can be
|
||||
// rescheduled to another CPU beween `getCycles()` and
|
||||
// `getCPU()`. We hope that this will happen rarely
|
||||
// enough that the impact on our statistics will remain
|
||||
// limited.
|
||||
const cpuid_t cpuEnd = this->getCPU();
|
||||
if (isSameCPU(cpuStart_, cpuEnd)) {
|
||||
const uint64_t cyclesEnd = getCycles();
|
||||
cyclesDelta = getDelta(cyclesEnd, cyclesStart_);
|
||||
}
|
||||
userTimeDelta = userTimeEnd - userTimeStart_;
|
||||
systemTimeDelta = systemTimeEnd - systemTimeStart_;
|
||||
#if (defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA) || defined(XP_LINUX)
|
||||
if (isSameCPU(cpuStart_, cpuEnd))
|
||||
runtime->stopwatch.testCpuRescheduling.stayed += 1;
|
||||
else
|
||||
runtime->stopwatch.testCpuRescheduling.moved += 1;
|
||||
#endif // defined(XP_WIN) || defined(XP_LINUX)
|
||||
}
|
||||
|
||||
uint64_t CPOWTimeDelta = 0;
|
||||
if (isMonitoringCPOW_ && runtime->stopwatch.isMonitoringCPOW()) {
|
||||
// We were monitoring CPOW when we entered and we still are.
|
||||
CPOWTimeDelta = runtime->stopwatch.performance.getOwnGroup()->data.totalCPOWTime - CPOWTimeStart_;
|
||||
const uint64_t CPOWTimeEnd = runtime->stopwatch.totalCPOWTime;
|
||||
CPOWTimeDelta = getDelta(CPOWTimeEnd, CPOWTimeStart_);
|
||||
|
||||
}
|
||||
commitDeltasToGroups(userTimeDelta, systemTimeDelta, CPOWTimeDelta);
|
||||
addToGroups(cyclesDelta, CPOWTimeDelta);
|
||||
}
|
||||
|
||||
// Attempt to acquire a group
|
||||
|
@ -547,121 +573,85 @@ class AutoStopwatch final
|
|||
group->releaseStopwatch(iteration_, this);
|
||||
}
|
||||
|
||||
void commitDeltasToGroups(uint64_t userTimeDelta, uint64_t systemTimeDelta,
|
||||
uint64_t CPOWTimeDelta) const {
|
||||
applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, sharedGroup_);
|
||||
applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, topGroup_);
|
||||
applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, ownGroup_);
|
||||
// Add recent changes to all the groups owned by this stopwatch.
|
||||
// Mark the groups as changed recently.
|
||||
void addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta) {
|
||||
addToGroup(cyclesDelta, CPOWTimeDelta, sharedGroup_);
|
||||
addToGroup(cyclesDelta, CPOWTimeDelta, topGroup_);
|
||||
addToGroup(cyclesDelta, CPOWTimeDelta, ownGroup_);
|
||||
}
|
||||
|
||||
void applyDeltas(uint64_t userTimeDelta, uint64_t systemTimeDelta,
|
||||
uint64_t CPOWTimeDelta, PerformanceGroup* group) const {
|
||||
// Add recent changes to a single group. Mark the group as changed recently.
|
||||
void addToGroup(uint64_t cyclesDelta, uint64_t CPOWTimeDelta, PerformanceGroup* group) {
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
group->data.ticks++;
|
||||
MOZ_ASSERT(group->hasStopwatch(iteration_, this));
|
||||
|
||||
uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
|
||||
group->data.totalUserTime += userTimeDelta;
|
||||
group->data.totalSystemTime += systemTimeDelta;
|
||||
group->data.totalCPOWTime += CPOWTimeDelta;
|
||||
|
||||
// Update an array containing the number of times we have missed
|
||||
// at least 2^0 successive ms, 2^1 successive ms, ...
|
||||
// 2^i successive ms.
|
||||
|
||||
// Duration of one frame, i.e. 16ms in museconds
|
||||
size_t i = 0;
|
||||
uint64_t duration = 1000;
|
||||
for (i = 0, duration = 1000;
|
||||
i < ArrayLength(group->data.durations) && duration < totalTimeDelta;
|
||||
++i, duration *= 2)
|
||||
{
|
||||
group->data.durations[i]++;
|
||||
if (group->recentTicks == 0) {
|
||||
// First time we meet this group during the tick,
|
||||
// mark it as needing updates.
|
||||
JSRuntime* runtime = cx_->runtime();
|
||||
runtime->stopwatch.addChangedGroup(group);
|
||||
}
|
||||
group->recentTicks++;
|
||||
group->recentCycles += cyclesDelta;
|
||||
group->recentCPOW += CPOWTimeDelta;
|
||||
}
|
||||
|
||||
// Get the OS-reported time spent in userland/systemland, in
|
||||
// microseconds. On most platforms, this data is per-thread,
|
||||
// but on some platforms we need to fall back to per-process.
|
||||
bool getTimes(JSRuntime* runtime, uint64_t* userTime, uint64_t* systemTime) const {
|
||||
MOZ_ASSERT(userTime);
|
||||
MOZ_ASSERT(systemTime);
|
||||
// Perform a subtraction for a quantity that should be monotonic
|
||||
// but is not guaranteed to be so.
|
||||
//
|
||||
// If `start <= end`, return `end - start`.
|
||||
// Otherwise, return `0`.
|
||||
uint64_t getDelta(const uint64_t end, const uint64_t start) const
|
||||
{
|
||||
if (start >= end)
|
||||
return 0;
|
||||
return end - start;
|
||||
}
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
// On MacOS X, to get we per-thread data, we need to
|
||||
// reach into the kernel.
|
||||
|
||||
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
|
||||
thread_basic_info_data_t info;
|
||||
mach_port_t port = mach_thread_self();
|
||||
kern_return_t err =
|
||||
thread_info(/* [in] targeted thread*/ port,
|
||||
/* [in] nature of information*/ THREAD_BASIC_INFO,
|
||||
/* [out] thread information */ (thread_info_t)&info,
|
||||
/* [inout] number of items */ &count);
|
||||
|
||||
// We do not need ability to communicate with the thread, so
|
||||
// let's release the port.
|
||||
mach_port_deallocate(mach_task_self(), port);
|
||||
|
||||
if (err != KERN_SUCCESS)
|
||||
return false;
|
||||
|
||||
*userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
|
||||
*systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;
|
||||
|
||||
#elif defined(XP_UNIX)
|
||||
struct rusage rusage;
|
||||
#if defined(RUSAGE_THREAD)
|
||||
// Under Linux, we can obtain per-thread statistics
|
||||
int err = getrusage(RUSAGE_THREAD, &rusage);
|
||||
// Return the value of the Timestamp Counter, as provided by the CPU.
|
||||
// 0 on platforms for which we do not have access to a Timestamp Counter.
|
||||
uint64_t getCycles() const
|
||||
{
|
||||
#if defined(MOZ_HAVE_RDTSC)
|
||||
return ReadTimestampCounter();
|
||||
#else
|
||||
// Under other Unices, we need to do with more noisy
|
||||
// per-process statistics.
|
||||
int err = getrusage(RUSAGE_SELF, &rusage);
|
||||
#endif // defined(RUSAGE_THREAD)
|
||||
|
||||
if (err)
|
||||
return false;
|
||||
|
||||
*userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
|
||||
*systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;
|
||||
|
||||
#elif defined(XP_WIN)
|
||||
// Under Windows, we can obtain per-thread statistics,
|
||||
// although experience seems to suggest that they are
|
||||
// not very good under Windows XP.
|
||||
FILETIME creationFileTime; // Ignored
|
||||
FILETIME exitFileTime; // Ignored
|
||||
FILETIME kernelFileTime;
|
||||
FILETIME userFileTime;
|
||||
BOOL success = GetThreadTimes(GetCurrentThread(),
|
||||
&creationFileTime, &exitFileTime,
|
||||
&kernelFileTime, &userFileTime);
|
||||
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
ULARGE_INTEGER kernelTimeInt;
|
||||
ULARGE_INTEGER userTimeInt;
|
||||
kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
|
||||
kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
|
||||
// Convert 100 ns to 1 us, make sure that the result is monotonic
|
||||
*systemTime = runtime->stopwatch.systemTimeFix.monotonize(kernelTimeInt.QuadPart / 10);
|
||||
|
||||
userTimeInt.LowPart = userFileTime.dwLowDateTime;
|
||||
userTimeInt.HighPart = userFileTime.dwHighDateTime;
|
||||
// Convert 100 ns to 1 us, make sure that the result is monotonic
|
||||
*userTime = runtime->stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
|
||||
|
||||
#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
|
||||
|
||||
return true;
|
||||
return 0;
|
||||
#endif // defined(MOZ_HAVE_RDTSC)
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
// Return the identifier of the current CPU, on platforms for which we have
|
||||
// access to the current CPU.
|
||||
cpuid_t inline getCPU() const
|
||||
{
|
||||
#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA
|
||||
PROCESSOR_NUMBER proc;
|
||||
GetCurrentProcessorNumberEx(&proc);
|
||||
|
||||
cpuid_t result(proc.Group, proc.Number);
|
||||
return result;
|
||||
#elif defined(XP_LINUX)
|
||||
return sched_getcpu();
|
||||
#else
|
||||
return {};
|
||||
#endif // defined(XP_WIN) || defined(XP_LINUX)
|
||||
}
|
||||
|
||||
// Compare two CPU identifiers.
|
||||
bool inline isSameCPU(const cpuid_t& a, const cpuid_t& b) const
|
||||
{
|
||||
#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA
|
||||
return a.group_ == b.group_ && a.number_ == b.number_;
|
||||
#elif defined(XP_LINUX)
|
||||
return a == b;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
private:
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
|
||||
};
|
||||
|
||||
|
@ -678,9 +668,9 @@ js::RunScript(JSContext* cx, RunState& state)
|
|||
{
|
||||
JS_CHECK_RECURSION(cx, return false);
|
||||
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
#if defined(NIGHTLY_BUILD) && defined(MOZ_HAVE_RDTSC)
|
||||
js::AutoStopwatch stopwatch(cx);
|
||||
#endif // defined(NIGHTLY_BUILD)
|
||||
#endif // defined(NIGHTLY_BUILD) && defined(MOZ_HAVE_RDTSC)
|
||||
|
||||
SPSEntryMarker marker(cx->runtime(), state.script());
|
||||
|
||||
|
|
|
@ -12,6 +12,15 @@
|
|||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
#include <mach/mach.h>
|
||||
#elif defined(XP_UNIX)
|
||||
#include <sys/resource.h>
|
||||
#elif defined(XP_WIN)
|
||||
#include <processthreadsapi.h>
|
||||
#include <windows.h>
|
||||
#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
|
||||
|
||||
#include <locale.h>
|
||||
#include <string.h>
|
||||
|
||||
|
@ -872,12 +881,299 @@ JS::IsProfilingEnabledForRuntime(JSRuntime* runtime)
|
|||
return runtime->spsProfiler.enabled();
|
||||
}
|
||||
|
||||
void
|
||||
js::ResetStopwatches(JSRuntime* rt)
|
||||
JS_PUBLIC_API(void)
|
||||
js::FlushPerformanceMonitoring(JSRuntime* runtime)
|
||||
{
|
||||
MOZ_ASSERT(rt);
|
||||
rt->stopwatch.reset();
|
||||
MOZ_ASSERT(runtime);
|
||||
return runtime->stopwatch.commit();
|
||||
}
|
||||
JS_PUBLIC_API(void)
|
||||
js::ResetPerformanceMonitoring(JSRuntime* runtime)
|
||||
{
|
||||
MOZ_ASSERT(runtime);
|
||||
return runtime->stopwatch.reset();
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::Stopwatch::reset()
|
||||
{
|
||||
// All ongoing measures are dependent on the current iteration#.
|
||||
// By incrementing it, we mark all data as stale. Stale data will
|
||||
// be overwritten progressively during the execution.
|
||||
++iteration_;
|
||||
touchedGroups.clear();
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::Stopwatch::start()
|
||||
{
|
||||
if (!isMonitoringJank_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (iteration_ == startedAtIteration_) {
|
||||
// The stopwatch is already started for this iteration.
|
||||
return;
|
||||
}
|
||||
|
||||
startedAtIteration_ = iteration_;
|
||||
if (!getResources(&userTimeStart_, &systemTimeStart_))
|
||||
return;
|
||||
}
|
||||
|
||||
// Commit the data that has been collected during the iteration
|
||||
// into the actual `PerformanceData`.
|
||||
//
|
||||
// We use the proportion of cycles-spent-in-group over
|
||||
// cycles-spent-in-toplevel-group as an approximation to allocate
|
||||
// system (kernel) time and user (CPU) time to each group. Note
|
||||
// that cycles are not an exact measure:
|
||||
//
|
||||
// 1. if the computer has gone to sleep, the clock may be reset to 0;
|
||||
// 2. if the process is moved between CPUs/cores, it may end up on a CPU
|
||||
// or core with an unsynchronized clock;
|
||||
// 3. the mapping between clock cycles and walltime varies with the current
|
||||
// frequency of the CPU;
|
||||
// 4. other threads/processes using the same CPU will also increment
|
||||
// the counter.
|
||||
//
|
||||
// ** Effect of 1. (computer going to sleep)
|
||||
//
|
||||
// We assume that this will happen very seldom. Since the final numbers
|
||||
// are bounded by the CPU time and Kernel time reported by `getresources`,
|
||||
// the effect will be contained to a single iteration of the event loop.
|
||||
//
|
||||
// ** Effect of 2. (moving between CPUs/cores)
|
||||
//
|
||||
// On platforms that support it, we only measure the number of cycles
|
||||
// if we start and end execution of a group on the same
|
||||
// CPU/core. While there is a small window (a few cycles) during which
|
||||
// the thread can be migrated without us noticing, we expect that this
|
||||
// will happen rarely enough that this won't affect the statistics
|
||||
// meaningfully.
|
||||
//
|
||||
// On other platforms, assuming that the probability of jumping
|
||||
// between CPUs/cores during a given (real) cycle is constant, and
|
||||
// that the distribution of differences between clocks is even, the
|
||||
// probability that the number of cycles reported by a measure is
|
||||
// modified by X cycles should be a gaussian distribution, with groups
|
||||
// with longer execution having a larger amplitude than groups with
|
||||
// shorter execution. Since we discard measures that result in a
|
||||
// negative number of cycles, this distribution is actually skewed
|
||||
// towards over-estimating the number of cycles of groups that already
|
||||
// have many cycles and under-estimating the number of cycles that
|
||||
// already have fewer cycles.
|
||||
//
|
||||
// Since the final numbers are bounded by the CPU time and Kernel time
|
||||
// reported by `getresources`, we accept this bias.
|
||||
//
|
||||
// ** Effect of 3. (mapping between clock cycles and walltime)
|
||||
//
|
||||
// Assuming that this is evenly distributed, we expect that this will
|
||||
// eventually balance out.
|
||||
//
|
||||
// ** Effect of 4. (cycles increase with system activity)
|
||||
//
|
||||
// Assuming that, within an iteration of the event loop, this happens
|
||||
// unformly over time, this will skew towards over-estimating the number
|
||||
// of cycles of groups that already have many cycles and under-estimating
|
||||
// the number of cycles that already have fewer cycles.
|
||||
//
|
||||
// Since the final numbers are bounded by the CPU time and Kernel time
|
||||
// reported by `getresources`, we accept this bias.
|
||||
//
|
||||
// ** Big picture
|
||||
//
|
||||
// Computing the number of cycles is fast and should be accurate
|
||||
// enough in practice. Alternatives (such as calling `getresources`
|
||||
// all the time or sampling from another thread) are very expensive
|
||||
// in system calls and/or battery and not necessarily more accurate.
|
||||
void
|
||||
JSRuntime::Stopwatch::commit()
|
||||
{
|
||||
#if !defined(MOZ_HAVE_RDTSC)
|
||||
// The AutoStopwatch is only executed if `MOZ_HAVE_RDTSC`.
|
||||
return;
|
||||
#endif // !defined(MOZ_HAVE_RDTSC)
|
||||
|
||||
if (!isMonitoringJank_) {
|
||||
// Either we have not started monitoring or monitoring has
|
||||
// been cancelled during the iteration.
|
||||
return;
|
||||
}
|
||||
|
||||
if (startedAtIteration_ != iteration_) {
|
||||
// No JS code has been monitored during this iteration.
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t userTimeStop, systemTimeStop;
|
||||
if (!getResources(&userTimeStop, &systemTimeStop))
|
||||
return;
|
||||
|
||||
// `getResources` is not guaranteed to be monotonic, so round up
|
||||
// any negative result to 0 milliseconds.
|
||||
uint64_t userTimeDelta = 0;
|
||||
if (userTimeStop > userTimeStart_)
|
||||
userTimeDelta = userTimeStop - userTimeStart_;
|
||||
|
||||
uint64_t systemTimeDelta = 0;
|
||||
if (systemTimeStop > systemTimeStart_)
|
||||
systemTimeDelta = systemTimeStop - systemTimeStart_;
|
||||
|
||||
mozilla::RefPtr<js::PerformanceGroup> group = performance.getOwnGroup();
|
||||
const uint64_t totalRecentCycles = group->recentCycles;
|
||||
|
||||
mozilla::Vector<mozilla::RefPtr<js::PerformanceGroup>> recentGroups;
|
||||
touchedGroups.swap(recentGroups);
|
||||
MOZ_ASSERT(recentGroups.length() > 0);
|
||||
|
||||
// We should only reach this stage if `group` has had some activity.
|
||||
MOZ_ASSERT(group->recentTicks > 0);
|
||||
for (mozilla::RefPtr<js::PerformanceGroup>* iter = recentGroups.begin(); iter != recentGroups.end(); ++iter) {
|
||||
transferDeltas(userTimeDelta, systemTimeDelta, totalRecentCycles, *iter);
|
||||
}
|
||||
|
||||
// Make sure that `group` was treated along with the other items of `recentGroups`.
|
||||
MOZ_ASSERT(group->recentTicks == 0);
|
||||
|
||||
// Finally, reset immediately, to make sure that we're not hit by the
|
||||
// end of a nested event loop (which would cause `commit` to be called
|
||||
// twice in succession).
|
||||
reset();
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::Stopwatch::transferDeltas(uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta,
|
||||
uint64_t totalCyclesDelta, js::PerformanceGroup* group) {
|
||||
|
||||
const uint64_t ticksDelta = group->recentTicks;
|
||||
const uint64_t cpowTimeDelta = group->recentCPOW;
|
||||
const uint64_t cyclesDelta = group->recentCycles;
|
||||
group->resetRecentData();
|
||||
|
||||
// We have now performed all cleanup and may `return` at any time without fear of leaks.
|
||||
|
||||
if (group->iteration() != iteration_) {
|
||||
// Stale data, don't commit.
|
||||
return;
|
||||
}
|
||||
|
||||
// When we add a group as changed, we immediately set its
|
||||
// `recentTicks` from 0 to 1. If we have `ticksDelta == 0` at
|
||||
// this stage, we have already called `resetRecentData` but we
|
||||
// haven't removed it from the list.
|
||||
MOZ_ASSERT(ticksDelta != 0);
|
||||
MOZ_ASSERT(cyclesDelta <= totalCyclesDelta);
|
||||
if (cyclesDelta == 0 || totalCyclesDelta == 0) {
|
||||
// Nothing useful, don't commit.
|
||||
return;
|
||||
}
|
||||
|
||||
double proportion = (double)cyclesDelta / (double)totalCyclesDelta;
|
||||
MOZ_ASSERT(proportion <= 1);
|
||||
|
||||
const uint64_t userTimeDelta = proportion * totalUserTimeDelta;
|
||||
const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta;
|
||||
|
||||
group->data.totalUserTime += userTimeDelta;
|
||||
group->data.totalSystemTime += systemTimeDelta;
|
||||
group->data.totalCPOWTime += cpowTimeDelta;
|
||||
group->data.ticks += ticksDelta;
|
||||
|
||||
const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
|
||||
|
||||
size_t i = 0;
|
||||
uint64_t duration = 1000; // 1ms in µs
|
||||
for (i = 0, duration = 1000;
|
||||
i < mozilla::ArrayLength(group->data.durations) && duration < totalTimeDelta;
|
||||
++i, duration *= 2) {
|
||||
group->data.durations[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the OS-reported time spent in userland/systemland, in
|
||||
// microseconds. On most platforms, this data is per-thread,
|
||||
// but on some platforms we need to fall back to per-process.
|
||||
// Data is not guaranteed to be monotonic.
|
||||
bool
|
||||
JSRuntime::Stopwatch::getResources(uint64_t* userTime,
|
||||
uint64_t* systemTime) const {
|
||||
MOZ_ASSERT(userTime);
|
||||
MOZ_ASSERT(systemTime);
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
// On MacOS X, to get we per-thread data, we need to
|
||||
// reach into the kernel.
|
||||
|
||||
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
|
||||
thread_basic_info_data_t info;
|
||||
mach_port_t port = mach_thread_self();
|
||||
kern_return_t err =
|
||||
thread_info(/* [in] targeted thread*/ port,
|
||||
/* [in] nature of information*/ THREAD_BASIC_INFO,
|
||||
/* [out] thread information */ (thread_info_t)&info,
|
||||
/* [inout] number of items */ &count);
|
||||
|
||||
// We do not need ability to communicate with the thread, so
|
||||
// let's release the port.
|
||||
mach_port_deallocate(mach_task_self(), port);
|
||||
|
||||
if (err != KERN_SUCCESS)
|
||||
return false;
|
||||
|
||||
*userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
|
||||
*systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;
|
||||
|
||||
#elif defined(XP_UNIX)
|
||||
struct rusage rusage;
|
||||
#if defined(RUSAGE_THREAD)
|
||||
// Under Linux, we can obtain per-thread statistics
|
||||
int err = getrusage(RUSAGE_THREAD, &rusage);
|
||||
#else
|
||||
// Under other Unices, we need to do with more noisy
|
||||
// per-process statistics.
|
||||
int err = getrusage(RUSAGE_SELF, &rusage);
|
||||
#endif // defined(RUSAGE_THREAD)
|
||||
|
||||
if (err)
|
||||
return false;
|
||||
|
||||
*userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
|
||||
*systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;
|
||||
|
||||
#elif defined(XP_WIN)
|
||||
// Under Windows, we can obtain per-thread statistics,
|
||||
// although experience seems to suggest that they are
|
||||
// not very good under Windows XP.
|
||||
FILETIME creationFileTime; // Ignored
|
||||
FILETIME exitFileTime; // Ignored
|
||||
FILETIME kernelFileTime;
|
||||
FILETIME userFileTime;
|
||||
BOOL success = GetThreadTimes(GetCurrentThread(),
|
||||
&creationFileTime, &exitFileTime,
|
||||
&kernelFileTime, &userFileTime);
|
||||
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
ULARGE_INTEGER kernelTimeInt;
|
||||
kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
|
||||
kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
|
||||
// Convert 100 ns to 1 us.
|
||||
*systemTime = kernelTimeInt.QuadPart / 10;
|
||||
|
||||
ULARGE_INTEGER userTimeInt;
|
||||
userTimeInt.LowPart = userFileTime.dwLowDateTime;
|
||||
userTimeInt.HighPart = userFileTime.dwHighDateTime;
|
||||
// Convert 100 ns to 1 us.
|
||||
*userTime = userTimeInt.QuadPart / 10;
|
||||
|
||||
#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
js::SetStopwatchIsMonitoringJank(JSRuntime* rt, bool value)
|
||||
|
@ -912,6 +1208,13 @@ js::GetStopwatchIsMonitoringPerCompartment(JSRuntime* rt)
|
|||
return rt->stopwatch.isMonitoringPerCompartment();
|
||||
}
|
||||
|
||||
void
|
||||
js::GetPerfMonitoringTestCpuRescheduling(JSRuntime* rt, uint64_t* stayed, uint64_t* moved)
|
||||
{
|
||||
*stayed = rt->stopwatch.testCpuRescheduling.stayed;
|
||||
*moved = rt->stopwatch.testCpuRescheduling.moved;
|
||||
}
|
||||
|
||||
js::PerformanceGroupHolder::~PerformanceGroupHolder()
|
||||
{
|
||||
unlink();
|
||||
|
@ -961,39 +1264,46 @@ js::PerformanceGroupHolder::getSharedGroup(JSContext* cx)
|
|||
} else {
|
||||
sharedGroup_ = runtime_->new_<PerformanceGroup>(cx, key);
|
||||
if (!sharedGroup_)
|
||||
return nullptr;
|
||||
|
||||
return nullptr;
|
||||
runtime_->stopwatch.groups().add(ptr, key, sharedGroup_);
|
||||
}
|
||||
|
||||
return sharedGroup_;
|
||||
}
|
||||
|
||||
PerformanceData*
|
||||
js::GetPerformanceData(JSRuntime* rt)
|
||||
void
|
||||
js::AddCPOWPerformanceDelta(JSRuntime* rt, uint64_t delta)
|
||||
{
|
||||
return &rt->stopwatch.performance.getOwnGroup()->data;
|
||||
rt->stopwatch.totalCPOWTime += delta;
|
||||
}
|
||||
|
||||
js::PerformanceGroup::PerformanceGroup(JSRuntime* rt)
|
||||
: uid(rt->stopwatch.uniqueId()),
|
||||
recentCycles(0),
|
||||
recentTicks(0),
|
||||
recentCPOW(0),
|
||||
runtime_(rt),
|
||||
stopwatch_(nullptr),
|
||||
iteration_(0),
|
||||
key_(nullptr),
|
||||
refCount_(0),
|
||||
isSharedGroup_(false)
|
||||
{ }
|
||||
{
|
||||
}
|
||||
|
||||
js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
|
||||
: uid(cx->runtime()->stopwatch.uniqueId()),
|
||||
runtime_(cx->runtime()),
|
||||
stopwatch_(nullptr),
|
||||
iteration_(0),
|
||||
key_(key),
|
||||
refCount_(0),
|
||||
isSharedGroup_(true)
|
||||
{ }
|
||||
js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
|
||||
: uid(cx->runtime()->stopwatch.uniqueId()),
|
||||
recentCycles(0),
|
||||
recentTicks(0),
|
||||
recentCPOW(0),
|
||||
runtime_(cx->runtime()),
|
||||
stopwatch_(nullptr),
|
||||
iteration_(0),
|
||||
key_(key),
|
||||
refCount_(0),
|
||||
isSharedGroup_(true)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
js::PerformanceGroup::AddRef()
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
|
@ -1518,17 +1519,6 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||
*/
|
||||
js::PerformanceGroupHolder performance;
|
||||
|
||||
/**
|
||||
* The number of times we have entered the event loop.
|
||||
* Used to reset counters whenever we enter the loop,
|
||||
* which may be caused either by having completed the
|
||||
* previous run of the event loop, or by entering a
|
||||
* nested loop.
|
||||
*
|
||||
* Always incremented by 1, may safely overflow.
|
||||
*/
|
||||
uint64_t iteration;
|
||||
|
||||
/**
|
||||
* Callback used to ask the embedding to determine in which
|
||||
* Performance Group the current execution belongs. Typically, this is
|
||||
|
@ -1541,31 +1531,56 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||
*/
|
||||
JSCurrentPerfGroupCallback currentPerfGroupCallback;
|
||||
|
||||
/**
|
||||
* The number of the current iteration of the event loop.
|
||||
*/
|
||||
uint64_t iteration() {
|
||||
return iteration_;
|
||||
}
|
||||
|
||||
explicit Stopwatch(JSRuntime* runtime)
|
||||
: performance(runtime)
|
||||
, iteration(0)
|
||||
, currentPerfGroupCallback(nullptr)
|
||||
, totalCPOWTime(0)
|
||||
, isMonitoringJank_(false)
|
||||
, isMonitoringCPOW_(false)
|
||||
, isMonitoringPerCompartment_(false)
|
||||
, iteration_(0)
|
||||
, startedAtIteration_(0)
|
||||
, idCounter_(0)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Reset the stopwatch.
|
||||
*
|
||||
* This method is meant to be called whenever we start processing
|
||||
* an event, to ensure that stop any ongoing measurement that would
|
||||
* otherwise provide irrelevant results.
|
||||
* This method is meant to be called whenever we start
|
||||
* processing an event, to ensure that we stop any ongoing
|
||||
* measurement that would otherwise provide irrelevant
|
||||
* results.
|
||||
*/
|
||||
void reset() {
|
||||
++iteration;
|
||||
}
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Start the stopwatch.
|
||||
*
|
||||
* This method is meant to be called once we know that the
|
||||
* current event contains JavaScript code to execute. Calling
|
||||
* this several times during the same iteration is idempotent.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Commit the performance data collected since the last call
|
||||
* to `start()`, unless `reset()` has been called since then.
|
||||
*/
|
||||
void commit();
|
||||
|
||||
/**
|
||||
* Activate/deactivate stopwatch measurement of jank.
|
||||
*
|
||||
* Noop if `value` is `true` and the stopwatch is already active,
|
||||
* or if `value` is `false` and the stopwatch is already inactive.
|
||||
* Noop if `value` is `true` and the stopwatch is already
|
||||
* measuring jank, or if `value` is `false` and the stopwatch
|
||||
* is not measuring jank.
|
||||
*
|
||||
* Otherwise, any pending measurements are dropped, but previous
|
||||
* measurements remain stored.
|
||||
|
@ -1588,6 +1603,18 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||
return isMonitoringJank_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate/deactivate stopwatch measurement per compartment.
|
||||
*
|
||||
* Noop if `value` is `true` and the stopwatch is already
|
||||
* measuring per compartment, or if `value` is `false` and the
|
||||
* stopwatch is not measuring per compartment.
|
||||
*
|
||||
* Otherwise, any pending measurements are dropped, but previous
|
||||
* measurements remain stored.
|
||||
*
|
||||
* May return `false` if the underlying hashtable cannot be allocated.
|
||||
*/
|
||||
bool setIsMonitoringPerCompartment(bool value) {
|
||||
if (isMonitoringPerCompartment_ != value)
|
||||
reset();
|
||||
|
@ -1606,8 +1633,25 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||
|
||||
/**
|
||||
* Activate/deactivate stopwatch measurement of CPOW.
|
||||
*
|
||||
* Noop if `value` is `true` and the stopwatch is already
|
||||
* measuring CPOW, or if `value` is `false` and the stopwatch
|
||||
* is not measuring CPOW.
|
||||
*
|
||||
* Otherwise, any pending measurements are dropped, but previous
|
||||
* measurements remain stored.
|
||||
*
|
||||
* May return `false` if the underlying hashtable cannot be allocated.
|
||||
*/
|
||||
bool setIsMonitoringCPOW(bool value) {
|
||||
if (isMonitoringCPOW_ != value)
|
||||
reset();
|
||||
|
||||
if (value && !groups_.initialized()) {
|
||||
if (!groups_.init(128))
|
||||
return false;
|
||||
}
|
||||
|
||||
isMonitoringCPOW_ = value;
|
||||
return true;
|
||||
}
|
||||
|
@ -1623,46 +1667,119 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||
return idCounter_++;
|
||||
}
|
||||
|
||||
// Some systems have non-monotonic clocks. While we cannot
|
||||
// improve the precision, we can make sure that our measures
|
||||
// are monotonic nevertheless. We do this by storing the
|
||||
// result of the latest call to the clock and making sure
|
||||
// that the next timestamp is greater or equal.
|
||||
struct MonotonicTimeStamp {
|
||||
MonotonicTimeStamp()
|
||||
: latestGood_(0)
|
||||
{}
|
||||
inline uint64_t monotonize(uint64_t stamp)
|
||||
{
|
||||
if (stamp <= latestGood_)
|
||||
return latestGood_;
|
||||
latestGood_ = stamp;
|
||||
return stamp;
|
||||
}
|
||||
private:
|
||||
uint64_t latestGood_;
|
||||
/**
|
||||
* Mark a group as changed during the current iteration.
|
||||
*
|
||||
* Recent data from this group will be post-processed and
|
||||
* committed at the end of the iteration.
|
||||
*/
|
||||
void addChangedGroup(js::PerformanceGroup* group) {
|
||||
MOZ_ASSERT(group->recentTicks == 0);
|
||||
touchedGroups.append(group);
|
||||
}
|
||||
|
||||
// The total amount of time spent waiting on CPOWs since the
|
||||
// start of the process, in microseconds.
|
||||
uint64_t totalCPOWTime;
|
||||
|
||||
// Data extracted by the AutoStopwatch to determine how often
|
||||
// we reschedule the process to a different CPU during the
|
||||
// execution of JS.
|
||||
//
|
||||
// Warning: These values are incremented *only* on platforms
|
||||
// that offer a syscall/libcall to check on which CPU a
|
||||
// process is currently executed.
|
||||
struct TestCpuRescheduling
|
||||
{
|
||||
// Incremented once we have finished executing code
|
||||
// in a group, if the CPU on which we started
|
||||
// execution is the same as the CPU on which
|
||||
// we finished.
|
||||
uint64_t stayed;
|
||||
// Incremented once we have finished executing code
|
||||
// in a group, if the CPU on which we started
|
||||
// execution is different from the CPU on which
|
||||
// we finished.
|
||||
uint64_t moved;
|
||||
TestCpuRescheduling()
|
||||
: stayed(0),
|
||||
moved(0)
|
||||
{ }
|
||||
};
|
||||
MonotonicTimeStamp systemTimeFix;
|
||||
MonotonicTimeStamp userTimeFix;
|
||||
TestCpuRescheduling testCpuRescheduling;
|
||||
|
||||
private:
|
||||
Stopwatch(const Stopwatch&) = delete;
|
||||
Stopwatch& operator=(const Stopwatch&) = delete;
|
||||
|
||||
// Commit a piece of data to a single group.
|
||||
// `totalUserTimeDelta`, `totalSystemTimeDelta`, `totalCyclesDelta`
|
||||
// represent the outer measures, taken for the entire runtime.
|
||||
void transferDeltas(uint64_t totalUserTimeDelta,
|
||||
uint64_t totalSystemTimeDelta,
|
||||
uint64_t totalCyclesDelta,
|
||||
js::PerformanceGroup* destination);
|
||||
|
||||
// Query the OS for the time spent in CPU/kernel since process
|
||||
// launch.
|
||||
bool getResources(uint64_t* userTime, uint64_t* systemTime) const;
|
||||
|
||||
private:
|
||||
Groups groups_;
|
||||
friend struct js::PerformanceGroupHolder;
|
||||
|
||||
/**
|
||||
* `true` if stopwatch monitoring is active, `false` otherwise.
|
||||
* `true` if stopwatch monitoring is active for Jank, `false` otherwise.
|
||||
*/
|
||||
bool isMonitoringJank_;
|
||||
/**
|
||||
* `true` if stopwatch monitoring is active for CPOW, `false` otherwise.
|
||||
*/
|
||||
bool isMonitoringCPOW_;
|
||||
/**
|
||||
* `true` if the stopwatch should udpdate data per-compartment, in
|
||||
* addition to data per-group.
|
||||
*/
|
||||
bool isMonitoringPerCompartment_;
|
||||
|
||||
/**
|
||||
* The number of times we have entered the event loop.
|
||||
* Used to reset counters whenever we enter the loop,
|
||||
* which may be caused either by having completed the
|
||||
* previous run of the event loop, or by entering a
|
||||
* nested loop.
|
||||
*
|
||||
* Always incremented by 1, may safely overflow.
|
||||
*/
|
||||
uint64_t iteration_;
|
||||
|
||||
/**
|
||||
* The iteration at which the stopwatch was last started.
|
||||
*
|
||||
* Used both to avoid starting the stopwatch several times
|
||||
* during the same event loop and to avoid committing stale
|
||||
* stopwatch results.
|
||||
*/
|
||||
uint64_t startedAtIteration_;
|
||||
|
||||
/**
|
||||
* A counter used to generate unique identifiers for groups.
|
||||
*/
|
||||
uint64_t idCounter_;
|
||||
|
||||
/**
|
||||
* The timestamps returned by `getResources()` during the call to
|
||||
* `start()` in the current iteration of the event loop.
|
||||
*/
|
||||
uint64_t userTimeStart_;
|
||||
uint64_t systemTimeStart_;
|
||||
|
||||
/**
|
||||
* Performance groups used during the current event.
|
||||
*
|
||||
* They are cleared by `commit()` and `reset()`.
|
||||
*/
|
||||
mozilla::Vector<mozilla::RefPtr<js::PerformanceGroup>> touchedGroups;
|
||||
};
|
||||
Stopwatch stopwatch;
|
||||
};
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
#include "nsCycleCollectionNoteRootCallback.h"
|
||||
#include "nsCycleCollector.h"
|
||||
#include "nsScriptLoader.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsapi.h"
|
||||
#include "jsprf.h"
|
||||
#include "js/MemoryMetrics.h"
|
||||
#include "mozilla/dom/GeneratedAtomList.h"
|
||||
|
@ -3602,7 +3602,10 @@ XPCJSRuntime::BeforeProcessTask(bool aMightBlock)
|
|||
// Start the slow script timer.
|
||||
mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
|
||||
mSlowScriptSecondHalf = false;
|
||||
js::ResetStopwatches(Get()->Runtime());
|
||||
|
||||
// As we may be entering a nested event loop, we need to
|
||||
// cancel any ongoing performance measurement.
|
||||
js::ResetPerformanceMonitoring(Get()->Runtime());
|
||||
|
||||
// Push a null JSContext so that we don't see any script during
|
||||
// event processing.
|
||||
|
@ -3624,6 +3627,10 @@ XPCJSRuntime::AfterProcessTask(uint32_t aNewRecursionDepth)
|
|||
|
||||
CycleCollectedJSRuntime::AfterProcessTask(aNewRecursionDepth);
|
||||
|
||||
// Now that we are certain that the event is complete,
|
||||
// we can flush any ongoing performance measurement.
|
||||
js::FlushPerformanceMonitoring(Get()->Runtime());
|
||||
|
||||
PopNullJSContext();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/BasicEvents.h"
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/gfx/PathHelpers.h"
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
@ -453,6 +454,15 @@ GetSuitableScale(float aMaxScale, float aMinScale,
|
|||
// transform animation, unless that would make us rasterize something
|
||||
// larger than the screen. But we never want to go smaller than the
|
||||
// minimum scale over the animation.
|
||||
if (FuzzyEqualsMultiplicative(displayVisibleRatio, aMaxScale, .01f)) {
|
||||
// Using aMaxScale may make us rasterize something a fraction larger than
|
||||
// the screen. However, if aMaxScale happens to be the final scale of a
|
||||
// transform animation it is better to use aMaxScale so that for the
|
||||
// fraction of a second before we delayerize the composited texture it has
|
||||
// a better chance of being pixel aligned and composited without resampling
|
||||
// (avoiding visually clunky delayerization).
|
||||
return aMaxScale;
|
||||
}
|
||||
return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="reftest-print">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style type="text/css">
|
||||
#menu { position: fixed; left: 0px; top: 0px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg id="canvas" width="2427" height="2295.5" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
|
||||
<div id="menu">
|
||||
<input id="chooseSize" type="range">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -62,3 +62,5 @@ load 960277-2.html
|
|||
load 997709-1.html
|
||||
load 1102791.html
|
||||
load 1140216.html
|
||||
load 1182414.html
|
||||
|
||||
|
|
|
@ -265,15 +265,18 @@ nsMeterFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
|
|||
bool
|
||||
nsMeterFrame::ShouldUseNativeStyle() const
|
||||
{
|
||||
nsIFrame* barFrame = mBarDiv->GetPrimaryFrame();
|
||||
|
||||
// Use the native style if these conditions are satisfied:
|
||||
// - both frames use the native appearance;
|
||||
// - neither frame has author specified rules setting the border or the
|
||||
// background.
|
||||
return StyleDisplay()->mAppearance == NS_THEME_METERBAR &&
|
||||
mBarDiv->GetPrimaryFrame()->StyleDisplay()->mAppearance == NS_THEME_METERBAR_CHUNK &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(this,
|
||||
NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND) &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(mBarDiv->GetPrimaryFrame(),
|
||||
barFrame &&
|
||||
barFrame->StyleDisplay()->mAppearance == NS_THEME_METERBAR_CHUNK &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(barFrame,
|
||||
NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND);
|
||||
}
|
||||
|
||||
|
|
|
@ -271,15 +271,18 @@ nsProgressFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
|
|||
bool
|
||||
nsProgressFrame::ShouldUseNativeStyle() const
|
||||
{
|
||||
nsIFrame* barFrame = mBarDiv->GetPrimaryFrame();
|
||||
|
||||
// Use the native style if these conditions are satisfied:
|
||||
// - both frames use the native appearance;
|
||||
// - neither frame has author specified rules setting the border or the
|
||||
// background.
|
||||
return StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR &&
|
||||
mBarDiv->GetPrimaryFrame()->StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR_CHUNK &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(this,
|
||||
NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND) &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(mBarDiv->GetPrimaryFrame(),
|
||||
barFrame &&
|
||||
barFrame->StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR_CHUNK &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(barFrame,
|
||||
NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND);
|
||||
}
|
||||
|
||||
|
|
|
@ -837,15 +837,22 @@ nsRangeFrame::GetType() const
|
|||
bool
|
||||
nsRangeFrame::ShouldUseNativeStyle() const
|
||||
{
|
||||
nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
|
||||
nsIFrame* progressFrame = mProgressDiv->GetPrimaryFrame();
|
||||
nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
|
||||
|
||||
return (StyleDisplay()->mAppearance == NS_THEME_RANGE) &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(this,
|
||||
(NS_AUTHOR_SPECIFIED_BORDER |
|
||||
NS_AUTHOR_SPECIFIED_BACKGROUND)) &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(mTrackDiv->GetPrimaryFrame(),
|
||||
trackFrame &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(trackFrame,
|
||||
STYLES_DISABLING_NATIVE_THEMING) &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(mProgressDiv->GetPrimaryFrame(),
|
||||
progressFrame &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(progressFrame,
|
||||
STYLES_DISABLING_NATIVE_THEMING) &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(mThumbDiv->GetPrimaryFrame(),
|
||||
thumbFrame &&
|
||||
!PresContext()->HasAuthorSpecifiedRules(thumbFrame,
|
||||
STYLES_DISABLING_NATIVE_THEMING);
|
||||
}
|
||||
|
||||
|
|
|
@ -557,9 +557,9 @@ skip-if(B2G||Mulet) == 363858-1.html 363858-1-ref.html # Initial mulet triage: p
|
|||
skip-if(B2G||Mulet) == 363858-2.html 363858-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) == 363858-3.html 363858-3-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) == 363858-4.html 363858-4-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
fuzzy-if(OSX>=1008,45,2) fuzzy-if(winWidget,37,1) == 363858-5a.html 363858-5-ref.html
|
||||
fuzzy-if(OSX>=1008,45,2) fuzzy-if(winWidget,114,1) == 363858-5a.html 363858-5-ref.html
|
||||
== 363858-5b.html 363858-5-ref.html
|
||||
fuzzy-if(OSX>=1008,45,2) fuzzy-if(winWidget,37,1) == 363858-6a.html 363858-6-ref.html
|
||||
fuzzy-if(OSX>=1008,45,2) fuzzy-if(winWidget,114,1) == 363858-6a.html 363858-6-ref.html
|
||||
== 363858-6b.html 363858-6-ref.html
|
||||
== 363874.html 363874-ref.html
|
||||
== 363874-max-width.html 363874-max-width-ref.html
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
== placeholder-3.html placeholder-overridden-ref.html
|
||||
== placeholder-4.html placeholder-overridden-ref.html
|
||||
== placeholder-5.html placeholder-visible-ref.html
|
||||
fuzzy-if(winWidget,160,7) fuzzy-if(asyncPan&&!layersGPUAccelerated,146,299) == placeholder-6.html placeholder-overflow-ref.html
|
||||
fuzzy-if(winWidget,160,10) fuzzy-if(asyncPan&&!layersGPUAccelerated,146,299) == placeholder-6.html placeholder-overflow-ref.html
|
||||
skip-if(B2G||Mulet) == placeholder-6-textarea.html placeholder-overflow-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
# needs-focus == placeholder-7.html placeholder-focus-ref.html
|
||||
# needs-focus == placeholder-8.html placeholder-focus-ref.html
|
||||
|
|
|
@ -455,14 +455,8 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
|
|||
animationChanged =
|
||||
oldEffect->Timing() != newEffect->Timing() ||
|
||||
oldEffect->Properties() != newEffect->Properties();
|
||||
oldEffect->Timing() = newEffect->Timing();
|
||||
oldEffect->SetTiming(newEffect->Timing(), *oldAnim);
|
||||
oldEffect->Properties() = newEffect->Properties();
|
||||
// FIXME: Currently assigning to KeyframeEffect::Timing() does not
|
||||
// update the corresponding Animation (which may, for example, no
|
||||
// longer be finished). Until we introduce proper setters for
|
||||
// properties on effects, we need to manually cause the owning
|
||||
// Animation to update its timing by setting the effect again.
|
||||
oldAnim->SetEffect(oldEffect);
|
||||
}
|
||||
|
||||
// Reset compositor state so animation will be re-synchronized.
|
||||
|
@ -507,10 +501,6 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
|
|||
newAnim = nullptr;
|
||||
newAnimations.ReplaceElementAt(newIdx, oldAnim);
|
||||
collection->mAnimations.RemoveElementAt(oldIdx);
|
||||
|
||||
// We've touched the old animation's timing properties, so this
|
||||
// could update the old animation's relevance.
|
||||
oldAnim->UpdateRelevance();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -549,34 +549,42 @@ operator!=(U* aLhs, const nsRefPtr<T>& aRhs)
|
|||
return const_cast<const U*>(aLhs) != static_cast<const T*>(aRhs.get());
|
||||
}
|
||||
|
||||
// Comparing an |nsRefPtr| to |nullptr|
|
||||
namespace detail {
|
||||
class nsRefPtrZero;
|
||||
} // namespace detail
|
||||
|
||||
// Comparing an |nsRefPtr| to |0|
|
||||
|
||||
template <class T>
|
||||
inline bool
|
||||
operator==(const nsRefPtr<T>& aLhs, decltype(nullptr))
|
||||
operator==(const nsRefPtr<T>& aLhs, ::detail::nsRefPtrZero* aRhs)
|
||||
// specifically to allow |smartPtr == 0|
|
||||
{
|
||||
return aLhs.get() == nullptr;
|
||||
return static_cast<const void*>(aLhs.get()) == reinterpret_cast<const void*>(aRhs);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool
|
||||
operator==(decltype(nullptr), const nsRefPtr<T>& aRhs)
|
||||
operator==(::detail::nsRefPtrZero* aLhs, const nsRefPtr<T>& aRhs)
|
||||
// specifically to allow |0 == smartPtr|
|
||||
{
|
||||
return nullptr == aRhs.get();
|
||||
return reinterpret_cast<const void*>(aLhs) == static_cast<const void*>(aRhs.get());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool
|
||||
operator!=(const nsRefPtr<T>& aLhs, decltype(nullptr))
|
||||
operator!=(const nsRefPtr<T>& aLhs, ::detail::nsRefPtrZero* aRhs)
|
||||
// specifically to allow |smartPtr != 0|
|
||||
{
|
||||
return aLhs.get() != nullptr;
|
||||
return static_cast<const void*>(aLhs.get()) != reinterpret_cast<const void*>(aRhs);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool
|
||||
operator!=(decltype(nullptr), const nsRefPtr<T>& aRhs)
|
||||
operator!=(::detail::nsRefPtrZero* aLhs, const nsRefPtr<T>& aRhs)
|
||||
// specifically to allow |0 != smartPtr|
|
||||
{
|
||||
return nullptr != aRhs.get();
|
||||
return reinterpret_cast<const void*>(aLhs) != static_cast<const void*>(aRhs.get());
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
|
|
@ -1 +1 @@
|
|||
NSPR_4_10_9_BETA3
|
||||
NSPR_4_10_9_RTM
|
||||
|
|
|
@ -10,3 +10,4 @@
|
|||
*/
|
||||
|
||||
#error "Do not include this header file."
|
||||
|
||||
|
|
|
@ -31,11 +31,11 @@ PR_BEGIN_EXTERN_C
|
|||
** The format of the version string is
|
||||
** "<major version>.<minor version>[.<patch level>] [<Beta>]"
|
||||
*/
|
||||
#define PR_VERSION "4.10.9 Beta"
|
||||
#define PR_VERSION "4.10.9"
|
||||
#define PR_VMAJOR 4
|
||||
#define PR_VMINOR 10
|
||||
#define PR_VPATCH 9
|
||||
#define PR_BETA PR_TRUE
|
||||
#define PR_BETA PR_FALSE
|
||||
|
||||
/*
|
||||
** PRVersionCheck
|
||||
|
|
|
@ -67,7 +67,7 @@ class TierStatus(object):
|
|||
def __init__(self, resources):
|
||||
"""Accepts a SystemResourceMonitor to record results against."""
|
||||
self.tiers = OrderedDict()
|
||||
self.active_tiers = set()
|
||||
self.tier_status = OrderedDict()
|
||||
self.resources = resources
|
||||
|
||||
def set_tiers(self, tiers):
|
||||
|
@ -78,29 +78,23 @@ class TierStatus(object):
|
|||
finish_time=None,
|
||||
duration=None,
|
||||
)
|
||||
self.tier_status[tier] = None
|
||||
|
||||
def begin_tier(self, tier):
|
||||
"""Record that execution of a tier has begun."""
|
||||
self.tier_status[tier] = 'active'
|
||||
t = self.tiers[tier]
|
||||
# We should ideally use a monotonic clock here. Unfortunately, we won't
|
||||
# have one until Python 3.
|
||||
t['begin_time'] = time.time()
|
||||
self.resources.begin_phase(tier)
|
||||
self.active_tiers.add(tier)
|
||||
|
||||
def finish_tier(self, tier):
|
||||
"""Record that execution of a tier has finished."""
|
||||
self.tier_status[tier] = 'finished'
|
||||
t = self.tiers[tier]
|
||||
t['finish_time'] = time.time()
|
||||
t['duration'] = self.resources.finish_phase(tier)
|
||||
self.active_tiers.remove(tier)
|
||||
|
||||
def tier_status(self):
|
||||
for tier, state in self.tiers.items():
|
||||
active = tier in self.active_tiers
|
||||
finished = state['finish_time'] is not None
|
||||
|
||||
yield tier, active, finished
|
||||
|
||||
def tiered_resource_usage(self):
|
||||
"""Obtains an object containing resource usage for tiers.
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import operator
|
||||
|
@ -125,21 +126,17 @@ class BuildProgressFooter(object):
|
|||
# terminal is a blessings.Terminal.
|
||||
self._t = terminal
|
||||
self._fh = sys.stdout
|
||||
self._monitor = monitor
|
||||
|
||||
def _clear_lines(self, n):
|
||||
self._fh.write(self._t.move_x(0))
|
||||
self._fh.write(self._t.clear_eos())
|
||||
self.tiers = monitor.tiers.tier_status.viewitems()
|
||||
|
||||
def clear(self):
|
||||
"""Removes the footer from the current terminal."""
|
||||
self._clear_lines(1)
|
||||
self._fh.write(self._t.move_x(0))
|
||||
self._fh.write(self._t.clear_eos())
|
||||
|
||||
def draw(self):
|
||||
"""Draws this footer in the terminal."""
|
||||
tiers = self._monitor.tiers
|
||||
|
||||
if not tiers.tiers:
|
||||
if not self.tiers:
|
||||
return
|
||||
|
||||
# The drawn terminal looks something like:
|
||||
|
@ -148,15 +145,15 @@ class BuildProgressFooter(object):
|
|||
# This is a list of 2-tuples of (encoding function, input). None means
|
||||
# no encoding. For a full reason on why we do things this way, read the
|
||||
# big comment below.
|
||||
parts = [('bold', 'TIER'), ':', ' ']
|
||||
|
||||
for tier, active, finished in tiers.tier_status():
|
||||
if active:
|
||||
parts.extend([('underline_yellow', tier), ' '])
|
||||
elif finished:
|
||||
parts.extend([('green', tier), ' '])
|
||||
parts = [('bold', 'TIER:')]
|
||||
append = parts.append
|
||||
for tier, status in self.tiers:
|
||||
if status is None:
|
||||
append(tier)
|
||||
elif status == 'finished':
|
||||
append(('green', tier))
|
||||
else:
|
||||
parts.extend([tier, ' '])
|
||||
append(('underline_yellow', tier))
|
||||
|
||||
# We don't want to write more characters than the current width of the
|
||||
# terminal otherwise wrapping may result in weird behavior. We can't
|
||||
|
@ -168,30 +165,25 @@ class BuildProgressFooter(object):
|
|||
written = 0
|
||||
write_pieces = []
|
||||
for part in parts:
|
||||
if isinstance(part, tuple):
|
||||
func, arg = part
|
||||
try:
|
||||
func, part = part
|
||||
encoded = getattr(self._t, func)(part)
|
||||
except ValueError:
|
||||
encoded = part
|
||||
|
||||
if written + len(arg) > max_width:
|
||||
write_pieces.append(arg[0:max_width - written])
|
||||
written += len(arg)
|
||||
break
|
||||
len_part = len(part)
|
||||
len_spaces = len(write_pieces)
|
||||
if written + len_part + len_spaces > max_width:
|
||||
write_pieces.append(part[0:max_width - written - len_spaces])
|
||||
written += len_part
|
||||
break
|
||||
|
||||
encoded = getattr(self._t, func)(arg)
|
||||
write_pieces.append(encoded)
|
||||
written += len_part
|
||||
|
||||
write_pieces.append(encoded)
|
||||
written += len(arg)
|
||||
else:
|
||||
if written + len(part) > max_width:
|
||||
write_pieces.append(part[0:max_width - written])
|
||||
written += len(part)
|
||||
break
|
||||
|
||||
write_pieces.append(part)
|
||||
written += len(part)
|
||||
with self._t.location():
|
||||
self._t.move(self._t.height-1,0)
|
||||
self._fh.write(''.join(write_pieces))
|
||||
self._fh.flush()
|
||||
self._fh.write(' '.join(write_pieces))
|
||||
|
||||
|
||||
class BuildOutputManager(LoggingMixin):
|
||||
|
|
|
@ -54,6 +54,7 @@ run-sequentially = hardcoded ports
|
|||
run-sequentially = hardcoded ports
|
||||
[test_cert_blocklist.js]
|
||||
skip-if = buildapp == "b2g"
|
||||
tags = addons
|
||||
[test_ocsp_stapling_expired.js]
|
||||
run-sequentially = hardcoded ports
|
||||
skip-if = (toolkit == 'gonk' && debug) # Bug 1029775
|
||||
|
@ -78,6 +79,7 @@ run-sequentially = hardcoded ports
|
|||
[test_signed_apps.js]
|
||||
[test_signed_apps-marketplace.js]
|
||||
[test_signed_dir.js]
|
||||
tags = addons
|
||||
|
||||
[test_cert_eku-CA_EP.js]
|
||||
[test_cert_eku-CA_EP_NS_OS_SA_TS.js]
|
||||
|
|
|
@ -1 +1 @@
|
|||
NSS_3_20_RC0
|
||||
NSS_3_20_RTM
|
||||
|
|
|
@ -10,4 +10,3 @@
|
|||
*/
|
||||
|
||||
#error "Do not include this header file."
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
|||
[test_healthreporter.js]
|
||||
[test_provider_addons.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
tags = addons
|
||||
[test_provider_appinfo.js]
|
||||
[test_provider_crashes.js]
|
||||
skip-if = !crashreporter
|
||||
|
|
|
@ -36,6 +36,7 @@ support-files =
|
|||
# We have a number of other libraries that are pretty much standalone.
|
||||
[test_addon_utils.js]
|
||||
run-sequentially = Restarts server, can't change pref.
|
||||
tags = addons
|
||||
[test_httpd_sync_server.js]
|
||||
[test_jpakeclient.js]
|
||||
# Bug 618233: this test produces random failures on Windows 7.
|
||||
|
@ -130,10 +131,14 @@ skip-if = os == "android"
|
|||
# Finally, we test each engine.
|
||||
[test_addons_engine.js]
|
||||
run-sequentially = Hardcoded port in static files.
|
||||
tags = addons
|
||||
[test_addons_reconciler.js]
|
||||
tags = addons
|
||||
[test_addons_store.js]
|
||||
run-sequentially = Hardcoded port in static files.
|
||||
tags = addons
|
||||
[test_addons_tracker.js]
|
||||
tags = addons
|
||||
[test_bookmark_batch_fail.js]
|
||||
[test_bookmark_engine.js]
|
||||
[test_bookmark_invalid.js]
|
||||
|
|
|
@ -2,11 +2,22 @@
|
|||
# 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 itertools
|
||||
|
||||
from marionette_driver import errors
|
||||
from marionette import marionette_test
|
||||
from marionette.marionette_test import MarionetteTestCase as TC
|
||||
|
||||
|
||||
class TestHandleError(marionette_test.MarionetteTestCase):
|
||||
class TestProtocol1Errors(TC):
|
||||
def setUp(self):
|
||||
TC.setUp(self)
|
||||
self.op = self.marionette.protocol
|
||||
self.marionette.protocol = 1
|
||||
|
||||
def tearDown(self):
|
||||
self.marionette.protocol = self.op
|
||||
TC.tearDown(self)
|
||||
|
||||
def test_malformed_packet(self):
|
||||
for t in [{}, {"error": None}]:
|
||||
with self.assertRaisesRegexp(errors.MarionetteException, "Malformed packet"):
|
||||
|
@ -29,3 +40,51 @@ class TestHandleError(marionette_test.MarionetteTestCase):
|
|||
def test_unknown_error_status(self):
|
||||
with self.assertRaises(errors.MarionetteException):
|
||||
self.marionette._handle_error({"error": {"status": "barbera"}})
|
||||
|
||||
|
||||
class TestProtocol2Errors(TC):
|
||||
def setUp(self):
|
||||
TC.setUp(self)
|
||||
self.op = self.marionette.protocol
|
||||
self.marionette.protocol = 2
|
||||
|
||||
def tearDown(self):
|
||||
self.marionette.protocol = self.op
|
||||
TC.tearDown(self)
|
||||
|
||||
def test_malformed_packet(self):
|
||||
req = ["error", "message", "stacktrace"]
|
||||
ps = []
|
||||
for p in [p for i in range(0, len(req) + 1) for p in itertools.permutations(req, i)]:
|
||||
ps.append(dict((x, None) for x in p))
|
||||
|
||||
for p in filter(lambda p: len(p) < 3, ps):
|
||||
self.assertRaises(KeyError, self.marionette._handle_error, p)
|
||||
|
||||
def test_known_error_code(self):
|
||||
with self.assertRaises(errors.NoSuchElementException):
|
||||
self.marionette._handle_error(
|
||||
{"error": errors.NoSuchElementException.code[0],
|
||||
"message": None,
|
||||
"stacktrace": None})
|
||||
|
||||
def test_known_error_status(self):
|
||||
with self.assertRaises(errors.NoSuchElementException):
|
||||
self.marionette._handle_error(
|
||||
{"error": errors.NoSuchElementException.status,
|
||||
"message": None,
|
||||
"stacktrace": None})
|
||||
|
||||
def test_unknown_error_code(self):
|
||||
with self.assertRaises(errors.MarionetteException):
|
||||
self.marionette._handle_error(
|
||||
{"error": 123456,
|
||||
"message": None,
|
||||
"stacktrace": None})
|
||||
|
||||
def test_unknown_error_status(self):
|
||||
with self.assertRaises(errors.MarionetteException):
|
||||
self.marionette._handle_error(
|
||||
{"error": "barbera",
|
||||
"message": None,
|
||||
"stacktrace": None})
|
||||
|
|
|
@ -15,7 +15,7 @@ class TestTearDownContext(MarionetteTestCase):
|
|||
MarionetteTestCase.tearDown(self)
|
||||
|
||||
def get_context(self):
|
||||
return self.marionette._send_message('getContext', 'value')
|
||||
return self.marionette._send_message("getContext", key="value")
|
||||
|
||||
def test_skipped_teardown_ok(self):
|
||||
raise SkipTest("This should leave our teardown method in chrome context")
|
||||
|
|
|
@ -14,13 +14,13 @@ class TestSetContext(MarionetteTestCase):
|
|||
self.chrome = self.marionette.CONTEXT_CHROME
|
||||
self.content = self.marionette.CONTEXT_CONTENT
|
||||
|
||||
test_url = self.marionette.absolute_url('empty.html')
|
||||
test_url = self.marionette.absolute_url("empty.html")
|
||||
self.marionette.navigate(test_url)
|
||||
self.marionette.set_context(self.content)
|
||||
self.assertEquals(self.get_context(), self.content)
|
||||
|
||||
def get_context(self):
|
||||
return self.marionette._send_message('getContext', 'value')
|
||||
return self.marionette._send_message("getContext", key="value")
|
||||
|
||||
def test_set_different_context_using_with_block(self):
|
||||
with self.marionette.using_context(self.chrome):
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
"""
|
||||
Copyright 2011 Software Freedom Conservancy.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
# Copyright 2015 Mozilla Foundation
|
||||
# Copyright 2011 Software Freedom Conservancy.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
class ApplicationCache(object):
|
||||
|
||||
UNCACHED = 0
|
||||
IDLE = 1
|
||||
CHECKING = 2
|
||||
|
@ -28,4 +26,4 @@ class ApplicationCache(object):
|
|||
|
||||
@property
|
||||
def status(self):
|
||||
return self.driver._send_message('getAppCacheStatus', 'value')
|
||||
return self.driver._send_message("getAppCacheStatus", key="value")
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -10,11 +10,11 @@ import time
|
|||
|
||||
|
||||
class MarionetteTransport(object):
|
||||
""" The Marionette socket client. This speaks the same protocol
|
||||
as the remote debugger inside Gecko, in which messages are
|
||||
always preceded by the message length and a colon, e.g.,
|
||||
"""The Marionette socket client. This speaks the same protocol
|
||||
as the remote debugger inside Gecko, in which messages are always
|
||||
preceded by the message length and a colon, e.g.:
|
||||
|
||||
20:{'command': 'test'}
|
||||
20:{"command": "test"}
|
||||
"""
|
||||
|
||||
max_packet_length = 4096
|
||||
|
@ -25,26 +25,25 @@ class MarionetteTransport(object):
|
|||
self.port = port
|
||||
self.socket_timeout = socket_timeout
|
||||
self.sock = None
|
||||
self.traits = None
|
||||
self.applicationType = None
|
||||
self.actor = 'root'
|
||||
self.protocol = 1
|
||||
self.application_type = None
|
||||
|
||||
def _recv_n_bytes(self, n):
|
||||
""" Convenience method for receiving exactly n bytes from
|
||||
self.sock (assuming it's open and connected).
|
||||
"""Convenience method for receiving exactly n bytes from self.sock
|
||||
(assuming it's open and connected).
|
||||
"""
|
||||
data = ''
|
||||
data = ""
|
||||
while len(data) < n:
|
||||
chunk = self.sock.recv(n - len(data))
|
||||
if chunk == '':
|
||||
if chunk == "":
|
||||
break
|
||||
data += chunk
|
||||
return data
|
||||
|
||||
def receive(self):
|
||||
""" Receive the next complete response from the server, and return
|
||||
it as a dict. Each response from the server is prepended by
|
||||
len(message) + ':'.
|
||||
"""Receive the next complete response from the server, and
|
||||
return it as a JSON structure. Each response from the server
|
||||
is prepended by len(message) + ":".
|
||||
"""
|
||||
assert(self.sock)
|
||||
now = time.time()
|
||||
|
@ -69,8 +68,10 @@ class MarionetteTransport(object):
|
|||
raise socket.timeout('connection timed out after %d s' % self.socket_timeout)
|
||||
|
||||
def connect(self):
|
||||
""" Connect to the server and process the hello message we expect
|
||||
to receive in response.
|
||||
"""Connect to the server and process the hello message we expect
|
||||
to receive in response.
|
||||
|
||||
Return a tuple of the protocol level and the application type.
|
||||
"""
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(self.socket_timeout)
|
||||
|
@ -82,27 +83,22 @@ class MarionetteTransport(object):
|
|||
self.sock = None
|
||||
raise
|
||||
self.sock.settimeout(2.0)
|
||||
|
||||
hello = self.receive()
|
||||
self.traits = hello.get('traits')
|
||||
self.applicationType = hello.get('applicationType')
|
||||
self.protocol = hello.get("marionetteProtocol", 1)
|
||||
self.application_type = hello.get("applicationType")
|
||||
|
||||
# get the marionette actor id
|
||||
response = self.send({'to': 'root', 'name': 'getMarionetteID'})
|
||||
self.actor = response['id']
|
||||
return (self.protocol, self.application_type)
|
||||
|
||||
def send(self, msg):
|
||||
""" Send a message on the socket, prepending it with len(msg) + ':'.
|
||||
"""
|
||||
def send(self, data):
|
||||
"""Send a message on the socket, prepending it with len(msg) + ":"."""
|
||||
if not self.sock:
|
||||
self.connect()
|
||||
if 'to' not in msg:
|
||||
msg['to'] = self.actor
|
||||
data = json.dumps(msg)
|
||||
data = '%s:%s' % (len(data), data)
|
||||
data = "%s:%s" % (len(data), data)
|
||||
|
||||
for packet in [data[i:i + self.max_packet_length] for i in
|
||||
range(0, len(data), self.max_packet_length)]:
|
||||
try:
|
||||
try:
|
||||
self.sock.send(packet)
|
||||
except IOError as e:
|
||||
if e.errno == errno.EPIPE:
|
||||
|
@ -110,12 +106,10 @@ class MarionetteTransport(object):
|
|||
else:
|
||||
raise e
|
||||
|
||||
response = self.receive()
|
||||
return response
|
||||
return self.receive()
|
||||
|
||||
def close(self):
|
||||
""" Close the socket.
|
||||
"""
|
||||
"""Close the socket."""
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import json
|
||||
from unittest import UnittestFormatter
|
||||
from xunit import XUnitFormatter
|
||||
from html import HTMLFormatter
|
||||
|
@ -10,5 +9,11 @@ from machformatter import MachFormatter
|
|||
from tbplformatter import TbplFormatter
|
||||
from errorsummary import ErrorSummaryFormatter
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
def JSONFormatter():
|
||||
return lambda x: json.dumps(x) + "\n"
|
||||
|
|
|
@ -109,7 +109,10 @@ config = {
|
|||
},
|
||||
"all_xpcshell_suites": {
|
||||
"xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME]
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME],
|
||||
"xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
|
||||
"--tag=addons",
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME]
|
||||
},
|
||||
"all_cppunittest_suites": {
|
||||
"cppunittest": ['tests/cppunittest']
|
||||
|
|
|
@ -90,7 +90,10 @@ config = {
|
|||
},
|
||||
"all_xpcshell_suites": {
|
||||
"xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME]
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME],
|
||||
"xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
|
||||
"--tag=addons",
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME]
|
||||
},
|
||||
"all_cppunittest_suites": {
|
||||
"cppunittest": ['tests/cppunittest']
|
||||
|
|
|
@ -105,7 +105,10 @@ config = {
|
|||
},
|
||||
"all_xpcshell_suites": {
|
||||
"xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME]
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME],
|
||||
"xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
|
||||
"--tag=addons",
|
||||
"%(abs_app_dir)s/" + XPCSHELL_NAME]
|
||||
},
|
||||
"all_cppunittest_suites": {
|
||||
"cppunittest": ['tests/cppunittest']
|
||||
|
|
|
@ -1405,10 +1405,11 @@ or run without that action (ie: --no-{action})"
|
|||
templates.extend(contents['l10n'])
|
||||
else:
|
||||
templates = contents['routes']
|
||||
index = self.config.get('taskcluster_index', 'index.garbage.staging')
|
||||
routes = []
|
||||
for template in templates:
|
||||
fmt = {
|
||||
'index': 'index.garbage.staging.mshal-testing', # TODO
|
||||
'index': index,
|
||||
'project': self.buildbot_config['properties']['branch'],
|
||||
'head_rev': self.query_revision(),
|
||||
'build_product': self.config['stage_product'],
|
||||
|
@ -1427,7 +1428,6 @@ or run without that action (ie: --no-{action})"
|
|||
self.log_obj,
|
||||
)
|
||||
|
||||
index = self.config.get('taskcluster_index', 'index.garbage.staging')
|
||||
# TODO: Bug 1165980 - these should be in tree
|
||||
routes.extend([
|
||||
"%s.buildbot.branches.%s.%s" % (index, self.branch, self.stage_platform),
|
||||
|
|
|
@ -1028,9 +1028,7 @@ class DesktopSingleLocale(LocalesMixin, ReleaseMixin, MockMixin, BuildbotMixin,
|
|||
routes = []
|
||||
for template in templates:
|
||||
fmt = {
|
||||
# TODO: Bug 1133074
|
||||
#index = self.config.get('taskcluster_index', 'index.garbage.staging')
|
||||
'index': 'index.garbage.staging.mshal-testing',
|
||||
'index': self.config.get('taskcluster_index', 'index.garbage.staging'),
|
||||
'project': branch,
|
||||
'head_rev': revision,
|
||||
'build_product': self.config['stage_product'],
|
||||
|
|
|
@ -287,7 +287,7 @@ class Graph(object):
|
|||
|
||||
# Template parameters used when expanding the graph
|
||||
parameters = dict(gaia_info().items() + {
|
||||
'index': 'index.garbage.staging.mshal-testing', #TODO
|
||||
'index': 'index',
|
||||
'project': project,
|
||||
'pushlog_id': params.get('pushlog_id', 0),
|
||||
'docker_image': docker_image,
|
||||
|
|
|
@ -2,27 +2,35 @@
|
|||
type: testharness
|
||||
prefs: [media.mediasource.enabled:true]
|
||||
[Test invalid MIME format "video/webm"]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
|
||||
expected: FAIL
|
||||
|
||||
[Test invalid MIME format "video/webm;"]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
|
||||
expected: FAIL
|
||||
|
||||
[Test invalid MIME format "video/webm;codecs"]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
|
||||
expected: FAIL
|
||||
|
||||
[Test invalid MIME format "video/webm;codecs="]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
|
||||
expected: FAIL
|
||||
|
||||
[Test invalid MIME format "video/webm;codecs=""]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
|
||||
expected: FAIL
|
||||
|
||||
[Test invalid MIME format "video/webm;codecs="""]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
|
||||
expected: FAIL
|
||||
|
||||
[Test valid WebM type "AUDIO/WEBM;CODECS="vorbis""]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
|
||||
expected: FAIL
|
||||
|
||||
[Test invalid mismatch between major type and codec ID "audio/webm;codecs="vp8""]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
|
||||
expected: FAIL
|
||||
|
||||
[Test valid MP4 type "audio/mp4;codecs="mp4a.67""]
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
|
||||
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
#include "nsIDOMWindow.h"
|
||||
#include "nsGlobalWindow.h"
|
||||
|
||||
#include "mozilla/unused.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
#include "windows.h"
|
||||
#else
|
||||
|
@ -440,7 +444,7 @@ NS_IMETHODIMP nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProc
|
|||
}
|
||||
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService)
|
||||
NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver)
|
||||
|
||||
nsPerformanceStatsService::nsPerformanceStatsService()
|
||||
#if defined(XP_WIN)
|
||||
|
@ -448,7 +452,13 @@ nsPerformanceStatsService::nsPerformanceStatsService()
|
|||
#else
|
||||
: mProcessId(getpid())
|
||||
#endif
|
||||
, mProcessStayed(0)
|
||||
, mProcessMoved(0)
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (obs) {
|
||||
mozilla::unused << obs->AddObserver(this, "profile-before-shutdown", false);
|
||||
}
|
||||
}
|
||||
|
||||
nsPerformanceStatsService::~nsPerformanceStatsService()
|
||||
|
@ -507,8 +517,28 @@ NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerforman
|
|||
return rv;
|
||||
}
|
||||
|
||||
js::GetPerfMonitoringTestCpuRescheduling(JS_GetRuntime(cx), &mProcessStayed, &mProcessMoved);
|
||||
snapshot.forget(aSnapshot);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
/* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
|
||||
NS_IMETHODIMP nsPerformanceStatsService::Observe(nsISupports *, const char *, const char16_t *)
|
||||
{
|
||||
// Upload telemetry
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (obs) {
|
||||
mozilla::unused << obs->RemoveObserver(this, "profile-before-shutdown");
|
||||
}
|
||||
|
||||
if (mProcessStayed + mProcessMoved == 0) {
|
||||
// Nothing to report.
|
||||
return NS_OK;
|
||||
}
|
||||
const uint32_t proportion = ( 100 * mProcessStayed ) / ( mProcessStayed + mProcessMoved );
|
||||
mozilla::Telemetry::Accumulate("PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED", proportion);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -6,13 +6,16 @@
|
|||
#ifndef nsPerformanceStats_h
|
||||
#define nsPerformanceStats_h
|
||||
|
||||
#include "nsIObserver.h"
|
||||
|
||||
#include "nsIPerformanceStats.h"
|
||||
|
||||
class nsPerformanceStatsService : public nsIPerformanceStatsService
|
||||
class nsPerformanceStatsService : public nsIPerformanceStatsService, nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPERFORMANCESTATSSERVICE
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
nsPerformanceStatsService();
|
||||
|
||||
|
@ -20,6 +23,8 @@ private:
|
|||
virtual ~nsPerformanceStatsService();
|
||||
|
||||
const uint64_t mProcessId;
|
||||
uint64_t mProcessStayed;
|
||||
uint64_t mProcessMoved;
|
||||
protected:
|
||||
};
|
||||
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const {utils: Cu, interfaces: Ci, classes: Cc} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm", this);
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
Cu.import("resource://gre/modules/PerformanceStats.jsm", this);
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
let promiseStatistics = Task.async(function*(name) {
|
||||
yield Promise.resolve(); // Make sure that we wait until
|
||||
// statistics have been updated.
|
||||
let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
|
||||
getService(Ci.nsIPerformanceStatsService);
|
||||
let snapshot = service.getSnapshot();
|
||||
let componentsData = [];
|
||||
let componentsEnum = snapshot.getComponentsData().enumerate();
|
||||
while (componentsEnum.hasMoreElements()) {
|
||||
let data = componentsEnum.getNext().QueryInterface(Ci.nsIPerformanceStats);
|
||||
let normalized = JSON.parse(JSON.stringify(data));
|
||||
componentsData.push(data);
|
||||
}
|
||||
return {
|
||||
processData: JSON.parse(JSON.stringify(snapshot.getProcessData())),
|
||||
componentsData
|
||||
};
|
||||
});
|
||||
|
||||
let promiseSetMonitoring = Task.async(function*(to) {
|
||||
let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
|
||||
getService(Ci.nsIPerformanceStatsService);
|
||||
service.isMonitoringJank = to;
|
||||
service.isMonitoringCPOW = to;
|
||||
yield Promise.resolve();
|
||||
});
|
||||
|
||||
let promiseSetPerCompartment = Task.async(function*(to) {
|
||||
let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
|
||||
getService(Ci.nsIPerformanceStatsService);
|
||||
service.isMonitoringPerCompartment = to;
|
||||
yield Promise.resolve();
|
||||
});
|
||||
|
||||
function getBuiltinStatistics(name, snapshot) {
|
||||
let stats = snapshot.componentsData.find(stats =>
|
||||
stats.isSystem && !stats.addonId
|
||||
);
|
||||
do_print(`Built-in statistics for ${name} were ${stats?"":"not "}found`);
|
||||
do_print(JSON.stringify(snapshot.componentsData, null, "\t"));
|
||||
return stats;
|
||||
}
|
||||
|
||||
function burnCPU(ms) {
|
||||
do_print("Burning CPU");
|
||||
let counter = 0;
|
||||
let ignored = [];
|
||||
let start = Date.now();
|
||||
while (Date.now() - start < ms) {
|
||||
ignored.push(0);
|
||||
ignored.shift();
|
||||
++counter;
|
||||
}
|
||||
do_print("Burning CPU over, after " + counter + " iterations");
|
||||
}
|
||||
|
||||
function ensureEquals(snap1, snap2, name) {
|
||||
for (let k of Object.keys(snap1.processData)) {
|
||||
if (k == "ticks") {
|
||||
// Ticks monitoring cannot be deactivated
|
||||
continue;
|
||||
}
|
||||
Assert.equal(snap1.processData[k], snap2.processData[k], `Same process data value ${k} (${name})`)
|
||||
}
|
||||
let stats1 = snap1.componentsData.sort((a, b) => a.name <= b.name);
|
||||
let stats2 = snap2.componentsData.sort((a, b) => a.name <= b.name);
|
||||
Assert.equal(stats1.length, stats2.length, `Same number of components (${name})`);
|
||||
for (let i = 0; i < stats1.length; ++i) {
|
||||
for (let k of Object.keys(stats1[i])) {
|
||||
if (k == "ticks") {
|
||||
// Ticks monitoring cannot be deactivated
|
||||
continue;
|
||||
}
|
||||
Assert.equal(stats1[i][k], stats1[i][k], `Same component data value ${i} ${k} (${name})`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasLowPrecision() {
|
||||
let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")];
|
||||
do_print(`Running ${sysName} version ${sysVersion}`);
|
||||
|
||||
if (sysName == "Windows_NT" && sysVersion < 6) {
|
||||
do_print("Running old Windows, need to deactivate tests due to bad precision.");
|
||||
return true;
|
||||
}
|
||||
if (sysName == "Linux" && sysVersion <= 2.6) {
|
||||
do_print("Running old Linux, need to deactivate tests due to bad precision.");
|
||||
return true;
|
||||
}
|
||||
do_print("This platform has good precision.")
|
||||
return false;
|
||||
}
|
||||
|
||||
add_task(function* test_measure() {
|
||||
let skipPrecisionTests = hasLowPrecision();
|
||||
yield promiseSetPerCompartment(false);
|
||||
|
||||
do_print("Burn CPU without the stopwatch");
|
||||
yield promiseSetMonitoring(false);
|
||||
let stats0 = yield promiseStatistics("Initial state");
|
||||
burnCPU(300);
|
||||
let stats1 = yield promiseStatistics("Initial state + burn, without stopwatch");
|
||||
|
||||
do_print("Burn CPU with the stopwatch");
|
||||
yield promiseSetMonitoring(true);
|
||||
burnCPU(300);
|
||||
let stats2 = yield promiseStatistics("Second burn, with stopwatch");
|
||||
|
||||
do_print("Burn CPU without the stopwatch again")
|
||||
yield promiseSetMonitoring(false);
|
||||
let stats3 = yield promiseStatistics("Before third burn, without stopwatch");
|
||||
burnCPU(300);
|
||||
let stats4 = yield promiseStatistics("After third burn, without stopwatch");
|
||||
|
||||
ensureEquals(stats0, stats1, "Initial state vs. Initial state + burn, without stopwatch");
|
||||
let process1 = stats1.processData;
|
||||
let process2 = stats2.processData;
|
||||
let process3 = stats3.processData;
|
||||
let process4 = stats4.processData;
|
||||
if (skipPrecisionTests) {
|
||||
do_print("Skipping totalUserTime check under Windows XP, as timer is not always updated by the OS.")
|
||||
} else {
|
||||
Assert.ok(process2.totalUserTime - process1.totalUserTime >= 10000, `At least 10ms counted for process time (${process2.totalUserTime - process1.totalUserTime})`);
|
||||
}
|
||||
Assert.equal(process2.totalCPOWTime, process1.totalCPOWTime, "We haven't used any CPOW time during the first burn");
|
||||
Assert.equal(process4.totalUserTime, process3.totalUserTime, "After deactivating the stopwatch, we didn't count any time");
|
||||
Assert.equal(process4.totalCPOWTime, process3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time");
|
||||
|
||||
let builtin1 = getBuiltinStatistics("Built-ins 1", stats1) || { totalUserTime: 0, totalCPOWTime: 0 };
|
||||
let builtin2 = getBuiltinStatistics("Built-ins 2", stats2);
|
||||
let builtin3 = getBuiltinStatistics("Built-ins 3", stats3);
|
||||
let builtin4 = getBuiltinStatistics("Built-ins 4", stats4);
|
||||
Assert.notEqual(builtin2, null, "Found the statistics for built-ins 2");
|
||||
Assert.notEqual(builtin3, null, "Found the statistics for built-ins 3");
|
||||
Assert.notEqual(builtin4, null, "Found the statistics for built-ins 4");
|
||||
|
||||
if (skipPrecisionTests) {
|
||||
do_print("Skipping totalUserTime check under Windows XP, as timer is not always updated by the OS.")
|
||||
} else {
|
||||
Assert.ok(builtin2.totalUserTime - builtin1.totalUserTime >= 10000, `At least 10ms counted for built-in statistics (${builtin2.totalUserTime - builtin1.totalUserTime})`);
|
||||
}
|
||||
Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "We haven't used any CPOW time during the first burn for the built-in");
|
||||
Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "No CPOW for built-in statistics");
|
||||
Assert.equal(builtin4.totalUserTime, builtin3.totalUserTime, "After deactivating the stopwatch, we didn't count any time for the built-in");
|
||||
Assert.equal(builtin4.totalCPOWTime, builtin3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time for the built-in");
|
||||
|
||||
// Ideally, we should be able to look for test_compartments.js, but
|
||||
// it doesn't have its own compartment.
|
||||
for (let stats of [stats1, stats2, stats3, stats4]) {
|
||||
Assert.ok(!stats.componentsData.find(x => x.name.includes("Task.jsm")), "At this stage, Task.jsm doesn't show up in the components data");
|
||||
}
|
||||
yield promiseSetPerCompartment(true);
|
||||
burnCPU(300);
|
||||
let stats5 = yield promiseStatistics("With per-compartment monitoring");
|
||||
Assert.ok(stats5.componentsData.find(x => x.name.includes("Task.jsm")), "With per-compartment monitoring, test_compartments.js shows up");
|
||||
});
|
|
@ -1,6 +0,0 @@
|
|||
[DEFAULT]
|
||||
head=
|
||||
tail=
|
||||
|
||||
[test_compartments.js]
|
||||
skip-if = toolkit == 'gonk' # Fails on b2g emulator, bug 1147664
|
|
@ -63,13 +63,17 @@ support-files =
|
|||
[test_async.js]
|
||||
[test_async_app.js]
|
||||
[test_async_addon.js]
|
||||
tags = addons
|
||||
[test_async_addon_no_override.js]
|
||||
tags = addons
|
||||
[test_async_distribution.js]
|
||||
[test_async_profile_engine.js]
|
||||
[test_sync.js]
|
||||
[test_sync_app.js]
|
||||
[test_sync_addon.js]
|
||||
tags = addons
|
||||
[test_sync_addon_no_override.js]
|
||||
tags = addons
|
||||
[test_sync_distribution.js]
|
||||
[test_sync_fallback.js]
|
||||
[test_sync_delay_fallback.js]
|
||||
|
|
|
@ -8934,5 +8934,13 @@
|
|||
"high": "30",
|
||||
"n_buckets": "29",
|
||||
"description": "The number of times AddIceCandidate failed on a given PeerConnection, given that ICE failed."
|
||||
},
|
||||
"PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED": {
|
||||
"alert_emails": ["dteller@mozilla.com"],
|
||||
"expires_in_version": "44",
|
||||
"kind": "linear",
|
||||
"high": "100",
|
||||
"n_buckets": "20",
|
||||
"description": "Proportion (%) of reschedulings of the main process to another CPU during the execution of code inside a JS compartment. Updated while we are measuring jank."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,11 @@ generated-files =
|
|||
|
||||
[test_nsITelemetry.js]
|
||||
[test_SubsessionChaining.js]
|
||||
tags = addons
|
||||
[test_TelemetryEnvironment.js]
|
||||
# Bug 1144395: crash on Android 4.3
|
||||
skip-if = android_version == "18"
|
||||
tags = addons
|
||||
[test_PingAPI.js]
|
||||
skip-if = os == "android"
|
||||
[test_TelemetryFlagClear.js]
|
||||
|
@ -37,20 +39,26 @@ skip-if = os == "android"
|
|||
# fail-if = os == "android"
|
||||
# Bug 1144395: crash on Android 4.3
|
||||
skip-if = android_version == "18"
|
||||
tags = addons
|
||||
[test_TelemetryController_idle.js]
|
||||
[test_TelemetryControllerShutdown.js]
|
||||
tags = addons
|
||||
[test_TelemetryStopwatch.js]
|
||||
[test_TelemetryControllerBuildID.js]
|
||||
# Bug 1144395: crash on Android 4.3
|
||||
skip-if = android_version == "18"
|
||||
[test_TelemetrySendOldPings.js]
|
||||
skip-if = os == "android" # Disabled due to intermittent orange on Android
|
||||
tags = addons
|
||||
[test_TelemetrySession.js]
|
||||
# Bug 1144395: crash on Android 4.3
|
||||
skip-if = android_version == "18"
|
||||
tags = addons
|
||||
[test_ThreadHangStats.js]
|
||||
run-sequentially = Bug 1046307, test can fail intermittently when CPU load is high
|
||||
[test_TelemetrySend.js]
|
||||
[test_ChildHistograms.js]
|
||||
skip-if = os == "android"
|
||||
tags = addons
|
||||
[test_TelemetryReportingPolicy.js]
|
||||
tags = addons
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# The file is shared between the two main xpcshell manifest files.
|
||||
[DEFAULT]
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
tags = addons
|
||||
|
||||
[test_AddonRepository.js]
|
||||
# Bug 676992: test consistently hangs on Android
|
||||
|
|
|
@ -4,5 +4,6 @@ tail =
|
|||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
dupe-manifest =
|
||||
tags = addons
|
||||
|
||||
[include:xpcshell-shared.ini]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[DEFAULT]
|
||||
skip-if = buildapp == 'mulet' || toolkit == 'android' || toolkit == 'gonk'
|
||||
tags = addons
|
||||
head = head_addons.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
|
|
|
@ -157,18 +157,6 @@ NS_IMETHODIMP nsDeviceContextSpecGTK::GetSurfaceForPrinter(gfxASurface **aSurfac
|
|||
// There is nothing to detect on Print Preview, use PS.
|
||||
format = nsIPrintSettings::kOutputFormatPS;
|
||||
} else {
|
||||
const gchar* fmtGTK = gtk_print_settings_get(mGtkPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT);
|
||||
if (fmtGTK) {
|
||||
if (nsDependentCString(fmtGTK).EqualsIgnoreCase("pdf")) {
|
||||
format = nsIPrintSettings::kOutputFormatPDF;
|
||||
} else {
|
||||
format = nsIPrintSettings::kOutputFormatPS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't found the format at this point, we're sunk. :(
|
||||
if (format == nsIPrintSettings::kOutputFormatNative) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,17 +200,44 @@ nsPrintSettingsGTK::SetGtkPrinter(GtkPrinter *aPrinter)
|
|||
g_object_unref(mGTKPrinter);
|
||||
|
||||
mGTKPrinter = (GtkPrinter*) g_object_ref(aPrinter);
|
||||
}
|
||||
|
||||
// Prior to gtk 2.24, gtk_printer_accepts_pdf() and
|
||||
// gtk_printer_accepts_ps() always returned true regardless of the
|
||||
// printer's capability.
|
||||
bool shouldTrustGTK =
|
||||
(gtk_major_version > 2 ||
|
||||
(gtk_major_version == 2 && gtk_minor_version >= 24));
|
||||
bool acceptsPDF = shouldTrustGTK && gtk_printer_accepts_pdf(mGTKPrinter);
|
||||
NS_IMETHODIMP nsPrintSettingsGTK::GetOutputFormat(int16_t *aOutputFormat)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputFormat);
|
||||
|
||||
SetOutputFormat(acceptsPDF ? nsIPrintSettings::kOutputFormatPDF
|
||||
: nsIPrintSettings::kOutputFormatPS);
|
||||
int16_t format;
|
||||
nsresult rv = nsPrintSettings::GetOutputFormat(&format);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (format == nsIPrintSettings::kOutputFormatNative) {
|
||||
const gchar* fmtGTK =
|
||||
gtk_print_settings_get(mPrintSettings,
|
||||
GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT);
|
||||
if (fmtGTK) {
|
||||
if (nsDependentCString(fmtGTK).EqualsIgnoreCase("pdf")) {
|
||||
format = nsIPrintSettings::kOutputFormatPDF;
|
||||
} else {
|
||||
format = nsIPrintSettings::kOutputFormatPS;
|
||||
}
|
||||
} else if (GTK_IS_PRINTER(mGTKPrinter)) {
|
||||
// Prior to gtk 2.24, gtk_printer_accepts_pdf() and
|
||||
// gtk_printer_accepts_ps() always returned true regardless of the
|
||||
// printer's capability.
|
||||
bool shouldTrustGTK =
|
||||
(gtk_major_version > 2 ||
|
||||
(gtk_major_version == 2 && gtk_minor_version >= 24));
|
||||
bool acceptsPDF = shouldTrustGTK && gtk_printer_accepts_pdf(mGTKPrinter);
|
||||
|
||||
format = acceptsPDF ? nsIPrintSettings::kOutputFormatPDF
|
||||
: nsIPrintSettings::kOutputFormatPS;
|
||||
}
|
||||
}
|
||||
|
||||
*aOutputFormat = format;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -113,6 +113,8 @@ public:
|
|||
NS_IMETHOD GetDuplex(int32_t *aDuplex) override;
|
||||
NS_IMETHOD SetDuplex(int32_t aDuplex) override;
|
||||
|
||||
NS_IMETHOD GetOutputFormat(int16_t *aOutputFormat) override;
|
||||
|
||||
protected:
|
||||
virtual ~nsPrintSettingsGTK();
|
||||
|
||||
|
|
|
@ -566,6 +566,8 @@ GetSysFontInfo(HDC aHDC, LookAndFeel::FontID anID,
|
|||
case LookAndFeel::eFont_Widget:
|
||||
case LookAndFeel::eFont_Dialog:
|
||||
case LookAndFeel::eFont_Button:
|
||||
case LookAndFeel::eFont_Field:
|
||||
case LookAndFeel::eFont_List:
|
||||
// XXX It's not clear to me whether this is exactly the right
|
||||
// set of LookAndFeel values to map to the dialog font; we may
|
||||
// want to add or remove cases here after reviewing the visual
|
||||
|
|
|
@ -55,18 +55,19 @@ static bool sLoggingBuffered = true;
|
|||
static bool sLoggingLogcat = true;
|
||||
#endif // defined(ANDROID)
|
||||
|
||||
nsConsoleService::MessageElement::~MessageElement()
|
||||
{
|
||||
}
|
||||
|
||||
nsConsoleService::nsConsoleService()
|
||||
: mMessages(nullptr)
|
||||
, mCurrent(0)
|
||||
, mFull(false)
|
||||
: mCurrentSize(0)
|
||||
, mDeliveringMessage(false)
|
||||
, mLock("nsConsoleService.mLock")
|
||||
{
|
||||
// XXX grab this from a pref!
|
||||
// hm, but worry about circularity, bc we want to be able to report
|
||||
// prefs errs...
|
||||
mBufferSize = 250;
|
||||
mMaximumSize = 250;
|
||||
}
|
||||
|
||||
|
||||
|
@ -75,53 +76,46 @@ nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID)
|
|||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Remove the messages related to this window
|
||||
for (uint32_t i = 0; i < mBufferSize && mMessages[i]; i++) {
|
||||
// Only messages implementing nsIScriptError interface exposes the inner window ID
|
||||
nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(mMessages[i]);
|
||||
for (MessageElement* e = mMessages.getFirst(); e != nullptr; ) {
|
||||
// Only messages implementing nsIScriptError interface expose the
|
||||
// inner window ID.
|
||||
nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get());
|
||||
if (!scriptError) {
|
||||
e = e->getNext();
|
||||
continue;
|
||||
}
|
||||
uint64_t innerWindowID;
|
||||
nsresult rv = scriptError->GetInnerWindowID(&innerWindowID);
|
||||
if (NS_FAILED(rv) || innerWindowID != innerID) {
|
||||
e = e->getNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Free this matching message!
|
||||
NS_RELEASE(mMessages[i]);
|
||||
MessageElement* next = e->getNext();
|
||||
e->remove();
|
||||
delete e;
|
||||
mCurrentSize--;
|
||||
MOZ_ASSERT(mCurrentSize < mMaximumSize);
|
||||
|
||||
uint32_t j = i;
|
||||
// Now shift all the following messages
|
||||
// XXXkhuey this is not an efficient way to iterate through an array ...
|
||||
for (; j < mBufferSize - 1 && mMessages[j + 1]; j++) {
|
||||
mMessages[j] = mMessages[j + 1];
|
||||
}
|
||||
// Nullify the current slot
|
||||
mMessages[j] = nullptr;
|
||||
mCurrent = j;
|
||||
|
||||
// The array is no longer full
|
||||
mFull = false;
|
||||
|
||||
// Ensure the next iteration handles the messages we just shifted down
|
||||
i--;
|
||||
e = next;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsConsoleService::ClearMessages()
|
||||
{
|
||||
while (!mMessages.isEmpty()) {
|
||||
MessageElement* e = mMessages.popFirst();
|
||||
delete e;
|
||||
}
|
||||
mCurrentSize = 0;
|
||||
}
|
||||
|
||||
nsConsoleService::~nsConsoleService()
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
uint32_t i = 0;
|
||||
while (i < mBufferSize && mMessages[i]) {
|
||||
NS_RELEASE(mMessages[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (mMessages) {
|
||||
free(mMessages);
|
||||
}
|
||||
ClearMessages();
|
||||
}
|
||||
|
||||
class AddConsolePrefWatchers : public nsRunnable
|
||||
|
@ -157,15 +151,6 @@ private:
|
|||
nsresult
|
||||
nsConsoleService::Init()
|
||||
{
|
||||
mMessages = (nsIConsoleMessage**)
|
||||
moz_xmalloc(mBufferSize * sizeof(nsIConsoleMessage*));
|
||||
if (!mMessages) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Array elements should be 0 initially for circular buffer algorithm.
|
||||
memset(mMessages, 0, mBufferSize * sizeof(nsIConsoleMessage*));
|
||||
|
||||
NS_DispatchToMainThread(new AddConsolePrefWatchers(this));
|
||||
|
||||
return NS_OK;
|
||||
|
@ -241,11 +226,7 @@ nsConsoleService::LogMessageWithMode(nsIConsoleMessage* aMessage,
|
|||
}
|
||||
|
||||
nsRefPtr<LogMessageRunnable> r;
|
||||
nsIConsoleMessage* retiredMessage;
|
||||
|
||||
if (sLoggingBuffered) {
|
||||
NS_ADDREF(aMessage); // early, in case it's same as replaced below.
|
||||
}
|
||||
nsCOMPtr<nsIConsoleMessage> retiredMessage;
|
||||
|
||||
/*
|
||||
* Lock while updating buffer, and while taking snapshot of
|
||||
|
@ -311,18 +292,16 @@ nsConsoleService::LogMessageWithMode(nsIConsoleMessage* aMessage,
|
|||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If there's already a message in the slot we're about to replace,
|
||||
* we've wrapped around, and we need to release the old message. We
|
||||
* save a pointer to it, so we can release below outside the lock.
|
||||
*/
|
||||
retiredMessage = mMessages[mCurrent];
|
||||
|
||||
if (sLoggingBuffered) {
|
||||
mMessages[mCurrent++] = aMessage;
|
||||
if (mCurrent == mBufferSize) {
|
||||
mCurrent = 0; // wrap around.
|
||||
mFull = true;
|
||||
MessageElement* e = new MessageElement(aMessage);
|
||||
mMessages.insertBack(e);
|
||||
if (mCurrentSize != mMaximumSize) {
|
||||
mCurrentSize++;
|
||||
} else {
|
||||
MessageElement* p = mMessages.popFirst();
|
||||
MOZ_ASSERT(p);
|
||||
retiredMessage = p->forget();
|
||||
delete p;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,21 +356,15 @@ nsConsoleService::GetMessageArray(uint32_t* aCount,
|
|||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsIConsoleMessage** messageArray;
|
||||
|
||||
/*
|
||||
* Lock the whole method, as we don't want anyone mucking with mCurrent or
|
||||
* mFull while we're copying out the buffer.
|
||||
*/
|
||||
MutexAutoLock lock(mLock);
|
||||
|
||||
if (mCurrent == 0 && !mFull) {
|
||||
if (mMessages.isEmpty()) {
|
||||
/*
|
||||
* Make a 1-length output array so that nobody gets confused,
|
||||
* and return a count of 0. This should result in a 0-length
|
||||
* array object when called from script.
|
||||
*/
|
||||
messageArray = (nsIConsoleMessage**)
|
||||
nsIConsoleMessage** messageArray = (nsIConsoleMessage**)
|
||||
moz_xmalloc(sizeof(nsIConsoleMessage*));
|
||||
*messageArray = nullptr;
|
||||
*aMessages = messageArray;
|
||||
|
@ -400,32 +373,21 @@ nsConsoleService::GetMessageArray(uint32_t* aCount,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t resultSize = mFull ? mBufferSize : mCurrent;
|
||||
messageArray =
|
||||
(nsIConsoleMessage**)moz_xmalloc((sizeof(nsIConsoleMessage*))
|
||||
* resultSize);
|
||||
MOZ_ASSERT(mCurrentSize <= mMaximumSize);
|
||||
nsIConsoleMessage** messageArray =
|
||||
static_cast<nsIConsoleMessage**>(moz_xmalloc(sizeof(nsIConsoleMessage*)
|
||||
* mCurrentSize));
|
||||
|
||||
if (!messageArray) {
|
||||
*aMessages = nullptr;
|
||||
*aCount = 0;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
uint32_t i = 0;
|
||||
for (MessageElement* e = mMessages.getFirst(); e != nullptr; e = e->getNext()) {
|
||||
nsCOMPtr<nsIConsoleMessage> m = e->Get();
|
||||
m.forget(&messageArray[i]);
|
||||
i++;
|
||||
};
|
||||
|
||||
uint32_t i;
|
||||
if (mFull) {
|
||||
for (i = 0; i < mBufferSize; i++) {
|
||||
// if full, fill the buffer starting from mCurrent (which'll be
|
||||
// oldest) wrapping around the buffer to the most recent.
|
||||
messageArray[i] = mMessages[(mCurrent + i) % mBufferSize];
|
||||
NS_ADDREF(messageArray[i]);
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < mCurrent; i++) {
|
||||
messageArray[i] = mMessages[i];
|
||||
NS_ADDREF(messageArray[i]);
|
||||
}
|
||||
}
|
||||
*aCount = resultSize;
|
||||
MOZ_ASSERT(i == mCurrentSize);
|
||||
|
||||
*aCount = i;
|
||||
*aMessages = messageArray;
|
||||
|
||||
return NS_OK;
|
||||
|
@ -480,16 +442,7 @@ nsConsoleService::Reset()
|
|||
*/
|
||||
MutexAutoLock lock(mLock);
|
||||
|
||||
mCurrent = 0;
|
||||
mFull = false;
|
||||
|
||||
/*
|
||||
* Free all messages stored so far (cf. destructor)
|
||||
*/
|
||||
for (uint32_t i = 0; i < mBufferSize && mMessages[i]; i++) {
|
||||
NS_RELEASE(mMessages[i]);
|
||||
}
|
||||
|
||||
ClearMessages();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,21 +60,45 @@ public:
|
|||
void CollectCurrentListeners(nsCOMArray<nsIConsoleListener>& aListeners);
|
||||
|
||||
private:
|
||||
class MessageElement : public mozilla::LinkedListElement<MessageElement>
|
||||
{
|
||||
public:
|
||||
explicit MessageElement(nsIConsoleMessage* aMessage) : mMessage(aMessage)
|
||||
{}
|
||||
|
||||
nsIConsoleMessage* Get()
|
||||
{
|
||||
return mMessage.get();
|
||||
}
|
||||
|
||||
already_AddRefed<nsIConsoleMessage> forget()
|
||||
{
|
||||
return mMessage.forget();
|
||||
}
|
||||
|
||||
~MessageElement();
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIConsoleMessage> mMessage;
|
||||
|
||||
MessageElement(const MessageElement&) = delete;
|
||||
MessageElement& operator=(const MessageElement&) = delete;
|
||||
MessageElement(MessageElement&&) = delete;
|
||||
MessageElement& operator=(MessageElement&&) = delete;
|
||||
};
|
||||
|
||||
~nsConsoleService();
|
||||
|
||||
void ClearMessagesForWindowID(const uint64_t innerID);
|
||||
void ClearMessages();
|
||||
|
||||
// Circular buffer of saved messages
|
||||
nsIConsoleMessage** mMessages;
|
||||
mozilla::LinkedList<MessageElement> mMessages;
|
||||
|
||||
// How big?
|
||||
uint32_t mBufferSize;
|
||||
// The current size of mMessages.
|
||||
uint32_t mCurrentSize;
|
||||
|
||||
// Index of slot in mMessages that'll be filled by *next* log message
|
||||
uint32_t mCurrent;
|
||||
|
||||
// Is the buffer full? (Has mCurrent wrapped around at least once?)
|
||||
bool mFull;
|
||||
// The maximum size of mMessages.
|
||||
uint32_t mMaximumSize;
|
||||
|
||||
// Are we currently delivering a console message on the main thread? If
|
||||
// so, we suppress incoming messages on the main thread only, to avoid
|
||||
|
|
|
@ -451,15 +451,15 @@ main()
|
|||
else
|
||||
printf("foo1p == foo2p\n");
|
||||
|
||||
printf("\n### Test 7.5: can you compare a |nsCOMPtr| with nullptr [!=]?\n");
|
||||
if ( foo1p != nullptr )
|
||||
printf("foo1p != nullptr\n");
|
||||
if ( nullptr != foo1p )
|
||||
printf("nullptr != foo1p\n");
|
||||
if ( foo1p == nullptr )
|
||||
printf("foo1p == nullptr\n");
|
||||
if ( nullptr == foo1p )
|
||||
printf("nullptr == foo1p\n");
|
||||
printf("\n### Test 7.5: can you compare a |nsCOMPtr| with NULL, 0, nullptr [!=]?\n");
|
||||
if ( foo1p != 0 )
|
||||
printf("foo1p != 0\n");
|
||||
if ( 0 != foo1p )
|
||||
printf("0 != foo1p\n");
|
||||
if ( foo1p == 0 )
|
||||
printf("foo1p == 0\n");
|
||||
if ( 0 == foo1p )
|
||||
printf("0 == foo1p\n");
|
||||
|
||||
|
||||
Foo* raw_foo2p = foo2p.get();
|
||||
|
@ -500,8 +500,8 @@ main()
|
|||
else
|
||||
printf("foo1p is NULL\n");
|
||||
|
||||
printf("\n### Test 13: null pointer test?\n");
|
||||
if ( foo1p == nullptr )
|
||||
printf("\n### Test 13: numeric pointer test?\n");
|
||||
if ( foo1p == 0 )
|
||||
printf("foo1p is NULL\n");
|
||||
else
|
||||
printf("foo1p is not NULL\n");
|
||||
|
|
Загрузка…
Ссылка в новой задаче