mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-15 06:15:43 +00:00
Merge mozilla-central to autoland. r=merge a=merge on CLOSED TREE
This commit is contained in:
commit
0a208387ad
@ -9,8 +9,10 @@
|
||||
#include "mozilla/a11y/HandlerProvider.h"
|
||||
|
||||
#include "Accessible2_3.h"
|
||||
#include "AccessibleDocument.h"
|
||||
#include "AccessibleTable.h"
|
||||
#include "AccessibleTable2.h"
|
||||
#include "AccessibleTableCell.h"
|
||||
#include "HandlerData.h"
|
||||
#include "HandlerData_i.c"
|
||||
#include "mozilla/Assertions.h"
|
||||
@ -340,8 +342,20 @@ REFIID
|
||||
HandlerProvider::GetEffectiveOutParamIid(REFIID aCallIid,
|
||||
ULONG aCallMethod)
|
||||
{
|
||||
if (aCallIid == IID_IAccessibleTable || aCallIid == IID_IAccessibleTable2) {
|
||||
return IID_IAccessible2_3;
|
||||
if (aCallIid == IID_IAccessibleTable ||
|
||||
aCallIid == IID_IAccessibleTable2 ||
|
||||
aCallIid == IID_IAccessibleDocument ||
|
||||
aCallIid == IID_IAccessibleTableCell ||
|
||||
aCallIid == IID_IAccessibleRelation) {
|
||||
return NEWEST_IA2_IID;
|
||||
}
|
||||
|
||||
// IAccessible2_2::accessibleWithCaret
|
||||
static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3,
|
||||
"You have modified NEWEST_IA2_IID. This code needs updating.");
|
||||
if ((aCallIid == IID_IAccessible2_2 || aCallIid == IID_IAccessible2_3) &&
|
||||
aCallMethod == 47) {
|
||||
return NEWEST_IA2_IID;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(false);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "AccessibleTable2.h"
|
||||
#include "AccessibleTableCell.h"
|
||||
|
||||
#include "AccessibleDocument_i.c"
|
||||
#include "AccessibleHypertext2_i.c"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -93,6 +93,7 @@ included_inclnames_to_ignore = set([
|
||||
'unicode/udisplaycontext.h',# ICU
|
||||
'unicode/uenum.h', # ICU
|
||||
'unicode/uloc.h', # ICU
|
||||
'unicode/unistr.h', # ICU
|
||||
'unicode/unorm2.h', # ICU
|
||||
'unicode/unum.h', # ICU
|
||||
'unicode/unumsys.h', # ICU
|
||||
|
@ -97,13 +97,15 @@ UNIFIED_SOURCES += [
|
||||
'nsDocShellLoadInfo.cpp',
|
||||
'nsDocShellTransferableHooks.cpp',
|
||||
'nsDocShellTreeOwner.cpp',
|
||||
'nsDownloadHistory.cpp',
|
||||
'nsDSURIContentListener.cpp',
|
||||
'nsWebNavigationInfo.cpp',
|
||||
'PendingGlobalHistoryEntry.cpp',
|
||||
'SerializedLoadContext.cpp',
|
||||
]
|
||||
|
||||
if not CONFIG['MOZ_PLACES']:
|
||||
UNIFIED_SOURCES += ['nsDownloadHistory.cpp']
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
@ -36,8 +36,10 @@
|
||||
#include "nsSHistory.h"
|
||||
#include "nsSHTransaction.h"
|
||||
|
||||
#ifndef MOZ_PLACES
|
||||
// download history
|
||||
#include "nsDownloadHistory.h"
|
||||
#endif
|
||||
|
||||
|
||||
// LoadContexts (used for testing)
|
||||
@ -100,8 +102,10 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHTransaction)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHistory)
|
||||
|
||||
#ifndef MOZ_PLACES
|
||||
// download history
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadHistory)
|
||||
#endif
|
||||
|
||||
NS_DEFINE_NAMED_CID(NS_DOCSHELL_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_DEFAULTURIFIXUP_CID);
|
||||
@ -126,7 +130,9 @@ NS_DEFINE_NAMED_CID(NS_SHENTRY_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_SHTRANSACTION_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_SHISTORY_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_SHISTORY_INTERNAL_CID);
|
||||
#ifndef MOZ_PLACES
|
||||
NS_DEFINE_NAMED_CID(NS_DOWNLOADHISTORY_CID);
|
||||
#endif
|
||||
NS_DEFINE_NAMED_CID(NS_CONTENTHANDLERSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_LOADCONTEXT_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_PRIVATELOADCONTEXT_CID);
|
||||
@ -157,7 +163,9 @@ const mozilla::Module::CIDEntry kDocShellCIDs[] = {
|
||||
{ &kNS_SHTRANSACTION_CID, false, nullptr, nsSHTransactionConstructor },
|
||||
{ &kNS_SHISTORY_CID, false, nullptr, nsSHistoryConstructor },
|
||||
{ &kNS_SHISTORY_INTERNAL_CID, false, nullptr, nsSHistoryConstructor },
|
||||
#ifndef MOZ_PLACES
|
||||
{ &kNS_DOWNLOADHISTORY_CID, false, nullptr, nsDownloadHistoryConstructor },
|
||||
#endif
|
||||
{ &kNS_LOADCONTEXT_CID, false, nullptr, mozilla::CreateTestLoadContext },
|
||||
{ &kNS_PRIVATELOADCONTEXT_CID, false, nullptr, mozilla::CreatePrivateTestLoadContext },
|
||||
{ nullptr }
|
||||
@ -218,7 +226,9 @@ const mozilla::Module::ContractIDEntry kDocShellContracts[] = {
|
||||
{ NS_SHTRANSACTION_CONTRACTID, &kNS_SHTRANSACTION_CID },
|
||||
{ NS_SHISTORY_CONTRACTID, &kNS_SHISTORY_CID },
|
||||
{ NS_SHISTORY_INTERNAL_CONTRACTID, &kNS_SHISTORY_INTERNAL_CID },
|
||||
#ifndef MOZ_PLACES
|
||||
{ NS_DOWNLOADHISTORY_CONTRACTID, &kNS_DOWNLOADHISTORY_CID },
|
||||
#endif
|
||||
{ NS_LOADCONTEXT_CONTRACTID, &kNS_LOADCONTEXT_CID },
|
||||
{ NS_PRIVATELOADCONTEXT_CONTRACTID, &kNS_PRIVATELOADCONTEXT_CID },
|
||||
{ nullptr }
|
||||
|
@ -159,7 +159,7 @@ IPCBlobInputStream::~IPCBlobInputStream()
|
||||
NS_IMETHODIMP
|
||||
IPCBlobInputStream::Available(uint64_t* aLength)
|
||||
{
|
||||
// We don't have a remoteStream yet. Let's return the full known size.
|
||||
// We don't have a remoteStream yet: let's return 0.
|
||||
if (mState == eInit || mState == ePending) {
|
||||
*aLength = 0;
|
||||
return NS_OK;
|
||||
|
@ -1077,7 +1077,7 @@ public:
|
||||
const FontVariation* aVariations, uint32_t aNumVariations,
|
||||
void* aBaton)
|
||||
{
|
||||
auto recordedScaledFontCreation = static_cast<RecordedScaledFontCreation*>(aBaton);
|
||||
auto recordedScaledFontCreation = static_cast<RecordedScaledFontCreationByIndex*>(aBaton);
|
||||
recordedScaledFontCreation->SetFontInstanceData(aData, aSize, aVariations, aNumVariations);
|
||||
}
|
||||
|
||||
@ -1098,7 +1098,8 @@ public:
|
||||
virtual std::string GetName() const { return "ScaledFont Creation"; }
|
||||
virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
|
||||
|
||||
void SetFontInstanceData(const uint8_t *aData, uint32_t aSize);
|
||||
void SetFontInstanceData(const uint8_t *aData, uint32_t aSize,
|
||||
const FontVariation* aVariations, uint32_t aNumVariations);
|
||||
|
||||
private:
|
||||
friend class RecordedEvent;
|
||||
@ -3038,9 +3039,11 @@ RecordedScaledFontCreationByIndex::OutputSimpleEventInfo(std::stringstream &aStr
|
||||
}
|
||||
|
||||
inline void
|
||||
RecordedScaledFontCreationByIndex::SetFontInstanceData(const uint8_t *aData, uint32_t aSize)
|
||||
RecordedScaledFontCreationByIndex::SetFontInstanceData(const uint8_t *aData, uint32_t aSize,
|
||||
const FontVariation* aVariations, uint32_t aNumVariations)
|
||||
{
|
||||
mInstanceData.assign(aData, aData + aSize);
|
||||
mVariations.assign(aVariations, aVariations + aNumVariations);
|
||||
}
|
||||
|
||||
template<class S>
|
||||
|
@ -21,6 +21,58 @@ namespace layers {
|
||||
|
||||
using namespace gfx;
|
||||
|
||||
bool
|
||||
CapturedBufferState::Copy::CopyBuffer()
|
||||
{
|
||||
if (mSource->Lock(OpenMode::OPEN_READ_ONLY)) {
|
||||
mDestination->UpdateDestinationFrom(*mSource, mBounds);
|
||||
mSource->Unlock();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
CapturedBufferState::Unrotate::UnrotateBuffer()
|
||||
{
|
||||
return mBuffer->UnrotateBufferTo(mParameters);
|
||||
}
|
||||
|
||||
bool
|
||||
CapturedBufferState::PrepareBuffer()
|
||||
{
|
||||
return (!mBufferCopy || mBufferCopy->CopyBuffer()) &&
|
||||
(!mBufferUnrotate || mBufferUnrotate->UnrotateBuffer());
|
||||
}
|
||||
|
||||
void
|
||||
CapturedBufferState::GetTextureClients(nsTArray<RefPtr<TextureClient>>& aTextureClients)
|
||||
{
|
||||
if (mBufferCopy) {
|
||||
if (TextureClient* source = mBufferCopy->mSource->GetClient()) {
|
||||
aTextureClients.AppendElement(source);
|
||||
}
|
||||
if (TextureClient* sourceOnWhite = mBufferCopy->mSource->GetClientOnWhite()) {
|
||||
aTextureClients.AppendElement(sourceOnWhite);
|
||||
}
|
||||
if (TextureClient* destination = mBufferCopy->mDestination->GetClient()) {
|
||||
aTextureClients.AppendElement(destination);
|
||||
}
|
||||
if (TextureClient* destinationOnWhite = mBufferCopy->mDestination->GetClientOnWhite()) {
|
||||
aTextureClients.AppendElement(destinationOnWhite);
|
||||
}
|
||||
}
|
||||
|
||||
if (mBufferUnrotate) {
|
||||
if (TextureClient* client = mBufferUnrotate->mBuffer->GetClient()) {
|
||||
aTextureClients.AppendElement(client);
|
||||
}
|
||||
if (TextureClient* clientOnWhite = mBufferUnrotate->mBuffer->GetClientOnWhite()) {
|
||||
aTextureClients.AppendElement(clientOnWhite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StaticAutoPtr<PaintThread> PaintThread::sSingleton;
|
||||
StaticRefPtr<nsIThread> PaintThread::sThread;
|
||||
PlatformThreadId PaintThread::sThreadId;
|
||||
@ -158,6 +210,51 @@ PaintThread::BeginLayerTransaction()
|
||||
MOZ_ASSERT(!mInAsyncPaintGroup);
|
||||
}
|
||||
|
||||
void
|
||||
PaintThread::PrepareBuffer(CapturedBufferState* aState)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aState);
|
||||
|
||||
// If painting asynchronously, we need to acquire the compositor bridge which
|
||||
// owns the underlying MessageChannel. Otherwise we leave it null and use
|
||||
// synchronous dispatch.
|
||||
RefPtr<CompositorBridgeChild> cbc;
|
||||
if (!gfxPrefs::LayersOMTPForceSync()) {
|
||||
cbc = CompositorBridgeChild::Get();
|
||||
cbc->NotifyBeginAsyncPrepareBuffer(aState);
|
||||
}
|
||||
RefPtr<CapturedBufferState> state(aState);
|
||||
|
||||
RefPtr<PaintThread> self = this;
|
||||
RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PrepareBuffer",
|
||||
[self, cbc, state]() -> void
|
||||
{
|
||||
self->AsyncPrepareBuffer(cbc,
|
||||
state);
|
||||
});
|
||||
|
||||
if (cbc) {
|
||||
sThread->Dispatch(task.forget());
|
||||
} else {
|
||||
SyncRunnable::DispatchToThread(sThread, task);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PaintThread::AsyncPrepareBuffer(CompositorBridgeChild* aBridge,
|
||||
CapturedBufferState* aState)
|
||||
{
|
||||
MOZ_ASSERT(IsOnPaintThread());
|
||||
MOZ_ASSERT(aState);
|
||||
|
||||
if (!aState->PrepareBuffer()) {
|
||||
gfxCriticalNote << "Failed to prepare buffers on the paint thread.";
|
||||
}
|
||||
|
||||
aBridge->NotifyFinishedAsyncPrepareBuffer(aState);
|
||||
}
|
||||
|
||||
void
|
||||
PaintThread::PaintContents(CapturedPaintState* aState,
|
||||
PrepDrawTargetForPaintingCallback aCallback)
|
||||
@ -174,11 +271,10 @@ PaintThread::PaintContents(CapturedPaintState* aState,
|
||||
cbc->NotifyBeginAsyncPaint(aState);
|
||||
}
|
||||
RefPtr<CapturedPaintState> state(aState);
|
||||
RefPtr<DrawTargetCapture> capture(aState->mCapture);
|
||||
|
||||
RefPtr<PaintThread> self = this;
|
||||
RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PaintContents",
|
||||
[self, cbc, capture, state, aCallback]() -> void
|
||||
[self, cbc, state, aCallback]() -> void
|
||||
{
|
||||
self->AsyncPaintContents(cbc,
|
||||
state,
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/layers/TextureClient.h"
|
||||
#include "RotatedBuffer.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -58,6 +59,56 @@ protected:
|
||||
virtual ~CapturedPaintState() {}
|
||||
};
|
||||
|
||||
// Holds the key operations for a ContentClient to prepare
|
||||
// its buffers for painting
|
||||
class CapturedBufferState final {
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CapturedBufferState)
|
||||
public:
|
||||
struct Copy {
|
||||
Copy(RefPtr<RotatedBuffer> aSource,
|
||||
RefPtr<RotatedBuffer> aDestination,
|
||||
gfx::IntRect aBounds)
|
||||
: mSource(aSource)
|
||||
, mDestination(aDestination)
|
||||
, mBounds(aBounds)
|
||||
{}
|
||||
|
||||
bool CopyBuffer();
|
||||
|
||||
RefPtr<RotatedBuffer> mSource;
|
||||
RefPtr<RotatedBuffer> mDestination;
|
||||
gfx::IntRect mBounds;
|
||||
};
|
||||
|
||||
struct Unrotate {
|
||||
Unrotate(RotatedBuffer::Parameters aParameters,
|
||||
RefPtr<RotatedBuffer> aBuffer)
|
||||
: mParameters(aParameters)
|
||||
, mBuffer(aBuffer)
|
||||
{}
|
||||
|
||||
bool UnrotateBuffer();
|
||||
|
||||
RotatedBuffer::Parameters mParameters;
|
||||
RefPtr<RotatedBuffer> mBuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares the rotated buffers for painting by copying a previous frame
|
||||
* into the buffer and/or unrotating the pixels and returns whether the
|
||||
* operations were successful. If this fails a new buffer should be created
|
||||
* for the frame.
|
||||
*/
|
||||
bool PrepareBuffer();
|
||||
void GetTextureClients(nsTArray<RefPtr<TextureClient>>& aTextureClients);
|
||||
|
||||
Maybe<Copy> mBufferCopy;
|
||||
Maybe<Unrotate> mBufferUnrotate;
|
||||
|
||||
protected:
|
||||
~CapturedBufferState() {}
|
||||
};
|
||||
|
||||
typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState* aPaintState);
|
||||
|
||||
class CompositorBridgeChild;
|
||||
@ -80,6 +131,8 @@ public:
|
||||
// or running while this is executing.
|
||||
void BeginLayerTransaction();
|
||||
|
||||
void PrepareBuffer(CapturedBufferState* aState);
|
||||
|
||||
void PaintContents(CapturedPaintState* aState,
|
||||
PrepDrawTargetForPaintingCallback aCallback);
|
||||
|
||||
@ -110,6 +163,8 @@ private:
|
||||
void ShutdownOnPaintThread();
|
||||
void InitOnPaintThread();
|
||||
|
||||
void AsyncPrepareBuffer(CompositorBridgeChild* aBridge,
|
||||
CapturedBufferState* aState);
|
||||
void AsyncPaintContents(CompositorBridgeChild* aBridge,
|
||||
CapturedPaintState* aState,
|
||||
PrepDrawTargetForPaintingCallback aCallback);
|
||||
|
@ -291,10 +291,29 @@ WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize)
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedBuffer::AdjustTo(const gfx::IntRect& aDestBufferRect,
|
||||
const gfx::IntRect& aDrawBounds,
|
||||
bool aCanHaveRotation,
|
||||
bool aCanDrawRotated)
|
||||
RotatedBuffer::Parameters::IsRotated() const
|
||||
{
|
||||
return mBufferRotation != IntPoint(0,0);
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedBuffer::Parameters::RectWrapsBuffer(const gfx::IntRect& aRect) const
|
||||
{
|
||||
int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x;
|
||||
int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y;
|
||||
return (aRect.x < xBoundary && xBoundary < aRect.XMost()) ||
|
||||
(aRect.y < yBoundary && yBoundary < aRect.YMost());
|
||||
}
|
||||
|
||||
void
|
||||
RotatedBuffer::Parameters::SetUnrotated()
|
||||
{
|
||||
mBufferRotation = IntPoint(0,0);
|
||||
mDidSelfCopy = true;
|
||||
}
|
||||
|
||||
RotatedBuffer::Parameters
|
||||
RotatedBuffer::AdjustedParameters(const gfx::IntRect& aDestBufferRect) const
|
||||
{
|
||||
IntRect keepArea;
|
||||
if (keepArea.IntersectRect(aDestBufferRect, mBufferRect)) {
|
||||
@ -308,23 +327,24 @@ RotatedBuffer::AdjustTo(const gfx::IntRect& aDestBufferRect,
|
||||
NS_ASSERTION(gfx::IntRect(gfx::IntPoint(0,0), mBufferRect.Size()).Contains(newRotation),
|
||||
"newRotation out of bounds");
|
||||
|
||||
int32_t xBoundary = aDestBufferRect.XMost() - newRotation.x;
|
||||
int32_t yBoundary = aDestBufferRect.YMost() - newRotation.y;
|
||||
bool drawWrapsBuffer = (aDrawBounds.x < xBoundary && xBoundary < aDrawBounds.XMost()) ||
|
||||
(aDrawBounds.y < yBoundary && yBoundary < aDrawBounds.YMost());
|
||||
return Parameters{aDestBufferRect, newRotation};
|
||||
}
|
||||
|
||||
if ((drawWrapsBuffer && !aCanDrawRotated) ||
|
||||
(newRotation != IntPoint(0,0) && !aCanHaveRotation)) {
|
||||
// The stuff we need to redraw will wrap around an edge of the
|
||||
// buffer (and the caller doesn't know how to support that), so
|
||||
// move the pixels we can keep into a position that lets us
|
||||
// redraw in just one quadrant.
|
||||
// No pixels are going to be kept. The whole visible region
|
||||
// will be redrawn, so we don't need to copy anything, so we don't
|
||||
// set destBuffer.
|
||||
return Parameters{aDestBufferRect, IntPoint(0,0)};
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedBuffer::UnrotateBufferTo(const Parameters& aParameters)
|
||||
{
|
||||
RefPtr<gfx::DrawTarget> dtBuffer = GetDTBuffer();
|
||||
RefPtr<gfx::DrawTarget> dtBufferOnWhite = GetDTBufferOnWhite();
|
||||
|
||||
if (mBufferRotation == IntPoint(0,0)) {
|
||||
IntRect srcRect(IntPoint(0, 0), mBufferRect.Size());
|
||||
IntPoint dest = mBufferRect.TopLeft() - aDestBufferRect.TopLeft();
|
||||
IntPoint dest = mBufferRect.TopLeft() - aParameters.mBufferRect.TopLeft();
|
||||
|
||||
MOZ_ASSERT(dtBuffer && dtBuffer->IsValid());
|
||||
dtBuffer->CopyRect(srcRect, dest);
|
||||
@ -332,9 +352,6 @@ RotatedBuffer::AdjustTo(const gfx::IntRect& aDestBufferRect,
|
||||
MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
|
||||
dtBufferOnWhite->CopyRect(srcRect, dest);
|
||||
}
|
||||
|
||||
mDidSelfCopy = true;
|
||||
mBufferRect = aDestBufferRect;
|
||||
} else {
|
||||
// With azure and a data surface perform an buffer unrotate
|
||||
// (SelfCopy).
|
||||
@ -348,7 +365,8 @@ RotatedBuffer::AdjustTo(const gfx::IntRect& aDestBufferRect,
|
||||
BufferUnrotate(data,
|
||||
size.width * bytesPerPixel,
|
||||
size.height, stride,
|
||||
newRotation.x * bytesPerPixel, newRotation.y);
|
||||
aParameters.mBufferRotation.x * bytesPerPixel,
|
||||
aParameters.mBufferRotation.y);
|
||||
dtBuffer->ReleaseBits(data);
|
||||
|
||||
if (HaveBufferOnWhite()) {
|
||||
@ -358,35 +376,29 @@ RotatedBuffer::AdjustTo(const gfx::IntRect& aDestBufferRect,
|
||||
BufferUnrotate(data,
|
||||
size.width * bytesPerPixel,
|
||||
size.height, stride,
|
||||
newRotation.x * bytesPerPixel, newRotation.y);
|
||||
aParameters.mBufferRotation.x * bytesPerPixel,
|
||||
aParameters.mBufferRotation.y);
|
||||
dtBufferOnWhite->ReleaseBits(data);
|
||||
}
|
||||
|
||||
// Buffer unrotate moves all the pixels
|
||||
mDidSelfCopy = true;
|
||||
mBufferRect = aDestBufferRect;
|
||||
mBufferRotation = IntPoint(0, 0);
|
||||
}
|
||||
|
||||
if (!mDidSelfCopy) {
|
||||
// We couldn't unrotate the buffer, so we need to create a
|
||||
// new one and start from scratch
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mBufferRect = aDestBufferRect;
|
||||
mBufferRotation = newRotation;
|
||||
}
|
||||
} else {
|
||||
// No pixels are going to be kept. The whole visible region
|
||||
// will be redrawn, so we don't need to copy anything, so we don't
|
||||
// set destBuffer.
|
||||
mBufferRect = aDestBufferRect;
|
||||
mBufferRotation = IntPoint(0,0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
void
|
||||
RotatedBuffer::SetParameters(const RotatedBuffer::Parameters& aParameters)
|
||||
{
|
||||
mBufferRect = aParameters.mBufferRect;
|
||||
mBufferRotation = aParameters.mBufferRotation;
|
||||
mDidSelfCopy = aParameters.mDidSelfCopy;
|
||||
}
|
||||
|
||||
RotatedBuffer::ContentType
|
||||
RotatedBuffer::GetContentType() const
|
||||
{
|
||||
return ContentForFormat(GetFormat());
|
||||
}
|
||||
|
||||
DrawTarget*
|
||||
|
@ -24,11 +24,9 @@
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
class CapturedPaintState;
|
||||
|
||||
typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState*);
|
||||
|
||||
class PaintedLayer;
|
||||
class CapturedBufferState;
|
||||
class ContentClient;
|
||||
|
||||
// Mixin class for classes which need logic for loaning out a draw target.
|
||||
// See comments on BorrowDrawTargetForQuadrantUpdate.
|
||||
@ -158,15 +156,38 @@ public:
|
||||
bool aSetTransform = true,
|
||||
gfx::Matrix* aOutTransform = nullptr);
|
||||
|
||||
struct Parameters {
|
||||
Parameters(const gfx::IntRect& aBufferRect,
|
||||
const gfx::IntPoint& aBufferRotation)
|
||||
: mBufferRect(aBufferRect)
|
||||
, mBufferRotation(aBufferRotation)
|
||||
, mDidSelfCopy(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsRotated() const;
|
||||
bool RectWrapsBuffer(const gfx::IntRect& aRect) const;
|
||||
|
||||
void SetUnrotated();
|
||||
|
||||
gfx::IntRect mBufferRect;
|
||||
gfx::IntPoint mBufferRotation;
|
||||
bool mDidSelfCopy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adjusts the buffer to be centered on the destination buffer rect,
|
||||
* and ready to draw the specified bounds. Returns whether a new buffer
|
||||
* needs to be created.
|
||||
* Returns the new buffer parameters for rotating to a
|
||||
* destination buffer rect.
|
||||
*/
|
||||
bool AdjustTo(const gfx::IntRect& aDestBufferRect,
|
||||
const gfx::IntRect& aDrawBounds,
|
||||
bool aCanHaveRotation,
|
||||
bool aCanDrawRotated);
|
||||
Parameters AdjustedParameters(const gfx::IntRect& aDestBufferRect) const;
|
||||
|
||||
/**
|
||||
* Unrotates the pixels of the rotated buffer for the specified
|
||||
* new buffer parameters.
|
||||
*/
|
||||
bool UnrotateBufferTo(const Parameters& aParameters);
|
||||
|
||||
void SetParameters(const Parameters& aParameters);
|
||||
|
||||
/**
|
||||
* |BufferRect()| is the rect of device pixels that this
|
||||
@ -204,6 +225,11 @@ public:
|
||||
*/
|
||||
void ClearDidSelfCopy() { mDidSelfCopy = false; }
|
||||
|
||||
/**
|
||||
* Gets the content type for this buffer.
|
||||
*/
|
||||
ContentType GetContentType() const;
|
||||
|
||||
virtual bool IsLocked() = 0;
|
||||
virtual bool Lock(OpenMode aMode) = 0;
|
||||
virtual void Unlock() = 0;
|
||||
@ -218,6 +244,20 @@ public:
|
||||
virtual gfx::DrawTarget* GetDTBuffer() const = 0;
|
||||
virtual gfx::DrawTarget* GetDTBufferOnWhite() const = 0;
|
||||
|
||||
virtual TextureClient* GetClient() const {
|
||||
return nullptr;
|
||||
}
|
||||
virtual TextureClient* GetClientOnWhite() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shallow copy of the rotated buffer with the same underlying
|
||||
* texture clients and draw targets. Rotated buffers are not thread safe,
|
||||
* so a copy needs to be sent for off main thread painting.
|
||||
*/
|
||||
virtual RefPtr<RotatedBuffer> ShallowCopy() const = 0;
|
||||
|
||||
protected:
|
||||
virtual ~RotatedBuffer() {}
|
||||
|
||||
@ -295,13 +335,32 @@ public:
|
||||
virtual gfx::DrawTarget* GetDTBuffer() const override;
|
||||
virtual gfx::DrawTarget* GetDTBufferOnWhite() const override;
|
||||
|
||||
TextureClient* GetClient() const { return mClient; }
|
||||
TextureClient* GetClientOnWhite() const { return mClientOnWhite; }
|
||||
virtual TextureClient* GetClient() const override { return mClient; }
|
||||
virtual TextureClient* GetClientOnWhite() const override { return mClientOnWhite; }
|
||||
|
||||
virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
|
||||
return new RemoteRotatedBuffer {
|
||||
mClient, mClientOnWhite,
|
||||
mTarget, mTargetOnWhite,
|
||||
mBufferRect, mBufferRotation
|
||||
};
|
||||
}
|
||||
|
||||
void SyncWithObject(SyncObjectClient* aSyncObject);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
RemoteRotatedBuffer(TextureClient* aClient, TextureClient* aClientOnWhite,
|
||||
gfx::DrawTarget* aTarget, gfx::DrawTarget* aTargetOnWhite,
|
||||
const gfx::IntRect& aBufferRect,
|
||||
const gfx::IntPoint& aBufferRotation)
|
||||
: RotatedBuffer(aBufferRect, aBufferRotation)
|
||||
, mClient(aClient)
|
||||
, mClientOnWhite(aClientOnWhite)
|
||||
, mTarget(aTarget)
|
||||
, mTargetOnWhite(aTargetOnWhite)
|
||||
{ }
|
||||
|
||||
RefPtr<TextureClient> mClient;
|
||||
RefPtr<TextureClient> mClientOnWhite;
|
||||
|
||||
@ -338,6 +397,13 @@ public:
|
||||
virtual gfx::DrawTarget* GetDTBuffer() const override;
|
||||
virtual gfx::DrawTarget* GetDTBufferOnWhite() const override;
|
||||
|
||||
virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
|
||||
return new DrawTargetRotatedBuffer {
|
||||
mTarget, mTargetOnWhite,
|
||||
mBufferRect, mBufferRotation
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<gfx::DrawTarget> mTarget;
|
||||
RefPtr<gfx::DrawTarget> mTargetOnWhite;
|
||||
@ -372,6 +438,10 @@ public:
|
||||
virtual gfx::DrawTarget* GetDTBuffer() const override { return nullptr; }
|
||||
virtual gfx::DrawTarget* GetDTBufferOnWhite() const override { return nullptr; }
|
||||
|
||||
virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<gfx::SourceSurface> mSource;
|
||||
RefPtr<gfx::SourceSurface> mSourceOnWhite;
|
||||
|
@ -210,11 +210,17 @@ ClientPaintedLayer::PaintOffMainThread()
|
||||
uint32_t flags = GetPaintFlags();
|
||||
|
||||
PaintState state = mContentClient->BeginPaint(this, flags | ContentClient::PAINT_ASYNC);
|
||||
bool didUpdate = false;
|
||||
|
||||
if (state.mBufferState) {
|
||||
PaintThread::Get()->PrepareBuffer(state.mBufferState);
|
||||
didUpdate = true;
|
||||
}
|
||||
|
||||
if (!UpdatePaintRegion(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool didUpdate = false;
|
||||
RotatedBuffer::DrawIterator iter;
|
||||
|
||||
// Debug Protip: Change to BorrowDrawTargetForPainting if using sync OMTP.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "mozilla/layers/LayerManagerComposite.h"
|
||||
#include "mozilla/layers/LayersMessages.h" // for ThebesBufferData
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
#include "mozilla/layers/PaintThread.h"
|
||||
#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc
|
||||
#include "nsISupportsImpl.h" // for gfxContext::Release, etc
|
||||
#include "nsIWidget.h" // for nsIWidget
|
||||
@ -127,17 +128,17 @@ ContentClient::BeginPaint(PaintedLayer* aLayer,
|
||||
MOZ_ASSERT(dest.mValidRegion.IsEmpty());
|
||||
|
||||
result.mRegionToInvalidate = aLayer->GetValidRegion();
|
||||
Clear();
|
||||
|
||||
#if defined(MOZ_DUMP_PAINTING)
|
||||
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
|
||||
if (result.mContentType != BufferContentType()) {
|
||||
if (result.mContentType != mBuffer->GetContentType()) {
|
||||
printf_stderr("Invalidating entire rotated buffer (layer %p): content type changed\n", aLayer);
|
||||
} else if ((dest.mBufferMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mBuffer->HaveBufferOnWhite()) {
|
||||
printf_stderr("Invalidating entire rotated buffer (layer %p): component alpha changed\n", aLayer);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Clear();
|
||||
}
|
||||
|
||||
result.mRegionToDraw.Sub(dest.mNeededRegion,
|
||||
@ -146,25 +147,6 @@ ContentClient::BeginPaint(PaintedLayer* aLayer,
|
||||
if (result.mRegionToDraw.IsEmpty())
|
||||
return result;
|
||||
|
||||
OpenMode lockMode = aFlags & PAINT_ASYNC ? OpenMode::OPEN_READ_ASYNC_WRITE
|
||||
: OpenMode::OPEN_READ_WRITE;
|
||||
|
||||
if (mBuffer) {
|
||||
if (mBuffer->Lock(lockMode)) {
|
||||
// Do not modify result.mRegionToDraw or result.mContentType after this call.
|
||||
// Do not modify the back buffer's bufferRect, bufferRotation, or didSelfCopy.
|
||||
FinalizeFrame(result.mRegionToDraw);
|
||||
} else {
|
||||
// Abandon everything and redraw it all. Ideally we'd reallocate and copy
|
||||
// the old to the new and then call FinalizeFrame on the new buffer so that
|
||||
// we only need to draw the latest bits, but we need a big refactor to support
|
||||
// that ordering.
|
||||
result.mRegionToDraw = dest.mNeededRegion;
|
||||
dest.mCanReuseBuffer = false;
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// We need to disable rotation if we're going to be resampled when
|
||||
// drawing, because we might sample across the rotation boundary.
|
||||
// Also disable buffer rotation when using webrender.
|
||||
@ -172,68 +154,127 @@ ContentClient::BeginPaint(PaintedLayer* aLayer,
|
||||
!(aFlags & (PAINT_WILL_RESAMPLE | PAINT_NO_ROTATION)) &&
|
||||
!(aLayer->Manager()->AsWebRenderLayerManager());
|
||||
bool canDrawRotated = aFlags & PAINT_CAN_DRAW_ROTATED;
|
||||
bool asyncPaint = (aFlags & PAINT_ASYNC);
|
||||
|
||||
IntRect drawBounds = result.mRegionToDraw.GetBounds();
|
||||
RefPtr<RotatedBuffer> newBuffer;
|
||||
uint32_t bufferFlags = 0;
|
||||
if (dest.mBufferMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
bufferFlags |= BUFFER_COMPONENT_ALPHA;
|
||||
}
|
||||
if (dest.mCanReuseBuffer && mBuffer) {
|
||||
if (!mBuffer->AdjustTo(dest.mBufferRect,
|
||||
drawBounds,
|
||||
canHaveRotation,
|
||||
canDrawRotated)) {
|
||||
dest.mBufferRect = ComputeBufferRect(dest.mNeededRegion.GetBounds());
|
||||
newBuffer = CreateBuffer(result.mContentType, dest.mBufferRect, bufferFlags);
|
||||
OpenMode lockMode = asyncPaint ? OpenMode::OPEN_READ_ASYNC_WRITE
|
||||
: OpenMode::OPEN_READ_WRITE;
|
||||
|
||||
if (!newBuffer) {
|
||||
if (Factory::ReasonableSurfaceSize(IntSize(dest.mBufferRect.Width(), dest.mBufferRect.Height()))) {
|
||||
gfxCriticalNote << "Failed 1 buffer for "
|
||||
<< dest.mBufferRect.x << ", "
|
||||
<< dest.mBufferRect.y << ", "
|
||||
<< dest.mBufferRect.Width() << ", "
|
||||
<< dest.mBufferRect.Height();
|
||||
if (dest.mCanReuseBuffer) {
|
||||
MOZ_ASSERT(mBuffer);
|
||||
|
||||
bool canReuseBuffer = false;
|
||||
|
||||
if (mBuffer->Lock(lockMode)) {
|
||||
RefPtr<CapturedBufferState> bufferState = new CapturedBufferState();
|
||||
|
||||
// Do not modify result.mRegionToDraw or result.mContentType after this call.
|
||||
FinalizeFrame(result.mRegionToDraw, bufferState);
|
||||
|
||||
auto newParameters = mBuffer->AdjustedParameters(dest.mBufferRect);
|
||||
|
||||
if ((!canHaveRotation && newParameters.IsRotated()) ||
|
||||
(!canDrawRotated && newParameters.RectWrapsBuffer(drawBounds))) {
|
||||
bufferState->mBufferUnrotate = Some(CapturedBufferState::Unrotate {
|
||||
newParameters,
|
||||
mBuffer->ShallowCopy(),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
||||
// If we're async painting then return the buffer state to
|
||||
// be dispatched to the paint thread, otherwise do it now
|
||||
if (asyncPaint) {
|
||||
// We cannot do a buffer unrotate if the buffer is already rotated
|
||||
// and we're async painting as that may fail
|
||||
if (!bufferState->mBufferUnrotate ||
|
||||
mBuffer->BufferRotation() == IntPoint(0,0)) {
|
||||
result.mBufferState = bufferState;
|
||||
|
||||
// We can then assume that preparing the buffer will always
|
||||
// succeed and update our parameters unconditionally
|
||||
if (bufferState->mBufferUnrotate) {
|
||||
newParameters.SetUnrotated();
|
||||
}
|
||||
mBuffer->SetParameters(newParameters);
|
||||
canReuseBuffer = true;
|
||||
}
|
||||
} else {
|
||||
// The buffer's not big enough, so allocate a new one
|
||||
newBuffer = CreateBuffer(result.mContentType, dest.mBufferRect, bufferFlags);
|
||||
if (!newBuffer) {
|
||||
if (Factory::ReasonableSurfaceSize(IntSize(dest.mBufferRect.Width(), dest.mBufferRect.Height()))) {
|
||||
gfxCriticalNote << "Failed 2 buffer for "
|
||||
<< dest.mBufferRect.x << ", "
|
||||
<< dest.mBufferRect.y << ", "
|
||||
<< dest.mBufferRect.Width() << ", "
|
||||
<< dest.mBufferRect.Height();
|
||||
if (bufferState->PrepareBuffer()) {
|
||||
if (bufferState->mBufferUnrotate) {
|
||||
newParameters.SetUnrotated();
|
||||
}
|
||||
return result;
|
||||
mBuffer->SetParameters(newParameters);
|
||||
canReuseBuffer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!canReuseBuffer) {
|
||||
if (mBuffer->IsLocked()) {
|
||||
mBuffer->Unlock();
|
||||
}
|
||||
dest.mBufferRect = ComputeBufferRect(dest.mNeededRegion.GetBounds());
|
||||
dest.mCanReuseBuffer = false;
|
||||
}
|
||||
}
|
||||
|
||||
NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || dest.mBufferRect == dest.mNeededRegion.GetBounds(),
|
||||
"If we're resampling, we need to validate the entire buffer");
|
||||
|
||||
// If needed, copy the old buffer over to the new one
|
||||
if (newBuffer) {
|
||||
if (!newBuffer->Lock(lockMode)) {
|
||||
gfxCriticalNote << "Failed to lock new back buffer.";
|
||||
Clear();
|
||||
// We never had a buffer, the buffer wasn't big enough, the content changed
|
||||
// types, or we failed to unrotate the buffer when requested. In any case,
|
||||
// we need to allocate a new one and prepare it for drawing.
|
||||
if (!dest.mCanReuseBuffer) {
|
||||
uint32_t bufferFlags = 0;
|
||||
if (dest.mBufferMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
bufferFlags |= BUFFER_COMPONENT_ALPHA;
|
||||
}
|
||||
|
||||
RefPtr<RotatedBuffer> newBuffer = CreateBuffer(result.mContentType,
|
||||
dest.mBufferRect,
|
||||
bufferFlags);
|
||||
|
||||
if (!newBuffer) {
|
||||
if (Factory::ReasonableSurfaceSize(IntSize(dest.mBufferRect.Width(), dest.mBufferRect.Height()))) {
|
||||
gfxCriticalNote << "Failed buffer for "
|
||||
<< dest.mBufferRect.x << ", "
|
||||
<< dest.mBufferRect.y << ", "
|
||||
<< dest.mBufferRect.Width() << ", "
|
||||
<< dest.mBufferRect.Height();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (mBuffer) {
|
||||
newBuffer->UpdateDestinationFrom(*mBuffer, newBuffer->BufferRect());
|
||||
if (!newBuffer->Lock(lockMode)) {
|
||||
gfxCriticalNote << "Failed to lock new back buffer.";
|
||||
return result;
|
||||
}
|
||||
|
||||
// We are done with the old back buffer now and it is about to be
|
||||
// destroyed, so unlock it.
|
||||
mBuffer->Unlock();
|
||||
// If we have an existing front buffer, copy it into the new back buffer
|
||||
if (RefPtr<RotatedBuffer> frontBuffer = GetFrontBuffer()) {
|
||||
RefPtr<CapturedBufferState> bufferState = new CapturedBufferState();
|
||||
|
||||
bufferState->mBufferCopy = Some(CapturedBufferState::Copy {
|
||||
frontBuffer->ShallowCopy(),
|
||||
newBuffer->ShallowCopy(),
|
||||
newBuffer->BufferRect(),
|
||||
});
|
||||
|
||||
// If we're async painting then return the buffer state to
|
||||
// be dispatched to the paint thread, otherwise do it now
|
||||
if (asyncPaint) {
|
||||
MOZ_ASSERT(!result.mBufferState);
|
||||
result.mBufferState = bufferState;
|
||||
} else {
|
||||
if (!bufferState->PrepareBuffer()) {
|
||||
gfxCriticalNote << "Failed to copy front buffer to back buffer.";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure our reference to the front buffer is released
|
||||
// as well as the old back buffer.
|
||||
// as well as the old back buffer
|
||||
Clear();
|
||||
|
||||
mBuffer = newBuffer;
|
||||
@ -384,7 +425,9 @@ ContentClient::CalculateBufferForPaint(PaintedLayer* aLayer,
|
||||
while (true) {
|
||||
mode = aLayer->GetSurfaceMode();
|
||||
neededRegion = aLayer->GetVisibleRegion().ToUnknownRegion();
|
||||
canReuseBuffer = canReuseBuffer && BufferSizeOkFor(neededRegion.GetBounds().Size());
|
||||
canReuseBuffer = canReuseBuffer && ValidBufferSize(mBufferSizePolicy,
|
||||
mBuffer->BufferRect().Size(),
|
||||
neededRegion.GetBounds().Size());
|
||||
contentType = layerContentType;
|
||||
|
||||
if (canReuseBuffer) {
|
||||
@ -435,11 +478,12 @@ ContentClient::CalculateBufferForPaint(PaintedLayer* aLayer,
|
||||
|
||||
// If we have an existing buffer, but the content type has changed or we
|
||||
// have transitioned into/out of component alpha, then we need to recreate it.
|
||||
if (canKeepBufferContents &&
|
||||
mBuffer &&
|
||||
(contentType != BufferContentType() ||
|
||||
(mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mBuffer->HaveBufferOnWhite()))
|
||||
{
|
||||
RefPtr<RotatedBuffer> frontBuffer = GetFrontBuffer();
|
||||
bool needsComponentAlpha = (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA);
|
||||
bool changedSurfaceOrContent = frontBuffer &&
|
||||
(contentType != frontBuffer->GetContentType() ||
|
||||
needsComponentAlpha != frontBuffer->HaveBufferOnWhite());
|
||||
if (canKeepBufferContents && changedSurfaceOrContent) {
|
||||
// Restart the decision process; we won't re-enter since we guard on
|
||||
// being able to keep the buffer contents.
|
||||
canReuseBuffer = false;
|
||||
@ -465,22 +509,20 @@ ContentClient::CalculateBufferForPaint(PaintedLayer* aLayer,
|
||||
return dest;
|
||||
}
|
||||
|
||||
gfxContentType
|
||||
ContentClient::BufferContentType()
|
||||
bool
|
||||
ContentClient::ValidBufferSize(BufferSizePolicy aPolicy,
|
||||
const gfx::IntSize& aBufferSize,
|
||||
const gfx::IntSize& aVisibleBoundsSize)
|
||||
{
|
||||
if (mBuffer) {
|
||||
return ContentForFormat(mBuffer->GetFormat());
|
||||
}
|
||||
return gfxContentType::SENTINEL;
|
||||
return (aVisibleBoundsSize == aBufferSize ||
|
||||
(SizedToVisibleBounds != aPolicy &&
|
||||
aVisibleBoundsSize < aBufferSize));
|
||||
}
|
||||
|
||||
bool
|
||||
ContentClient::BufferSizeOkFor(const IntSize& aSize)
|
||||
RefPtr<RotatedBuffer>
|
||||
ContentClient::GetFrontBuffer() const
|
||||
{
|
||||
MOZ_ASSERT(mBuffer);
|
||||
return (aSize == mBuffer->BufferRect().Size() ||
|
||||
(SizedToVisibleBounds != mBufferSizePolicy &&
|
||||
aSize < mBuffer->BufferRect().Size()));
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
void
|
||||
@ -841,47 +883,36 @@ ContentClient::PaintState
|
||||
ContentClientDoubleBuffered::BeginPaint(PaintedLayer* aLayer,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
EnsureBackBufferIfFrontBuffer();
|
||||
|
||||
mIsNewBuffer = false;
|
||||
|
||||
if (!mFrontBuffer || !mBuffer) {
|
||||
mFrontAndBackBufferDiffer = false;
|
||||
}
|
||||
|
||||
if (mFrontAndBackBufferDiffer) {
|
||||
if (mFrontBuffer->DidSelfCopy()) {
|
||||
// We can't easily draw our front buffer into us, since we're going to be
|
||||
// copying stuff around anyway it's easiest if we just move our situation
|
||||
// to non-rotated while we're at it. If this situation occurs we'll have
|
||||
// hit a self-copy path in PaintThebes before as well anyway.
|
||||
gfx::IntRect backBufferRect = mBuffer->BufferRect();
|
||||
backBufferRect.MoveTo(mFrontBuffer->BufferRect().TopLeft());
|
||||
|
||||
mBuffer->SetBufferRect(backBufferRect);
|
||||
mBuffer->SetBufferRotation(IntPoint(0,0));
|
||||
} else {
|
||||
mBuffer->SetBufferRect(mFrontBuffer->BufferRect());
|
||||
mBuffer->SetBufferRotation(mFrontBuffer->BufferRotation());
|
||||
}
|
||||
}
|
||||
|
||||
return ContentClient::BeginPaint(aLayer, aFlags);
|
||||
}
|
||||
|
||||
RefPtr<RotatedBuffer>
|
||||
ContentClientDoubleBuffered::GetFrontBuffer() const
|
||||
{
|
||||
return mFrontBuffer;
|
||||
}
|
||||
|
||||
// Sync front/back buffers content
|
||||
// After executing, the new back buffer has the same (interesting) pixels as
|
||||
// the new front buffer, and mValidRegion et al. are correct wrt the new
|
||||
// back buffer (i.e. as they were for the old back buffer)
|
||||
void
|
||||
ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
|
||||
ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw,
|
||||
CapturedBufferState* aPrepareState)
|
||||
{
|
||||
if (!mFrontAndBackBufferDiffer) {
|
||||
MOZ_ASSERT(!mFrontBuffer->DidSelfCopy(), "If we have to copy the world, then our buffers are different, right?");
|
||||
MOZ_ASSERT(!mFrontBuffer || !mFrontBuffer->DidSelfCopy(),
|
||||
"If the front buffer did a self copy then our front and back buffer must be different.");
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mFrontBuffer);
|
||||
if (!mFrontBuffer) {
|
||||
|
||||
MOZ_ASSERT(mFrontBuffer && mBuffer);
|
||||
if (!mFrontBuffer || !mBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -894,8 +925,20 @@ ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
|
||||
|
||||
mFrontAndBackBufferDiffer = false;
|
||||
|
||||
// Move the back buffer rect and rotation to the front buffer rect and rotation
|
||||
// so that we can update the pixels that changed between frames
|
||||
gfx::IntRect backBufferRect = mBuffer->BufferRect();
|
||||
backBufferRect.MoveTo(mFrontBuffer->BufferRect().TopLeft());
|
||||
mBuffer->SetBufferRect(backBufferRect);
|
||||
mBuffer->SetBufferRotation(mBuffer->BufferRotation());
|
||||
|
||||
// Calculate the region to update
|
||||
nsIntRegion updateRegion = mFrontUpdatedRegion;
|
||||
if (mFrontBuffer->DidSelfCopy()) {
|
||||
// If we did an unrotate operation on the front buffer we might as well
|
||||
// unrotate as well because we will be reading back the whole front buffer
|
||||
mBuffer->SetBufferRotation(IntPoint(0,0));
|
||||
|
||||
mFrontBuffer->ClearDidSelfCopy();
|
||||
updateRegion = mBuffer->BufferRect();
|
||||
}
|
||||
@ -907,25 +950,12 @@ ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFrontBuffer->Lock(OpenMode::OPEN_READ_ONLY)) {
|
||||
mBuffer->UpdateDestinationFrom(*mFrontBuffer, updateRegion.GetBounds());
|
||||
mFrontBuffer->Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ContentClientDoubleBuffered::EnsureBackBufferIfFrontBuffer()
|
||||
{
|
||||
if (!mBuffer && mFrontBuffer) {
|
||||
mBuffer = CreateBufferInternal(mFrontBuffer->BufferRect(),
|
||||
mFrontBuffer->GetFormat(),
|
||||
mTextureFlags);
|
||||
MOZ_ASSERT(mBuffer);
|
||||
}
|
||||
MOZ_ASSERT(!aPrepareState->mBufferCopy);
|
||||
aPrepareState->mBufferCopy = Some(CapturedBufferState::Copy {
|
||||
mFrontBuffer->ShallowCopy(),
|
||||
mBuffer->ShallowCopy(),
|
||||
updateRegion.GetBounds(),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor
|
||||
#include "mozilla/layers/LayersTypes.h" // for TextureDumpMode
|
||||
#include "mozilla/layers/TextureClient.h" // for TextureClient
|
||||
#include "mozilla/layers/PaintThread.h" // for CapturedBufferState
|
||||
#include "mozilla/Maybe.h" // for Maybe
|
||||
#include "mozilla/mozalloc.h" // for operator delete
|
||||
#include "ReadbackProcessor.h" // For ReadbackProcessor::Update
|
||||
@ -40,6 +41,9 @@ namespace layers {
|
||||
|
||||
class PaintedLayer;
|
||||
class CapturedPaintState;
|
||||
class CapturedBufferState;
|
||||
|
||||
typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState*);
|
||||
|
||||
/**
|
||||
* A compositable client for PaintedLayers. These are different to Image/Canvas
|
||||
@ -114,6 +118,7 @@ public:
|
||||
SurfaceMode mMode;
|
||||
DrawRegionClip mClip;
|
||||
gfxContentType mContentType;
|
||||
RefPtr<CapturedBufferState> mBufferState;
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -203,15 +208,11 @@ protected:
|
||||
BufferDecision CalculateBufferForPaint(PaintedLayer* aLayer,
|
||||
uint32_t aFlags);
|
||||
|
||||
/**
|
||||
* Return the buffer's content type. Requires a valid buffer.
|
||||
*/
|
||||
gfxContentType BufferContentType();
|
||||
/**
|
||||
* Returns whether the specified size is adequate for the current
|
||||
* buffer and buffer size policy.
|
||||
*/
|
||||
bool BufferSizeOkFor(const gfx::IntSize& aSize);
|
||||
static bool ValidBufferSize(BufferSizePolicy aPolicy,
|
||||
const gfx::IntSize& aBufferSize,
|
||||
const gfx::IntSize& aVisibleBoundsSize);
|
||||
|
||||
virtual RefPtr<RotatedBuffer> GetFrontBuffer() const;
|
||||
|
||||
/**
|
||||
* Any actions that should be performed at the last moment before we begin
|
||||
@ -220,7 +221,8 @@ protected:
|
||||
* aRegionToDraw is the region which is guaranteed to be overwritten when
|
||||
* drawing the next frame.
|
||||
*/
|
||||
virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) {}
|
||||
virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw,
|
||||
CapturedBufferState* aState) {}
|
||||
|
||||
/**
|
||||
* Create a new rotated buffer for the specified content type, buffer rect,
|
||||
@ -363,7 +365,10 @@ public:
|
||||
|
||||
virtual PaintState BeginPaint(PaintedLayer* aLayer, uint32_t aFlags) override;
|
||||
|
||||
virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) override;
|
||||
virtual RefPtr<RotatedBuffer> GetFrontBuffer() const override;
|
||||
|
||||
virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw,
|
||||
CapturedBufferState* aState) override;
|
||||
|
||||
virtual TextureInfo GetTextureInfo() const override
|
||||
{
|
||||
@ -371,8 +376,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void EnsureBackBufferIfFrontBuffer();
|
||||
|
||||
RefPtr<RemoteRotatedBuffer> mFrontBuffer;
|
||||
nsIntRegion mFrontUpdatedRegion;
|
||||
bool mFrontAndBackBufferDiffer;
|
||||
|
@ -692,10 +692,6 @@ TextureClient::BorrowDrawTarget()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!mBorrowedDrawTarget) {
|
||||
mBorrowedDrawTarget = mData->BorrowDrawTarget();
|
||||
#ifdef DEBUG
|
||||
|
@ -764,7 +764,7 @@ CreateTextureHostD3D11(const SurfaceDescriptor& aDesc,
|
||||
already_AddRefed<DrawTarget>
|
||||
D3D11TextureData::BorrowDrawTarget()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(NS_IsMainThread() || PaintThread::IsOnPaintThread());
|
||||
|
||||
if (!mDrawTarget && mTexture) {
|
||||
// This may return a null DrawTarget
|
||||
|
@ -1193,6 +1193,34 @@ CompositorBridgeChild::FlushAsyncPaints()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CompositorBridgeChild::NotifyBeginAsyncPrepareBuffer(CapturedBufferState* aState)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
MonitorAutoLock lock(mPaintLock);
|
||||
|
||||
// We must not be waiting for paints (or buffer copying) to complete yet. This
|
||||
// would imply we started a new paint without waiting for a previous one, which
|
||||
// could lead to incorrect rendering or IPDL deadlocks.
|
||||
MOZ_ASSERT(!mIsDelayingForAsyncPaints);
|
||||
|
||||
mOutstandingAsyncPaints++;
|
||||
|
||||
// Mark texture clients that they are being used for async painting, and
|
||||
// make sure we hold them alive on the main thread.
|
||||
aState->GetTextureClients(mTextureClientsForAsyncPaint);
|
||||
}
|
||||
|
||||
void
|
||||
CompositorBridgeChild::NotifyFinishedAsyncPrepareBuffer(CapturedBufferState* aState)
|
||||
{
|
||||
MOZ_ASSERT(PaintThread::IsOnPaintThread());
|
||||
|
||||
MonitorAutoLock lock(mPaintLock);
|
||||
mOutstandingAsyncPaints--;
|
||||
}
|
||||
|
||||
void
|
||||
CompositorBridgeChild::NotifyBeginAsyncPaint(CapturedPaintState* aState)
|
||||
{
|
||||
|
@ -45,6 +45,7 @@ class CompositorManagerChild;
|
||||
class CompositorOptions;
|
||||
class TextureClient;
|
||||
class TextureClientPool;
|
||||
class CapturedBufferState;
|
||||
class CapturedPaintState;
|
||||
struct FrameMetrics;
|
||||
|
||||
@ -227,6 +228,14 @@ public:
|
||||
// operation completes.
|
||||
void FlushAsyncPaints();
|
||||
|
||||
// Must only be called from the main thread. Notifies the CompositorBridge
|
||||
// that the paint thread is going to begin preparing a buffer asynchronously.
|
||||
void NotifyBeginAsyncPrepareBuffer(CapturedBufferState* aState);
|
||||
|
||||
// Must only be called from the paint thread. Notifies the CompositorBridge
|
||||
// that the paint thread has finished an asynchronous buffer prepare.
|
||||
void NotifyFinishedAsyncPrepareBuffer(CapturedBufferState* aState);
|
||||
|
||||
// Must only be called from the main thread. Notifies the CompositorBridge
|
||||
// that the paint thread is going to begin painting asynchronously.
|
||||
void NotifyBeginAsyncPaint(CapturedPaintState* aState);
|
||||
|
@ -223,6 +223,7 @@ EXPORTS.mozilla.layers += [
|
||||
'PaintThread.h',
|
||||
'PersistentBufferProvider.h',
|
||||
'RenderTrace.h',
|
||||
'RotatedBuffer.h',
|
||||
'ShareableCanvasRenderer.h',
|
||||
'SourceSurfaceSharedData.h',
|
||||
'SourceSurfaceVolatileData.h',
|
||||
|
@ -1141,10 +1141,8 @@ WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::In
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t maxPendingFrameCount = 1;
|
||||
|
||||
if (!mForceRendering &&
|
||||
wr::RenderThread::Get()->GetPendingFrameCount(mApi->GetId()) >= maxPendingFrameCount) {
|
||||
wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) {
|
||||
// Render thread is busy, try next time.
|
||||
ScheduleComposition();
|
||||
return;
|
||||
|
@ -23,7 +23,7 @@ static StaticRefPtr<RenderThread> sRenderThread;
|
||||
|
||||
RenderThread::RenderThread(base::Thread* aThread)
|
||||
: mThread(aThread)
|
||||
, mPendingFrameCountMapLock("RenderThread.mPendingFrameCountMapLock")
|
||||
, mFrameCountMapLock("RenderThread.mFrameCountMapLock")
|
||||
, mRenderTextureMapLock("RenderThread.mRenderTextureMapLock")
|
||||
, mHasShutdown(false)
|
||||
{
|
||||
@ -117,8 +117,8 @@ RenderThread::AddRenderer(wr::WindowId aWindowId, UniquePtr<RendererOGL> aRender
|
||||
|
||||
mRenderers[aWindowId] = Move(aRenderer);
|
||||
|
||||
MutexAutoLock lock(mPendingFrameCountMapLock);
|
||||
mPendingFrameCounts.Put(AsUint64(aWindowId), 0);
|
||||
MutexAutoLock lock(mFrameCountMapLock);
|
||||
mPendingFrameCounts.Put(AsUint64(aWindowId), FrameCount());
|
||||
}
|
||||
|
||||
void
|
||||
@ -132,7 +132,7 @@ RenderThread::RemoveRenderer(wr::WindowId aWindowId)
|
||||
|
||||
mRenderers.erase(aWindowId);
|
||||
|
||||
MutexAutoLock lock(mPendingFrameCountMapLock);
|
||||
MutexAutoLock lock(mFrameCountMapLock);
|
||||
mPendingFrameCounts.Remove(AsUint64(aWindowId));
|
||||
}
|
||||
|
||||
@ -265,46 +265,77 @@ RenderThread::Resume(wr::WindowId aWindowId)
|
||||
return renderer->Resume();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
RenderThread::GetPendingFrameCount(wr::WindowId aWindowId)
|
||||
bool
|
||||
RenderThread::TooManyPendingFrames(wr::WindowId aWindowId)
|
||||
{
|
||||
MutexAutoLock lock(mPendingFrameCountMapLock);
|
||||
uint32_t count = 0;
|
||||
MOZ_ASSERT(mPendingFrameCounts.Get(AsUint64(aWindowId), &count));
|
||||
mPendingFrameCounts.Get(AsUint64(aWindowId), &count);
|
||||
return count;
|
||||
const int64_t maxFrameCount = 1;
|
||||
|
||||
// Too many pending frames if pending frames exit more than maxFrameCount
|
||||
// or if RenderBackend is still processing a frame.
|
||||
|
||||
MutexAutoLock lock(mFrameCountMapLock);
|
||||
FrameCount count;
|
||||
if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &count)) {
|
||||
MOZ_ASSERT(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count.mPendingCount > maxFrameCount) {
|
||||
return true;
|
||||
}
|
||||
MOZ_ASSERT(count.mPendingCount >= count.mRenderingCount);
|
||||
return count.mPendingCount > count.mRenderingCount;
|
||||
}
|
||||
|
||||
void
|
||||
RenderThread::IncPendingFrameCount(wr::WindowId aWindowId)
|
||||
{
|
||||
MutexAutoLock lock(mPendingFrameCountMapLock);
|
||||
MutexAutoLock lock(mFrameCountMapLock);
|
||||
// Get the old count.
|
||||
uint32_t oldCount = 0;
|
||||
if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &oldCount)) {
|
||||
FrameCount count;
|
||||
if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &count)) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
// Update pending frame count.
|
||||
mPendingFrameCounts.Put(AsUint64(aWindowId), oldCount + 1);
|
||||
count.mPendingCount = count.mPendingCount + 1;
|
||||
mPendingFrameCounts.Put(AsUint64(aWindowId), count);
|
||||
}
|
||||
|
||||
void
|
||||
RenderThread::IncRenderingFrameCount(wr::WindowId aWindowId)
|
||||
{
|
||||
MutexAutoLock lock(mFrameCountMapLock);
|
||||
// Get the old count.
|
||||
FrameCount count;
|
||||
if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &count)) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
// Update rendering frame count.
|
||||
count.mRenderingCount = count.mRenderingCount + 1;
|
||||
mPendingFrameCounts.Put(AsUint64(aWindowId), count);
|
||||
}
|
||||
|
||||
void
|
||||
RenderThread::DecPendingFrameCount(wr::WindowId aWindowId)
|
||||
{
|
||||
MutexAutoLock lock(mPendingFrameCountMapLock);
|
||||
MutexAutoLock lock(mFrameCountMapLock);
|
||||
// Get the old count.
|
||||
uint32_t oldCount = 0;
|
||||
if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &oldCount)) {
|
||||
FrameCount count;
|
||||
if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &count)) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(oldCount > 0);
|
||||
if (oldCount <= 0) {
|
||||
MOZ_ASSERT(count.mPendingCount > 0);
|
||||
MOZ_ASSERT(count.mRenderingCount > 0);
|
||||
if (count.mPendingCount <= 0) {
|
||||
return;
|
||||
}
|
||||
// Update pending frame count.
|
||||
mPendingFrameCounts.Put(AsUint64(aWindowId), oldCount - 1);
|
||||
// Update frame counts.
|
||||
count.mPendingCount = count.mPendingCount - 1;
|
||||
count.mRenderingCount = count.mRenderingCount - 1;
|
||||
mPendingFrameCounts.Put(AsUint64(aWindowId), count);
|
||||
}
|
||||
|
||||
void
|
||||
@ -379,6 +410,7 @@ extern "C" {
|
||||
|
||||
void wr_notifier_new_frame_ready(mozilla::wr::WrWindowId aWindowId)
|
||||
{
|
||||
mozilla::wr::RenderThread::Get()->IncRenderingFrameCount(aWindowId);
|
||||
mozilla::wr::RenderThread::Get()->NewFrameReady(mozilla::wr::WindowId(aWindowId));
|
||||
}
|
||||
|
||||
|
@ -128,10 +128,12 @@ public:
|
||||
RenderTextureHost* GetRenderTexture(WrExternalImageId aExternalImageId);
|
||||
|
||||
/// Can be called from any thread.
|
||||
uint32_t GetPendingFrameCount(wr::WindowId aWindowId);
|
||||
bool TooManyPendingFrames(wr::WindowId aWindowId);
|
||||
/// Can be called from any thread.
|
||||
void IncPendingFrameCount(wr::WindowId aWindowId);
|
||||
/// Can be called from any thread.
|
||||
void IncRenderingFrameCount(wr::WindowId aWindowId);
|
||||
/// Can be called from any thread.
|
||||
void DecPendingFrameCount(wr::WindowId aWindowId);
|
||||
|
||||
/// Can be called from any thread.
|
||||
@ -151,8 +153,13 @@ private:
|
||||
|
||||
std::map<wr::WindowId, UniquePtr<RendererOGL>> mRenderers;
|
||||
|
||||
Mutex mPendingFrameCountMapLock;
|
||||
nsDataHashtable<nsUint64HashKey, uint32_t> mPendingFrameCounts;
|
||||
struct FrameCount {
|
||||
int64_t mPendingCount = 0;
|
||||
int64_t mRenderingCount = 0;
|
||||
};
|
||||
|
||||
Mutex mFrameCountMapLock;
|
||||
nsDataHashtable<nsUint64HashKey, FrameCount> mPendingFrameCounts;
|
||||
|
||||
Mutex mRenderTextureMapLock;
|
||||
nsRefPtrHashtable<nsUint64HashKey, RenderTextureHost> mRenderTextures;
|
||||
|
@ -89,4 +89,4 @@ include('/ipc/chromium/chromium-config.mozbuild')
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
with Files("**"):
|
||||
BUG_COMPONENT = ("Core", "Disability Access APIs")
|
||||
BUG_COMPONENT = ("Core", "IPC: MSCOM")
|
||||
|
@ -67,7 +67,7 @@ namespace JS {
|
||||
D(EVICT_NURSERY) \
|
||||
D(DELAYED_ATOMS_GC) \
|
||||
D(SHARED_MEMORY_LIMIT) \
|
||||
D(UNUSED1) \
|
||||
D(IDLE_TIME_COLLECTION) \
|
||||
D(INCREMENTAL_TOO_SLOW) \
|
||||
D(ABORT_GC) \
|
||||
D(FULL_WHOLE_CELL_BUFFER) \
|
||||
|
@ -474,7 +474,9 @@ js::Nursery::calcPromotionRate(bool *validForTenuring) const {
|
||||
float used = float(previousGC.nurseryUsedBytes);
|
||||
float capacity = float(previousGC.nurseryCapacity);
|
||||
float tenured = float(previousGC.tenuredBytes);
|
||||
float rate;
|
||||
|
||||
if (previousGC.nurseryUsedBytes > 0) {
|
||||
if (validForTenuring) {
|
||||
/*
|
||||
* We can only use promotion rates if they're likely to be valid,
|
||||
@ -482,7 +484,14 @@ js::Nursery::calcPromotionRate(bool *validForTenuring) const {
|
||||
*/
|
||||
*validForTenuring = used > capacity * 0.9f;
|
||||
}
|
||||
return tenured / used;
|
||||
rate = tenured / used;
|
||||
} else {
|
||||
if (validForTenuring)
|
||||
*validForTenuring = false;
|
||||
rate = 0.0f;
|
||||
}
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
void
|
||||
@ -501,20 +510,22 @@ js::Nursery::renderProfileJSON(JSONPrinter& json) const
|
||||
// requested. (And as a public API, this function should not crash in
|
||||
// such a case.)
|
||||
json.beginObject();
|
||||
json.property("status", "no collection");
|
||||
json.property("status", "nursery empty");
|
||||
json.endObject();
|
||||
return;
|
||||
}
|
||||
|
||||
json.beginObject();
|
||||
|
||||
json.property("status", "complete");
|
||||
|
||||
json.property("reason", JS::gcreason::ExplainReason(previousGC.reason));
|
||||
json.property("bytes_tenured", previousGC.tenuredBytes);
|
||||
json.floatProperty("promotion_rate", calcPromotionRate(nullptr), 0);
|
||||
json.property("nursery_bytes", previousGC.nurseryUsedBytes);
|
||||
json.property("new_nursery_bytes", numChunks() * ChunkSize);
|
||||
json.property("bytes_used", previousGC.nurseryUsedBytes);
|
||||
json.property("cur_capacity", previousGC.nurseryCapacity);
|
||||
json.property("new_capacity", spaceToEnd());
|
||||
|
||||
json.beginObjectProperty("timings");
|
||||
json.beginObjectProperty("phase_times");
|
||||
|
||||
#define EXTRACT_NAME(name, text) #name,
|
||||
static const char* names[] = {
|
||||
|
@ -279,12 +279,20 @@ class Nursery
|
||||
JS::gcreason::Reason minorGCTriggerReason() const { return minorGCTriggerReason_; }
|
||||
void clearMinorGCRequest() { minorGCTriggerReason_ = JS::gcreason::NO_REASON; }
|
||||
|
||||
bool needIdleTimeCollection() const {
|
||||
return minorGCRequested() ||
|
||||
(freeSpace() < kIdleTimeCollectionThreshold);
|
||||
}
|
||||
|
||||
bool enableProfiling() const { return enableProfiling_; }
|
||||
|
||||
private:
|
||||
/* The amount of space in the mapped nursery available to allocations. */
|
||||
static const size_t NurseryChunkUsableSize = gc::ChunkSize - gc::ChunkTrailerSize;
|
||||
|
||||
/* Attemp to run a minor GC in the idle time if the free space falls below this threshold. */
|
||||
static constexpr size_t kIdleTimeCollectionThreshold = NurseryChunkUsableSize / 4;
|
||||
|
||||
JSRuntime* runtime_;
|
||||
|
||||
/* Vector of allocated chunks to allocate from. */
|
||||
|
@ -554,6 +554,14 @@ Statistics::renderNurseryJson(JSRuntime* rt) const
|
||||
UniqueChars
|
||||
Statistics::renderJsonMessage(uint64_t timestamp, bool includeSlices) const
|
||||
{
|
||||
/*
|
||||
* The format of the JSON message is specified by the GCMajorMarkerPayload
|
||||
* type in perf.html
|
||||
* https://github.com/devtools-html/perf.html/blob/master/src/types/markers.js#L62
|
||||
*
|
||||
* All the properties listed here are created within the timings property
|
||||
* of the GCMajor marker.
|
||||
*/
|
||||
if (aborted)
|
||||
return DuplicateString("{status:\"aborted\"}"); // May return nullptr
|
||||
|
||||
@ -563,10 +571,11 @@ Statistics::renderJsonMessage(uint64_t timestamp, bool includeSlices) const
|
||||
JSONPrinter json(printer);
|
||||
|
||||
json.beginObject();
|
||||
json.property("status", "completed");
|
||||
formatJsonDescription(timestamp, json);
|
||||
|
||||
if (includeSlices) {
|
||||
json.beginListProperty("slices");
|
||||
json.beginListProperty("slices_list");
|
||||
for (unsigned i = 0; i < slices_.length(); i++)
|
||||
formatJsonSlice(i, json);
|
||||
json.endList();
|
||||
@ -611,10 +620,12 @@ Statistics::formatJsonDescription(uint64_t timestamp, JSONPrinter& json) const
|
||||
|
||||
json.property("nonincremental_reason", ExplainAbortReason(nonincrementalReason_));
|
||||
json.property("allocated", uint64_t(preBytes)/1024/1024);
|
||||
json.property("allocated_bytes", preBytes);
|
||||
json.property("added_chunks", getCount(STAT_NEW_CHUNK));
|
||||
json.property("removed_chunks", getCount(STAT_DESTROY_CHUNK));
|
||||
json.property("major_gc_number", startingMajorGCNumber);
|
||||
json.property("minor_gc_number", startingMinorGCNumber);
|
||||
json.property("slice_number", startingSliceNumber);
|
||||
}
|
||||
|
||||
void
|
||||
@ -880,6 +891,7 @@ Statistics::beginGC(JSGCInvocationKind kind)
|
||||
|
||||
preBytes = runtime->gc.usage.gcBytes();
|
||||
startingMajorGCNumber = runtime->gc.majorGCCount();
|
||||
startingSliceNumber = runtime->gc.gcNumber();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -317,6 +317,7 @@ struct Statistics
|
||||
/* GC numbers as of the beginning of the collection. */
|
||||
uint64_t startingMinorGCNumber;
|
||||
uint64_t startingMajorGCNumber;
|
||||
uint64_t startingSliceNumber;
|
||||
|
||||
/* Records the maximum GC pause in an API-controlled interval (in us). */
|
||||
mutable TimeDuration maxPauseInInterval;
|
||||
|
20
js/src/jit-test/tests/arrays/sort-update-types.js
Normal file
20
js/src/jit-test/tests/arrays/sort-update-types.js
Normal file
@ -0,0 +1,20 @@
|
||||
Object.setPrototypeOf(Array.prototype, {
|
||||
get 0() {
|
||||
Object.setPrototypeOf(Array.prototype, Object.prototype);
|
||||
return "159".repeat(5).substring(2, 5);
|
||||
}
|
||||
});
|
||||
|
||||
var array = [
|
||||
/*0*/, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
|
||||
];
|
||||
|
||||
array.sort();
|
||||
|
||||
gc();
|
||||
|
||||
var r = array[array.length - 1] * 1;
|
||||
assertEq(r, 915);
|
6
js/src/jit-test/tests/auto-regress/bug1343513-2.js
Normal file
6
js/src/jit-test/tests/auto-regress/bug1343513-2.js
Normal file
@ -0,0 +1,6 @@
|
||||
// |jit-test| error:RangeError
|
||||
var i = 0;
|
||||
do {
|
||||
i++;
|
||||
var ta = new Int32Array(inIon() ? 0x20000001 : 1);
|
||||
} while (!inIon());
|
6
js/src/jit-test/tests/auto-regress/bug1343513.js
Normal file
6
js/src/jit-test/tests/auto-regress/bug1343513.js
Normal file
@ -0,0 +1,6 @@
|
||||
// |jit-test| error:RangeError
|
||||
var i = 0;
|
||||
do {
|
||||
i++;
|
||||
var ta = new Int32Array(inIon() ? 0x7fffffff : 1);
|
||||
} while (!inIon());
|
@ -1439,6 +1439,20 @@ JS_RemoveExtraGCRootsTracer(JSContext* cx, JSTraceDataOp traceOp, void* data)
|
||||
return cx->runtime()->gc.removeBlackRootsTracer(traceOp, data);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
JS::IsIdleGCTaskNeeded(JSRuntime* rt) {
|
||||
// Currently, we only collect nursery during idle time.
|
||||
return rt->gc.nursery().needIdleTimeCollection();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS::RunIdleTimeGCTask(JSRuntime* rt) {
|
||||
GCRuntime& gc = rt->gc;
|
||||
if (gc.nursery().needIdleTimeCollection()) {
|
||||
gc.minorGC(JS::gcreason::IDLE_TIME_COLLECTION);
|
||||
}
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_GC(JSContext* cx)
|
||||
{
|
||||
|
@ -1748,6 +1748,16 @@ JS_RemoveExtraGCRootsTracer(JSContext* cx, JSTraceDataOp traceOp, void* data);
|
||||
/*
|
||||
* Garbage collector API.
|
||||
*/
|
||||
namespace JS {
|
||||
|
||||
extern JS_PUBLIC_API(bool)
|
||||
IsIdleGCTaskNeeded(JSRuntime* rt);
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
RunIdleTimeGCTask(JSRuntime* rt);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_GC(JSContext* cx);
|
||||
|
||||
|
@ -2207,6 +2207,9 @@ js::intrinsic_ArrayNativeSort(JSContext* cx, unsigned argc, Value* vp)
|
||||
}
|
||||
}
|
||||
|
||||
// We can omit the type update when neither collecting the elements
|
||||
// nor calling the default comparator can execute a (getter) function
|
||||
// that might run user code.
|
||||
ShouldUpdateTypes updateTypes = !extraIndexed && (allStrings || allInts)
|
||||
? ShouldUpdateTypes::DontUpdate
|
||||
: ShouldUpdateTypes::Update;
|
||||
|
@ -0,0 +1,3 @@
|
||||
if (typeof setTimeZone === "undefined") {
|
||||
var setTimeZone = SpecialPowers.Cu.getJSTestingFunctions().setTimeZone;
|
||||
}
|
67
js/src/tests/Intl/DateTimeFormat/tz-environment-variable.js
Normal file
67
js/src/tests/Intl/DateTimeFormat/tz-environment-variable.js
Normal file
@ -0,0 +1,67 @@
|
||||
// |reftest| skip-if(!this.hasOwnProperty("Intl")||(xulRuntime.OS=="WINNT"&&!xulRuntime.shell)) -- Windows browser in automation doesn't pick up new time zones correctly
|
||||
|
||||
// From bug 1330149:
|
||||
//
|
||||
// Windows only supports a very limited set of IANA time zone names for the TZ
|
||||
// environment variable.
|
||||
//
|
||||
// TZ format supported by Windows: "TZ=tzn[+|-]hh[:mm[:ss]][dzn]".
|
||||
//
|
||||
// Complete list of all IANA time zone ids matching that format.
|
||||
//
|
||||
// From tzdata's "northamerica" file:
|
||||
// EST5EDT
|
||||
// CST6CDT
|
||||
// MST7MDT
|
||||
// PST8PDT
|
||||
//
|
||||
// From tzdata's "backward" file:
|
||||
// GMT+0
|
||||
// GMT-0
|
||||
// GMT0
|
||||
//
|
||||
// Also supported on Windows even though they don't match the format listed
|
||||
// above.
|
||||
//
|
||||
// From tzdata's "backward" file:
|
||||
// UCT
|
||||
// UTC
|
||||
//
|
||||
// From tzdata's "etcetera" file:
|
||||
// GMT
|
||||
|
||||
function inTimeZone(tzname, fn) {
|
||||
setTimeZone(tzname);
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
setTimeZone("PST8PDT");
|
||||
}
|
||||
}
|
||||
|
||||
const timeZones = [
|
||||
{ id: "EST5EDT" },
|
||||
{ id: "CST6CDT" },
|
||||
{ id: "MST7MDT" },
|
||||
{ id: "PST8PDT" },
|
||||
// ICU on non-Windows platforms doesn't accept these three time zone
|
||||
// identifiers, cf. isValidOlsonID in $ICU/source/common/putil.cpp. We
|
||||
// could add support for them, but it seems unlikely they're used in
|
||||
// practice, so we just skip over them.
|
||||
// { id: "GMT+0", normalized: "UTC" },
|
||||
// { id: "GMT-0", normalized: "UTC" },
|
||||
// { id: "GMT0", normalized: "UTC" },
|
||||
{ id: "UCT", normalized: "Etc/UCT" },
|
||||
{ id: "UTC", normalized: "UTC" },
|
||||
{ id: "GMT", normalized: "UTC" },
|
||||
];
|
||||
|
||||
for (let {id, normalized = id} of timeZones) {
|
||||
inTimeZone(id, () => {
|
||||
let opts = new Intl.DateTimeFormat().resolvedOptions();
|
||||
assertEq(opts.timeZone, normalized);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0, "ok");
|
@ -1,4 +1,4 @@
|
||||
// |reftest| random-if(xulRuntime.OS=="Linux") skip-if(xulRuntime.OS=="WINNT")
|
||||
// |reftest| random-if(xulRuntime.OS=="Linux")
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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
|
||||
|
@ -1,4 +1,3 @@
|
||||
// |reftest| skip-if(xulRuntime.OS=="WINNT")
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* 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
|
||||
|
@ -1,4 +1,3 @@
|
||||
// |reftest| skip-if(xulRuntime.OS=="WINNT")
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* 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
|
||||
|
@ -22,6 +22,17 @@
|
||||
// GMT+0
|
||||
// GMT-0
|
||||
// GMT0
|
||||
//
|
||||
// Also supported on Windows even though they don't match the format listed
|
||||
// above.
|
||||
//
|
||||
// From tzdata's "backward" file:
|
||||
// UCT
|
||||
// UTC
|
||||
//
|
||||
// From tzdata's "etcetera" file:
|
||||
// GMT
|
||||
|
||||
|
||||
// Perform the following replacements:
|
||||
// America/New_York -> EST5EDT
|
||||
|
@ -6,6 +6,12 @@
|
||||
|
||||
#include "vm/DateTime.h"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#endif /* defined(XP_WIN) */
|
||||
#include <time.h>
|
||||
|
||||
#include "jsutil.h"
|
||||
@ -14,7 +20,10 @@
|
||||
#include "threading/ExclusiveData.h"
|
||||
#if ENABLE_INTL_API
|
||||
#include "unicode/timezone.h"
|
||||
#if defined(XP_WIN)
|
||||
#include "unicode/unistr.h"
|
||||
#endif
|
||||
#endif /* ENABLE_INTL_API */
|
||||
#include "vm/MutexIDs.h"
|
||||
|
||||
using mozilla::UnspecifiedNaN;
|
||||
@ -334,12 +343,86 @@ JS::ResetTimeZone()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(XP_WIN)
|
||||
static bool
|
||||
IsOlsonCompatibleWindowsTimeZoneId(const char* tz)
|
||||
{
|
||||
// ICU ignores the TZ environment variable on Windows and instead directly
|
||||
// invokes Win API functions to retrieve the current time zone. But since
|
||||
// we're still using the POSIX-derived localtime_s() function on Windows
|
||||
// and localtime_s() does return a time zone adjusted value based on the
|
||||
// TZ environment variable, we need to manually adjust the default ICU
|
||||
// time zone if TZ is set.
|
||||
//
|
||||
// Windows supports the following format for TZ: tzn[+|-]hh[:mm[:ss]][dzn]
|
||||
// where "tzn" is the time zone name for standard time, the time zone
|
||||
// offset is positive for time zones west of GMT, and "dzn" is the
|
||||
// optional time zone name when daylight savings are observed. Daylight
|
||||
// savings are always based on the U.S. daylight saving rules, that means
|
||||
// for example it's not possible to use "TZ=CET-1CEST" to select the IANA
|
||||
// time zone "CET".
|
||||
//
|
||||
// When comparing this restricted format for TZ to all IANA time zone
|
||||
// names, the following time zones are in the intersection of what's
|
||||
// supported by Windows and is also a valid IANA time zone identifier.
|
||||
//
|
||||
// Even though the time zone offset is marked as mandatory on MSDN, it
|
||||
// appears it defaults to zero when omitted. This in turn means we can
|
||||
// also allow the time zone identifiers "UCT", "UTC", and "GMT".
|
||||
|
||||
static const char* const allowedIds[] = {
|
||||
// From tzdata's "northamerica" file:
|
||||
"EST5EDT",
|
||||
"CST6CDT",
|
||||
"MST7MDT",
|
||||
"PST8PDT",
|
||||
|
||||
// From tzdata's "backward" file:
|
||||
"GMT+0",
|
||||
"GMT-0",
|
||||
"GMT0",
|
||||
"UCT",
|
||||
"UTC",
|
||||
|
||||
// From tzdata's "etcetera" file:
|
||||
"GMT",
|
||||
};
|
||||
for (const auto& allowedId : allowedIds) {
|
||||
if (std::strcmp(allowedId, tz) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
js::ResyncICUDefaultTimeZone()
|
||||
{
|
||||
#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
|
||||
auto guard = IcuTimeZoneState->lock();
|
||||
if (guard.get() == IcuTimeZoneStatus::NeedsUpdate) {
|
||||
bool recreate = true;
|
||||
#if defined(XP_WIN)
|
||||
// If TZ is set and its value is valid under Windows' and IANA's time
|
||||
// zone identifier rules, update the ICU default time zone to use this
|
||||
// value.
|
||||
const char* tz = std::getenv("TZ");
|
||||
if (tz && IsOlsonCompatibleWindowsTimeZoneId(tz)) {
|
||||
icu::UnicodeString tzid(tz, -1, US_INV);
|
||||
mozilla::UniquePtr<icu::TimeZone> newTimeZone(icu::TimeZone::createTimeZone(tzid));
|
||||
MOZ_ASSERT(newTimeZone);
|
||||
if (*newTimeZone != icu::TimeZone::getUnknown()) {
|
||||
// adoptDefault() takes ownership of the time zone.
|
||||
icu::TimeZone::adoptDefault(newTimeZone.release());
|
||||
recreate = false;
|
||||
}
|
||||
} else {
|
||||
// If |tz| isn't a supported time zone identifier, use the default
|
||||
// Windows time zone for ICU.
|
||||
// TODO: Handle invalid time zone identifiers (bug 342068).
|
||||
}
|
||||
#endif
|
||||
if (recreate)
|
||||
icu::TimeZone::recreateDefault();
|
||||
guard.get() = IcuTimeZoneStatus::Valid;
|
||||
}
|
||||
|
@ -3168,7 +3168,7 @@ Preferences::HandleDirty()
|
||||
static const int PREF_DELAY_MS = 500;
|
||||
NS_DelayedDispatchToCurrentThread(
|
||||
mozilla::NewRunnableMethod("Preferences::SavePrefFileAsynchronous",
|
||||
sPreferences,
|
||||
sPreferences.get(),
|
||||
&Preferences::SavePrefFileAsynchronous),
|
||||
PREF_DELAY_MS);
|
||||
}
|
||||
@ -3211,9 +3211,7 @@ static const char kPrefFileHeader[] =
|
||||
NS_LINEBREAK;
|
||||
// clang-format on
|
||||
|
||||
Preferences* Preferences::sPreferences = nullptr;
|
||||
nsIPrefBranch* Preferences::sRootBranch = nullptr;
|
||||
nsIPrefBranch* Preferences::sDefaultRootBranch = nullptr;
|
||||
StaticRefPtr<Preferences> Preferences::sPreferences;
|
||||
bool Preferences::sShutdown = false;
|
||||
|
||||
// This globally enables or disables OMT pref writing, both sync and async.
|
||||
@ -3546,13 +3544,13 @@ Preferences::SizeOfIncludingThisAndOtherStuff(
|
||||
}
|
||||
}
|
||||
|
||||
if (sRootBranch) {
|
||||
n += reinterpret_cast<nsPrefBranch*>(sRootBranch)
|
||||
if (sPreferences->mRootBranch) {
|
||||
n += static_cast<nsPrefBranch*>(sPreferences->mRootBranch.get())
|
||||
->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
if (sDefaultRootBranch) {
|
||||
n += reinterpret_cast<nsPrefBranch*>(sDefaultRootBranch)
|
||||
if (sPreferences->mDefaultRootBranch) {
|
||||
n += static_cast<nsPrefBranch*>(sPreferences->mDefaultRootBranch.get())
|
||||
->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
@ -3709,19 +3707,11 @@ Preferences::GetInstanceForService()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sRootBranch = new nsPrefBranch("", false);
|
||||
NS_ADDREF(sRootBranch);
|
||||
sDefaultRootBranch = new nsPrefBranch("", true);
|
||||
NS_ADDREF(sDefaultRootBranch);
|
||||
|
||||
sPreferences = new Preferences();
|
||||
NS_ADDREF(sPreferences);
|
||||
|
||||
Result<Ok, const char*> res = sPreferences->Init();
|
||||
if (res.isErr()) {
|
||||
// The singleton instance will delete sRootBranch and sDefaultRootBranch.
|
||||
sPreferences = nullptr;
|
||||
gCacheDataDesc = res.unwrapErr();
|
||||
NS_RELEASE(sPreferences);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -3766,13 +3756,7 @@ Preferences::Shutdown()
|
||||
{
|
||||
if (!sShutdown) {
|
||||
sShutdown = true; // Don't create the singleton instance after here.
|
||||
|
||||
// Don't set sPreferences to nullptr here. The instance may be grabbed by
|
||||
// other modules. The utility methods of Preferences should be available
|
||||
// until the singleton instance actually released.
|
||||
if (sPreferences) {
|
||||
sPreferences->Release();
|
||||
}
|
||||
sPreferences = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3782,11 +3766,15 @@ Preferences::Shutdown()
|
||||
// Constructor/Destructor
|
||||
//
|
||||
|
||||
Preferences::Preferences() = default;
|
||||
Preferences::Preferences()
|
||||
: mRootBranch(new nsPrefBranch("", false))
|
||||
, mDefaultRootBranch(new nsPrefBranch("", true))
|
||||
{
|
||||
}
|
||||
|
||||
Preferences::~Preferences()
|
||||
{
|
||||
NS_ASSERTION(sPreferences == this, "Isn't this the singleton instance?");
|
||||
MOZ_ASSERT(!sPreferences);
|
||||
|
||||
delete gObserverTable;
|
||||
gObserverTable = nullptr;
|
||||
@ -3794,11 +3782,6 @@ Preferences::~Preferences()
|
||||
delete gCacheData;
|
||||
gCacheData = nullptr;
|
||||
|
||||
NS_RELEASE(sRootBranch);
|
||||
NS_RELEASE(sDefaultRootBranch);
|
||||
|
||||
sPreferences = nullptr;
|
||||
|
||||
PREF_Cleanup();
|
||||
}
|
||||
|
||||
@ -4170,7 +4153,7 @@ Preferences::GetBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal)
|
||||
prefBranch.forget(aRetVal);
|
||||
} else {
|
||||
// Special case: caching the default root.
|
||||
nsCOMPtr<nsIPrefBranch> root(sRootBranch);
|
||||
nsCOMPtr<nsIPrefBranch> root(sPreferences->mRootBranch);
|
||||
root.forget(aRetVal);
|
||||
}
|
||||
|
||||
@ -4181,7 +4164,7 @@ NS_IMETHODIMP
|
||||
Preferences::GetDefaultBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal)
|
||||
{
|
||||
if (!aPrefRoot || !aPrefRoot[0]) {
|
||||
nsCOMPtr<nsIPrefBranch> root(sDefaultRootBranch);
|
||||
nsCOMPtr<nsIPrefBranch> root(sPreferences->mDefaultRootBranch);
|
||||
root.forget(aRetVal);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -4865,7 +4848,7 @@ Preferences::GetLocalizedString(const char* aPref, nsAString& aResult)
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
|
||||
nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
|
||||
nsresult rv = sRootBranch->GetComplexValue(
|
||||
nsresult rv = sPreferences->mRootBranch->GetComplexValue(
|
||||
aPref, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(prefLocalString));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL");
|
||||
@ -4878,7 +4861,7 @@ Preferences::GetLocalizedString(const char* aPref, nsAString& aResult)
|
||||
Preferences::GetComplex(const char* aPref, const nsIID& aType, void** aResult)
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
|
||||
return sRootBranch->GetComplexValue(aPref, aType, aResult);
|
||||
return sPreferences->mRootBranch->GetComplexValue(aPref, aType, aResult);
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
@ -4941,7 +4924,7 @@ Preferences::SetComplex(const char* aPref,
|
||||
nsISupports* aValue)
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
|
||||
return sRootBranch->SetComplexValue(aPref, aType, aValue);
|
||||
return sPreferences->mRootBranch->SetComplexValue(aPref, aType, aValue);
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
@ -4964,7 +4947,7 @@ Preferences::GetType(const char* aPref)
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
|
||||
int32_t result;
|
||||
return NS_SUCCEEDED(sRootBranch->GetPrefType(aPref, &result))
|
||||
return NS_SUCCEEDED(sPreferences->mRootBranch->GetPrefType(aPref, &result))
|
||||
? result
|
||||
: nsIPrefBranch::PREF_INVALID;
|
||||
}
|
||||
@ -4974,7 +4957,7 @@ Preferences::AddStrongObserver(nsIObserver* aObserver, const char* aPref)
|
||||
{
|
||||
MOZ_ASSERT(aObserver);
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
|
||||
return sRootBranch->AddObserver(aPref, aObserver, false);
|
||||
return sPreferences->mRootBranch->AddObserver(aPref, aObserver, false);
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
@ -4982,7 +4965,7 @@ Preferences::AddWeakObserver(nsIObserver* aObserver, const char* aPref)
|
||||
{
|
||||
MOZ_ASSERT(aObserver);
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
|
||||
return sRootBranch->AddObserver(aPref, aObserver, true);
|
||||
return sPreferences->mRootBranch->AddObserver(aPref, aObserver, true);
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
@ -4993,7 +4976,7 @@ Preferences::RemoveObserver(nsIObserver* aObserver, const char* aPref)
|
||||
return NS_OK; // Observers have been released automatically.
|
||||
}
|
||||
NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
|
||||
return sRootBranch->RemoveObserver(aPref, aObserver);
|
||||
return sPreferences->mRootBranch->RemoveObserver(aPref, aObserver);
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
@ -5340,7 +5323,7 @@ Preferences::GetDefaultLocalizedString(const char* aPref, nsAString& aResult)
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
|
||||
nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
|
||||
nsresult rv = sDefaultRootBranch->GetComplexValue(
|
||||
nsresult rv = sPreferences->mDefaultRootBranch->GetComplexValue(
|
||||
aPref, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(prefLocalString));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL");
|
||||
@ -5355,7 +5338,8 @@ Preferences::GetDefaultComplex(const char* aPref,
|
||||
void** aResult)
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
|
||||
return sDefaultRootBranch->GetComplexValue(aPref, aType, aResult);
|
||||
return sPreferences->mDefaultRootBranch->GetComplexValue(
|
||||
aPref, aType, aResult);
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
@ -5363,7 +5347,8 @@ Preferences::GetDefaultType(const char* aPref)
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
|
||||
int32_t result;
|
||||
return NS_SUCCEEDED(sDefaultRootBranch->GetPrefType(aPref, &result))
|
||||
return NS_SUCCEEDED(
|
||||
sPreferences->mDefaultRootBranch->GetPrefType(aPref, &result))
|
||||
? result
|
||||
: nsIPrefBranch::PREF_INVALID;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIPrefBranch.h"
|
||||
@ -64,7 +65,7 @@ public:
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIPREFSERVICE
|
||||
NS_FORWARD_NSIPREFBRANCH(sRootBranch->)
|
||||
NS_FORWARD_NSIPREFBRANCH(mRootBranch->)
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
Preferences();
|
||||
@ -94,14 +95,14 @@ public:
|
||||
static nsIPrefBranch* GetRootBranch()
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), nullptr);
|
||||
return sRootBranch;
|
||||
return sPreferences->mRootBranch;
|
||||
}
|
||||
|
||||
// Returns shared default pref branch instance. NOTE: not addreffed.
|
||||
static nsIPrefBranch* GetDefaultRootBranch()
|
||||
{
|
||||
NS_ENSURE_TRUE(InitStaticMembers(), nullptr);
|
||||
return sDefaultRootBranch;
|
||||
return sPreferences->mDefaultRootBranch;
|
||||
}
|
||||
|
||||
// Gets int or bool type pref value with default value if failed to get the
|
||||
@ -403,9 +404,10 @@ private:
|
||||
// mDirty and mSavePending will both be true.
|
||||
bool mSavePending = false;
|
||||
|
||||
static Preferences* sPreferences;
|
||||
static nsIPrefBranch* sRootBranch;
|
||||
static nsIPrefBranch* sDefaultRootBranch;
|
||||
nsCOMPtr<nsIPrefBranch> mRootBranch;
|
||||
nsCOMPtr<nsIPrefBranch> mDefaultRootBranch;
|
||||
|
||||
static StaticRefPtr<Preferences> sPreferences;
|
||||
static bool sShutdown;
|
||||
|
||||
// Init static members. Returns true on success.
|
||||
|
5
netwerk/cache/nsCacheService.cpp
vendored
5
netwerk/cache/nsCacheService.cpp
vendored
@ -3064,8 +3064,11 @@ void nsCacheService::GetAppCacheDirectory(nsIFile ** result)
|
||||
void
|
||||
nsCacheService::LogCacheStatistics()
|
||||
{
|
||||
uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
|
||||
uint32_t hitPercentage = 0;
|
||||
if (!mCacheHits || !mCacheMisses) {
|
||||
hitPercentage = (uint32_t)((((double)mCacheHits) /
|
||||
((double)(mCacheHits + mCacheMisses))) * 100);
|
||||
}
|
||||
CACHE_LOG_INFO(("\nCache Service Statistics:\n\n"));
|
||||
CACHE_LOG_INFO((" TotalEntries = %d\n", mTotalEntries));
|
||||
CACHE_LOG_INFO((" Cache Hits = %d\n", mCacheHits));
|
||||
|
@ -187,7 +187,7 @@ const TASKS = [
|
||||
filter.disconnect();
|
||||
checkState("disconnected");
|
||||
|
||||
for (let method of ["suspend", "resume", "close"]) {
|
||||
for (let method of ["suspend", "resume", "close", "disconnect"]) {
|
||||
browser.test.assertThrows(
|
||||
() => {
|
||||
filter[method]();
|
||||
@ -203,8 +203,63 @@ const TASKS = [
|
||||
/.*/,
|
||||
`(${num}): write() should throw while disconnected`);
|
||||
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
filter.onerror = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected error event: ${filter.error}`);
|
||||
};
|
||||
},
|
||||
verify(response) {
|
||||
is(response, PARTS.join(""), "Got expected final HTML");
|
||||
},
|
||||
},
|
||||
{
|
||||
url: "slow_response.sjs",
|
||||
task(filter, resolve, num) {
|
||||
let encoder = new TextEncoder("utf-8");
|
||||
|
||||
filter.onstop = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected onStop event while disconnected`);
|
||||
};
|
||||
|
||||
let n = 0;
|
||||
filter.ondata = async event => {
|
||||
n++;
|
||||
|
||||
filter.write(event.data);
|
||||
|
||||
if (n == 3) {
|
||||
filter.suspend();
|
||||
await new Promise(resolve => setTimeout(resolve, TIMEOUT));
|
||||
filter.resume();
|
||||
filter.suspend();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, TIMEOUT));
|
||||
|
||||
filter.resume();
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
filter.suspend();
|
||||
|
||||
filter.disconnect();
|
||||
|
||||
for (let method of ["suspend", "resume", "close", "disconnect"]) {
|
||||
browser.test.assertThrows(
|
||||
() => {
|
||||
filter[method]();
|
||||
},
|
||||
/.*/,
|
||||
`(${num}): ${method}() should throw while disconnected`);
|
||||
}
|
||||
|
||||
browser.test.assertThrows(
|
||||
() => {
|
||||
filter.write(encoder.encode("Foo bar"));
|
||||
},
|
||||
/.*/,
|
||||
`(${num}): write() should throw while disconnected`);
|
||||
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
@ -113,6 +113,20 @@ StreamFilterChild::Resume(ErrorResult& aRv)
|
||||
break;
|
||||
|
||||
case State::Resuming:
|
||||
switch (mNextState) {
|
||||
case State::Suspending:
|
||||
mNextState = State::Resuming;
|
||||
break;
|
||||
|
||||
case State::TransferringData:
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::TransferringData:
|
||||
break;
|
||||
|
||||
@ -142,8 +156,9 @@ StreamFilterChild::Disconnect(ErrorResult& aRv)
|
||||
case State::Resuming:
|
||||
switch (mNextState) {
|
||||
case State::Suspended:
|
||||
case State::Suspending:
|
||||
case State::Resuming:
|
||||
case State::Disconnecting:
|
||||
case State::TransferringData:
|
||||
mNextState = State::Disconnecting;
|
||||
break;
|
||||
|
||||
@ -153,10 +168,6 @@ StreamFilterChild::Disconnect(ErrorResult& aRv)
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Disconnecting:
|
||||
case State::Disconnected:
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
@ -178,9 +189,20 @@ StreamFilterChild::Close(ErrorResult& aRv)
|
||||
|
||||
case State::Suspending:
|
||||
case State::Resuming:
|
||||
switch (mNextState) {
|
||||
case State::Suspended:
|
||||
case State::Suspending:
|
||||
case State::Resuming:
|
||||
case State::TransferringData:
|
||||
mNextState = State::Closing;
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Closing:
|
||||
MOZ_DIAGNOSTIC_ASSERT(mNextState == State::Closed);
|
||||
break;
|
||||
@ -223,6 +245,8 @@ StreamFilterChild::SetNextState()
|
||||
|
||||
case State::Disconnecting:
|
||||
mNextState = State::Disconnected;
|
||||
|
||||
WriteBufferedData();
|
||||
SendDisconnect();
|
||||
break;
|
||||
|
||||
|
@ -52,8 +52,8 @@ class GCData {
|
||||
|
||||
data.timestamp = fixup(data.timestamp);
|
||||
|
||||
for (let i = 0; i < data.slices.length; i++) {
|
||||
let slice = data.slices[i];
|
||||
for (let i = 0; i < data.slices_list.length; i++) {
|
||||
let slice = data.slices_list[i];
|
||||
slice.start_timestamp = fixup(slice.start_timestamp);
|
||||
slice.end_timestamp = fixup(slice.end_timestamp);
|
||||
}
|
||||
@ -110,7 +110,7 @@ class GCData {
|
||||
// make sure to update the JSON schema at:
|
||||
// https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/master/telemetry/main.schema.json
|
||||
// You should also adjust browser_TelemetryGC.js.
|
||||
const MAX_GC_KEYS = 25;
|
||||
const MAX_GC_KEYS = 30;
|
||||
const MAX_SLICES = 4;
|
||||
const MAX_SLICE_KEYS = 15;
|
||||
const MAX_PHASES = 65;
|
||||
@ -127,26 +127,26 @@ function limitProperties(obj, count) {
|
||||
|
||||
function limitSize(data) {
|
||||
// Store the number of slices so we know if we lost any at the end.
|
||||
data.num_slices = data.slices.length;
|
||||
data.num_slices = data.slices_list.length;
|
||||
|
||||
data.slices.sort((a, b) => b.pause - a.pause);
|
||||
data.slices_list.sort((a, b) => b.pause - a.pause);
|
||||
|
||||
if (data.slices.length > MAX_SLICES) {
|
||||
if (data.slices_list.length > MAX_SLICES) {
|
||||
// Make sure we always keep the first slice since it has the
|
||||
// reason the GC was started.
|
||||
let firstSliceIndex = data.slices.findIndex(s => s.slice == 0);
|
||||
let firstSliceIndex = data.slices_list.findIndex(s => s.slice == 0);
|
||||
if (firstSliceIndex >= MAX_SLICES) {
|
||||
data.slices[MAX_SLICES - 1] = data.slices[firstSliceIndex];
|
||||
data.slices_list[MAX_SLICES - 1] = data.slices_list[firstSliceIndex];
|
||||
}
|
||||
|
||||
data.slices.length = MAX_SLICES;
|
||||
data.slices_list.length = MAX_SLICES;
|
||||
}
|
||||
|
||||
data.slices.sort((a, b) => a.slice - b.slice);
|
||||
data.slices_list.sort((a, b) => a.slice - b.slice);
|
||||
|
||||
limitProperties(data, MAX_GC_KEYS);
|
||||
|
||||
for (let slice of data.slices) {
|
||||
for (let slice of data.slices_list) {
|
||||
limitProperties(slice, MAX_SLICE_KEYS);
|
||||
limitProperties(slice.times, MAX_PHASES);
|
||||
}
|
||||
|
@ -467,6 +467,8 @@ Structure:
|
||||
"gc": {
|
||||
"random": [
|
||||
{
|
||||
// "completed" or "aborted" if an OOM occured.
|
||||
"status": "completed",
|
||||
// Timestamps are in milliseconds since startup. All the times here
|
||||
// are wall-clock times, which may not be monotonically increasing.
|
||||
"timestamp": 294872.2,
|
||||
@ -486,15 +488,17 @@ Structure:
|
||||
// "ZoneChange", "CompartmentRevived".
|
||||
"nonincremental_reason": "None",
|
||||
"allocated": 37, // In megabytes.
|
||||
"allocated_bytes": 38853696 // in bytes
|
||||
"added_chunks": 54,
|
||||
"removed_chunks": 12,
|
||||
// Total number of slices (some of which may not appear
|
||||
// in the "slices" array).
|
||||
"num_slices": 15,
|
||||
"slices": 15,
|
||||
// We record at most 4 slices.
|
||||
"slices": [
|
||||
"slice_number": 218, // The first slice number for this GC event.
|
||||
"slices_list": [
|
||||
{
|
||||
"slice": 0, // The index of this slice.
|
||||
"slice": 218, // The global index of this slice.
|
||||
"pause": 23.221, // How long the slice took.
|
||||
"when": 0, // Milliseconds since the start of the GC.
|
||||
"reason": "SET_NEW_DOCUMENT",
|
||||
|
@ -37,35 +37,37 @@ function check(entries) {
|
||||
ok(entries[f].length <= 2, "not too many GCs");
|
||||
|
||||
for (let gc of entries[f]) {
|
||||
ok(gc !== null, "GC is non-null");
|
||||
isnot(gc, null, "GC is non-null");
|
||||
|
||||
foundGCs++;
|
||||
|
||||
ok(Object.keys(gc).length <= 25, "number of keys in GC is not too large");
|
||||
ok(Object.keys(gc).length <= 30, "number of keys in GC is not too large");
|
||||
|
||||
// Sanity check the GC data.
|
||||
ok("status" in gc, "status field present");
|
||||
is(gc.status, "completed", "status field correct");
|
||||
ok("total_time" in gc, "total_time field present");
|
||||
ok("max_pause" in gc, "max_pause field present");
|
||||
|
||||
ok("slices" in gc, "slices field present");
|
||||
ok(Array.isArray(gc.slices), "slices is an array");
|
||||
ok(gc.slices.length > 0, "slices array non-empty");
|
||||
ok(gc.slices.length <= 4, "slices array is not too long");
|
||||
ok("slices_list" in gc, "slices_list field present");
|
||||
ok(Array.isArray(gc.slices_list), "slices_list is an array");
|
||||
ok(gc.slices_list.length > 0, "slices_list array non-empty");
|
||||
ok(gc.slices_list.length <= 4, "slices_list array is not too long");
|
||||
|
||||
ok("totals" in gc, "totals field present");
|
||||
ok(typeof(gc.totals) == "object", "totals is an object");
|
||||
is(typeof(gc.totals), "object", "totals is an object");
|
||||
ok(Object.keys(gc.totals).length <= 65, "totals array is not too long");
|
||||
|
||||
// Make sure we don't skip any big objects.
|
||||
for (let key in gc) {
|
||||
if (key != "slices" && key != "totals") {
|
||||
ok(typeof(gc[key]) != "object", `${key} property should be primitive`);
|
||||
if (key != "slices_list" && key != "totals") {
|
||||
isnot(typeof(gc[key]), "object", `${key} property should be primitive`);
|
||||
}
|
||||
}
|
||||
|
||||
let phases = new Set();
|
||||
|
||||
for (let slice of gc.slices) {
|
||||
for (let slice of gc.slices_list) {
|
||||
ok(Object.keys(slice).length <= 15, "slice is not too large");
|
||||
|
||||
ok("pause" in slice, "pause field present in slice");
|
||||
@ -75,7 +77,7 @@ function check(entries) {
|
||||
// Make sure we don't skip any big objects.
|
||||
for (let key in slice) {
|
||||
if (key != "times") {
|
||||
ok(typeof(slice[key]) != "object", `${key} property should be primitive`);
|
||||
isnot(typeof(slice[key]), "object", `${key} property should be primitive`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,14 +85,14 @@ function check(entries) {
|
||||
|
||||
for (let phase in slice.times) {
|
||||
phases.add(phase);
|
||||
ok(typeof(slice.times[phase]) == "number", `${phase} property should be a number`);
|
||||
is(typeof(slice.times[phase]), "number", `${phase} property should be a number`);
|
||||
}
|
||||
}
|
||||
|
||||
let totals = gc.totals;
|
||||
// Make sure we don't skip any big objects.
|
||||
for (let phase in totals) {
|
||||
ok(typeof(totals[phase]) == "number", `${phase} property should be a number`);
|
||||
is(typeof(totals[phase]), "number", `${phase} property should be a number`);
|
||||
}
|
||||
|
||||
for (let phase of phases) {
|
||||
|
149
toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
Normal file
149
toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
Normal file
@ -0,0 +1,149 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MinidumpAnalyzerUtils_h
|
||||
#define MinidumpAnalyzerUtils_h
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
struct MinidumpAnalyzerOptions {
|
||||
bool fullMinidump;
|
||||
std::string forceUseModule;
|
||||
};
|
||||
|
||||
extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
|
||||
|
||||
#ifdef XP_WIN
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
static inline std::string
|
||||
WideToMBCP(const std::wstring& wide, unsigned int cp, bool* success = nullptr)
|
||||
{
|
||||
char* buffer = nullptr;
|
||||
int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
|
||||
-1, nullptr, 0, nullptr, nullptr);
|
||||
if (buffer_size == 0) {
|
||||
if (success) {
|
||||
*success = false;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
buffer = new char[buffer_size];
|
||||
if (buffer == nullptr) {
|
||||
if (success) {
|
||||
*success = false;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
WideCharToMultiByte(cp, 0, wide.c_str(),
|
||||
-1, buffer, buffer_size, nullptr, nullptr);
|
||||
std::string mb = buffer;
|
||||
delete [] buffer;
|
||||
|
||||
if (success) {
|
||||
*success = true;
|
||||
}
|
||||
|
||||
return mb;
|
||||
}
|
||||
#endif /* !defined(_MSC_VER) */
|
||||
|
||||
static inline std::wstring
|
||||
UTF8ToWide(const std::string& aUtf8Str, bool *aSuccess = nullptr)
|
||||
{
|
||||
wchar_t* buffer = nullptr;
|
||||
int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
|
||||
-1, nullptr, 0);
|
||||
if (buffer_size == 0) {
|
||||
if (aSuccess) {
|
||||
*aSuccess = false;
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
buffer = new wchar_t[buffer_size];
|
||||
|
||||
if (buffer == nullptr) {
|
||||
if (aSuccess) {
|
||||
*aSuccess = false;
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
|
||||
-1, buffer, buffer_size);
|
||||
std::wstring str = buffer;
|
||||
delete [] buffer;
|
||||
|
||||
if (aSuccess) {
|
||||
*aSuccess = true;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static inline std::string
|
||||
WideToMBCS(const std::wstring &inp) {
|
||||
int buffer_size = WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
|
||||
nullptr, 0, NULL, NULL);
|
||||
if (buffer_size == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<char> buffer(buffer_size);
|
||||
buffer[0] = 0;
|
||||
|
||||
WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
|
||||
buffer.data(), buffer_size, NULL, NULL);
|
||||
|
||||
return buffer.data();
|
||||
}
|
||||
|
||||
static inline std::string
|
||||
UTF8toMBCS(const std::string &inp) {
|
||||
std::wstring wide = UTF8ToWide(inp);
|
||||
std::string ret = WideToMBCS(wide);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // XP_WIN
|
||||
|
||||
// Check if a file exists at the specified path
|
||||
|
||||
static inline bool
|
||||
FileExists(const std::string& aPath)
|
||||
{
|
||||
#if defined(XP_WIN)
|
||||
DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
|
||||
return (attrs != INVALID_FILE_ATTRIBUTES);
|
||||
#else // Non-Windows
|
||||
struct stat sb;
|
||||
int ret = stat(aPath.c_str(), &sb);
|
||||
if (ret == -1 || !(sb.st_mode & S_IFREG)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif // XP_WIN
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // MinidumpAnalyzerUtils_h
|
@ -0,0 +1,120 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
#include "MozStackFrameSymbolizer.h"
|
||||
|
||||
#include "MinidumpAnalyzerUtils.h"
|
||||
|
||||
#include "processor/cfi_frame_info.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
|
||||
|
||||
using google_breakpad::CFIFrameInfo;
|
||||
|
||||
MozStackFrameSymbolizer::MozStackFrameSymbolizer() :
|
||||
StackFrameSymbolizer(nullptr, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
MozStackFrameSymbolizer::SymbolizerResult
|
||||
MozStackFrameSymbolizer::FillSourceLineInfo(const CodeModules* modules,
|
||||
const SystemInfo* system_info,
|
||||
StackFrame* stack_frame)
|
||||
{
|
||||
SymbolizerResult ret = StackFrameSymbolizer::FillSourceLineInfo(
|
||||
modules, system_info, stack_frame);
|
||||
|
||||
if (ret == kNoError && this->HasImplementation() &&
|
||||
stack_frame->function_name.empty()) {
|
||||
// Breakpad's Stackwalker::InstructionAddressSeemsValid only considers an
|
||||
// address valid if it has associated symbols.
|
||||
//
|
||||
// This makes sense for complete & accurate symbols, but ours may be
|
||||
// incomplete or wrong. Returning a function name tells Breakpad we
|
||||
// recognize this address as code, so it's OK to use in stack scanning.
|
||||
// This function is only called with addresses that land in this module.
|
||||
//
|
||||
// This allows us to fall back to stack scanning in the case where we were
|
||||
// unable to provide CFI.
|
||||
stack_frame->function_name = "<unknown code>";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CFIFrameInfo*
|
||||
MozStackFrameSymbolizer::FindCFIFrameInfo(const StackFrame* frame)
|
||||
{
|
||||
std::string modulePath;
|
||||
|
||||
// For unit testing, support loading a specified module instead of
|
||||
// the real one.
|
||||
bool moduleHasBeenReplaced = false;
|
||||
if (gMinidumpAnalyzerOptions.forceUseModule.size() > 0) {
|
||||
modulePath = gMinidumpAnalyzerOptions.forceUseModule;
|
||||
moduleHasBeenReplaced = true;
|
||||
} else {
|
||||
if (!frame->module) {
|
||||
return nullptr;
|
||||
}
|
||||
modulePath = frame->module->code_file();
|
||||
}
|
||||
|
||||
// Get/create the unwind parser.
|
||||
auto itMod = mModuleMap.find(modulePath);
|
||||
std::shared_ptr<ModuleUnwindParser> unwindParser;
|
||||
if (itMod != mModuleMap.end()) {
|
||||
unwindParser = itMod->second;
|
||||
} else {
|
||||
unwindParser.reset(new ModuleUnwindParser(modulePath));
|
||||
mModuleMap[modulePath] = unwindParser;
|
||||
}
|
||||
|
||||
UnwindCFI cfi;
|
||||
DWORD offsetAddr;
|
||||
|
||||
if (moduleHasBeenReplaced) {
|
||||
// If we are replacing a module, addresses will never line up.
|
||||
// So just act like the 1st entry is correct.
|
||||
offsetAddr = unwindParser->GetAnyOffsetAddr();
|
||||
} else {
|
||||
offsetAddr = frame->instruction - frame->module->base_address();
|
||||
}
|
||||
|
||||
if (!unwindParser->GetCFI(offsetAddr, cfi)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
|
||||
|
||||
static const size_t exprSize = 50;
|
||||
char expr[exprSize];
|
||||
if (cfi.stackSize == 0) {
|
||||
snprintf(expr, exprSize, "$rsp");
|
||||
} else {
|
||||
snprintf(expr, exprSize, "$rsp %d +", cfi.stackSize);
|
||||
}
|
||||
rules->SetCFARule(expr);
|
||||
|
||||
if (cfi.ripOffset == 0) {
|
||||
snprintf(expr, exprSize, ".cfa ^");
|
||||
} else {
|
||||
snprintf(expr, exprSize, ".cfa %d - ^", cfi.ripOffset);
|
||||
}
|
||||
rules->SetRARule(expr);
|
||||
|
||||
return rules.release();
|
||||
}
|
||||
|
||||
} // namespace CrashReporter
|
||||
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
@ -0,0 +1,48 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MozStackFrameSymbolizer_h
|
||||
#define MozStackFrameSymbolizer_h
|
||||
|
||||
#if XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
#include "Win64ModuleUnwindMetadata.h"
|
||||
|
||||
#include "google_breakpad/processor/stack_frame_symbolizer.h"
|
||||
#include "google_breakpad/processor/stack_frame.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
using google_breakpad::CodeModule;
|
||||
using google_breakpad::CodeModules;
|
||||
using google_breakpad::SourceLineResolverInterface;
|
||||
using google_breakpad::StackFrame;
|
||||
using google_breakpad::StackFrameSymbolizer;
|
||||
using google_breakpad::SymbolSupplier;
|
||||
using google_breakpad::SystemInfo;
|
||||
|
||||
class MozStackFrameSymbolizer : public StackFrameSymbolizer {
|
||||
using google_breakpad::StackFrameSymbolizer::SymbolizerResult;
|
||||
|
||||
std::map<std::string, std::shared_ptr<ModuleUnwindParser>> mModuleMap;
|
||||
|
||||
public:
|
||||
MozStackFrameSymbolizer();
|
||||
|
||||
virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules,
|
||||
const SystemInfo* system_info,
|
||||
StackFrame* stack_frame);
|
||||
|
||||
virtual class google_breakpad::CFIFrameInfo* FindCFIFrameInfo(
|
||||
const StackFrame* frame);
|
||||
};
|
||||
|
||||
} // namespace CrashReporter
|
||||
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
#endif // MozStackFrameSymbolizer_h
|
@ -0,0 +1,265 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
#include "Win64ModuleUnwindMetadata.h"
|
||||
|
||||
#include "MinidumpAnalyzerUtils.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <winnt.h>
|
||||
#include <ImageHlp.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
union UnwindCode {
|
||||
struct {
|
||||
uint8_t offset_in_prolog;
|
||||
uint8_t unwind_operation_code : 4;
|
||||
uint8_t operation_info : 4;
|
||||
};
|
||||
USHORT frame_offset;
|
||||
};
|
||||
|
||||
enum UnwindOperationCodes {
|
||||
UWOP_PUSH_NONVOL = 0, // info == register number
|
||||
UWOP_ALLOC_LARGE = 1, // no info, alloc size in next 2 slots
|
||||
UWOP_ALLOC_SMALL = 2, // info == size of allocation / 8 - 1
|
||||
UWOP_SET_FPREG = 3, // no info, FP = RSP + UNWIND_INFO.FPRegOffset*16
|
||||
UWOP_SAVE_NONVOL = 4, // info == register number, offset in next slot
|
||||
UWOP_SAVE_NONVOL_FAR = 5, // info == register number, offset in next 2 slots
|
||||
UWOP_SAVE_XMM = 6, // Version 1; undocumented
|
||||
UWOP_EPILOG = 6, // Version 2; undocumented
|
||||
UWOP_SAVE_XMM_FAR = 7, // Version 1; undocumented
|
||||
UWOP_SPARE = 7, // Version 2; undocumented
|
||||
UWOP_SAVE_XMM128 = 8, // info == XMM reg number, offset in next slot
|
||||
UWOP_SAVE_XMM128_FAR = 9, // info == XMM reg number, offset in next 2 slots
|
||||
UWOP_PUSH_MACHFRAME = 10 // info == 0: no error-code, 1: error-code
|
||||
};
|
||||
|
||||
struct UnwindInfo {
|
||||
uint8_t version : 3;
|
||||
uint8_t flags : 5;
|
||||
uint8_t size_of_prolog;
|
||||
uint8_t count_of_codes;
|
||||
uint8_t frame_register : 4;
|
||||
uint8_t frame_offset : 4;
|
||||
UnwindCode unwind_code[1];
|
||||
};
|
||||
|
||||
ModuleUnwindParser::~ModuleUnwindParser()
|
||||
{
|
||||
if (mImg) {
|
||||
ImageUnload(mImg);
|
||||
}
|
||||
}
|
||||
|
||||
void*
|
||||
ModuleUnwindParser::RvaToVa(ULONG aRva)
|
||||
{
|
||||
return ImageRvaToVa(
|
||||
mImg->FileHeader, mImg->MappedAddress, aRva, &mImg->LastRvaSection);
|
||||
}
|
||||
|
||||
ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath)
|
||||
: mPath(aPath)
|
||||
{
|
||||
// Convert wchar to native charset because ImageLoad only takes
|
||||
// a PSTR as input.
|
||||
std::string code_file = UTF8toMBCS(aPath);
|
||||
|
||||
mImg = ImageLoad((PSTR)code_file.c_str(), NULL);
|
||||
if (!mImg || !mImg->FileHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader;
|
||||
if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD exception_rva = optional_header->
|
||||
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress;
|
||||
|
||||
DWORD exception_size = optional_header->
|
||||
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
|
||||
|
||||
auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva);
|
||||
if (!funcs) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) {
|
||||
mUnwindMap[funcs[i].BeginAddress] = &funcs[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleUnwindParser::GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
|
||||
UnwindCFI& aRet)
|
||||
{
|
||||
DWORD unwind_rva = aFunc.UnwindInfoAddress;
|
||||
// Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid
|
||||
// circular references.
|
||||
std::set<DWORD> visited;
|
||||
|
||||
// Follow chained function entries
|
||||
while (unwind_rva & 0x1) {
|
||||
unwind_rva ^= 0x1;
|
||||
|
||||
if (visited.end() != visited.find(unwind_rva)) {
|
||||
return false;
|
||||
}
|
||||
visited.insert(unwind_rva);
|
||||
|
||||
auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva);
|
||||
if (!chained_func) {
|
||||
return false;
|
||||
}
|
||||
unwind_rva = chained_func->UnwindInfoAddress;
|
||||
}
|
||||
|
||||
visited.insert(unwind_rva);
|
||||
|
||||
auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva);
|
||||
if (!unwind_info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD stack_size = 8; // minimal stack size is 8 for RIP
|
||||
DWORD rip_offset = 8;
|
||||
do {
|
||||
for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) {
|
||||
UnwindCode* unwind_code = &unwind_info->unwind_code[c];
|
||||
switch (unwind_code->unwind_operation_code) {
|
||||
case UWOP_PUSH_NONVOL: {
|
||||
stack_size += 8;
|
||||
break;
|
||||
}
|
||||
case UWOP_ALLOC_LARGE: {
|
||||
if (unwind_code->operation_info == 0) {
|
||||
c++;
|
||||
if (c < unwind_info->count_of_codes) {
|
||||
stack_size += (unwind_code + 1)->frame_offset * 8;
|
||||
}
|
||||
} else {
|
||||
c += 2;
|
||||
if (c < unwind_info->count_of_codes) {
|
||||
stack_size += (unwind_code + 1)->frame_offset |
|
||||
((unwind_code + 2)->frame_offset << 16);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UWOP_ALLOC_SMALL: {
|
||||
stack_size += unwind_code->operation_info * 8 + 8;
|
||||
break;
|
||||
}
|
||||
case UWOP_SET_FPREG:
|
||||
// To correctly track RSP when it's been transferred to another
|
||||
// register, we would need to emit CFI records for every unwind op.
|
||||
// For simplicity, don't emit CFI records for this function as
|
||||
// we know it will be incorrect after this point.
|
||||
return false;
|
||||
case UWOP_SAVE_NONVOL:
|
||||
case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG
|
||||
case UWOP_SAVE_XMM128: {
|
||||
c++; // skip slot with offset
|
||||
break;
|
||||
}
|
||||
case UWOP_SAVE_NONVOL_FAR:
|
||||
case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE
|
||||
case UWOP_SAVE_XMM128_FAR: {
|
||||
c += 2; // skip 2 slots with offset
|
||||
break;
|
||||
}
|
||||
case UWOP_PUSH_MACHFRAME: {
|
||||
if (unwind_code->operation_info) {
|
||||
stack_size += 88;
|
||||
} else {
|
||||
stack_size += 80;
|
||||
}
|
||||
rip_offset += 80;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unwind_info->flags & UNW_FLAG_CHAININFO) {
|
||||
auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(
|
||||
(unwind_info->unwind_code +
|
||||
((unwind_info->count_of_codes + 1) & ~1)));
|
||||
|
||||
if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) {
|
||||
return false; // Circular reference
|
||||
}
|
||||
|
||||
visited.insert(chained_func->UnwindInfoAddress);
|
||||
|
||||
unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress);
|
||||
} else {
|
||||
unwind_info = nullptr;
|
||||
}
|
||||
} while (unwind_info);
|
||||
|
||||
aRet.beginAddress = aFunc.BeginAddress;
|
||||
aRet.size = aFunc.EndAddress - aFunc.BeginAddress;
|
||||
aRet.stackSize = stack_size;
|
||||
aRet.ripOffset = rip_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
// For unit testing we sometimes need any address that's valid in this module.
|
||||
// Just return the first address we know of.
|
||||
DWORD
|
||||
ModuleUnwindParser::GetAnyOffsetAddr() const {
|
||||
if (mUnwindMap.size() < 1) {
|
||||
return 0;
|
||||
}
|
||||
return mUnwindMap.begin()->first;
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet)
|
||||
{
|
||||
// Figure out the begin address of the requested address.
|
||||
auto itUW = mUnwindMap.lower_bound(aAddress + 1);
|
||||
if (itUW == mUnwindMap.begin()) {
|
||||
return false; // address before this module.
|
||||
}
|
||||
--itUW;
|
||||
|
||||
// Ensure that the function entry is big enough to contain this address.
|
||||
IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second;
|
||||
if (aAddress > func.EndAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do we have CFI for this function already?
|
||||
auto itCFI = mCFIMap.find(aAddress);
|
||||
if (itCFI != mCFIMap.end()) {
|
||||
aRet = itCFI->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
// No, generate it.
|
||||
if (!GenerateCFIForFunction(func, aRet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mCFIMap[func.BeginAddress] = aRet;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
@ -0,0 +1,57 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef Win64ModuleUnwindMetadata_h
|
||||
#define Win64ModuleUnwindMetadata_h
|
||||
|
||||
#if XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
#include <winnt.h>
|
||||
#include <ImageHlp.h>
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
struct UnwindCFI
|
||||
{
|
||||
uint32_t beginAddress;
|
||||
uint32_t size;
|
||||
uint32_t stackSize;
|
||||
uint32_t ripOffset;
|
||||
};
|
||||
|
||||
// Does lazy-parsing of unwind info.
|
||||
class ModuleUnwindParser {
|
||||
PLOADED_IMAGE mImg;
|
||||
std::string mPath;
|
||||
|
||||
// Maps begin address to exception record.
|
||||
// Populated upon construction.
|
||||
std::map<DWORD, PIMAGE_RUNTIME_FUNCTION_ENTRY> mUnwindMap;
|
||||
|
||||
// Maps begin address to CFI.
|
||||
// Populated as needed.
|
||||
std::map<DWORD, UnwindCFI> mCFIMap;
|
||||
|
||||
bool GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
|
||||
UnwindCFI& aRet);
|
||||
void* RvaToVa(ULONG aRva);
|
||||
|
||||
public:
|
||||
explicit ModuleUnwindParser(const std::string& aPath);
|
||||
~ModuleUnwindParser();
|
||||
bool GetCFI(DWORD aAddress, UnwindCFI& aRet);
|
||||
DWORD GetAnyOffsetAddr() const;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
#endif // Win64ModuleUnwindMetadata_h
|
@ -32,12 +32,11 @@
|
||||
|
||||
#endif
|
||||
|
||||
// Path of the minidump to be analyzed.
|
||||
static string gMinidumpPath;
|
||||
#include "MinidumpAnalyzerUtils.h"
|
||||
|
||||
// When set to true print out the full minidump analysis, otherwise only
|
||||
// include the crashing thread in the output.
|
||||
static bool gFullMinidump = false;
|
||||
#if XP_WIN && HAVE_64BIT_BUILD
|
||||
#include "MozStackFrameSymbolizer.h"
|
||||
#endif
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
@ -62,77 +61,10 @@ using google_breakpad::ProcessResult;
|
||||
using google_breakpad::ProcessState;
|
||||
using google_breakpad::StackFrame;
|
||||
|
||||
#ifdef XP_WIN
|
||||
MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
static string WideToMBCP(const wstring& wide,
|
||||
unsigned int cp,
|
||||
bool* success = nullptr)
|
||||
{
|
||||
char* buffer = nullptr;
|
||||
int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
|
||||
-1, nullptr, 0, nullptr, nullptr);
|
||||
if(buffer_size == 0) {
|
||||
if (success)
|
||||
*success = false;
|
||||
return "";
|
||||
}
|
||||
|
||||
buffer = new char[buffer_size];
|
||||
if(buffer == nullptr) {
|
||||
if (success)
|
||||
*success = false;
|
||||
return "";
|
||||
}
|
||||
|
||||
WideCharToMultiByte(cp, 0, wide.c_str(),
|
||||
-1, buffer, buffer_size, nullptr, nullptr);
|
||||
string mb = buffer;
|
||||
delete [] buffer;
|
||||
|
||||
if (success)
|
||||
*success = true;
|
||||
|
||||
return mb;
|
||||
}
|
||||
#endif /* !defined(_MSC_VER) */
|
||||
|
||||
static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr)
|
||||
{
|
||||
wchar_t* buffer = nullptr;
|
||||
int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
|
||||
-1, nullptr, 0);
|
||||
if (buffer_size == 0) {
|
||||
if (aSuccess) {
|
||||
*aSuccess = false;
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
buffer = new wchar_t[buffer_size];
|
||||
|
||||
if (buffer == nullptr) {
|
||||
if (aSuccess) {
|
||||
*aSuccess = false;
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
|
||||
-1, buffer, buffer_size);
|
||||
wstring str = buffer;
|
||||
delete [] buffer;
|
||||
|
||||
if (aSuccess) {
|
||||
*aSuccess = true;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
#endif
|
||||
// Path of the minidump to be analyzed.
|
||||
static string gMinidumpPath;
|
||||
|
||||
struct ModuleCompare {
|
||||
bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const {
|
||||
@ -317,7 +249,8 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot)
|
||||
// Record the crashing thread index only if this is a full minidump
|
||||
// and all threads' stacks are present, otherwise only the crashing
|
||||
// thread stack is written out and this field is set to 0.
|
||||
crashInfo["crashing_thread"] = gFullMinidump ? requestingThread : 0;
|
||||
crashInfo["crashing_thread"] =
|
||||
gMinidumpAnalyzerOptions.fullMinidump ? requestingThread : 0;
|
||||
}
|
||||
} else {
|
||||
crashInfo["type"] = Json::Value(Json::nullValue);
|
||||
@ -345,7 +278,7 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot)
|
||||
Json::Value threads(Json::arrayValue);
|
||||
int threadCount = aProcessState.threads()->size();
|
||||
|
||||
if (!gFullMinidump && (requestingThread != -1)) {
|
||||
if (!gMinidumpAnalyzerOptions.fullMinidump && (requestingThread != -1)) {
|
||||
// Only add the crashing thread
|
||||
Json::Value thread;
|
||||
Json::Value stack(Json::arrayValue);
|
||||
@ -374,9 +307,14 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot)
|
||||
|
||||
static bool
|
||||
ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
|
||||
#if XP_WIN && HAVE_64BIT_BUILD
|
||||
MozStackFrameSymbolizer symbolizer;
|
||||
MinidumpProcessor minidumpProcessor(&symbolizer, false);
|
||||
#else
|
||||
BasicSourceLineResolver resolver;
|
||||
// We don't have a valid symbol resolver so we pass nullptr instead.
|
||||
MinidumpProcessor minidumpProcessor(nullptr, &resolver);
|
||||
#endif
|
||||
|
||||
// Process the minidump.
|
||||
Minidump dump(aDumpFile);
|
||||
@ -415,25 +353,6 @@ OpenAppend(const string& aFilename)
|
||||
return file;
|
||||
}
|
||||
|
||||
// Check if a file exists at the specified path
|
||||
|
||||
static bool
|
||||
FileExists(const string& aPath)
|
||||
{
|
||||
#if defined(XP_WIN)
|
||||
DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
|
||||
return (attrs != INVALID_FILE_ATTRIBUTES);
|
||||
#else // Non-Windows
|
||||
struct stat sb;
|
||||
int ret = stat(aPath.c_str(), &sb);
|
||||
if (ret == -1 || !(sb.st_mode & S_IFREG)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif // XP_WIN
|
||||
}
|
||||
|
||||
// Update the extra data file by adding the StackTraces field holding the
|
||||
// JSON output of this program.
|
||||
|
||||
@ -473,7 +392,10 @@ ParseArguments(int argc, char** argv) {
|
||||
|
||||
for (int i = 1; i < argc - 1; i++) {
|
||||
if (strcmp(argv[i], "--full") == 0) {
|
||||
gFullMinidump = true;
|
||||
gMinidumpAnalyzerOptions.fullMinidump = true;
|
||||
} else if ((strcmp(argv[i], "--force-use-module") == 0) && (i < argc - 2)) {
|
||||
gMinidumpAnalyzerOptions.forceUseModule = argv[i + 1];
|
||||
++i;
|
||||
} else {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
@ -27,6 +27,18 @@ if CONFIG['OS_TARGET'] != 'Android':
|
||||
if CONFIG['OS_TARGET'] == 'Darwin':
|
||||
DIST_SUBDIR = 'crashreporter.app/Contents/MacOS'
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
|
||||
UNIFIED_SOURCES += [
|
||||
'MozStackFrameSymbolizer.cpp',
|
||||
'Win64ModuleUnwindMetadata.cpp',
|
||||
]
|
||||
|
||||
OS_LIBS += [
|
||||
'Dbghelp',
|
||||
'Imagehlp'
|
||||
]
|
||||
|
||||
|
||||
# Don't use the STL wrappers in the crashreporter clients; they don't
|
||||
# link with -lmozalloc, and it really doesn't matter here anyway.
|
||||
DisableStlWrapping()
|
||||
|
@ -8,6 +8,7 @@ this.CrashTestUtils = {
|
||||
crash: null,
|
||||
dumpHasStream: null,
|
||||
dumpHasInstructionPointerMemory: null,
|
||||
dumpWin64CFITestSymbols: null,
|
||||
|
||||
// Constants for crash()
|
||||
// Keep these in sync with nsTestCrasher.cpp!
|
||||
@ -18,6 +19,18 @@ this.CrashTestUtils = {
|
||||
CRASH_MOZ_CRASH: 4,
|
||||
CRASH_ABORT: 5,
|
||||
CRASH_UNCAUGHT_EXCEPTION: 6,
|
||||
CRASH_X64CFI_NO_MANS_LAND: 7,
|
||||
CRASH_X64CFI_LAUNCHER: 8,
|
||||
CRASH_X64CFI_UNKNOWN_OPCODE: 9,
|
||||
CRASH_X64CFI_PUSH_NONVOL: 10,
|
||||
CRASH_X64CFI_ALLOC_SMALL: 11,
|
||||
CRASH_X64CFI_ALLOC_LARGE: 12,
|
||||
CRASH_X64CFI_SAVE_NONVOL: 15,
|
||||
CRASH_X64CFI_SAVE_NONVOL_FAR: 16,
|
||||
CRASH_X64CFI_SAVE_XMM128: 17,
|
||||
CRASH_X64CFI_SAVE_XMM128_FAR: 18,
|
||||
CRASH_X64CFI_EPILOG: 19,
|
||||
CRASH_X64CFI_EOF: 20,
|
||||
|
||||
// Constants for dumpHasStream()
|
||||
// From google_breakpad/common/minidump_format.h
|
||||
@ -63,3 +76,9 @@ CrashTestUtils.dumpCheckMemory = lib.declare("DumpCheckMemory",
|
||||
ctypes.default_abi,
|
||||
ctypes.bool,
|
||||
ctypes.char.ptr);
|
||||
|
||||
CrashTestUtils.getWin64CFITestFnAddrOffset =
|
||||
lib.declare("GetWin64CFITestFnAddrOffset",
|
||||
ctypes.default_abi,
|
||||
ctypes.int32_t,
|
||||
ctypes.int16_t);
|
||||
|
@ -24,6 +24,11 @@ SOURCES += [
|
||||
'ExceptionThrower.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
|
||||
SOURCES += [
|
||||
'win64UnwindInfoTests.asm',
|
||||
]
|
||||
|
||||
if CONFIG['CLANG_CL']:
|
||||
SOURCES['ExceptionThrower.cpp'].flags += [
|
||||
'-Xclang',
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "ExceptionThrower.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <malloc.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
@ -43,6 +44,24 @@ void PureVirtualCall()
|
||||
b.use(); // make sure b's actually used
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
#if XP_WIN && HAVE_64BIT_BUILD
|
||||
// Implementation in win64unwindInfoTests.asm
|
||||
uint64_t x64CrashCFITest_NO_MANS_LAND(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_Launcher(uint64_t returnpfn, void* testProc);
|
||||
uint64_t x64CrashCFITest_UnknownOpcode(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_PUSH_NONVOL(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_ALLOC_SMALL(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_ALLOC_LARGE(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_SAVE_NONVOL(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_SAVE_XMM128(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_SAVE_XMM128_FAR(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_EPILOG(uint64_t returnpfn, void*);
|
||||
uint64_t x64CrashCFITest_EOF(uint64_t returnpfn, void*);
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
||||
}
|
||||
|
||||
// Keep these in sync with CrashTestUtils.jsm!
|
||||
const int16_t CRASH_INVALID_POINTER_DEREF = 0;
|
||||
const int16_t CRASH_PURE_VIRTUAL_CALL = 1;
|
||||
@ -50,6 +69,55 @@ const int16_t CRASH_OOM = 3;
|
||||
const int16_t CRASH_MOZ_CRASH = 4;
|
||||
const int16_t CRASH_ABORT = 5;
|
||||
const int16_t CRASH_UNCAUGHT_EXCEPTION = 6;
|
||||
const int16_t CRASH_X64CFI_NO_MANS_LAND = 7;
|
||||
const int16_t CRASH_X64CFI_LAUNCHER = 8;
|
||||
const int16_t CRASH_X64CFI_UNKNOWN_OPCODE = 9;
|
||||
const int16_t CRASH_X64CFI_PUSH_NONVOL = 10;
|
||||
const int16_t CRASH_X64CFI_ALLOC_SMALL = 11;
|
||||
const int16_t CRASH_X64CFI_ALLOC_LARGE = 12;
|
||||
const int16_t CRASH_X64CFI_SAVE_NONVOL = 15;
|
||||
const int16_t CRASH_X64CFI_SAVE_NONVOL_FAR = 16;
|
||||
const int16_t CRASH_X64CFI_SAVE_XMM128 = 17;
|
||||
const int16_t CRASH_X64CFI_SAVE_XMM128_FAR = 18;
|
||||
const int16_t CRASH_X64CFI_EPILOG = 19;
|
||||
const int16_t CRASH_X64CFI_EOF = 20;
|
||||
|
||||
#if XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
typedef decltype(&x64CrashCFITest_UnknownOpcode) win64CFITestFnPtr_t;
|
||||
|
||||
static std::map<int16_t, win64CFITestFnPtr_t>
|
||||
GetWin64CFITestMap() {
|
||||
std::map<int16_t, win64CFITestFnPtr_t> ret = {
|
||||
{ CRASH_X64CFI_NO_MANS_LAND, x64CrashCFITest_NO_MANS_LAND},
|
||||
{ CRASH_X64CFI_LAUNCHER, x64CrashCFITest_Launcher},
|
||||
{ CRASH_X64CFI_UNKNOWN_OPCODE, x64CrashCFITest_UnknownOpcode},
|
||||
{ CRASH_X64CFI_PUSH_NONVOL, x64CrashCFITest_PUSH_NONVOL},
|
||||
{ CRASH_X64CFI_ALLOC_SMALL, x64CrashCFITest_ALLOC_SMALL },
|
||||
{ CRASH_X64CFI_ALLOC_LARGE, x64CrashCFITest_ALLOC_LARGE },
|
||||
{ CRASH_X64CFI_SAVE_NONVOL, x64CrashCFITest_SAVE_NONVOL },
|
||||
{ CRASH_X64CFI_SAVE_NONVOL_FAR, x64CrashCFITest_SAVE_NONVOL_FAR },
|
||||
{ CRASH_X64CFI_SAVE_XMM128, x64CrashCFITest_SAVE_XMM128 },
|
||||
{ CRASH_X64CFI_SAVE_XMM128_FAR, x64CrashCFITest_SAVE_XMM128_FAR },
|
||||
{ CRASH_X64CFI_EPILOG, x64CrashCFITest_EPILOG },
|
||||
{ CRASH_X64CFI_EOF, x64CrashCFITest_EOF }
|
||||
};
|
||||
// ret values point to jump table entries, not the actual function bodies.
|
||||
// Get the correct pointer by calling the function with returnpfn=1
|
||||
for (auto it = ret.begin(); it != ret.end(); ++ it) {
|
||||
it->second = (win64CFITestFnPtr_t)it->second(1, nullptr);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ReserveStack() {
|
||||
// This ensures our tests have enough reserved stack space.
|
||||
uint8_t* p = (uint8_t*)alloca(1024000);
|
||||
// This ensures we don't optimized away this meaningless code at build time.
|
||||
mozilla::Unused << (int)(uint64_t)p;
|
||||
}
|
||||
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
extern "C" NS_EXPORT
|
||||
void Crash(int16_t how)
|
||||
@ -84,6 +152,27 @@ void Crash(int16_t how)
|
||||
ThrowException();
|
||||
break;
|
||||
}
|
||||
#if XP_WIN && HAVE_64BIT_BUILD
|
||||
case CRASH_X64CFI_UNKNOWN_OPCODE:
|
||||
case CRASH_X64CFI_PUSH_NONVOL:
|
||||
case CRASH_X64CFI_ALLOC_SMALL:
|
||||
case CRASH_X64CFI_ALLOC_LARGE:
|
||||
case CRASH_X64CFI_SAVE_NONVOL:
|
||||
case CRASH_X64CFI_SAVE_NONVOL_FAR:
|
||||
case CRASH_X64CFI_SAVE_XMM128:
|
||||
case CRASH_X64CFI_SAVE_XMM128_FAR:
|
||||
case CRASH_X64CFI_EPILOG: {
|
||||
ReserveStack();
|
||||
auto m = GetWin64CFITestMap();
|
||||
if (m.find(how) == m.end()) {
|
||||
break;
|
||||
}
|
||||
auto pfnTest = m[how];
|
||||
auto pfnLauncher = m[CRASH_X64CFI_LAUNCHER];
|
||||
pfnLauncher(0, pfnTest);
|
||||
break;
|
||||
}
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -119,3 +208,20 @@ void TryOverrideExceptionHandler()
|
||||
SetUnhandledExceptionFilter(HandleException);
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" NS_EXPORT uint32_t
|
||||
GetWin64CFITestFnAddrOffset(int16_t fnid) {
|
||||
#if XP_WIN && HAVE_64BIT_BUILD
|
||||
// fnid uses the same constants as Crash().
|
||||
// Returns the RVA of the requested function.
|
||||
// Returns 0 on failure.
|
||||
auto m = GetWin64CFITestMap();
|
||||
if (m.find(fnid) == m.end()) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t moduleBase = (uint64_t)GetModuleHandleW(L"testcrasher.dll");
|
||||
return ((uint64_t)m[fnid]) - moduleBase;
|
||||
#else
|
||||
return 0;
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
var {utils: Cu} = Components;
|
||||
var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
Cu.import("resource://testing-common/AppData.jsm", this);
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
function getEventDir() {
|
||||
return OS.Path.join(do_get_tempdir().path, "crash-events");
|
||||
@ -24,15 +25,17 @@ function getEventDir() {
|
||||
*
|
||||
* @param callback
|
||||
* A JavaScript function to be called after the subprocess
|
||||
* crashes. It will be passed (minidump, extra), where
|
||||
* minidump is an nsIFile of the minidump file produced,
|
||||
* and extra is an object containing the key,value pairs from
|
||||
* crashes. It will be passed (minidump, extra, extrafile), where
|
||||
* - minidump is an nsIFile of the minidump file produced,
|
||||
* - extra is an object containing the key,value pairs from
|
||||
* the .extra file.
|
||||
* - extrafile is an nsIFile of the extra file
|
||||
*
|
||||
* @param canReturnZero
|
||||
* If true, the subprocess may return with a zero exit code.
|
||||
* Certain types of crashes may not cause the process to
|
||||
* exit with an error.
|
||||
*
|
||||
*/
|
||||
function do_crash(setup, callback, canReturnZero) {
|
||||
// get current process filename (xpcshell)
|
||||
@ -100,6 +103,32 @@ function getMinidump() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function runMinidumpAnalyzer(dumpFile, additionalArgs) {
|
||||
if (AppConstants.platform !== "win") {
|
||||
return;
|
||||
}
|
||||
|
||||
// find minidump-analyzer executable.
|
||||
let ds = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties);
|
||||
let bin = ds.get("XREExeF", Ci.nsIFile);
|
||||
ok(bin && bin.exists());
|
||||
bin = bin.parent;
|
||||
ok(bin && bin.exists());
|
||||
bin.append("minidump-analyzer.exe");
|
||||
ok(bin.exists());
|
||||
|
||||
let process = Cc["@mozilla.org/process/util;1"]
|
||||
.createInstance(Ci.nsIProcess);
|
||||
process.init(bin);
|
||||
let args = [];
|
||||
if (additionalArgs) {
|
||||
args = args.concat(additionalArgs);
|
||||
}
|
||||
args.push(dumpFile.path);
|
||||
process.run(true /* blocking */, args, args.length);
|
||||
}
|
||||
|
||||
function handleMinidump(callback) {
|
||||
// find minidump
|
||||
let minidump = getMinidump();
|
||||
@ -131,7 +160,7 @@ function handleMinidump(callback) {
|
||||
let extra = parseKeyValuePairsFromFile(extrafile);
|
||||
|
||||
if (callback) {
|
||||
callback(minidump, extra);
|
||||
callback(minidump, extra, extrafile);
|
||||
}
|
||||
|
||||
if (minidump.exists()) {
|
||||
|
193
toolkit/crashreporter/test/unit/head_win64cfi.js
Normal file
193
toolkit/crashreporter/test/unit/head_win64cfi.js
Normal file
@ -0,0 +1,193 @@
|
||||
/* import-globals-from head_crashreporter.js */
|
||||
|
||||
let gTestCrasherSyms = null;
|
||||
let gModules = null;
|
||||
|
||||
// Returns the offset (int) of an IP with a given base address.
|
||||
// This is effectively (ip - base), except a bit more complication due to
|
||||
// Javascript's shaky handling of 64-bit integers.
|
||||
// base & ip are passed as hex strings.
|
||||
function getModuleOffset(base, ip) {
|
||||
let i = 0;
|
||||
// Find where the two addresses diverge, which enables us to perform a 32-bit
|
||||
// subtraction.
|
||||
// e.g. "0x1111111111112222"
|
||||
// - "0x1111111111111111"
|
||||
// becomes 2222 - 1111
|
||||
for (; i < base.length; ++i) {
|
||||
if (base[i] != ip[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == base.length) {
|
||||
return 0;
|
||||
}
|
||||
let lhs2 = "0x" + base.substring(i);
|
||||
let rhs2 = "0x" + ip.substring(i);
|
||||
return parseInt(rhs2) - parseInt(lhs2);
|
||||
}
|
||||
|
||||
// Uses gTestCrasherSyms to convert an address to a symbol.
|
||||
function findNearestTestCrasherSymbol(addr) {
|
||||
addr += 1; // Breakpad sometimes offsets addresses; correct for this.
|
||||
let closestDistance = null;
|
||||
let closestSym = null;
|
||||
for (let sym in gTestCrasherSyms) {
|
||||
if (addr >= gTestCrasherSyms[sym]) {
|
||||
let thisDistance = addr - gTestCrasherSyms[sym];
|
||||
if (closestDistance === null || thisDistance < closestDistance) {
|
||||
closestDistance = thisDistance;
|
||||
closestSym = sym;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (closestSym === null) {
|
||||
return null;
|
||||
}
|
||||
return { symbol: closestSym, offset: closestDistance }
|
||||
}
|
||||
|
||||
// Populate known symbols for testcrasher.dll.
|
||||
// Use the same prop names as from CrashTestUtils to avoid the need for mapping.
|
||||
function initTestCrasherSymbols() {
|
||||
gTestCrasherSyms = { };
|
||||
for (let k in CrashTestUtils) {
|
||||
// Not all keys here are valid symbol names. getWin64CFITestFnAddrOffset
|
||||
// will return 0 in those cases, no need to filter here.
|
||||
if (Number.isInteger(CrashTestUtils[k])) {
|
||||
let t = CrashTestUtils.getWin64CFITestFnAddrOffset(CrashTestUtils[k]);
|
||||
if (t > 0) {
|
||||
gTestCrasherSyms[k] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stackFrameToString(frameIndex, frame) {
|
||||
// Calculate the module offset.
|
||||
let ip = frame.ip;
|
||||
let symbol = "";
|
||||
let moduleOffset = "unknown_offset";
|
||||
let filename = "unknown_module";
|
||||
|
||||
if (typeof frame.module_index !== "undefined" && (frame.module_index >= 0)
|
||||
&& (frame.module_index < gModules.length)) {
|
||||
|
||||
let base = gModules[frame.module_index].base_addr;
|
||||
moduleOffset = getModuleOffset(base, ip);
|
||||
filename = gModules[frame.module_index].filename;
|
||||
|
||||
if (filename === "testcrasher.dll") {
|
||||
let nearestSym = findNearestTestCrasherSymbol(moduleOffset);
|
||||
if (nearestSym !== null) {
|
||||
symbol = nearestSym.symbol + "+" + nearestSym.offset.toString(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ret = "frames[" + frameIndex + "] ip=" + ip +
|
||||
" " + symbol +
|
||||
", module:" + filename +
|
||||
", trust:" + frame.trust +
|
||||
", moduleOffset:" + moduleOffset.toString(16);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function dumpStackFrames(frames, maxFrames) {
|
||||
for (let i = 0; i < Math.min(maxFrames, frames.length); ++i) {
|
||||
do_print(stackFrameToString(i, frames[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the top of the given stack (from extra data) matches the given
|
||||
// expected frames.
|
||||
//
|
||||
// expected is { symbol: "", trust: "" }
|
||||
function assertStack(stack, expected) {
|
||||
for (let i = 0; i < stack.length; ++i) {
|
||||
if (i >= expected.length) {
|
||||
ok("Top stack frames were expected");
|
||||
return;
|
||||
}
|
||||
let frame = stack[i];
|
||||
let expectedFrame = expected[i];
|
||||
let dumpThisFrame = function() {
|
||||
do_print(" Actual frame: " + stackFrameToString(i, frame));
|
||||
do_print("Expected { symbol: " + expectedFrame.symbol + ", trust: " + expectedFrame.trust + "}");
|
||||
};
|
||||
|
||||
if (expectedFrame.trust) {
|
||||
if (frame.trust !== expectedFrame.trust) {
|
||||
dumpThisFrame();
|
||||
do_print("Expected frame trust did not match.");
|
||||
ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (expectedFrame.symbol) {
|
||||
if (typeof frame.module_index === "undefined") {
|
||||
// Without a module_index, it happened in an unknown module. Currently
|
||||
// you can't specify an expected "unknown" module.
|
||||
do_print("Unknown symbol in unknown module.");
|
||||
ok(false);
|
||||
}
|
||||
if (frame.module_index < 0 || frame.module_index >= gModules.length) {
|
||||
dumpThisFrame();
|
||||
do_print("Unknown module.");
|
||||
ok(false);
|
||||
return;
|
||||
}
|
||||
let base = gModules[frame.module_index].base_addr;
|
||||
let moduleOffset = getModuleOffset(base, frame.ip);
|
||||
let filename = gModules[frame.module_index].filename;
|
||||
if (filename == "testcrasher.dll") {
|
||||
let nearestSym = findNearestTestCrasherSymbol(moduleOffset);
|
||||
if (nearestSym === null) {
|
||||
dumpThisFrame();
|
||||
do_print("Unknown symbol.");
|
||||
ok(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nearestSym.symbol !== expectedFrame.symbol) {
|
||||
dumpThisFrame();
|
||||
do_print("Mismatching symbol.");
|
||||
ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Performs a crash, runs minidump-analyzer, and checks expected stack analysis.
|
||||
//
|
||||
// how: The crash to perform. Constants defined in both CrashTestUtils.jsm
|
||||
// and nsTestCrasher.cpp (i.e. CRASH_X64CFI_PUSH_NONVOL)
|
||||
// expectedStack: An array of {"symbol", "trust"} where trust is "cfi",
|
||||
// "context", "scan", et al. May be null if you don't need to check the stack.
|
||||
// minidumpAnalyzerArgs: An array of additional arguments to pass to
|
||||
// minidump-analyzer.exe.
|
||||
function do_x64CFITest(how, expectedStack, minidumpAnalyzerArgs) {
|
||||
|
||||
// Setup is run in the subprocess so we cannot use any closures.
|
||||
let setupFn = "crashType = CrashTestUtils." + how + ";";
|
||||
|
||||
let callbackFn = function(minidumpFile, extra, extraFile) {
|
||||
runMinidumpAnalyzer(minidumpFile, minidumpAnalyzerArgs);
|
||||
|
||||
// Refresh updated extra data
|
||||
extra = parseKeyValuePairsFromFile(extraFile);
|
||||
|
||||
initTestCrasherSymbols();
|
||||
let stackTraces = JSON.parse(extra.StackTraces);
|
||||
let crashingThreadIndex = stackTraces.crash_info.crashing_thread;
|
||||
gModules = stackTraces.modules;
|
||||
let crashingFrames = stackTraces.threads[crashingThreadIndex].frames;
|
||||
|
||||
dumpStackFrames(crashingFrames, 10);
|
||||
|
||||
assertStack(crashingFrames, expectedStack);
|
||||
};
|
||||
|
||||
do_crash(setupFn, callbackFn, true, true);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
function run_test() {
|
||||
do_x64CFITest("CRASH_X64CFI_ALLOC_LARGE", [
|
||||
{ symbol: "CRASH_X64CFI_ALLOC_LARGE", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
|
||||
]);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
function run_test() {
|
||||
do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", [
|
||||
{ symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
|
||||
]);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
function run_test() {
|
||||
do_x64CFITest("CRASH_X64CFI_EPILOG", [
|
||||
{ symbol: "CRASH_X64CFI_EPILOG", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
|
||||
]);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,17 @@
|
||||
|
||||
function run_test() {
|
||||
// Test that minidump-analyzer gracefully handles chained
|
||||
// unwind code entries that form a circular reference
|
||||
// (infinite loop).
|
||||
let exe = do_get_file("test_crash_win64cfi_infinite_code_chain.exe");
|
||||
ok(exe);
|
||||
|
||||
// Perform a crash. We won't get unwind info, but make sure the stack scan
|
||||
// still works.
|
||||
do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
|
||||
[
|
||||
{ symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
|
||||
],
|
||||
["--force-use-module", exe.path]);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,17 @@
|
||||
|
||||
function run_test() {
|
||||
// Test that minidump-analyzer gracefully handles chained
|
||||
// IMAGE_RUNTIME_FUNCTION_ENTRY items that form a circular reference
|
||||
// (infinite loop).
|
||||
let exe = do_get_file("test_crash_win64cfi_infinite_entry_chain.exe");
|
||||
ok(exe);
|
||||
|
||||
// Perform a crash. We won't get unwind info, but make sure the stack scan
|
||||
// still works.
|
||||
do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
|
||||
[
|
||||
{ symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
|
||||
],
|
||||
["--force-use-module", exe.path]);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,16 @@
|
||||
|
||||
function run_test() {
|
||||
// Test that minidump-analyzer gracefully handles an invalid pointer to the
|
||||
// exception unwind information.
|
||||
let exe = do_get_file("test_crash_win64cfi_invalid_exception_rva.exe");
|
||||
ok(exe);
|
||||
|
||||
// Perform a crash. We won't get unwind info, but make sure the stack scan
|
||||
// still works.
|
||||
do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
|
||||
[
|
||||
{ symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
|
||||
],
|
||||
["--force-use-module", exe.path]);
|
||||
}
|
@ -0,0 +1 @@
|
||||
this is not a valid PE file.
|
@ -0,0 +1,15 @@
|
||||
|
||||
function run_test() {
|
||||
// Test that minidump-analyzer gracefully handles corrupt PE files.
|
||||
let exe = do_get_file("test_crash_win64cfi_not_a_pe.exe");
|
||||
ok(exe);
|
||||
|
||||
// Perform a crash. We won't get unwind info, but make sure the stack scan
|
||||
// still works.
|
||||
do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
|
||||
[
|
||||
{ symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
|
||||
],
|
||||
["--force-use-module", exe.path]);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
function run_test() {
|
||||
do_x64CFITest("CRASH_X64CFI_PUSH_NONVOL", [
|
||||
{ symbol: "CRASH_X64CFI_PUSH_NONVOL", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
|
||||
]);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
function run_test() {
|
||||
do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL", [
|
||||
{ symbol: "CRASH_X64CFI_SAVE_NONVOL", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
|
||||
]);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
function run_test() {
|
||||
do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL_FAR", [
|
||||
{ symbol: "CRASH_X64CFI_SAVE_NONVOL_FAR", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
|
||||
]);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
function run_test() {
|
||||
do_x64CFITest("CRASH_X64CFI_SAVE_XMM128", [
|
||||
{ symbol: "CRASH_X64CFI_SAVE_XMM128", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
|
||||
]);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
function run_test() {
|
||||
do_x64CFITest("CRASH_X64CFI_SAVE_XMM128_FAR", [
|
||||
{ symbol: "CRASH_X64CFI_SAVE_XMM128_FAR", trust: "context" },
|
||||
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
|
||||
]);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
|
||||
function run_test() {
|
||||
// In the case of an unknown unwind code or missing CFI,
|
||||
// make certain we can still walk the stack via stack scan. The crashing
|
||||
// function places NO_MANS_LAND on the stack so it will get picked up via
|
||||
// stack scan.
|
||||
do_x64CFITest("CRASH_X64CFI_UNKNOWN_OPCODE", [
|
||||
{ symbol: "CRASH_X64CFI_UNKNOWN_OPCODE", trust: "context" },
|
||||
// Trust may either be scan or frame_pointer; we don't really care as
|
||||
// long as the address is expected.
|
||||
{ symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
|
||||
]);
|
||||
}
|
@ -37,3 +37,65 @@ skip-if = (os != 'win' && os != 'linux') || (os=='linux' && bits==32)
|
||||
[test_crash_AsyncShutdown.js]
|
||||
[test_event_files.js]
|
||||
[test_crash_terminator.js]
|
||||
|
||||
[test_crash_win64cfi_unknown_op.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_push_nonvol.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_alloc_small.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_alloc_large.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_save_nonvol.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_save_nonvol_far.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_save_xmm128.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_save_xmm128_far.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_epilog.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
|
||||
[test_crash_win64cfi_infinite_entry_chain.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
support-files = test_crash_win64cfi_infinite_entry_chain.exe
|
||||
|
||||
[test_crash_win64cfi_infinite_code_chain.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
support-files = test_crash_win64cfi_infinite_code_chain.exe
|
||||
|
||||
[test_crash_win64cfi_invalid_exception_rva.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
support-files = test_crash_win64cfi_invalid_exception_rva.exe
|
||||
|
||||
[test_crash_win64cfi_not_a_pe.js]
|
||||
head = head_crashreporter.js head_win64cfi.js
|
||||
skip-if = os != 'win' || bits != 64
|
||||
support-files = test_crash_win64cfi_not_a_pe.exe
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
382
toolkit/crashreporter/test/win64UnwindInfoTests.asm
Normal file
382
toolkit/crashreporter/test/win64UnwindInfoTests.asm
Normal file
@ -0,0 +1,382 @@
|
||||
; 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/.
|
||||
|
||||
; Comments indicate stack memory layout during execution.
|
||||
; For example at the top of a function, where RIP just points to the return
|
||||
; address, the stack looks like
|
||||
; rip = [ra]
|
||||
; And after pushing rax to the stack,
|
||||
; rip = [rax][ra]
|
||||
; And then, after allocating 20h bytes on the stack,
|
||||
; rip = [..20..][rax][ra]
|
||||
; And then, after pushing a function pointer,
|
||||
; rip = [pfn][..20..][rax][ra]
|
||||
|
||||
include ksamd64.inc
|
||||
|
||||
.code
|
||||
|
||||
; It helps to add padding between functions so they're not right up against
|
||||
; each other. Adds clarity to debugging, and gives a bit of leeway when
|
||||
; searching for symbols (e.g. a function whose last instruction is CALL
|
||||
; would push a return address that's in the next function.)
|
||||
PaddingBetweenFunctions macro
|
||||
repeat 10h
|
||||
int 3
|
||||
endm
|
||||
endm
|
||||
|
||||
DoCrash macro
|
||||
mov rax, 7
|
||||
mov byte ptr [rax], 9
|
||||
endm
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; There is no rip addressing mode in x64. The only way to get the value
|
||||
; of rip is to call a function, and pop it from the stack.
|
||||
WhoCalledMe proc
|
||||
pop rax ; rax is now ra
|
||||
push rax ; Restore ra so this function can return.
|
||||
sub rax, 5 ; Correct for the size of the call instruction
|
||||
ret
|
||||
WhoCalledMe endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; Any function that we expect to test against on the stack, we'll need its
|
||||
; real address. If we use function pointers in C, we'll get the address to jump
|
||||
; table entries. This bit of code at the beginning of each function will
|
||||
; return the real address we'd expect to see in stack traces.
|
||||
;
|
||||
; rcx (1st arg) = mode
|
||||
; rax (return) = address of either NO_MANS_LAND or this function.
|
||||
;
|
||||
; When mode is 0, we place the address of NO_MANS_LAND in RAX, for the function
|
||||
; to use as it wants. This is just for convenience because almost all functions
|
||||
; here need this address at some point.
|
||||
;
|
||||
; When mode is 1, the address of this function is returned.
|
||||
TestHeader macro
|
||||
call WhoCalledMe
|
||||
test rcx, rcx
|
||||
je continue_test
|
||||
ret
|
||||
continue_test:
|
||||
inc rcx
|
||||
call x64CrashCFITest_NO_MANS_LAND
|
||||
xor rcx, rcx
|
||||
endm
|
||||
|
||||
; The point of this is to add a stack frame to test against.
|
||||
; void* x64CrashCFITest_Launcher(int getAddress, void* pTestFn)
|
||||
x64CrashCFITest_Launcher proc frame
|
||||
TestHeader
|
||||
|
||||
.endprolog
|
||||
call rdx
|
||||
ret
|
||||
x64CrashCFITest_Launcher endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_NO_MANS_LAND(uint64_t mode);
|
||||
; Not meant to be called. Only when mode = 1 in order to return its address.
|
||||
; Place this function's address on the stack so the stack scanning algorithm
|
||||
; thinks this is a return address, and places it on the stack trace.
|
||||
x64CrashCFITest_NO_MANS_LAND proc frame
|
||||
TestHeader
|
||||
.endprolog
|
||||
ret
|
||||
x64CrashCFITest_NO_MANS_LAND endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; Test that we:
|
||||
; - handle unknown opcodes gracefully
|
||||
; - fall back to other stack unwind strategies if CFI doesn't work
|
||||
;
|
||||
; In order to properly unwind this frame, we'd need to fully support
|
||||
; SET_FPREG with offsets, plus restoring registers via PUSH_NONVOL.
|
||||
; To do this, sprinkle the stack with bad return addresses
|
||||
; and stack pointers.
|
||||
x64CrashCFITest_UnknownOpcode proc frame
|
||||
TestHeader
|
||||
|
||||
push rax
|
||||
.allocstack 8
|
||||
|
||||
push rbp
|
||||
.pushreg rbp
|
||||
|
||||
push rax
|
||||
push rsp
|
||||
push rax
|
||||
push rsp
|
||||
.allocstack 20h
|
||||
; rsp = [rsp][pfn][rsp][pfn][rbp][pfn][ra]
|
||||
|
||||
lea rbp, [rsp+10h]
|
||||
.setframe rbp, 10h
|
||||
; rsp = [rsp][pfn] [rsp][pfn][rbp][pfn][ra]
|
||||
; rbp = ^
|
||||
|
||||
.endprolog
|
||||
|
||||
; Now modify RSP so measuring stack size from unwind ops will not help
|
||||
; finding the return address.
|
||||
push rax
|
||||
push rsp
|
||||
; rsp = [rsp][pfn][rsp][pfn] [rsp][pfn][rbp][pfn][ra]
|
||||
|
||||
DoCrash
|
||||
|
||||
x64CrashCFITest_UnknownOpcode endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_PUSH_NONVOL(uint64_t mode);
|
||||
;
|
||||
; Test correct handling of PUSH_NONVOL unwind code.
|
||||
;
|
||||
x64CrashCFITest_PUSH_NONVOL proc frame
|
||||
TestHeader
|
||||
|
||||
push r10
|
||||
.pushreg r10
|
||||
push r15
|
||||
.pushreg r15
|
||||
push rbx
|
||||
.pushreg rbx
|
||||
push rsi
|
||||
.pushreg rsi
|
||||
push rbp
|
||||
.pushreg rbp
|
||||
; rsp = [rbp][rsi][rbx][r15][r10][ra]
|
||||
|
||||
push rax
|
||||
.allocstack 8
|
||||
; rsp = [pfn][rbp][rsi][rbx][r15][r10][ra]
|
||||
|
||||
.endprolog
|
||||
|
||||
DoCrash
|
||||
|
||||
x64CrashCFITest_PUSH_NONVOL endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_ALLOC_SMALL(uint64_t mode);
|
||||
;
|
||||
; Small allocations are between 8bytes and 512kb-8bytes
|
||||
;
|
||||
x64CrashCFITest_ALLOC_SMALL proc frame
|
||||
TestHeader
|
||||
|
||||
; Trash rbp to force stack scan. This will force
|
||||
; correct behavior for test_crash_win64cfi_not_a_pe, et al.
|
||||
xor rbp, rbp
|
||||
|
||||
push rax
|
||||
push rax
|
||||
push rax
|
||||
push rax
|
||||
.allocstack 20h
|
||||
; rsp = [pfn][pfn][pfn][pfn][ra]
|
||||
|
||||
.endprolog
|
||||
|
||||
DoCrash
|
||||
|
||||
x64CrashCFITest_ALLOC_SMALL endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_ALLOC_LARGE(uint64_t mode);
|
||||
;
|
||||
; Allocations between 512kb and 4gb
|
||||
; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
|
||||
; space for this.
|
||||
x64CrashCFITest_ALLOC_LARGE proc frame
|
||||
TestHeader
|
||||
|
||||
sub rsp, 0a000h
|
||||
.allocstack 0a000h
|
||||
; rsp = [..640kb..][ra]
|
||||
|
||||
mov qword ptr [rsp], rax
|
||||
; rsp = [pfn][..640kb-8..][ra]
|
||||
|
||||
.endprolog
|
||||
|
||||
DoCrash
|
||||
|
||||
x64CrashCFITest_ALLOC_LARGE endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_SAVE_NONVOL(uint64_t mode);
|
||||
;
|
||||
; Test correct handling of SAVE_NONVOL unwind code.
|
||||
;
|
||||
x64CrashCFITest_SAVE_NONVOL proc frame
|
||||
TestHeader
|
||||
|
||||
sub rsp, 30h
|
||||
.allocstack 30h
|
||||
; rsp = [..30..][ra]
|
||||
|
||||
mov qword ptr [rsp+28h], r10
|
||||
.savereg r10, 28h
|
||||
mov qword ptr [rsp+20h], rbp
|
||||
.savereg rbp, 20h
|
||||
mov qword ptr [rsp+18h], rsi
|
||||
.savereg rsi, 18h
|
||||
mov qword ptr [rsp+10h], rbx
|
||||
.savereg rbx, 10h
|
||||
mov qword ptr [rsp+8], r15
|
||||
.savereg r15, 8
|
||||
; rsp = [r15][rbx][rsi][rbp][r10][ra]
|
||||
|
||||
mov qword ptr [rsp], rax
|
||||
|
||||
; rsp = [pfn][r15][rbx][rsi][rbp][r10][ra]
|
||||
|
||||
.endprolog
|
||||
|
||||
DoCrash
|
||||
|
||||
x64CrashCFITest_SAVE_NONVOL endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t mode);
|
||||
;
|
||||
; Similar to the test above but adding 640kb to most offsets.
|
||||
; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
|
||||
; space for this.
|
||||
x64CrashCFITest_SAVE_NONVOL_FAR proc frame
|
||||
TestHeader
|
||||
|
||||
sub rsp, 0a0030h
|
||||
.allocstack 0a0030h
|
||||
; rsp = [..640k..][..30..][ra]
|
||||
|
||||
mov qword ptr [rsp+28h+0a0000h], r10
|
||||
.savereg r10, 28h+0a0000h
|
||||
mov qword ptr [rsp+20h+0a0000h], rbp
|
||||
.savereg rbp, 20h+0a0000h
|
||||
mov qword ptr [rsp+18h+0a0000h], rsi
|
||||
.savereg rsi, 18h+0a0000h
|
||||
mov qword ptr [rsp+10h+0a0000h], rbx
|
||||
.savereg rbx, 10h+0a0000h
|
||||
mov qword ptr [rsp+8+0a0000h], r15
|
||||
.savereg r15, 8+0a0000h
|
||||
; rsp = [..640k..][..8..][r15][rbx][rsi][rbp][r10][ra]
|
||||
|
||||
mov qword ptr [rsp], rax
|
||||
|
||||
; rsp = [pfn][..640k..][r15][rbx][rsi][rbp][r10][ra]
|
||||
|
||||
.endprolog
|
||||
|
||||
DoCrash
|
||||
|
||||
x64CrashCFITest_SAVE_NONVOL_FAR endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode);
|
||||
;
|
||||
; Test correct handling of SAVE_XMM128 unwind code.
|
||||
x64CrashCFITest_SAVE_XMM128 proc frame
|
||||
TestHeader
|
||||
|
||||
sub rsp, 30h
|
||||
.allocstack 30h
|
||||
; rsp = [..30..][ra]
|
||||
|
||||
movdqu [rsp+20h], xmm6
|
||||
.savexmm128 xmm6, 20h
|
||||
; rsp = [..20..][xmm6][ra]
|
||||
|
||||
movdqu [rsp+10h], xmm15
|
||||
.savexmm128 xmm15, 10h
|
||||
; rsp = [..10..][xmm15][xmm6][ra]
|
||||
|
||||
mov qword ptr [rsp], rax
|
||||
; rsp = [pfn][..8..][xmm15][xmm6][ra]
|
||||
|
||||
.endprolog
|
||||
|
||||
DoCrash
|
||||
|
||||
x64CrashCFITest_SAVE_XMM128 endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode);
|
||||
;
|
||||
; Similar to the test above but adding 640kb to most offsets.
|
||||
; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
|
||||
; space for this.
|
||||
x64CrashCFITest_SAVE_XMM128_FAR proc frame
|
||||
TestHeader
|
||||
|
||||
sub rsp, 0a0030h
|
||||
.allocstack 0a0030h
|
||||
; rsp = [..640kb..][..30..][ra]
|
||||
|
||||
movdqu [rsp+20h+0a0000h], xmm6
|
||||
.savexmm128 xmm6, 20h+0a0000h
|
||||
; rsp = [..640kb..][..20..][xmm6][ra]
|
||||
|
||||
movdqu [rsp+10h+0a0000h], xmm6
|
||||
.savexmm128 xmm15, 10h+0a0000h
|
||||
; rsp = [..640kb..][..10..][xmm15][xmm6][ra]
|
||||
|
||||
mov qword ptr [rsp], rax
|
||||
; rsp = [pfn][..640kb..][..8..][xmm15][xmm6][ra]
|
||||
|
||||
.endprolog
|
||||
|
||||
DoCrash
|
||||
|
||||
x64CrashCFITest_SAVE_XMM128_FAR endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; void* x64CrashCFITest_EPILOG(uint64_t mode);
|
||||
;
|
||||
; The epilog unwind op will also set the unwind version to 2.
|
||||
; Test that we don't choke on UWOP_EPILOG or version 2 unwind info.
|
||||
x64CrashCFITest_EPILOG proc frame
|
||||
TestHeader
|
||||
|
||||
push rax
|
||||
.allocstack 8
|
||||
; rsp = [pfn][ra]
|
||||
|
||||
.endprolog
|
||||
|
||||
DoCrash
|
||||
|
||||
.beginepilog
|
||||
|
||||
ret
|
||||
|
||||
x64CrashCFITest_EPILOG endp
|
||||
|
||||
PaddingBetweenFunctions
|
||||
|
||||
; Having an EOF symbol at the end of this file contains symbolication to this
|
||||
; file. So addresses beyond this file don't get mistakenly symbolicated as a
|
||||
; meaningful function name.
|
||||
x64CrashCFITest_EOF proc frame
|
||||
TestHeader
|
||||
.endprolog
|
||||
ret
|
||||
x64CrashCFITest_EOF endp
|
||||
|
||||
end
|
@ -1447,7 +1447,7 @@ StreamMetaJSCustomObject(PSLockRef aLock, SpliceableJSONWriter& aWriter,
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
|
||||
|
||||
aWriter.IntProperty("version", 8);
|
||||
aWriter.IntProperty("version", 9);
|
||||
|
||||
// The "startTime" field holds the number of milliseconds since midnight
|
||||
// January 1, 1970 GMT. This grotty code computes (Now - (Now -
|
||||
|
@ -370,6 +370,9 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
|
||||
|
||||
// Step 4.2 Execute any events that were waiting for a stable state.
|
||||
ProcessStableStateQueue();
|
||||
|
||||
// This should be a fast test so that it won't affect the next task processing.
|
||||
IsIdleGCTaskNeeded();
|
||||
}
|
||||
|
||||
void
|
||||
@ -379,6 +382,36 @@ CycleCollectedJSContext::AfterProcessMicrotask()
|
||||
AfterProcessMicrotask(RecursionDepth());
|
||||
}
|
||||
|
||||
void CycleCollectedJSContext::IsIdleGCTaskNeeded()
|
||||
{
|
||||
class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable
|
||||
{
|
||||
public:
|
||||
using mozilla::IdleRunnable::IdleRunnable;
|
||||
|
||||
public:
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
|
||||
if (ccrt) {
|
||||
ccrt->RunIdleTimeGCTask();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult Cancel() override
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
if (Runtime()->IsIdleGCTaskNeeded()) {
|
||||
nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
|
||||
NS_IdleDispatchToCurrentThread(gc_task.forget());
|
||||
Runtime()->SetPendingIdleGCTask();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth)
|
||||
{
|
||||
|
@ -194,6 +194,9 @@ public:
|
||||
// microtask processor entry point
|
||||
void AfterProcessMicrotask();
|
||||
|
||||
// Check whether we need an idle GC task.
|
||||
void IsIdleGCTaskNeeded();
|
||||
|
||||
uint32_t RecursionDepth();
|
||||
|
||||
// Run in stable state (call through nsContentUtils)
|
||||
|
@ -510,6 +510,7 @@ CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
|
||||
: mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal)
|
||||
, mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal)
|
||||
, mJSRuntime(JS_GetRuntime(aCx))
|
||||
, mHasPendingIdleGCTask(false)
|
||||
, mPrevGCSliceCallback(nullptr)
|
||||
, mPrevGCNurseryCollectionCallback(nullptr)
|
||||
, mJSHolderMap(256)
|
||||
|
@ -247,6 +247,34 @@ public:
|
||||
void OnLargeAllocationFailure();
|
||||
|
||||
JSRuntime* Runtime() { return mJSRuntime; }
|
||||
const JSRuntime* Runtime() const { return mJSRuntime; }
|
||||
|
||||
bool HasPendingIdleGCTask() const
|
||||
{
|
||||
// Idle GC task associates with JSRuntime.
|
||||
MOZ_ASSERT_IF(mHasPendingIdleGCTask, Runtime());
|
||||
return mHasPendingIdleGCTask;
|
||||
}
|
||||
void SetPendingIdleGCTask()
|
||||
{
|
||||
// Idle GC task associates with JSRuntime.
|
||||
MOZ_ASSERT(Runtime());
|
||||
mHasPendingIdleGCTask = true;
|
||||
}
|
||||
void ClearPendingIdleGCTask() { mHasPendingIdleGCTask = false; }
|
||||
|
||||
void RunIdleTimeGCTask()
|
||||
{
|
||||
if (HasPendingIdleGCTask()) {
|
||||
JS::RunIdleTimeGCTask(Runtime());
|
||||
ClearPendingIdleGCTask();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsIdleGCTaskNeeded()
|
||||
{
|
||||
return !HasPendingIdleGCTask() && Runtime() && JS::IsIdleGCTaskNeeded(Runtime());
|
||||
}
|
||||
|
||||
public:
|
||||
void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer);
|
||||
@ -308,6 +336,7 @@ private:
|
||||
JSZoneParticipant mJSZoneCycleCollectorGlobal;
|
||||
|
||||
JSRuntime* mJSRuntime;
|
||||
bool mHasPendingIdleGCTask;
|
||||
|
||||
JS::GCSliceCallback mPrevGCSliceCallback;
|
||||
JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback;
|
||||
|
Loading…
Reference in New Issue
Block a user