Merge mozilla-central to autoland

This commit is contained in:
Carsten "Tomcat" Book 2017-07-11 13:02:38 +02:00
Родитель 4a1523e31c 31311070d9
Коммит 049436ef06
73 изменённых файлов: 1782 добавлений и 983 удалений

Просмотреть файл

@ -82,6 +82,14 @@ public:
{
return mLoadEventEnd;
}
DOMTimeMilliSec GetTimeToNonBlankPaint() const
{
if (mNonBlankPaintTimeStamp.IsNull()) {
return 0;
}
return TimeStampToDOMHighRes(mNonBlankPaintTimeStamp);
}
enum class DocShellState : uint8_t {
eActive,
@ -110,7 +118,7 @@ public:
DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const;
inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp)
inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp) const
{
mozilla::TimeDuration duration = aStamp - mNavigationStartTimeStamp;
return duration.ToMilliseconds();

Просмотреть файл

@ -4241,7 +4241,7 @@ nsDOMWindowUtils::TriggerDeviceReset()
GPUProcessManager* pm = GPUProcessManager::Get();
if (pm) {
pm->TriggerDeviceResetForTesting();
pm->SimulateDeviceReset();
}
return NS_OK;
}

Просмотреть файл

@ -2792,10 +2792,14 @@ nsDocument::InitCSP(nsIChannel* aChannel)
mSandboxFlags |= cspSandboxFlags;
if (cspSandboxFlags & SANDBOXED_ORIGIN) {
// If the new CSP sandbox flags do not have the allow-same-origin flag
// reset the document principal to a null principal
principal = NullPrincipal::Create();
// Probably the iframe sandbox attribute already caused the creation of a
// new NullPrincipal. Only create a new NullPrincipal if CSP requires so
// and no one has been created yet.
bool needNewNullPrincipal =
(cspSandboxFlags & SANDBOXED_ORIGIN) && !(mSandboxFlags & SANDBOXED_ORIGIN);
if (needNewNullPrincipal) {
principal = NullPrincipal::CreateWithInheritedAttributes(principal);
principal->SetCsp(csp);
SetPrincipal(principal);
}

Просмотреть файл

@ -180,4 +180,3 @@ skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
[test_bug1332699.html]
[test_bug1339758.html]
[test_dnd_with_modifiers.html]
[test_submitevent_on_form.html]

Просмотреть файл

@ -1,37 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test submit event on form</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<form action="javascript:doDefault()" id="form">
<input type="submit" value="Do Default Action">
</form>
<pre id="test">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTests);
var doDefaultAction = false;
function doDefault()
{
doDefaultAction = true;
}
function runTests()
{
let form = document.getElementById("form");
form.dispatchEvent(new Event('submit'));
setTimeout(() => {
ok(!doDefaultAction, "untrusted submit event shouldn't trigger form default action");
SimpleTest.finish();
});
}
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -474,10 +474,7 @@ nsresult
HTMLFormElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
aVisitor.mWantsWillHandleEvent = true;
// According to the UI events spec section "Trusted events", we shouldn't
// trigger UA default action with an untrusted event except click.
if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
aVisitor.mEvent->IsTrusted()) {
if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
uint32_t msg = aVisitor.mEvent->mMessage;
if (msg == eFormSubmit) {
if (mGeneratingSubmit) {
@ -519,10 +516,7 @@ HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor)
nsresult
HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
{
// According to the UI events spec section "Trusted events", we shouldn't
// trigger UA default action with an untrusted event except click.
if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
aVisitor.mEvent->IsTrusted()) {
if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
EventMessage msg = aVisitor.mEvent->mMessage;
if (msg == eFormSubmit) {
// let the form know not to defer subsequent submissions

Просмотреть файл

@ -232,8 +232,8 @@ RejectPromises(const nsTArray<RefPtr<Promise>>& aPromises, nsresult aError)
class nsMediaEvent : public Runnable
{
public:
explicit nsMediaEvent(HTMLMediaElement* aElement)
: Runnable("dom::nsMediaEvent")
explicit nsMediaEvent(const char* aName, HTMLMediaElement* aElement)
: Runnable(aName)
, mElement(aElement)
, mLoadID(mElement->GetCurrentLoadID())
{
@ -258,7 +258,7 @@ private:
public:
nsAsyncEventRunner(const nsAString& aName, HTMLMediaElement* aElement) :
nsMediaEvent(aElement), mName(aName)
nsMediaEvent("HTMLMediaElement::nsAsyncEventRunner", aElement), mName(aName)
{
}
@ -291,7 +291,7 @@ public:
nsResolveOrRejectPendingPlayPromisesRunner(HTMLMediaElement* aElement,
nsTArray<RefPtr<Promise>>&& aPromises,
nsresult aError = NS_OK)
: nsMediaEvent(aElement)
: nsMediaEvent("HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner", aElement)
, mPromises(Move(aPromises))
, mError(aError)
{
@ -347,7 +347,7 @@ private:
public:
nsSourceErrorEventRunner(HTMLMediaElement* aElement,
nsIContent* aSource)
: nsMediaEvent(aElement),
: nsMediaEvent("dom::nsSourceErrorEventRunner", aElement),
mSource(aSource)
{
}
@ -373,8 +373,12 @@ class HTMLMediaElement::StreamSizeListener : public DirectMediaStreamTrackListen
public:
explicit StreamSizeListener(HTMLMediaElement* aElement) :
mElement(aElement),
mMainThreadEventTarget(aElement->MainThreadEventTarget()),
mInitialSizeFound(false)
{}
{
MOZ_ASSERT(mElement);
MOZ_ASSERT(mMainThreadEventTarget);
}
void Forget() { mElement = nullptr; }
@ -407,16 +411,15 @@ public:
for (VideoSegment::ConstChunkIterator c(video); !c.IsEnded(); c.Next()) {
if (c->mFrame.GetIntrinsicSize() != gfx::IntSize(0,0)) {
mInitialSizeFound = true;
nsCOMPtr<nsIRunnable> event = NewRunnableMethod<gfx::IntSize>(
"dom::HTMLMediaElement::StreamSizeListener::ReceivedSize",
this,
&StreamSizeListener::ReceivedSize,
c->mFrame.GetIntrinsicSize());
// This is fine to dispatch straight to main thread (instead of via
// ...AfterStreamUpdate()) since it reflects state of the element,
// not the stream. Events reflecting stream or track state should be
// dispatched so their order is preserved.
NS_DispatchToMainThread(event.forget());
mMainThreadEventTarget->Dispatch(NewRunnableMethod<gfx::IntSize>(
"dom::HTMLMediaElement::StreamSizeListener::ReceivedSize",
this,
&StreamSizeListener::ReceivedSize,
c->mFrame.GetIntrinsicSize()));
return;
}
}
@ -425,6 +428,9 @@ public:
private:
// These fields may only be accessed on the main thread
HTMLMediaElement* mElement;
// We hold mElement->MainThreadEventTarget() here because the mElement could
// be reset in Forget().
nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
// These fields may only be accessed on the MSG's appending thread.
// (this is a direct listener so we get called by whoever is producing
@ -974,7 +980,7 @@ private:
}
uint64_t windowID = mAudioChannelAgent->WindowID();
NS_DispatchToMainThread(NS_NewRunnableFunction(
mOwner->MainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
"dom::HTMLMediaElement::AudioChannelAgentCallback::"
"MaybeNotifyMediaResumed",
[windowID]() -> void {
@ -1231,11 +1237,12 @@ public:
nsresult Load(HTMLMediaElement* aElement)
{
MOZ_ASSERT(aElement);
// Per bug 1235183 comment 8, we can't spin the event loop from stable
// state. Defer NS_NewChannel() to a new regular runnable.
return NS_DispatchToMainThread(NewRunnableMethod<HTMLMediaElement*>(
"ChannelLoader::LoadInternal",
this, &ChannelLoader::LoadInternal, aElement));
return aElement->MainThreadEventTarget()->Dispatch(
NewRunnableMethod<HTMLMediaElement*>("ChannelLoader::LoadInternal",
this, &ChannelLoader::LoadInternal, aElement));
}
void Cancel()
@ -1541,7 +1548,7 @@ HTMLMediaElement::MozRequestDebugInfo(ErrorResult& aRv)
if (mDecoder) {
mDecoder->RequestDebugInfo()->Then(
AbstractThread::MainThread(), __func__,
mAbstractMainThread, __func__,
[promise, result] (const nsACString& aString) {
promise->MaybeResolve(result + NS_ConvertUTF8toUTF16(aString));
},
@ -1843,7 +1850,7 @@ private:
public:
nsSyncSection(HTMLMediaElement* aElement,
nsIRunnable* aRunnable) :
nsMediaEvent(aElement),
nsMediaEvent("dom::nsSyncSection", aElement),
mRunnable(aRunnable)
{
}
@ -2029,10 +2036,9 @@ void HTMLMediaElement::SelectResource()
// The media element has neither a src attribute nor a source element child:
// set the networkState to NETWORK_EMPTY, and abort these steps; the
// synchronous section ends.
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod<nsCString>("HTMLMediaElement::NoSupportedMediaSourceError",
this, &HTMLMediaElement::NoSupportedMediaSourceError, nsCString());
NS_DispatchToMainThread(event);
mMainThreadEventTarget->Dispatch(NewRunnableMethod<nsCString>(
"HTMLMediaElement::NoSupportedMediaSourceError",
this, &HTMLMediaElement::NoSupportedMediaSourceError, nsCString()));
} else {
// Otherwise, the source elements will be used.
mIsLoadingFromSourceChildren = true;
@ -2189,9 +2195,9 @@ void HTMLMediaElement::NotifyMediaTrackDisabled(MediaTrack* aTrack)
ms.mTrackPorts[i].second()->GetDestinationTrackId());
MOZ_ASSERT(outputTrack);
if (outputTrack) {
NS_DispatchToMainThread(
NewRunnableMethod("MediaStreamTrack::OverrideEnded",
outputTrack, &MediaStreamTrack::OverrideEnded));
mMainThreadEventTarget->Dispatch(NewRunnableMethod(
"MediaStreamTrack::OverrideEnded",
outputTrack, &MediaStreamTrack::OverrideEnded));
}
ms.mTrackPorts[i].second()->Destroy();
@ -2236,10 +2242,9 @@ void HTMLMediaElement::DealWithFailedElement(nsIContent* aSourceElement)
}
DispatchAsyncSourceError(aSourceElement);
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod("HTMLMediaElement::QueueLoadFromSourceTask",
this, &HTMLMediaElement::QueueLoadFromSourceTask);
NS_DispatchToMainThread(event);
mMainThreadEventTarget->Dispatch(NewRunnableMethod(
"HTMLMediaElement::QueueLoadFromSourceTask",
this, &HTMLMediaElement::QueueLoadFromSourceTask));
}
void
@ -3369,7 +3374,7 @@ HTMLMediaElement::AddCaptureMediaTrackToOutputStream(MediaTrack* aTrack,
aOutputStream.mStream->CreateDOMTrack(destinationTrackID, type, source);
if (aAsyncAddtrack) {
NS_DispatchToMainThread(
mMainThreadEventTarget->Dispatch(
NewRunnableMethod<StoreRefPtrPassByPtr<MediaStreamTrack>>(
"DOMMediaStream::AddTrackInternal",
aOutputStream.mStream, &DOMMediaStream::AddTrackInternal, track));
@ -3775,6 +3780,7 @@ NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver, nsIObserver)
HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
: nsGenericHTMLElement(aNodeInfo),
mMainThreadEventTarget(OwnerDoc()->EventTargetFor(TaskCategory::Other)),
mAbstractMainThread(OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other)),
mWatchManager(this, mAbstractMainThread),
mSrcStreamTracksAvailable(false),
@ -3838,6 +3844,9 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
mErrorSink(new ErrorSink(this)),
mAudioChannelWrapper(new AudioChannelAgentCallback(this, mAudioChannel))
{
MOZ_ASSERT(mMainThreadEventTarget);
MOZ_ASSERT(mAbstractMainThread);
ErrorResult rv;
double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
@ -4148,6 +4157,8 @@ HTMLMediaElement::WakeLockBoolWrapper::SetCanPlay(bool aCanPlay)
void
HTMLMediaElement::WakeLockBoolWrapper::UpdateWakeLock()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mOuter) {
return;
}
@ -4166,6 +4177,7 @@ HTMLMediaElement::WakeLockBoolWrapper::UpdateWakeLock()
int timeout = Preferences::GetInt("media.wakelock_timeout", 2000);
mTimer = do_CreateInstance("@mozilla.org/timer;1");
if (mTimer) {
mTimer->SetTarget(mOuter->MainThreadEventTarget());
mTimer->InitWithNamedFuncCallback(
TimerCallback,
this,
@ -4397,6 +4409,7 @@ void HTMLMediaElement::HiddenVideoStart()
return;
}
mVideoDecodeSuspendTimer = do_CreateInstance("@mozilla.org/timer;1");
mVideoDecodeSuspendTimer->SetTarget(mMainThreadEventTarget);
mVideoDecodeSuspendTimer->InitWithNamedFuncCallback(
VideoDecodeSuspendTimerCallback, this,
MediaPrefs::MDSMSuspendBackgroundVideoDelay(), nsITimer::TYPE_ONE_SHOT,
@ -5593,6 +5606,7 @@ void HTMLMediaElement::StartProgressTimer()
NS_ASSERTION(!mProgressTimer, "Already started progress timer.");
mProgressTimer = do_CreateInstance("@mozilla.org/timer;1");
mProgressTimer->SetTarget(mMainThreadEventTarget);
mProgressTimer->InitWithNamedFuncCallback(
ProgressTimerCallback, this, PROGRESS_MS, nsITimer::TYPE_REPEATING_SLACK,
"HTMLMediaElement::ProgressTimerCallback");
@ -6139,9 +6153,7 @@ nsresult HTMLMediaElement::DispatchAsyncEvent(const nsAString& aName)
event = new nsAsyncEventRunner(aName, this);
}
OwnerDoc()->Dispatch("HTMLMediaElement::DispatchAsyncEvent",
TaskCategory::Other,
event.forget());
mMainThreadEventTarget->Dispatch(event.forget());
if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {
mPlayTime.Start();
@ -6385,11 +6397,10 @@ void HTMLMediaElement::AddRemoveSelfReference()
} else {
// Dispatch Release asynchronously so that we don't destroy this object
// inside a call stack of method calls on this object
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod("dom::HTMLMediaElement::DoRemoveSelfReference",
this,
&HTMLMediaElement::DoRemoveSelfReference);
NS_DispatchToMainThread(event);
mMainThreadEventTarget->Dispatch(NewRunnableMethod(
"dom::HTMLMediaElement::DoRemoveSelfReference",
this,
&HTMLMediaElement::DoRemoveSelfReference));
}
}
}
@ -6417,7 +6428,7 @@ void HTMLMediaElement::DispatchAsyncSourceError(nsIContent* aSourceElement)
LOG_EVENT(LogLevel::Debug, ("%p Queuing simple source error event", this));
nsCOMPtr<nsIRunnable> event = new nsSourceErrorEventRunner(this, aSourceElement);
NS_DispatchToMainThread(event);
mMainThreadEventTarget->Dispatch(event.forget());
}
void HTMLMediaElement::NotifyAddedSource()
@ -7407,9 +7418,7 @@ HTMLMediaElement::AsyncResolvePendingPlayPromises()
= new nsResolveOrRejectPendingPlayPromisesRunner(this,
TakePendingPlayPromises());
OwnerDoc()->Dispatch("nsResolveOrRejectPendingPlayPromisesRunner",
TaskCategory::Other,
event.forget());
mMainThreadEventTarget->Dispatch(event.forget());
}
void
@ -7424,9 +7433,7 @@ HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError)
TakePendingPlayPromises(),
aError);
OwnerDoc()->Dispatch("nsResolveOrRejectPendingPlayPromisesRunner",
TaskCategory::Other,
event.forget());
mMainThreadEventTarget->Dispatch(event.forget());
}
void
@ -7588,10 +7595,12 @@ HTMLMediaElement::ReportCanPlayTelemetry()
return;
}
RefPtr<AbstractThread> abstractThread = mAbstractMainThread;
thread->Dispatch(
NS_NewRunnableFunction(
"dom::HTMLMediaElement::ReportCanPlayTelemetry",
[thread]() {
[thread, abstractThread]() {
#if XP_WIN
// Windows Media Foundation requires MSCOM to be inited.
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
@ -7604,7 +7613,7 @@ HTMLMediaElement::ReportCanPlayTelemetry()
#if XP_WIN
CoUninitialize();
#endif
AbstractThread::MainThread()->Dispatch(NS_NewRunnableFunction(
abstractThread->Dispatch(NS_NewRunnableFunction(
"dom::HTMLMediaElement::ReportCanPlayTelemetry",
[thread, aac, h264]() {
LOG(LogLevel::Debug, ("MediaTelemetry aac=%d h264=%d", aac, h264));

Просмотреть файл

@ -65,6 +65,7 @@ class nsIChannel;
class nsIHttpChannel;
class nsILoadGroup;
class nsIRunnable;
class nsISerialEventTarget;
class nsITimer;
class nsRange;
@ -775,6 +776,11 @@ public:
void AsyncResolveSeekDOMPromiseIfExists() override;
void AsyncRejectSeekDOMPromiseIfExists() override;
nsISerialEventTarget* MainThreadEventTarget()
{
return mMainThreadEventTarget;
}
protected:
virtual ~HTMLMediaElement();
@ -1317,6 +1323,10 @@ protected:
// At most one of mDecoder and mSrcStream can be non-null.
RefPtr<MediaDecoder> mDecoder;
// The DocGroup-specific nsISerialEventTarget of this HTML element on the main
// thread.
nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
// The DocGroup-specific AbstractThread::MainThread() of this HTML element.
RefPtr<AbstractThread> mAbstractMainThread;

Просмотреть файл

@ -5432,7 +5432,7 @@ ContentParent::RecvDeviceReset()
{
GPUProcessManager* pm = GPUProcessManager::Get();
if (pm) {
pm->TriggerDeviceResetForTesting();
pm->SimulateDeviceReset();
}
return IPC_OK();

Просмотреть файл

@ -244,6 +244,15 @@ public:
return GetDOMTiming()->GetLoadEventEnd();
}
DOMTimeMilliSec TimeToNonBlankPaint() const
{
if (!nsContentUtils::IsPerformanceTimingEnabled() ||
nsContentUtils::ShouldResistFingerprinting()) {
return 0;
}
return GetDOMTiming()->GetTimeToNonBlankPaint();
}
private:
~PerformanceTiming();

Просмотреть файл

@ -106,6 +106,15 @@ var testCases = [
results: { img12_bad: -1, script12_bad: -1 },
nrOKmessages: 4 // sends 4 ok message
},
{
// Test 13: same as Test 5 and Test 11, but:
// * using sandbox flag 'allow-scripts' in CSP and not as iframe attribute
// * not using allow-same-origin in CSP (so a new NullPrincipal is created).
csp: "default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts",
file: "file_sandbox_5.html",
results: { img13_bad: -1, img13a_bad: -1, script13_bad: -1, script13a_bad: -1 },
nrOKmessages: 2 // sends 2 ok message
},
];
// a postMessage handler that is used by sandboxed iframes without

Просмотреть файл

@ -34,5 +34,11 @@ interface PerformanceTiming {
readonly attribute unsigned long long loadEventStart;
readonly attribute unsigned long long loadEventEnd;
// This is a Chrome proprietary extension and not part of the
// performance/navigation timing specification.
// Returns 0 if a non-blank paint has not happened.
[Pref="dom.performance.time_to_non_blank_paint.enabled"]
readonly attribute unsigned long long timeToNonBlankPaint;
jsonifier;
};

Просмотреть файл

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gfxConfig.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/GraphicsMessages.h"
#include "plstr.h"
@ -142,6 +144,13 @@ gfxConfig::Reenable(Feature aFeature, Fallback aFallback)
state.SetRuntime(FeatureStatus::Available, nullptr);
}
/* static */ void
gfxConfig::Reset(Feature aFeature)
{
FeatureState& state = sConfig->GetState(aFeature);
state.Reset();
}
/* static */ void
gfxConfig::Inherit(Feature aFeature, FeatureStatus aStatus)
{
@ -178,7 +187,23 @@ gfxConfig::UseFallback(Fallback aFallback)
/* static */ void
gfxConfig::EnableFallback(Fallback aFallback, const char* aMessage)
{
// Ignore aMessage for now.
if (!NS_IsMainThread()) {
nsCString message(aMessage);
NS_DispatchToMainThread(
NS_NewRunnableFunction("gfxConfig::EnableFallback",
[=]() -> void {
gfxConfig::EnableFallback(aFallback, message.get());
}));
return;
}
if (XRE_IsGPUProcess()) {
nsCString message(aMessage);
Unused << GPUParent::GetSingleton()->SendUsedFallback(aFallback, message);
return;
}
sConfig->EnableFallbackImpl(aFallback, aMessage);
}

Просмотреть файл

@ -62,6 +62,9 @@ public:
// 5. Return the default status.
static FeatureStatus GetValue(Feature aFeature);
// Reset the entire state of a feature.
static void Reset(Feature aFeature);
// Initialize the base value of a parameter. The return value is aEnable.
static bool SetDefault(Feature aFeature,
bool aEnable,
@ -164,7 +167,8 @@ public:
// Query whether a fallback has been toggled.
static bool UseFallback(Fallback aFallback);
// Enable a fallback.
// Add a log entry denoting that a given fallback had to be used. This can
// be called from any thread in the UI or GPU process.
static void EnableFallback(Fallback aFallback, const char* aMessage);
// Run a callback for each initialized FeatureState.

Просмотреть файл

@ -14,7 +14,7 @@ namespace gfx {
#define GFX_FALLBACK_MAP(_) \
/* Name */ \
_(PLACEHOLDER_DO_NOT_USE) \
_(NO_CONSTANT_BUFFER_OFFSETTING) \
/* Add new entries above this comment */
enum class Fallback : uint32_t {

Просмотреть файл

@ -95,29 +95,24 @@ Advanced Layers currently has five layer-related shader pipelines:
- Blend (ContainerLayers with mix-blend modes)
There are also three special shader pipelines:
- MaskCombiner, which is used to combine mask layers into a single texture.
- Clear, which is used for fast region-based clears when not directly supported by the GPU.
- Diagnostic, which is used to display the diagnostic overlay texture.
The basic layer shaders follow a unified structure. Each pipeline has a vertex and pixel shader.
The vertex shader takes a layers ID, a z-buffer depth, a vertex (in a triangle list), and any
ancillary data needed for the pixel shader. Often this ancillary data is just an index into
a constant buffer (more on this below).
The vertex shader applies transforms and sends data down to the pixel shader, which performs
clipping and masking.
The layer shaders follow a unified structure. Each pipeline has a vertex and pixel shader.
The vertex shader takes a layers ID, a z-buffer depth, a unit position in either a unit
square or unit triangle, and either rectangular or triangular geometry. Shaders can also
have ancillary data needed like texture coordinates or colors.
Most of the time, layers have simple rectangular clips with simple rectilinear transforms, and
pixel shaders do not need to perform masking or clipping. We take advantage of this for common
layer types, and use a second set of vertex and pixel shaders. These shaders have a unified
input layout: a draw rect, a layers ID, and a z-buffer depth. The pipeline uses a unit quad
as input. Ancillary data is stored in a constant buffer, which is indexed by the instance ID.
This path performs clipping in the vertex shader, and the pixel shader does not support masks.
pixel shaders do not need to perform masking or clipping. For these layers we use a fast-path
pipeline, using unit-quad shaders that are able to clip geometry so the pixel shader does not
have to. This type of pipeline does not support complex masks.
Most shader types use ancillary data (such as texture coordinates, or a color value). This is
stored in a constant buffer, which is bound to the vertex shader. Unit quad shaders use
instancing to access the buffer. Full-fledged, triangle list shaders store the index in each
vertex.
If a layer has a complex mask, a rotation or 3d transform, or a complex operation like blending,
then we use shaders capable of handling arbitrary geometry. Their input is a unit triangle,
and these shaders are generally more expensive.
All of the shader-specific data is modelled in ShaderDefinitionsMLGPU.h.

Просмотреть файл

@ -280,6 +280,13 @@ GPUChild::RecvUpdateFeature(const Feature& aFeature, const FeatureFailure& aChan
return IPC_OK();
}
mozilla::ipc::IPCResult
GPUChild::RecvUsedFallback(const Fallback& aFallback, const nsCString& aMessage)
{
gfxConfig::EnableFallback(aFallback, aMessage.get());
return IPC_OK();
}
class DeferredDeleteGPUChild : public Runnable
{
public:

Просмотреть файл

@ -59,6 +59,7 @@ public:
mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport) override;
mozilla::ipc::IPCResult RecvFinishMemoryReport(const uint32_t& aGeneration) override;
mozilla::ipc::IPCResult RecvUpdateFeature(const Feature& aFeature, const FeatureFailure& aChange) override;
mozilla::ipc::IPCResult RecvUsedFallback(const Fallback& aFallback, const nsCString& aMessage) override;
bool SendRequestMemoryReport(const uint32_t& aGeneration,
const bool& aAnonymize,

Просмотреть файл

@ -397,8 +397,11 @@ ShouldLimitDeviceResets(uint32_t count, int32_t deltaMilliseconds)
}
void
GPUProcessManager::TriggerDeviceResetForTesting()
GPUProcessManager::SimulateDeviceReset()
{
// Make sure we rebuild environment and configuration for accelerated features.
gfxPlatform::GetPlatform()->CompositorUpdated();
if (mProcess) {
OnRemoteProcessDeviceReset(mProcess);
} else {

Просмотреть файл

@ -144,7 +144,7 @@ public:
void OnProcessLaunchComplete(GPUProcessHost* aHost) override;
void OnProcessUnexpectedShutdown(GPUProcessHost* aHost) override;
void TriggerDeviceResetForTesting();
void SimulateDeviceReset();
void OnInProcessDeviceReset();
void OnRemoteProcessDeviceReset(GPUProcessHost* aHost) override;
void NotifyListenersOnCompositeDeviceReset();

Просмотреть файл

@ -14,6 +14,7 @@
#include "base/process_util.h"
#include "chrome/common/ipc_message_utils.h"
#include "gfxFeature.h"
#include "gfxFallback.h"
#include "gfxPoint.h"
#include "gfxRect.h"
#include "gfxTelemetry.h"
@ -228,6 +229,14 @@ struct ParamTraits<mozilla::gfx::Feature>
mozilla::gfx::Feature::NumValues>
{};
template <>
struct ParamTraits<mozilla::gfx::Fallback>
: public ContiguousEnumSerializer<
mozilla::gfx::Fallback,
mozilla::gfx::Fallback::NO_CONSTANT_BUFFER_OFFSETTING,
mozilla::gfx::Fallback::NumValues>
{};
template <>
struct ParamTraits<mozilla::gfx::FeatureStatus>
: public ContiguousEnumSerializer<

Просмотреть файл

@ -22,6 +22,7 @@ using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
using mozilla::gfx::Feature from "gfxFeature.h";
using mozilla::gfx::Fallback from "gfxFallback.h";
namespace mozilla {
namespace gfx {
@ -120,6 +121,9 @@ child:
// Update the UI process after a feature's status has changed. This is used
// outside of the normal startup flow.
async UpdateFeature(Feature aFeature, FeatureFailure aChange);
// Notify about:support/Telemetry that a fallback occurred.
async UsedFallback(Fallback aFallback, nsCString message);
};
} // namespace gfx

Просмотреть файл

@ -603,6 +603,11 @@ LayerManagerComposite::RenderDebugOverlay(const IntRect& aBounds)
bool drawFps = gfxPrefs::LayersDrawFPS();
bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars();
// Don't draw diagnostic overlays if we want to snapshot the output.
if (mTarget) {
return;
}
if (drawFps) {
float alpha = 1;
#ifdef ANDROID

Просмотреть файл

@ -17,6 +17,7 @@
#include "mozilla/widget/WinCompositorWidget.h"
#include "MLGShaders.h"
#include "TextureD3D11.h"
#include "gfxConfig.h"
#include "gfxPrefs.h"
namespace mozilla {
@ -791,8 +792,13 @@ MLGDeviceD3D11::Initialize()
if (SUCCEEDED(hr)) {
if (IsWin8OrLater()) {
mCanUseConstantBufferOffsetBinding = (options.ConstantBufferOffsetting != FALSE);
} else {
gfxConfig::EnableFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING,
"Unsupported by driver");
}
mCanUseClearView = (options.ClearView != FALSE);
} else {
gfxCriticalNote << "Failed to query D3D11.1 feature support: " << hexa(hr);
}
}
@ -825,6 +831,21 @@ MLGDeviceD3D11::Initialize()
}
}
{
struct Vertex3D { float x; float y; float z; };
Vertex3D vertices[3] = {
{ 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }
};
mUnitTriangleVB = CreateBuffer(
MLGBufferType::Vertex,
sizeof(Vertex3D) * 3,
MLGUsage::Immutable,
&vertices);
if (!mUnitTriangleVB) {
return Fail("FEATURE_FAILURE_UNIT_TRIANGLE_BUFFER");
}
}
// Define pixel shaders.
#define LAZY_PS(cxxName, enumName) mLazyPixelShaders[PixelShaderID::enumName] = &s##cxxName;
LAZY_PS(TexturedVertexRGB, TexturedVertexRGB);
@ -884,35 +905,50 @@ MLGDeviceD3D11::Initialize()
return Fail("FEATURE_FAILURE_CRITICAL_SHADER_FAILURE");
}
// Common unit quad layout: vPos, vRect, vLayerIndex, vDepth
# define BASE_UNIT_QUAD_LAYOUT \
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, \
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, \
{ "TEXCOORD", 1, DXGI_FORMAT_R32_UINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, \
{ "TEXCOORD", 2, DXGI_FORMAT_R32_SINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
// Common unit triangle layout: vUnitPos, vPos1-3, vLayerIndex, vDepth
# define BASE_UNIT_TRIANGLE_LAYOUT \
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, \
{ "POSITION", 1, DXGI_FORMAT_R32G32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, \
{ "POSITION", 2, DXGI_FORMAT_R32G32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, \
{ "POSITION", 3, DXGI_FORMAT_R32G32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, \
{ "TEXCOORD", 0, DXGI_FORMAT_R32_UINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, \
{ "TEXCOORD", 1, DXGI_FORMAT_R32_SINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
// Initialize input layouts.
{
D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
// vPos
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// vRect
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
// vLayerOffset
{ "TEXCOORD", 1, DXGI_FORMAT_R32_UINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
// vDepth
{ "TEXCOORD", 2, DXGI_FORMAT_R32_SINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
BASE_UNIT_QUAD_LAYOUT,
// vTexRect
{ "TEXCOORD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
};
if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), sTexturedQuadVS, VertexShaderID::TexturedQuad)) {
return Fail("FEATURE_FAILURE_UNIT_QUAD_INPUT_LAYOUT");
return Fail("FEATURE_FAILURE_UNIT_QUAD_TEXTURED_LAYOUT");
}
// Propagate the input layout to other vertex shaders that use the same.
// All quad-based layer shaders use the same layout.
mInputLayouts[VertexShaderID::ColoredQuad] = mInputLayouts[VertexShaderID::TexturedQuad];
}
{
D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
// vLayerPos
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// vTexCoord
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// vLayerOffset
{ "TEXCOORD", 1, DXGI_FORMAT_R32_UINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// vDepth
{ "TEXCOORD", 2, DXGI_FORMAT_R32_SINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
BASE_UNIT_QUAD_LAYOUT,
// vColor
{ "TEXCOORD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
};
if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), sColoredQuadVS, VertexShaderID::ColoredQuad)) {
return Fail("FEATURE_FAILURE_UNIT_QUAD_COLORED_LAYOUT");
}
}
{
D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
BASE_UNIT_TRIANGLE_LAYOUT,
// vTexCoord1, vTexCoord2, vTexCoord3
{ "TEXCOORD", 2, DXGI_FORMAT_R32G32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "TEXCOORD", 3, DXGI_FORMAT_R32G32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "TEXCOORD", 4, DXGI_FORMAT_R32G32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
};
if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), sTexturedVertexVS, VertexShaderID::TexturedVertex)) {
return Fail("FEATURE_FAILURE_TEXTURED_INPUT_LAYOUT");
@ -922,19 +958,18 @@ MLGDeviceD3D11::Initialize()
}
{
D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
// vPos
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// vLayerOffset
{ "TEXCOORD", 0, DXGI_FORMAT_R32_UINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// vDepth
{ "TEXCOORD", 1, DXGI_FORMAT_R32_SINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// vIndex
{ "TEXCOORD", 2, DXGI_FORMAT_R32_UINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
BASE_UNIT_TRIANGLE_LAYOUT,
{ "TEXCOORD", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
};
if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), sColoredVertexVS, VertexShaderID::ColoredVertex)) {
return Fail("FEATURE_FAILURE_COLORED_INPUT_LAYOUT");
}
}
# undef BASE_UNIT_QUAD_LAYOUT
# undef BASE_UNIT_TRIANGLE_LAYOUT
// Ancillary shaders that are not used for batching.
{
D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
// vPos
@ -1403,14 +1438,28 @@ MLGDeviceD3D11::SetVertexShader(VertexShaderID aShader)
InitVertexShader(aShader);
MOZ_ASSERT(mInputLayouts[aShader]);
}
if (mCurrentVertexShader != mVertexShaders[aShader]) {
mCtx->VSSetShader(mVertexShaders[aShader], nullptr, 0);
mCurrentVertexShader = mVertexShaders[aShader];
SetVertexShader(mVertexShaders[aShader]);
SetInputLayout(mInputLayouts[aShader]);
}
void
MLGDeviceD3D11::SetInputLayout(ID3D11InputLayout* aLayout)
{
if (mCurrentInputLayout == aLayout) {
return;
}
if (mCurrentInputLayout != mInputLayouts[aShader]) {
mCtx->IASetInputLayout(mInputLayouts[aShader]);
mCurrentInputLayout = mInputLayouts[aShader];
mCtx->IASetInputLayout(aLayout);
mCurrentInputLayout = aLayout;
}
void
MLGDeviceD3D11::SetVertexShader(ID3D11VertexShader* aShader)
{
if (mCurrentVertexShader == aShader) {
return;
}
mCtx->VSSetShader(aShader, nullptr, 0);
mCurrentVertexShader = aShader;
}
void
@ -1502,11 +1551,14 @@ MLGDeviceD3D11::SetPrimitiveTopology(MLGPrimitiveTopology aTopology)
case MLGPrimitiveTopology::TriangleList:
topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
break;
case MLGPrimitiveTopology::UnitQuad: {
case MLGPrimitiveTopology::UnitQuad:
SetVertexBuffer(0, mUnitQuadVB, sizeof(float) * 2, 0);
topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
break;
}
case MLGPrimitiveTopology::UnitTriangle:
SetVertexBuffer(0, mUnitTriangleVB, sizeof(float) * 3, 0);
topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
break;
default:
MOZ_ASSERT_UNREACHABLE("Unknown topology");
break;
@ -1931,6 +1983,130 @@ MLGDeviceD3D11::CopyTexture(MLGTexture* aDest,
&box);
}
bool
MLGDeviceD3D11::VerifyConstantBufferOffsetting()
{
RefPtr<ID3D11VertexShader> vs;
HRESULT hr = mDevice->CreateVertexShader(
sTestConstantBuffersVS.mData,
sTestConstantBuffersVS.mLength,
nullptr,
getter_AddRefs(vs));
if (FAILED(hr)) {
gfxCriticalNote << "Failed creating vertex shader for buffer test: " << hexa(hr);
return false;
}
D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
RefPtr<ID3D11InputLayout> layout;
hr = mDevice->CreateInputLayout(
inputDesc,
sizeof(inputDesc) / sizeof(inputDesc[0]),
sTestConstantBuffersVS.mData,
sTestConstantBuffersVS.mLength,
getter_AddRefs(layout));
if (FAILED(hr)) {
gfxCriticalNote << "Failed creating input layout for buffer test: " << hexa(hr);
return false;
}
RefPtr<MLGRenderTarget> rt = CreateRenderTarget(IntSize(2, 2), MLGRenderTargetFlags::Default);
if (!rt) {
return false;
}
static const size_t kConstantSize = 4 * sizeof(float);
static const size_t kMinConstants = 16;
static const size_t kNumBindings = 3;
RefPtr<MLGBuffer> buffer = CreateBuffer(
MLGBufferType::Constant,
kConstantSize * kMinConstants * kNumBindings,
MLGUsage::Dynamic,
nullptr);
if (!buffer) {
return false;
}
// Populate the buffer. The shader will pick R from buffer 1, G from buffer
// 2, and B from buffer 3.
{
MLGMappedResource map;
if (!Map(buffer, MLGMapType::WRITE_DISCARD, &map)) {
return false;
}
*reinterpret_cast<Color*>(map.mData) =
Color(1.0f, 0.2f, 0.3f, 1.0f);
*reinterpret_cast<Color*>(map.mData + kConstantSize * kMinConstants) =
Color(0.4f, 0.0f, 0.5f, 1.0f);
*reinterpret_cast<Color*>(map.mData + (kConstantSize * kMinConstants) * 2) =
Color(0.6f, 0.7f, 1.0f, 1.0f);
Unmap(buffer);
}
Clear(rt, Color(0.0f, 0.0f, 0.0f, 1.0f));
SetRenderTarget(rt);
SetViewport(IntRect(0, 0, 2, 2));
SetScissorRect(Nothing());
SetBlendState(MLGBlendState::Over);
SetTopology(MLGPrimitiveTopology::UnitQuad);
SetInputLayout(layout);
SetVertexShader(vs);
SetPixelShader(PixelShaderID::ColoredQuad);
ID3D11Buffer* buffers[3] = {
buffer->AsD3D11()->GetBuffer(),
buffer->AsD3D11()->GetBuffer(),
buffer->AsD3D11()->GetBuffer()
};
UINT offsets[3] = { 0 * kMinConstants, 1 * kMinConstants, 2 * kMinConstants };
UINT counts[3] = { kMinConstants, kMinConstants, kMinConstants };
mCtx1->VSSetConstantBuffers1(0, 3, buffers, offsets, counts);
mCtx->Draw(4, 0);
// Kill bindings to resources.
SetRenderTarget(nullptr);
ID3D11Buffer* nulls[3] = { nullptr, nullptr, nullptr };
mCtx->VSSetConstantBuffers(0, 3, nulls);
RefPtr<MLGTexture> copy = CreateTexture(
IntSize(2, 2),
SurfaceFormat::B8G8R8A8,
MLGUsage::Staging,
MLGTextureFlags::None);
if (!copy) {
return false;
}
CopyTexture(copy, IntPoint(0, 0), rt->GetTexture(), IntRect(0, 0, 2, 2));
uint8_t r, g, b, a;
{
MLGMappedResource map;
if (!Map(copy, MLGMapType::READ, &map)) {
return false;
}
r = map.mData[0];
g = map.mData[1];
b = map.mData[2];
a = map.mData[3];
Unmap(copy);
}
return r == 255 &&
g == 0 &&
b == 255 &&
a == 255;
}
static D3D11_BOX
RectToBox(const gfx::IntRect& aRect)
{

Просмотреть файл

@ -272,6 +272,10 @@ private:
bool InitSamplerStates();
bool InitBlendStates();
bool InitDepthStencilState();
bool VerifyConstantBufferOffsetting() override;
void SetInputLayout(ID3D11InputLayout* aLayout);
void SetVertexShader(ID3D11VertexShader* aShader);
private:
RefPtr<ID3D11Device> mDevice;
@ -302,6 +306,7 @@ private:
RefPtr<MLGRenderTarget> mCurrentRT;
RefPtr<MLGBuffer> mUnitQuadVB;
RefPtr<MLGBuffer> mUnitTriangleVB;
RefPtr<ID3D11VertexShader> mCurrentVertexShader;
RefPtr<ID3D11InputLayout> mCurrentInputLayout;
RefPtr<ID3D11PixelShader> mCurrentPixelShader;

Просмотреть файл

@ -35,7 +35,18 @@ VS_BLEND_OUTPUT BlendImpl(const VertexInfo aInfo, float2 aTexCoord)
VS_BLEND_OUTPUT BlendVertexVS(const VS_TEXTUREDVERTEX aVertex)
{
VertexInfo info = ComputePosition(aVertex.vLayerPos, aVertex.vLayerId, aVertex.vDepth);
float2 layerPos = UnitTriangleToPos(
aVertex.vUnitPos,
aVertex.vPos1,
aVertex.vPos2,
aVertex.vPos3);
return BlendImpl(info, aVertex.vTexCoord);
float2 texCoord = UnitTriangleToPos(
aVertex.vUnitPos,
aVertex.vTexCoord1,
aVertex.vTexCoord2,
aVertex.vTexCoord3);
VertexInfo info = ComputePosition(layerPos, aVertex.vLayerId, aVertex.vDepth);
return BlendImpl(info, texCoord);
}

Просмотреть файл

@ -5,35 +5,24 @@
#include "common-vs.hlsl"
#include "color-common.hlsl"
struct ColorItem
{
float4 color;
};
#define SIZEOF_COLORITEM 1
ColorItem GetItem(uint aIndex)
{
uint offset = aIndex * SIZEOF_COLORITEM;
ColorItem item;
item.color = sItems[offset + 0];
return item;
}
struct VS_COLORQUAD
{
float2 vPos : POSITION;
float4 vRect : TEXCOORD0;
uint vLayerId : TEXCOORD1;
int vDepth : TEXCOORD2;
uint vIndex : SV_InstanceID;
float4 vColor : TEXCOORD3;
};
struct VS_COLORVERTEX
{
float2 vLayerPos : POSITION;
float3 vUnitPos : POSITION0;
float2 vPos1 : POSITION1;
float2 vPos2 : POSITION2;
float2 vPos3 : POSITION3;
uint vLayerId : TEXCOORD0;
int vDepth : TEXCOORD1;
uint vIndex : TEXCOORD2;
float4 vColor : TEXCOORD2;
};
VS_COLOROUTPUT ColorImpl(float4 aColor, const VertexInfo aInfo)
@ -49,7 +38,6 @@ VS_COLOROUTPUT ColorImpl(float4 aColor, const VertexInfo aInfo)
VS_COLOROUTPUT_CLIPPED ColoredQuadVS(const VS_COLORQUAD aInput)
{
ColorItem item = GetItem(aInput.vIndex);
float4 worldPos = ComputeClippedPosition(
aInput.vPos,
aInput.vRect,
@ -58,13 +46,13 @@ VS_COLOROUTPUT_CLIPPED ColoredQuadVS(const VS_COLORQUAD aInput)
VS_COLOROUTPUT_CLIPPED output;
output.vPosition = worldPos;
output.vColor = item.color;
output.vColor = aInput.vColor;
return output;
}
VS_COLOROUTPUT ColoredVertexVS(const VS_COLORVERTEX aInput)
{
ColorItem item = GetItem(aInput.vIndex);
VertexInfo info = ComputePosition(aInput.vLayerPos, aInput.vLayerId, aInput.vDepth);
return ColorImpl(item.color, info);
float2 layerPos = UnitTriangleToPos(aInput.vUnitPos, aInput.vPos1, aInput.vPos2, aInput.vPos3);
VertexInfo info = ComputePosition(layerPos, aInput.vLayerId, aInput.vDepth);
return ColorImpl(aInput.vColor, info);
}

Просмотреть файл

@ -27,11 +27,6 @@ cbuffer Layers : register(b1)
Layer sLayers[682];
};
cbuffer Items : register(b2)
{
float4 sItems[4096];
};
cbuffer MaskRects : register(b3)
{
float4 sMaskRects[4096];
@ -62,6 +57,16 @@ float3 ComputeMaskCoords(float4 aPosition, Layer aLayer)
return float3(mul(transform, aPosition / aPosition.w).xy, 1.0) * aPosition.w;
}
float2 UnitTriangleToPos(const float3 aVertex,
const float2 aPos1,
const float2 aPos2,
const float2 aPos3)
{
return aVertex.x * aPos1 +
aVertex.y * aPos2 +
aVertex.z * aPos3;
}
float2 UnitQuadToRect(const float2 aVertex, const float4 aRect)
{
return float2(aRect.x + aVertex.x * aRect.z, aRect.y + aVertex.y * aRect.w);

Просмотреть файл

@ -92,3 +92,7 @@
shaders:
- DiagnosticTextPS
- type: vs_4_0
file: test-features-vs.hlsl
shaders:
- TestConstantBuffersVS

Просмотреть файл

@ -0,0 +1,32 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "common-vs.hlsl"
#include "color-common.hlsl"
struct VS_INPUT {
float2 vPos : POSITION;
};
cbuffer Buffer0 : register(b0) {
float4 aValue0;
};
cbuffer Buffer1 : register(b1) {
float4 aValue1;
};
cbuffer Buffer2 : register(b2) {
float4 aValue2;
};
VS_COLOROUTPUT_CLIPPED TestConstantBuffersVS(VS_INPUT aInput)
{
// Draw to the entire viewport.
float2 pos = UnitQuadToRect(aInput.vPos, float4(-1, -1, 2, 2));
VS_COLOROUTPUT_CLIPPED output;
output.vPosition = float4(pos, 0, 1);
output.vColor = float4(aValue0.r, aValue1.g, aValue2.b, 1.0);
return output;
}

Просмотреть файл

@ -3,22 +3,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef VERTEX_SHADER
struct TexturedItem
{
float4 texCoords;
};
#define SIZEOF_TEXTUREDITEM 1
TexturedItem GetItem(uint aIndex)
{
uint offset = aIndex * SIZEOF_TEXTUREDITEM;
TexturedItem item;
item.texCoords = sItems[offset + 0];
return item;
}
#endif
// Instanced version.
struct VS_TEXTUREDINPUT
{
@ -26,16 +10,21 @@ struct VS_TEXTUREDINPUT
float4 vRect : TEXCOORD0;
uint vLayerId : TEXCOORD1;
int vDepth : TEXCOORD2;
uint vIndex : SV_InstanceID;
float4 vTexRect : TEXCOORD3;
};
// Non-instanced version.
struct VS_TEXTUREDVERTEX
{
float2 vLayerPos : POSITION;
float2 vTexCoord : TEXCOORD0;
uint vLayerId : TEXCOORD1;
int vDepth : TEXCOORD2;
float3 vUnitPos : POSITION0;
float2 vPos1: POSITION1;
float2 vPos2: POSITION2;
float2 vPos3: POSITION3;
uint vLayerId : TEXCOORD0;
int vDepth : TEXCOORD1;
float2 vTexCoord1 : TEXCOORD2;
float2 vTexCoord2 : TEXCOORD3;
float2 vTexCoord3 : TEXCOORD4;
};
struct VS_SAMPLEOUTPUT

Просмотреть файл

@ -18,8 +18,6 @@ VS_SAMPLEOUTPUT TexturedQuadImpl(const VertexInfo aInfo, const float2 aTexCoord)
VS_SAMPLEOUTPUT_CLIPPED TexturedQuadVS(const VS_TEXTUREDINPUT aVertex)
{
TexturedItem item = GetItem(aVertex.vIndex);
float4 worldPos = ComputeClippedPosition(
aVertex.vPos,
aVertex.vRect,
@ -28,13 +26,24 @@ VS_SAMPLEOUTPUT_CLIPPED TexturedQuadVS(const VS_TEXTUREDINPUT aVertex)
VS_SAMPLEOUTPUT_CLIPPED output;
output.vPosition = worldPos;
output.vTexCoords = UnitQuadToRect(aVertex.vPos, item.texCoords);
output.vTexCoords = UnitQuadToRect(aVertex.vPos, aVertex.vTexRect);
return output;
}
VS_SAMPLEOUTPUT TexturedVertexVS(const VS_TEXTUREDVERTEX aVertex)
{
VertexInfo info = ComputePosition(aVertex.vLayerPos, aVertex.vLayerId, aVertex.vDepth);
float2 layerPos = UnitTriangleToPos(
aVertex.vUnitPos,
aVertex.vPos1,
aVertex.vPos2,
aVertex.vPos3);
return TexturedQuadImpl(info, aVertex.vTexCoord);
float2 texCoord = UnitTriangleToPos(
aVertex.vUnitPos,
aVertex.vTexCoord1,
aVertex.vTexCoord2,
aVertex.vTexCoord3);
VertexInfo info = ComputePosition(layerPos, aVertex.vLayerId, aVertex.vDepth);
return TexturedQuadImpl(info, texCoord);
}

Просмотреть файл

@ -263,7 +263,8 @@ LayerManagerMLGPU::EndTransaction(const TimeStamp& aTimeStamp, EndTransactionFla
}
}
mDrawDiagnostics = gfxPrefs::LayersDrawFPS();
// Don't draw the diagnostic overlay if we want to snapshot the output.
mDrawDiagnostics = gfxPrefs::LayersDrawFPS() && !mTarget;
mUsingInvalidation = gfxPrefs::AdvancedLayersUseInvalidation();
// Compute transforms - and the changed area, if enabled.

Просмотреть файл

@ -6,6 +6,7 @@
#include "MLGDevice.h"
#include "mozilla/layers/TextureHost.h"
#include "BufferCache.h"
#include "gfxConfig.h"
#include "gfxPrefs.h"
#include "gfxUtils.h"
#include "ShaderDefinitionsMLGPU.h"
@ -86,9 +87,16 @@ MLGDevice::Initialize()
return Fail("FEATURE_FAILURE_MIN_MAX_CB_BIND_SIZE", "Minimum constant buffer bind size not met");
}
// We allow this to be pref'd off for testing. Switching it on enables
// We allow this to be pref'd off for testing. Switching it off enables
// Direct3D 11.0/Windows 7/OpenGL-style buffer code paths.
if (!gfxPrefs::AdvancedLayersEnableBufferSharing()) {
gfxConfig::EnableFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING,
"Disabled by pref");
mCanUseConstantBufferOffsetBinding = false;
}
if (mCanUseConstantBufferOffsetBinding && !VerifyConstantBufferOffsetting()) {
gfxConfig::EnableFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING,
"Constant buffer offset binding does not work");
mCanUseConstantBufferOffsetBinding = false;
}

Просмотреть файл

@ -418,6 +418,12 @@ protected:
virtual void SetPrimitiveTopology(MLGPrimitiveTopology aTopology) = 0;
// Optionally run a runtime test to determine if constant buffer offset
// binding works.
virtual bool VerifyConstantBufferOffsetting() {
return true;
}
// Used during initialization to record failure reasons.
bool Fail(const nsCString& aFailureId, const nsCString* aMessage);

Просмотреть файл

@ -67,7 +67,8 @@ enum class MLGPrimitiveTopology
Unknown = 0,
TriangleStrip = 1,
TriangleList = 2,
UnitQuad = 3
UnitQuad = 3,
UnitTriangle = 4
};
struct MLGMappedResource

Просмотреть файл

@ -0,0 +1,70 @@
/* -*- Mode: C++; tab-width: 20; 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 mozilla_gfx_layers_mlgpu_RenderPassMLGPU_inl_h
#define mozilla_gfx_layers_mlgpu_RenderPassMLGPU_inl_h
namespace mozilla {
namespace layers {
template <typename Traits>
static inline bool
AddShaderTriangles(VertexStagingBuffer* aBuffer,
const Traits& aTraits,
const gfx::Polygon* aGeometry = nullptr)
{
typedef typename Traits::TriangleVertices TriangleVertices;
typedef typename Traits::FirstTriangle FirstTriangle;
typedef typename Traits::SecondTriangle SecondTriangle;
if (!aGeometry) {
TriangleVertices base1 = aTraits.MakeVertex(FirstTriangle());
TriangleVertices base2 = aTraits.MakeVertex(SecondTriangle());
auto data1 = aTraits.MakeVertexData(FirstTriangle());
auto data2 = aTraits.MakeVertexData(SecondTriangle());
return aBuffer->PrependItem(base1, data1) && aBuffer->PrependItem(base2, data2);
}
auto triangles = aTraits.GenerateTriangles(*aGeometry);
for (const auto& triangle : triangles) {
TriangleVertices base = aTraits.MakeVertex(triangle);
auto data = aTraits.MakeVertexData(triangle);
if (!aBuffer->PrependItem(base, data)) {
return false;
}
}
return true;
}
template <typename Traits>
inline bool
BatchRenderPass<Traits>::Txn::AddImpl(const Traits& aTraits)
{
VertexStagingBuffer* instances = mPass->GetInstances();
if (mPass->mGeometry == GeometryMode::Polygon) {
if (const Maybe<gfx::Polygon>& geometry = aTraits.geometry()) {
gfx::Polygon polygon = geometry->ClipPolygon(aTraits.rect());
if (polygon.IsEmpty()) {
return true;
}
return AddShaderTriangles(instances, aTraits, &polygon);
}
return AddShaderTriangles(instances, aTraits);
}
typedef typename Traits::UnitQuadVertex UnitQuadVertex;
typedef typename Traits::UnitQuad UnitQuad;
UnitQuadVertex base = aTraits.MakeUnitQuadVertex();
auto data = aTraits.MakeVertexData(UnitQuad());
return instances->AddItem(base, data);
}
} // namespace layers
} // namespace mozilla
#endif // mozilla_gfx_layers_mlgpu_RenderPassMLGPU_inl_h

Просмотреть файл

@ -17,6 +17,7 @@
#include "SharedBufferMLGPU.h"
#include "mozilla/layers/LayersHelpers.h"
#include "mozilla/layers/LayersMessages.h"
#include "RenderPassMLGPU-inl.h"
namespace mozilla {
namespace layers {
@ -209,8 +210,7 @@ RenderPassMLGPU::PrepareForRendering()
ShaderRenderPass::ShaderRenderPass(FrameBuilder* aBuilder, const ItemInfo& aItem)
: RenderPassMLGPU(aBuilder, aItem),
mGeometry(GeometryMode::Unknown),
mHasRectTransformAndClip(aItem.HasRectTransformAndClip()),
mItems(mDevice)
mHasRectTransformAndClip(aItem.HasRectTransformAndClip())
{
mMask = aItem.layer->GetMask();
if (mMask) {
@ -267,35 +267,17 @@ ShaderRenderPass::SetGeometry(const ItemInfo& aItem, GeometryMode aMode)
// in the wrong order. We address this by automatically reversing
// the buffers we use to build vertices.
if (aItem.renderOrder != RenderOrder::FrontToBack) {
mVertices.SetReversed();
mInstances.SetReversed();
// For arbitrary geometry items, each vertex explicitly indexes into
// the constant buffer, and so we must preserve the association it
// created. However for normal unit-quad items, the constant buffer
// order must match the vertex order.
if (mGeometry != GeometryMode::Polygon) {
mItems.SetReversed();
}
}
}
void
ShaderRenderPass::PrepareForRendering()
{
if (mItems.IsEmpty()) {
if (mInstances.IsEmpty()) {
return;
}
if (!mVertices.IsEmpty()) {
if (!PrepareVertexBuffer()) {
return;
}
} else {
if (!PrepareInstanceBuffer()) {
return;
}
}
if (!PrepareItemBuffer() ||
if (!mDevice->GetSharedVertexBuffer()->Allocate(&mInstanceBuffer, mInstances) ||
!SetupPSBuffer0(GetOpacity()) ||
!OnPrepareBuffers())
{
@ -304,35 +286,6 @@ ShaderRenderPass::PrepareForRendering()
return RenderPassMLGPU::PrepareForRendering();
}
bool
ShaderRenderPass::PrepareVertexBuffer()
{
// Geometry batches always build vertices, and do not have an instance buffer.
MOZ_ASSERT(!mVertices.IsEmpty());
MOZ_ASSERT(mGeometry == GeometryMode::Polygon);
MOZ_ASSERT(mInstances.IsEmpty());
return mDevice->GetSharedVertexBuffer()->Allocate(&mVertexBuffer, mVertices);
}
bool
ShaderRenderPass::PrepareInstanceBuffer()
{
// We should not be using the polygon vertex buffer, and we should have
// added items.
MOZ_ASSERT(mVertices.IsEmpty());
MOZ_ASSERT(mGeometry == GeometryMode::UnitQuad);
MOZ_ASSERT(!mInstances.IsEmpty());
return mDevice->GetSharedVertexBuffer()->Allocate(&mInstanceBuffer, mInstances);
}
bool
ShaderRenderPass::PrepareItemBuffer()
{
return mDevice->GetSharedVSBuffer()->Allocate(&mItemBuffer, mItems);
}
bool
ShaderRenderPass::SetupPSBuffer0(float aOpacity)
{
@ -348,7 +301,7 @@ ShaderRenderPass::SetupPSBuffer0(float aOpacity)
void
ShaderRenderPass::ExecuteRendering()
{
if (mVertices.IsEmpty() && mInstances.IsEmpty()) {
if (mInstances.IsEmpty()) {
return;
}
@ -361,19 +314,16 @@ ShaderRenderPass::ExecuteRendering()
SetupPipeline();
if (mGeometry == GeometryMode::Polygon) {
mDevice->SetTopology(MLGPrimitiveTopology::TriangleList);
mDevice->SetVertexBuffer(0, &mVertexBuffer);
mDevice->SetTopology(MLGPrimitiveTopology::UnitTriangle);
} else {
mDevice->SetTopology(MLGPrimitiveTopology::UnitQuad);
mDevice->SetVertexBuffer(1, &mInstanceBuffer);
}
mDevice->SetVSConstantBuffer(kItemBufferSlot, &mItemBuffer);
mDevice->SetVertexBuffer(1, &mInstanceBuffer);
if (mGeometry == GeometryMode::Polygon) {
mDevice->Draw(mVertexBuffer.NumVertices(), 0);
mDevice->DrawInstanced(3, mInstanceBuffer.NumVertices(), 0, 0);
} else {
mDevice->DrawInstanced(4, mItemBuffer.NumItems(), 0, 0);
mDevice->DrawInstanced(4, mInstanceBuffer.NumVertices(), 0, 0);
}
}
@ -462,9 +412,9 @@ SolidColorPass::AddToPass(LayerMLGPU* aLayer, ItemInfo& aInfo)
const LayerIntRegion& region = aLayer->GetShadowVisibleRegion();
for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
const IntRect rect = iter.Get().ToUnknownRect();
ColorTraits traits(Rect(rect), color);
ColorTraits traits(aInfo, Rect(rect), color);
if (!txn.Add(traits, aInfo)) {
if (!txn.Add(traits)) {
return false;
}
}
@ -566,8 +516,8 @@ TexturedRenderPass::AddClippedItem(Txn& aTxn,
DecomposeIntoNoRepeatRects(aDrawRect, textureCoords, &layerRects, &textureRects);
for (size_t i = 0; i < numRects; i++) {
TexturedTraits traits(layerRects[i], textureRects[i]);
if (!aTxn.Add(traits, aInfo)) {
TexturedTraits traits(aInfo, layerRects[i], textureRects[i]);
if (!aTxn.Add(traits)) {
return false;
}
}

Просмотреть файл

@ -159,15 +159,9 @@ public:
ShaderRenderPass(FrameBuilder* aBuilder, const ItemInfo& aItem);
// Used by ShaderDefinitions for writing traits.
VertexStagingBuffer* GetVertices() {
return &mVertices;
}
VertexStagingBuffer* GetInstances() {
return &mInstances;
}
ConstantStagingBuffer* GetItems() {
return &mItems;
}
bool IsCompatible(const ItemInfo& aItem) override;
void PrepareForRendering() override;
@ -204,15 +198,6 @@ protected:
return true;
}
// Prepare the buffer bound to "sItems" in shaders. This is used for opaque
// batches when the items can be drawn in front-to-back order.
bool PrepareItemBuffer();
// Prepare the vertex buffer, if not using a unit quad. This is used for
// opaque batches when the items can be drawn in front-to-back order.
bool PrepareVertexBuffer();
bool PrepareInstanceBuffer();
// Prepare the mask/opacity buffer bound in most pixel shaders.
bool SetupPSBuffer0(float aOpacity);
@ -228,15 +213,9 @@ protected:
RefPtr<MaskOperation> mMask;
bool mHasRectTransformAndClip;
VertexStagingBuffer mVertices;
VertexBufferSection mVertexBuffer;
VertexStagingBuffer mInstances;
VertexBufferSection mInstanceBuffer;
ConstantStagingBuffer mItems;
ConstantBufferSection mItemBuffer;
ConstantBufferSection mPSBuffer0;
};
@ -261,44 +240,21 @@ protected:
public:
explicit Txn(BatchRenderPass* aPass)
: mPass(aPass),
mPrevVertexPos(aPass->mVertices.GetPosition()),
mPrevItemPos(aPass->mItems.GetPosition()),
mPrevInstancePos(aPass->mInstances.GetPosition())
{}
// Add an item based on a draw rect, layer, and optional geometry. The Traits
// must contain, at minimum:
//
// - An "mRect" member as a gfx::Rect, containing the draw rect.
// - An "AddInstanceTo" method, which adds instance data for
// shaders using unit-quad vertices.
// - An "AddVerticesTo" method, which adds triangle list vertices
// to a batch's shader data, with optional geometry.
// - An "AddItemTo" method, which adds constant buffer data if
// needed.
//
bool Add(const Traits& aTraits, const ItemInfo& aInfo) {
// If this succeeds, but we clip the polygon below, that's okay.
// Polygons do not use instanced rendering so this won't break
// ordering.
if (!aTraits.AddItemTo(mPass)) {
return false;
bool Add(const Traits& aTraits) {
if (!AddImpl(aTraits)) {
return Fail();
}
if (mPass->mGeometry == GeometryMode::Polygon) {
size_t itemIndex = mPass->GetItems()->NumItems() - 1;
if (aInfo.geometry) {
gfx::Polygon polygon = aInfo.geometry->ClipPolygon(aTraits.mRect);
if (polygon.IsEmpty()) {
return true;
}
return aTraits.AddVerticesTo(mPass, aInfo, itemIndex, &polygon);
}
return aTraits.AddVerticesTo(mPass, aInfo, itemIndex);
}
return aTraits.AddInstanceTo(mPass, aInfo);
return true;
}
// Add an item based on a draw rect, layer, and optional geometry. This is
// defined in RenderPassMLGPU-inl.h, since it needs access to
// ShaderDefinitionsMLGPU-inl.h.
bool AddImpl(const Traits& aTraits);
bool Fail() {
MOZ_ASSERT(!mStatus.isSome() || !mStatus.value());
mStatus = Some(false);
@ -316,9 +272,7 @@ protected:
~Txn() {
if (!mStatus.isSome() || !mStatus.value()) {
mPass->mVertices.RestorePosition(mPrevVertexPos);
mPass->mInstances.RestorePosition(mPrevInstancePos);
mPass->mItems.RestorePosition(mPrevItemPos);
}
}

Просмотреть файл

@ -11,41 +11,80 @@ namespace mozilla {
namespace layers {
namespace mlg {
// This is a helper class for writing vertices for unit-quad based
// shaders, since they all share the same input layout.
struct SimpleVertex
inline const Maybe<gfx::Polygon>&
SimpleTraits::geometry() const
{
SimpleVertex(const gfx::Rect& aRect,
uint32_t aLayerIndex,
int aDepth)
: rect(aRect),
layerIndex(aLayerIndex),
depth(aDepth)
{}
gfx::Rect rect;
uint32_t layerIndex;
int depth;
};
bool
SimpleTraits::AddInstanceTo(ShaderRenderPass* aPass, const ItemInfo& aItem) const
{
return aPass->GetInstances()->AddItem(SimpleVertex(
mRect, aItem.layerIndex, aItem.sortOrder));
return mItem.geometry;
}
inline bool
ColorTraits::AddItemTo(ShaderRenderPass* aPass) const
inline nsTArray<gfx::Triangle>
SimpleTraits::GenerateTriangles(const gfx::Polygon& aPolygon) const
{
return aPass->GetItems()->AddItem(mColor);
return aPolygon.ToTriangles();
}
inline bool
TexturedTraits::AddItemTo(ShaderRenderPass* aPass) const
inline SimpleTraits::TriangleVertices
SimpleTraits::MakeVertex(const FirstTriangle& aIgnore) const
{
return aPass->GetItems()->AddItem(mTexCoords);
TriangleVertices v = {
mRect.BottomLeft(), mRect.TopLeft(), mRect.TopRight(),
mItem.layerIndex, mItem.sortOrder
};
return v;
}
inline SimpleTraits::TriangleVertices
SimpleTraits::MakeVertex(const SecondTriangle& aIgnore) const
{
TriangleVertices v = {
mRect.TopRight(), mRect.BottomRight(), mRect.BottomLeft(),
mItem.layerIndex, mItem.sortOrder
};
return v;
}
inline SimpleTraits::TriangleVertices
SimpleTraits::MakeVertex(const gfx::Triangle& aTriangle) const
{
TriangleVertices v = {
aTriangle.p1, aTriangle.p2, aTriangle.p3,
mItem.layerIndex, mItem.sortOrder
};
return v;
}
inline SimpleTraits::UnitQuadVertex
SimpleTraits::MakeUnitQuadVertex() const
{
UnitQuadVertex v = { mRect, mItem.layerIndex, mItem.sortOrder };
return v;
}
inline nsTArray<gfx::TexturedTriangle>
TexturedTraits::GenerateTriangles(const gfx::Polygon& aPolygon) const
{
return GenerateTexturedTriangles(aPolygon, mRect, mTexCoords);
}
inline TexturedTraits::VertexData
TexturedTraits::MakeVertexData(const FirstTriangle& aIgnore) const
{
VertexData v = { mTexCoords.BottomLeft(), mTexCoords.TopLeft(), mTexCoords.TopRight() };
return v;
}
inline TexturedTraits::VertexData
TexturedTraits::MakeVertexData(const SecondTriangle& aIgnore) const
{
VertexData v = { mTexCoords.TopRight(), mTexCoords.BottomRight(), mTexCoords.BottomLeft() };
return v;
}
inline TexturedTraits::VertexData
TexturedTraits::MakeVertexData(const gfx::TexturedTriangle& aTriangle) const
{
VertexData v = { aTriangle.textureCoords.p1, aTriangle.textureCoords.p2, aTriangle.textureCoords.p3 };
return v;
}
} // namespace mlg

Просмотреть файл

@ -1,127 +0,0 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#include "ShaderDefinitionsMLGPU.h"
#include "RenderPassMLGPU.h"
namespace mozilla {
namespace layers {
namespace mlg {
using namespace gfx;
// Helper function for adding triangle vertices to shader buffers.
struct TriangleVertex
{
TriangleVertex(const gfx::Point& aPoint,
const ItemInfo& aItem,
uint32_t aItemIndex)
: point(aPoint),
layerIndex(aItem.layerIndex),
depth(aItem.sortOrder),
itemIndex(aItemIndex)
{}
gfx::Point point;
uint32_t layerIndex;
int depth;
uint32_t itemIndex;
};
bool
SimpleTraits::AddVerticesTo(ShaderRenderPass* aPass,
const ItemInfo& aItem,
uint32_t aItemIndex,
const gfx::Polygon* aGeometry) const
{
VertexStagingBuffer* vertices = aPass->GetVertices();
// If we don't have geometry, take a fast path where we can hardcode
// the set of triangles.
if (!aGeometry) {
if (!vertices->PrependItem(TriangleVertex(mRect.BottomLeft(), aItem, aItemIndex)) ||
!vertices->PrependItem(TriangleVertex(mRect.TopLeft(), aItem, aItemIndex)) ||
!vertices->PrependItem(TriangleVertex(mRect.TopRight(), aItem, aItemIndex)) ||
!vertices->PrependItem(TriangleVertex(mRect.TopRight(), aItem, aItemIndex)) ||
!vertices->PrependItem(TriangleVertex(mRect.BottomRight(), aItem, aItemIndex)) ||
!vertices->PrependItem(TriangleVertex(mRect.BottomLeft(), aItem, aItemIndex)))
{
return false;
}
return true;
}
// Slow path: full-fledged geometry.
nsTArray<Triangle> triangles = aGeometry->ToTriangles();
for (const Triangle& t : triangles) {
if (!vertices->PrependItem(TriangleVertex(t.p1, aItem, aItemIndex)) ||
!vertices->PrependItem(TriangleVertex(t.p2, aItem, aItemIndex)) ||
!vertices->PrependItem(TriangleVertex(t.p3, aItem, aItemIndex)))
{
return false;
}
}
return true;
}
struct TexturedTriangleVertex
{
TexturedTriangleVertex(const gfx::Point& aPoint,
const gfx::Point& aTexCoord,
const ItemInfo& aItem)
: point(aPoint),
texCoord(aTexCoord),
layerIndex(aItem.layerIndex),
depth(aItem.sortOrder)
{}
gfx::Point point;
gfx::Point texCoord;
uint32_t layerIndex;
int depth;
};
bool
TexturedTraits::AddVerticesTo(ShaderRenderPass* aPass,
const ItemInfo& aItem,
uint32_t aItemIndex,
const gfx::Polygon* aGeometry) const
{
VertexStagingBuffer* vertices = aPass->GetVertices();
using Vertex = TexturedTriangleVertex;
// If we don't have geometry, take a fast path where we can hardcode
// the set of triangles.
if (!aGeometry) {
if (!vertices->PrependItem(Vertex(mRect.BottomLeft(), mTexCoords.BottomLeft(), aItem)) ||
!vertices->PrependItem(Vertex(mRect.TopLeft(), mTexCoords.TopLeft(), aItem)) ||
!vertices->PrependItem(Vertex(mRect.TopRight(), mTexCoords.TopRight(), aItem)) ||
!vertices->PrependItem(Vertex(mRect.TopRight(), mTexCoords.TopRight(), aItem)) ||
!vertices->PrependItem(Vertex(mRect.BottomRight(), mTexCoords.BottomRight(), aItem)) ||
!vertices->PrependItem(Vertex(mRect.BottomLeft(), mTexCoords.BottomLeft(), aItem)))
{
return false;
}
return true;
}
// Slow path: full-fledged geometry.
nsTArray<TexturedTriangle> triangles =
GenerateTexturedTriangles(*aGeometry, mRect, mTexCoords);
for (const TexturedTriangle& t: triangles) {
if (!vertices->PrependItem(Vertex(t.p1, t.textureCoords.p1, aItem)) ||
!vertices->PrependItem(Vertex(t.p2, t.textureCoords.p2, aItem)) ||
!vertices->PrependItem(Vertex(t.p3, t.textureCoords.p3, aItem)))
{
return false;
}
}
return true;
}
} // namespace mlg
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -29,7 +29,6 @@ static const size_t kMaxConstantBufferSize = 4096 * kConstantBufferElementSize;
// uniformity.
static const uint32_t kWorldConstantBufferSlot = 0;
static const uint32_t kLayerBufferSlot = 1;
static const uint32_t kItemBufferSlot = 2;
static const uint32_t kMaskBufferSlot = 3;
static const uint32_t kBlendConstantBufferSlot = 4;
static const uint32_t kClearConstantBufferSlot = 2;
@ -98,41 +97,92 @@ struct BlendVertexShaderConstants {
struct SimpleTraits
{
explicit SimpleTraits(const gfx::Rect& aRect)
: mRect(aRect)
explicit SimpleTraits(const ItemInfo& aItem, const gfx::Rect& aRect)
: mItem(aItem),
mRect(aRect)
{}
bool AddInstanceTo(ShaderRenderPass* aPass, const ItemInfo& aItem) const;
bool AddVerticesTo(ShaderRenderPass* aPass,
const ItemInfo& aItem,
uint32_t aItemIndex,
const gfx::Polygon* aGeometry = nullptr) const;
// Helper nonce structs so functions can break vertex data up by each
// triangle in a quad, or return vertex info for a unit quad.
struct AnyTriangle { };
struct FirstTriangle : AnyTriangle { };
struct SecondTriangle : AnyTriangle { };
struct UnitQuad { };
// This is the base vertex layout used by all unit quad shaders.
struct UnitQuadVertex {
gfx::Rect rect;
uint32_t layerIndex;
int depth;
};
// This is the base vertex layout used by all unit triangle shaders.
struct TriangleVertices {
gfx::Point p1, p2, p3;
uint32_t layerIndex;
int depth;
};
// Helper functions for populating a TriangleVertices. The first two use mRect
// to generate triangles, the third function uses coordinates from an already
// computed triangle.
TriangleVertices MakeVertex(const FirstTriangle& aIgnore) const;
TriangleVertices MakeVertex(const SecondTriangle& aIgnore) const;
TriangleVertices MakeVertex(const gfx::Triangle& aTriangle) const;
UnitQuadVertex MakeUnitQuadVertex() const;
// This default GenerateTriangles only computes the 3 points of each triangle
// in the polygon. If needed, shaders can override this and return a more
// complex triangle, to encode dependent information in extended vertex data.
//
// AddShaderVertices will deduce this return type. It should be an nsTArray<T>
// where T inherits from Triangle.
nsTArray<gfx::Triangle> GenerateTriangles(const gfx::Polygon& aPolygon) const;
// Accessors.
const Maybe<gfx::Polygon>& geometry() const;
const gfx::Rect& rect() const {
return mRect;
}
const ItemInfo& mItem;
gfx::Rect mRect;
};
struct ColorTraits : public SimpleTraits
{
ColorTraits(const gfx::Rect& aRect, const gfx::Color& aColor)
: SimpleTraits(aRect), mColor(aColor)
ColorTraits(const ItemInfo& aItem, const gfx::Rect& aRect, const gfx::Color& aColor)
: SimpleTraits(aItem, aRect), mColor(aColor)
{}
bool AddItemTo(ShaderRenderPass* aPass) const;
// Color data is the same across all vertex types.
template <typename VertexType>
const gfx::Color& MakeVertexData(const VertexType& aIgnore) const {
return mColor;
}
gfx::Color mColor;
};
struct TexturedTraits : public SimpleTraits
{
TexturedTraits(const gfx::Rect& aRect, const gfx::Rect& aTexCoords)
: SimpleTraits(aRect), mTexCoords(aTexCoords)
TexturedTraits(const ItemInfo& aItem, const gfx::Rect& aRect, const gfx::Rect& aTexCoords)
: SimpleTraits(aItem, aRect), mTexCoords(aTexCoords)
{}
bool AddVerticesTo(ShaderRenderPass* aPass,
const ItemInfo& aItem,
uint32_t aItemIndex,
const gfx::Polygon* aGeometry = nullptr) const;
bool AddItemTo(ShaderRenderPass* aPass) const;
// Textured triangles need to compute a texture coordinate for each vertex.
nsTArray<gfx::TexturedTriangle> GenerateTriangles(const gfx::Polygon& aPolygon) const;
struct VertexData {
gfx::Point p1, p2, p3;
};
VertexData MakeVertexData(const FirstTriangle& aIgnore) const;
VertexData MakeVertexData(const SecondTriangle& aIgnore) const;
VertexData MakeVertexData(const gfx::TexturedTriangle& aTriangle) const;
const gfx::Rect& MakeVertexData(const UnitQuad& aIgnore) const {
return mTexCoords;
}
gfx::Rect mTexCoords;
};

Просмотреть файл

@ -51,6 +51,15 @@ public:
return AppendItem(aItem);
}
// Helper for adding a single item as two components.
template <typename T1, typename T2>
bool AddItem(const T1& aItem1, const T2& aItem2) {
if (mReversed) {
return PrependItem(aItem1, aItem2);
}
return AppendItem(aItem1, aItem2);
}
// This may only be called on forward buffers.
template <typename T>
bool AppendItem(const T& aItem) {
@ -77,6 +86,20 @@ public:
return true;
}
// Append an item in two stages.
template <typename T1, typename T2>
bool AppendItem(const T1& aFirst, const T2& aSecond) {
struct Combined {
T1 first;
T2 second;
} value = { aFirst, aSecond };
// The combined value must be packed.
static_assert(sizeof(value) == sizeof(aFirst) + sizeof(aSecond),
"Items must be packed within struct");
return AppendItem(value);
}
// This may only be called on reversed buffers.
template <typename T>
bool PrependItem(const T& aItem) {
@ -103,6 +126,20 @@ public:
return true;
}
// Prepend an item in two stages.
template <typename T1, typename T2>
bool PrependItem(const T1& aFirst, const T2& aSecond) {
struct Combined {
T1 first;
T2 second;
} value = { aFirst, aSecond };
// The combined value must be packed.
static_assert(sizeof(value) == sizeof(aFirst) + sizeof(aSecond),
"Items must be packed within struct");
return PrependItem(value);
}
size_t NumBytes() const {
return mReversed
? mEnd - mPos

Просмотреть файл

@ -428,7 +428,6 @@ UNIFIED_SOURCES += [
'mlgpu/PaintedLayerMLGPU.cpp',
'mlgpu/RenderPassMLGPU.cpp',
'mlgpu/RenderViewMLGPU.cpp',
'mlgpu/ShaderDefinitionsMLGPU.cpp',
'mlgpu/SharedBufferMLGPU.cpp',
'mlgpu/StagingBuffer.cpp',
'mlgpu/TexturedLayerMLGPU.cpp',

Просмотреть файл

@ -837,6 +837,18 @@ WebRenderBridgeParent::RecvClearCachedResources()
return IPC_OK();
}
mCompositorBridge->ObserveLayerUpdate(GetLayersId(), GetChildLayerObserverEpoch(), false);
// Clear resources
// XXX Can we clear more resources?
++mWrEpoch; // Update webrender epoch
mApi->ClearRootDisplayList(wr::NewEpoch(mWrEpoch), mPipelineId);
// Schedule composition to clean up Pipeline
mCompositorScheduler->ScheduleComposition();
// Remove animations.
for (std::unordered_set<uint64_t>::iterator iter = mActiveAnimations.begin(); iter != mActiveAnimations.end(); iter++) {
mAnimStorage->ClearById(*iter);
}
mActiveAnimations.clear();
return IPC_OK();
}

Просмотреть файл

@ -868,17 +868,12 @@ DeviceManagerDx::ForceDeviceReset(ForcedDeviceResetReason aReason)
Telemetry::Accumulate(Telemetry::FORCED_DEVICE_RESET_REASON, uint32_t(aReason));
{
MutexAutoLock lock(mDeviceLock);
mDeviceResetReason = Some(DeviceResetReason::FORCED_RESET);
if (!mDeviceResetReason) {
mDeviceResetReason = Some(DeviceResetReason::FORCED_RESET);
}
}
}
void
DeviceManagerDx::NotifyD3D9DeviceReset()
{
MutexAutoLock lock(mDeviceLock);
mDeviceResetReason = Some(DeviceResetReason::D3D9_RESET);
}
void
DeviceManagerDx::DisableD3D11AfterCrash()
{

Просмотреть файл

@ -103,7 +103,6 @@ public:
// Note: these set the cached device reset reason, which will be picked up
// on the next frame.
void ForceDeviceReset(ForcedDeviceResetReason aReason);
void NotifyD3D9DeviceReset();
private:
// Pre-load any compositor resources that are expensive, and are needed when we

Просмотреть файл

@ -441,12 +441,12 @@ gfxWindowsPlatform::HandleDeviceReset()
imgLoader::PrivateBrowsingLoader()->ClearCache(false);
gfxAlphaBoxBlur::ShutdownBlurCache();
if (XRE_IsContentProcess()) {
// Fetch updated device parameters.
FetchAndImportContentDeviceData();
UpdateANGLEConfig();
}
gfxConfig::Reset(Feature::D3D11_COMPOSITING);
gfxConfig::Reset(Feature::ADVANCED_LAYERS);
gfxConfig::Reset(Feature::D3D11_HW_ANGLE);
gfxConfig::Reset(Feature::DIRECT2D);
InitializeConfig();
InitializeDevices();
UpdateANGLEConfig();
return true;
@ -1419,6 +1419,11 @@ gfxWindowsPlatform::InitializeAdvancedLayersConfig()
nsCString message, failureId;
if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_ADVANCED_LAYERS, &message, failureId)) {
al.Disable(FeatureStatus::Blacklisted, message.get(), failureId);
} else if (Preferences::GetBool("layers.mlgpu.sanity-test-failed", false)) {
al.Disable(
FeatureStatus::Broken,
"Failed to render sanity test",
NS_LITERAL_CSTRING("FEATURE_FAILURE_FAILED_TO_RENDER"));
}
}

Просмотреть файл

@ -372,9 +372,17 @@ Scope::clone(JSContext* cx, HandleScope scope, HandleScope enclosing)
}
switch (scope->kind_) {
case ScopeKind::Function:
case ScopeKind::Function: {
RootedScript script(cx, scope->as<FunctionScope>().script());
const char* filename = script->filename();
// If the script has an internal URL, include it in the crash reason. If
// not, it may be a web URL, and therefore privacy-sensitive.
if (!strncmp(filename, "chrome:", 7) || !strncmp(filename, "resource:", 9))
MOZ_CRASH_UNSAFE_PRINTF("Use FunctionScope::clone (script URL: %s)", filename);
MOZ_CRASH("Use FunctionScope::clone.");
break;
}
case ScopeKind::FunctionBodyVar:
case ScopeKind::ParameterExpressionVar: {

Просмотреть файл

@ -40,8 +40,7 @@ public:
~OverflowChangedTracker()
{
// XXXheycam Temporarily downgrade this assertion (bug 1324647).
NS_ASSERTION_STYLO_WARNING(mEntryList.empty(), "Need to flush before destroying!");
NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!");
}
/**

Просмотреть файл

@ -753,12 +753,32 @@ StackTrace::Get(Thread* aT)
StackTrace tmp;
{
AutoUnlockState unlock;
uint32_t skipFrames = 2;
if (MozStackWalk(StackWalkCallback, skipFrames,
MaxFrames, &tmp, 0, nullptr)) {
// Handle the common case first. All is ok. Nothing to do.
} else {
tmp.mLength = 0;
// In each of the following cases, skipFrames is chosen so that the
// first frame in each stack trace is a replace_* function.
#ifdef XP_MACOSX
// This avoids MozStackWalk(), which has become unusably slow on Mac due to
// changes in libunwind.
//
// This code is cribbed from the Gecko Profiler, which also uses
// FramePointerStackWalk() on Mac: Registers::SyncPopulate() for the frame
// pointer, and GetStackTop() for the stack end.
void* fp;
asm (
// Dereference %rbp to get previous %rbp
"movq (%%rbp), %0\n\t"
:
"=r"(fp)
);
void* stackEnd = pthread_get_stackaddr_np(pthread_self());
bool ok = FramePointerStackWalk(StackWalkCallback, /* skipFrames = */ 0,
MaxFrames, &tmp,
reinterpret_cast<void**>(fp), stackEnd);
#else
bool ok = MozStackWalk(StackWalkCallback, /* skipFrames = */ 2,
MaxFrames, &tmp, 0, nullptr);
#endif
if (!ok) {
tmp.mLength = 0; // re-zero in case the stack walk function changed it
}
}

Просмотреть файл

@ -180,6 +180,9 @@ pref("dom.performance.enable_notify_performance_timing", false);
// Enable Permission API's .revoke() method
pref("dom.permissions.revoke.enable", false);
// Enable exposing timeToNonBlankPaint
pref("dom.performance.time_to_non_blank_paint.enabled", false);
// Enable Performance Observer API
#ifdef NIGHTLY_BUILD
pref("dom.enable_performance_observer", true);

Просмотреть файл

@ -127,46 +127,28 @@ class TerminalLoggingHandler(logging.Handler):
self.release()
class BuildProgressFooter(object):
"""Handles display of a build progress indicator in a terminal.
class Footer(object):
"""Handles display of a footer in a terminal.
When mach builds inside a blessings-supported terminal, it will render
progress information collected from a BuildMonitor. This class converts the
state of BuildMonitor into terminal output.
This class implements the functionality common to all mach commands
that render a footer.
"""
def __init__(self, terminal, monitor):
def __init__(self, terminal):
# terminal is a blessings.Terminal.
self._t = terminal
self._fh = sys.stdout
self.tiers = monitor.tiers.tier_status.viewitems()
def clear(self):
"""Removes the footer from the current terminal."""
self._fh.write(self._t.move_x(0))
self._fh.write(self._t.clear_eol())
def draw(self):
"""Draws this footer in the terminal."""
def write(self, parts):
"""Write some output in the footer, accounting for terminal width.
if not self.tiers:
return
# The drawn terminal looks something like:
# TIER: base nspr nss js platform app SUBTIER: static export libs tools DIRECTORIES: 06/09 (memory)
# This is a list of 2-tuples of (encoding function, input). None means
# no encoding. For a full reason on why we do things this way, read the
# big comment below.
parts = [('bold', 'TIER:')]
append = parts.append
for tier, status in self.tiers:
if status is None:
append(tier)
elif status == 'finished':
append(('green', tier))
else:
append(('underline_yellow', tier))
parts is a list of 2-tuples of (encoding_function, input).
None means no encoding."""
# We don't want to write more characters than the current width of the
# terminal otherwise wrapping may result in weird behavior. We can't
@ -199,15 +181,47 @@ class BuildProgressFooter(object):
self._fh.write(' '.join(write_pieces))
class BuildOutputManager(LoggingMixin):
"""Handles writing build output to a terminal, to logs, etc."""
class BuildProgressFooter(Footer):
"""Handles display of a build progress indicator in a terminal.
def __init__(self, log_manager, monitor):
When mach builds inside a blessings-supported terminal, it will render
progress information collected from a BuildMonitor. This class converts the
state of BuildMonitor into terminal output.
"""
def __init__(self, terminal, monitor):
Footer.__init__(self, terminal)
self.tiers = monitor.tiers.tier_status.viewitems()
def draw(self):
"""Draws this footer in the terminal."""
if not self.tiers:
return
# The drawn terminal looks something like:
# TIER: static export libs tools
parts = [('bold', 'TIER:')]
append = parts.append
for tier, status in self.tiers:
if status is None:
append(tier)
elif status == 'finished':
append(('green', tier))
else:
append(('underline_yellow', tier))
self.write(parts)
class OutputManager(LoggingMixin):
"""Handles writing job output to a terminal or log."""
def __init__(self, log_manager, footer):
self.populate_logger()
self.monitor = monitor
self.footer = None
terminal = log_manager.terminal
# TODO convert terminal footer to config file setting.
@ -217,7 +231,7 @@ class BuildOutputManager(LoggingMixin):
return
self.t = terminal
self.footer = BuildProgressFooter(terminal, monitor)
self.footer = footer
self._handler = TerminalLoggingHandler()
self._handler.setFormatter(log_manager.terminal_formatter)
@ -235,11 +249,6 @@ class BuildOutputManager(LoggingMixin):
# Prevents the footer from being redrawn if logging occurs.
self._handler.footer = None
# Ensure the resource monitor is stopped because leaving it running
# could result in the process hanging on exit because the resource
# collection child process hasn't been told to stop.
self.monitor.stop_resource_recording()
def write_line(self, line):
if self.footer:
self.footer.clear()
@ -256,6 +265,22 @@ class BuildOutputManager(LoggingMixin):
self.footer.clear()
self.footer.draw()
class BuildOutputManager(OutputManager):
"""Handles writing build output to a terminal, to logs, etc."""
def __init__(self, log_manager, monitor, footer):
self.monitor = monitor
OutputManager.__init__(self, log_manager, footer)
def __exit__(self, exc_type, exc_value, traceback):
OutputManager.__exit__(self, exc_type, exc_value, traceback)
# Ensure the resource monitor is stopped because leaving it running
# could result in the process hanging on exit because the resource
# collection child process hasn't been told to stop.
self.monitor.stop_resource_recording()
def on_line(self, line):
warning, state_changed, relevant = self.monitor.on_line(line)
@ -331,12 +356,13 @@ class Build(MachCommandBase):
monitor = self._spawn(BuildMonitor)
monitor.init(warnings_path)
ccache_start = monitor.ccache_stats()
footer = BuildProgressFooter(self.log_manager.terminal, monitor)
# Disable indexing in objdir because it is not necessary and can slow
# down builds.
mkdir(self.topobjdir, not_indexed=True)
with BuildOutputManager(self.log_manager, monitor) as output:
with BuildOutputManager(self.log_manager, monitor, footer) as output:
monitor.start()
if directory is not None and not what:

Просмотреть файл

@ -271,7 +271,9 @@ var UninstallObserver = {
if (!this.leaveStorage) {
// Clear browser.local.storage
ExtensionStorage.clear(addon.id);
AsyncShutdown.profileChangeTeardown.addBlocker(
`Clear Extension Storage ${addon.id}`,
ExtensionStorage.clear(addon.id));
// Clear any IndexedDB storage created by the extension
let baseURI = NetUtil.newURI(`moz-extension://${uuid}/`);

Просмотреть файл

@ -806,10 +806,14 @@ class ChildAPIManager {
* @param {Array} args The parameters for the function.
* @param {function(*)} [callback] The callback to be called when the function
* completes.
* @param {object} [options] Extra options.
* @param {boolean} [options.noClone = false] If true, do not clone
* the arguments into an extension sandbox before calling the API
* method.
* @returns {Promise|undefined} Must be void if `callback` is set, and a
* promise otherwise. The promise is resolved when the function completes.
*/
callParentAsyncFunction(path, args, callback) {
callParentAsyncFunction(path, args, callback, options = {}) {
let callId = getUniqueId();
let deferred = PromiseUtils.defer();
this.callPromises.set(callId, deferred);
@ -819,6 +823,7 @@ class ChildAPIManager {
callId,
path,
args,
noClone: options.noClone || false,
});
return this.context.wrapPromise(deferred.promise, callback);

Просмотреть файл

@ -1101,7 +1101,7 @@ class SchemaAPIManager extends EventEmitter {
sandboxName: `Namespace of ext-*.js scripts for ${this.processType}`,
});
Object.assign(global, {global, Cc, Ci, Cu, Cr, XPCOMUtils, ChromeWorker, ExtensionCommon, MatchPattern, MatchPatternSet, extensions: this});
Object.assign(global, {global, Cc, Ci, Cu, Cr, XPCOMUtils, ChromeWorker, ExtensionCommon, MatchPattern, MatchPatternSet, StructuredCloneHolder, extensions: this});
Cu.import("resource://gre/modules/AppConstants.jsm", global);
Cu.import("resource://gre/modules/ExtensionAPI.jsm", global);

Просмотреть файл

@ -667,7 +667,7 @@ ParentAPIManager = {
};
try {
let args = Cu.cloneInto(data.args, context.sandbox);
let args = data.noClone ? data.args : Cu.cloneInto(data.args, context.sandbox);
let pendingBrowser = context.pendingEventBrowser;
let fun = await context.apiCan.asyncFindAPIPath(data.path);
let result = context.withPendingBrowser(pendingBrowser,

Просмотреть файл

@ -13,66 +13,123 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
"resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
"resource://gre/modules/JSONFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
const global = this;
function isStructuredCloneHolder(value) {
return (value && typeof value === "object" &&
Cu.getClassName(value, true) === "StructuredCloneHolder");
}
class SerializeableMap extends Map {
toJSON() {
let result = {};
for (let [key, value] of this) {
if (isStructuredCloneHolder(value)) {
value = value.deserialize(global);
this.set(key, value);
}
result[key] = value;
}
return result;
}
/**
* Like toJSON, but attempts to serialize every value separately, and
* elides any which fail to serialize. Should only be used if initial
* JSON serialization fails.
*
* @returns {object}
*/
toJSONSafe() {
let result = {};
for (let [key, value] of this) {
try {
void JSON.serialize(value);
result[key] = value;
} catch (e) {
Cu.reportError(new Error(`Failed to serialize browser.storage key "${key}": ${e}`));
}
}
return result;
}
}
/**
* Helper function used to sanitize the objects that have to be saved in the ExtensionStorage.
* Serializes an arbitrary value into a StructuredCloneHolder, if
* appropriate. Existing StructuredCloneHolders are returned unchanged.
* Non-object values are also returned unchanged. Anything else is
* serialized, and a new StructuredCloneHolder returned.
*
* @param {BaseContext} context
* The current extension context.
* @param {string} key
* The key of the current JSON property.
* @param {any} value
* The value of the current JSON property.
* This allows us to avoid a second structured clone operation after
* sending a storage value across a message manager, before cloning it
* into an extension scope.
*
* @returns {any}
* The sanitized value of the property.
* @param {StructuredCloneHolder|*} value
* A value to serialize.
* @returns {*}
*/
function jsonReplacer(context, key, value) {
switch (typeof(value)) {
// Serialize primitive types as-is.
case "string":
case "number":
case "boolean":
return value;
case "object":
if (value === null) {
return value;
}
switch (Cu.getClassName(value, true)) {
// Serialize arrays and ordinary objects as-is.
case "Array":
case "Object":
return value;
// Serialize Date objects and regular expressions as their
// string representations.
case "Date":
case "RegExp":
return String(value);
}
break;
function serialize(value) {
if (value && typeof value === "object" && !isStructuredCloneHolder(value)) {
return new StructuredCloneHolder(value);
}
if (!key) {
// If this is the root object, and we can't serialize it, serialize
// the value to an empty object.
return new context.cloneScope.Object();
}
// Everything else, omit entirely.
return undefined;
return value;
}
this.ExtensionStorage = {
cache: new Map(),
// Map<extension-id, Promise<JSONFile>>
jsonFilePromises: new Map(),
listeners: new Map(),
/**
* Asynchronously reads the storage file for the given extension ID
* and returns a Promise for its initialized JSONFile object.
*
* @param {string} extensionId
* The ID of the extension for which to return a file.
* @returns {Promise<JSONFile>}
*/
async _readFile(extensionId) {
OS.File.makeDir(this.getExtensionDir(extensionId), {
ignoreExisting: true,
from: OS.Constants.Path.profileDir,
});
let jsonFile = new JSONFile({path: this.getStorageFile(extensionId)});
await jsonFile.load();
jsonFile.data = new SerializeableMap(Object.entries(jsonFile.data));
return jsonFile;
},
/**
* Returns a Promise for initialized JSONFile instance for the
* extension's storage file.
*
* @param {string} extensionId
* The ID of the extension for which to return a file.
* @returns {Promise<JSONFile>}
*/
getFile(extensionId) {
let promise = this.jsonFilePromises.get(extensionId);
if (!promise) {
promise = this._readFile(extensionId);
this.jsonFilePromises.set(extensionId, promise);
}
return promise;
},
/**
* Sanitizes the given value, and returns a JSON-compatible
* representation of it, based on the privileges of the given global.
@ -85,129 +142,167 @@ this.ExtensionStorage = {
* The sanitized value.
*/
sanitize(value, context) {
let json = context.jsonStringify(value, jsonReplacer.bind(null, context));
let json = context.jsonStringify(value === undefined ? null : value);
if (json == undefined) {
throw new ExtensionUtils.ExtensionError("DataCloneError: The object could not be cloned.");
}
return JSON.parse(json);
},
/**
* Returns the path to the storage directory within the profile for
* the given extension ID.
*
* @param {string} extensionId
* The ID of the extension for which to return a directory path.
* @returns {string}
*/
getExtensionDir(extensionId) {
return OS.Path.join(this.extensionDir, extensionId);
},
/**
* Returns the path to the JSON storage file for the given extension
* ID.
*
* @param {string} extensionId
* The ID of the extension for which to return a file path.
* @returns {string}
*/
getStorageFile(extensionId) {
return OS.Path.join(this.extensionDir, extensionId, "storage.js");
},
read(extensionId) {
if (this.cache.has(extensionId)) {
return this.cache.get(extensionId);
/**
* Asynchronously sets the values of the given storage items for the
* given extension.
*
* @param {string} extensionId
* The ID of the extension for which to set storage values.
* @param {object} items
* The storage items to set. For each property in the object,
* the storage value for that property is set to its value in
* said object. Any values which are StructuredCloneHolder
* instances are deserialized before being stored.
* @returns {Promise<void>}
*/
async set(extensionId, items) {
let jsonFile = await this.getFile(extensionId);
let changes = {};
for (let prop in items) {
let item = items[prop];
changes[prop] = {oldValue: serialize(jsonFile.data.get(prop)), newValue: serialize(item)};
jsonFile.data.set(prop, item);
}
let path = this.getStorageFile(extensionId);
let decoder = new TextDecoder();
let promise = OS.File.read(path);
promise = promise.then(array => {
return JSON.parse(decoder.decode(array));
}).catch((error) => {
if (!error.becauseNoSuchFile) {
Cu.reportError("Unable to parse JSON data for extension storage.");
}
return {};
});
this.cache.set(extensionId, promise);
return promise;
this.notifyListeners(extensionId, changes);
jsonFile.saveSoon();
return null;
},
write(extensionId) {
let promise = this.read(extensionId).then(extData => {
let encoder = new TextEncoder();
let array = encoder.encode(JSON.stringify(extData));
let path = this.getStorageFile(extensionId);
OS.File.makeDir(this.getExtensionDir(extensionId), {
ignoreExisting: true,
from: OS.Constants.Path.profileDir,
});
let promise = OS.File.writeAtomic(path, array);
return promise;
}).catch(() => {
// Make sure this promise is never rejected.
Cu.reportError("Unable to write JSON data for extension storage.");
});
/**
* Asynchronously removes the given storage items for the given
* extension ID.
*
* @param {string} extensionId
* The ID of the extension for which to remove storage values.
* @param {Array<string>} items
* A list of storage items to remove.
* @returns {Promise<void>}
*/
async remove(extensionId, items) {
let jsonFile = await this.getFile(extensionId);
AsyncShutdown.profileBeforeChange.addBlocker(
"ExtensionStorage: Finish writing extension data",
promise);
let changed = false;
let changes = {};
return promise.then(() => {
AsyncShutdown.profileBeforeChange.removeBlocker(promise);
});
},
set(extensionId, items) {
return this.read(extensionId).then(extData => {
let changes = {};
for (let prop in items) {
let item = items[prop];
changes[prop] = {oldValue: extData[prop], newValue: item};
extData[prop] = item;
for (let prop of [].concat(items)) {
if (jsonFile.data.has(prop)) {
changes[prop] = {oldValue: serialize(jsonFile.data.get(prop))};
jsonFile.data.delete(prop);
changed = true;
}
}
if (changed) {
this.notifyListeners(extensionId, changes);
return this.write(extensionId);
});
jsonFile.saveSoon();
}
return null;
},
remove(extensionId, items) {
return this.read(extensionId).then(extData => {
let changes = {};
for (let prop of [].concat(items)) {
changes[prop] = {oldValue: extData[prop]};
delete extData[prop];
}
/**
* Asynchronously clears all storage entries for the given extension
* ID.
*
* @param {string} extensionId
* The ID of the extension for which to clear storage.
* @returns {Promise<void>}
*/
async clear(extensionId) {
let jsonFile = await this.getFile(extensionId);
let changed = false;
let changes = {};
for (let [prop, oldValue] of jsonFile.data.entries()) {
changes[prop] = {oldValue: serialize(oldValue)};
jsonFile.data.delete(prop);
changed = true;
}
if (changed) {
this.notifyListeners(extensionId, changes);
return this.write(extensionId);
});
jsonFile.saveSoon();
}
return null;
},
clear(extensionId) {
return this.read(extensionId).then(extData => {
let changes = {};
for (let prop of Object.keys(extData)) {
changes[prop] = {oldValue: extData[prop]};
delete extData[prop];
}
/**
* Asynchronously retrieves the values for the given storage items for
* the given extension ID.
*
* @param {string} extensionId
* The ID of the extension for which to get storage values.
* @param {Array<string>|object|null} [keys]
* The storage items to get. If an array, the value of each key
* in the array is returned. If null, the values of all items
* are returned. If an object, the value for each key in the
* object is returned, or that key's value if the item is not
* set.
* @returns {Promise<object>}
* An object which a property for each requested key,
* containing that key's storage value. Values are
* StructuredCloneHolder objects which can be deserialized to
* the original storage value.
*/
async get(extensionId, keys) {
let jsonFile = await this.getFile(extensionId);
let {data} = jsonFile;
this.notifyListeners(extensionId, changes);
return this.write(extensionId);
});
},
get(extensionId, keys) {
return this.read(extensionId).then(extData => {
let result = {};
if (keys === null) {
Object.assign(result, extData);
} else if (typeof(keys) == "object" && !Array.isArray(keys)) {
for (let prop in keys) {
if (prop in extData) {
result[prop] = extData[prop];
} else {
result[prop] = keys[prop];
}
}
} else {
for (let prop of [].concat(keys)) {
if (prop in extData) {
result[prop] = extData[prop];
}
let result = {};
if (keys === null) {
Object.assign(result, data.toJSON());
} else if (typeof(keys) == "object" && !Array.isArray(keys)) {
for (let prop in keys) {
if (data.has(prop)) {
result[prop] = serialize(data.get(prop));
} else {
result[prop] = keys[prop];
}
}
} else {
for (let prop of [].concat(keys)) {
if (data.has(prop)) {
result[prop] = serialize(data.get(prop));
}
}
}
return result;
});
return result;
},
addOnChangedListener(extensionId, listener) {
@ -243,7 +338,10 @@ this.ExtensionStorage = {
Services.obs.removeObserver(this, "extension-invalidate-storage-cache");
Services.obs.removeObserver(this, "xpcom-shutdown");
} else if (topic == "extension-invalidate-storage-cache") {
this.cache.clear();
for (let promise of this.jsonFilePromises.values()) {
promise.then(jsonFile => { jsonFile.finalize(); });
}
this.jsonFilePromises.clear();
}
},
};

Просмотреть файл

@ -17,6 +17,8 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
"resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChild",
@ -205,9 +207,15 @@ class EmbeddedExtension {
// If there is a pending startup, wait to be completed and then shutdown.
if (this.startupPromise) {
return this.startupPromise.then(() => {
this.extension.shutdown();
let promise = this.startupPromise.then(() => {
return this.extension.shutdown();
});
AsyncShutdown.profileChangeTeardown.addBlocker(
`Legacy Extension Shutdown: ${this.addonId}`,
promise.catch(() => {}));
return promise;
}
// Run shutdown now if the embedded webextension has been correctly started

Просмотреть файл

@ -1,16 +1,71 @@
"use strict";
/* import-globals-from ext-c-toolkit.js */
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
"resource://gre/modules/ExtensionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
"resource://gre/modules/TelemetryStopwatch.jsm");
Cu.import("resource://gre/modules/Services.jsm");
var {
ExtensionError,
} = ExtensionUtils;
const storageGetHistogram = "WEBEXT_STORAGE_LOCAL_GET_MS";
const storageSetHistogram = "WEBEXT_STORAGE_LOCAL_SET_MS";
this.storage = class extends ExtensionAPI {
getAPI(context) {
/**
* Serializes the given storage items for transporting to the parent
* process.
*
* @param {Array<string>|object} items
* The items to serialize. If an object is provided, its
* values are serialized to StructuredCloneHolder objects.
* Otherwise, it is returned as-is.
* @returns {Array<string>|object}
*/
function serialize(items) {
if (items && typeof items === "object" && !Array.isArray(items)) {
let result = {};
for (let [key, value] of Object.entries(items)) {
try {
result[key] = new StructuredCloneHolder(value, context.cloneScope);
} catch (e) {
throw new ExtensionError(String(e));
}
}
return result;
}
return items;
}
/**
* Deserializes the given storage items from the parent process into
* the extension context.
*
* @param {object} items
* The items to deserialize. Any property of the object which
* is a StructuredCloneHolder instance is deserialized into
* the extension scope. Any other object is cloned into the
* extension scope directly.
* @returns {object}
*/
function deserialize(items) {
let result = new context.cloneScope.Object();
for (let [key, value] of Object.entries(items)) {
if (value && typeof value === "object" && Cu.getClassName(value, true) === "StructuredCloneHolder") {
value = value.deserialize(context.cloneScope);
} else {
value = Cu.cloneInto(value, context.cloneScope);
}
result[key] = value;
}
return result;
}
function sanitize(items) {
// The schema validator already takes care of arrays (which are only allowed
// to contain strings). Strings and null are safe values.
@ -30,6 +85,7 @@ this.storage = class extends ExtensionAPI {
}
return sanitized;
}
return {
storage: {
local: {
@ -37,10 +93,9 @@ this.storage = class extends ExtensionAPI {
const stopwatchKey = {};
TelemetryStopwatch.start(storageGetHistogram, stopwatchKey);
try {
keys = sanitize(keys);
let result = await context.childManager.callParentAsyncFunction("storage.local.get", [
keys,
]);
serialize(keys),
], null, {noClone: true}).then(deserialize);
TelemetryStopwatch.finish(storageGetHistogram, stopwatchKey);
return result;
} catch (e) {
@ -52,10 +107,9 @@ this.storage = class extends ExtensionAPI {
const stopwatchKey = {};
TelemetryStopwatch.start(storageSetHistogram, stopwatchKey);
try {
items = sanitize(items);
let result = await context.childManager.callParentAsyncFunction("storage.local.set", [
items,
]);
serialize(items),
], null, {noClone: true});
TelemetryStopwatch.finish(storageSetHistogram, stopwatchKey);
return result;
} catch (e) {
@ -79,6 +133,22 @@ this.storage = class extends ExtensionAPI {
]);
},
},
onChanged: new EventManager(context, "storage.onChanged", fire => {
let onChanged = (data, area) => {
let changes = new context.cloneScope.Object();
for (let [key, value] of Object.entries(data)) {
changes[key] = deserialize(value);
}
fire.raw(changes, area);
};
let parent = context.childManager.getParentEvent("storage.onChanged");
parent.addListener(onChanged);
return () => {
parent.removeListener(onChanged);
};
}).api(),
},
};
}

Просмотреть файл

@ -65,7 +65,7 @@ this.storage = class extends ExtensionAPI {
onChanged: new EventManager(context, "storage.onChanged", fire => {
let listenerLocal = changes => {
fire.async(changes, "local");
fire.raw(changes, "local");
};
let listenerSync = changes => {
fire.async(changes, "sync");

Просмотреть файл

@ -149,6 +149,8 @@ async function contentScript(checkGet) {
// Make sure the set() handler landed.
await globalChanges;
let date = new Date(0);
clearGlobalChanges();
await storage.set({
"test-prop1": {
@ -160,12 +162,19 @@ async function contentScript(checkGet) {
arr: [1, 2],
date: new Date(0),
regexp: /regexp/,
func: function func() {},
window,
},
});
await storage.set({"test-prop2": function func() {}});
await browser.test.assertRejects(
storage.set({
window,
}),
/DataCloneError|cyclic object value/);
await browser.test.assertRejects(
storage.set({"test-prop2": function func() {}}),
/DataCloneError/);
const recentChanges = await globalChanges;
browser.test.assertEq("value1", recentChanges["test-prop1"].oldValue, "oldValue correct");
@ -175,24 +184,26 @@ async function contentScript(checkGet) {
data = await storage.get({"test-prop1": undefined, "test-prop2": undefined});
let obj = data["test-prop1"];
if (areaName === "local") {
browser.test.assertEq(String(date), String(obj.date), "date part correct");
browser.test.assertEq("/regexp/", obj.regexp.toSource(), "regexp part correct");
} else {
browser.test.assertEq("1970-01-01T00:00:00.000Z", String(obj.date), "date part correct");
browser.test.assertEq("object", typeof obj.regexp, "regexp part is an object");
browser.test.assertEq(0, Object.keys(obj.regexp).length, "regexp part is an empty object");
}
browser.test.assertEq("hello", obj.str, "string part correct");
browser.test.assertEq(true, obj.bool, "bool part correct");
browser.test.assertEq(null, obj.null, "null part correct");
browser.test.assertEq(undefined, obj.undef, "undefined part correct");
browser.test.assertEq(undefined, obj.func, "function part correct");
browser.test.assertEq(undefined, obj.window, "window part correct");
browser.test.assertEq("1970-01-01T00:00:00.000Z", obj.date, "date part correct");
browser.test.assertEq("/regexp/", obj.regexp, "regexp part correct");
browser.test.assertEq("object", typeof(obj.obj), "object part correct");
browser.test.assertTrue(Array.isArray(obj.arr), "array part present");
browser.test.assertEq(1, obj.arr[0], "arr[0] part correct");
browser.test.assertEq(2, obj.arr[1], "arr[1] part correct");
browser.test.assertEq(2, obj.arr.length, "arr.length part correct");
obj = data["test-prop2"];
browser.test.assertEq("[object Object]", {}.toString.call(obj), "function serialized as a plain object");
browser.test.assertEq(0, Object.keys(obj).length, "function serialized as an empty object");
} catch (e) {
browser.test.fail(`Error: ${e} :: ${e.stack}`);
browser.test.notifyFail("storage");

Просмотреть файл

@ -253,6 +253,8 @@ add_task(async function test_backgroundScript() {
// Make sure the set() handler landed.
await globalChanges;
let date = new Date(0);
clearGlobalChanges();
await storage.set({
"test-prop1": {
@ -262,14 +264,21 @@ add_task(async function test_backgroundScript() {
undef: undefined,
obj: {},
arr: [1, 2],
date: new Date(0),
date,
regexp: /regexp/,
func: function func() {},
window,
},
});
await storage.set({"test-prop2": function func() {}});
await browser.test.assertRejects(
storage.set({
window,
}),
/DataCloneError|cyclic object value/);
await browser.test.assertRejects(
storage.set({"test-prop2": function func() {}}),
/DataCloneError/);
const recentChanges = await globalChanges;
browser.test.assertEq("value1", recentChanges["test-prop1"].oldValue, "oldValue correct");
@ -279,24 +288,26 @@ add_task(async function test_backgroundScript() {
data = await storage.get({"test-prop1": undefined, "test-prop2": undefined});
let obj = data["test-prop1"];
if (areaName === "local") {
browser.test.assertEq(String(date), String(obj.date), "date part correct");
browser.test.assertEq("/regexp/", obj.regexp.toSource(), "regexp part correct");
} else {
browser.test.assertEq("1970-01-01T00:00:00.000Z", String(obj.date), "date part correct");
browser.test.assertEq("object", typeof obj.regexp, "regexp part is an object");
browser.test.assertEq(0, Object.keys(obj.regexp).length, "regexp part is an empty object");
}
browser.test.assertEq("hello", obj.str, "string part correct");
browser.test.assertEq(true, obj.bool, "bool part correct");
browser.test.assertEq(null, obj.null, "null part correct");
browser.test.assertEq(undefined, obj.undef, "undefined part correct");
browser.test.assertEq(undefined, obj.func, "function part correct");
browser.test.assertEq(undefined, obj.window, "window part correct");
browser.test.assertEq("1970-01-01T00:00:00.000Z", obj.date, "date part correct");
browser.test.assertEq("/regexp/", obj.regexp, "regexp part correct");
browser.test.assertEq("object", typeof(obj.obj), "object part correct");
browser.test.assertTrue(Array.isArray(obj.arr), "array part present");
browser.test.assertEq(1, obj.arr[0], "arr[0] part correct");
browser.test.assertEq(2, obj.arr[1], "arr[1] part correct");
browser.test.assertEq(2, obj.arr.length, "arr.length part correct");
obj = data["test-prop2"];
browser.test.assertEq("[object Object]", {}.toString.call(obj), "function serialized as a plain object");
browser.test.assertEq(0, Object.keys(obj).length, "function serialized as an empty object");
} catch (e) {
browser.test.fail(`Error: ${e} :: ${e.stack}`);
browser.test.notifyFail("storage");

Просмотреть файл

@ -18,10 +18,14 @@ const PAGE_HEIGHT = 166;
const DRIVER_PREF = "sanity-test.driver-version";
const DEVICE_PREF = "sanity-test.device-id";
const VERSION_PREF = "sanity-test.version";
const ADVANCED_LAYERS_PREF = "sanity-test.advanced-layers";
const DISABLE_VIDEO_PREF = "media.hardware-video-decoding.failed";
const RUNNING_PREF = "sanity-test.running";
const TIMEOUT_SEC = 20;
const AL_ENABLED_PREF = "layers.mlgpu.dev-enabled";
const AL_TEST_FAILED_PREF = "layers.mlgpu.sanity-test-failed";
// GRAPHICS_SANITY_TEST histogram enumeration values
const TEST_PASSED = 0;
const TEST_FAILED_RENDER = 1;
@ -34,6 +38,7 @@ const REASON_FIRST_RUN = 0;
const REASON_FIREFOX_CHANGED = 1;
const REASON_DEVICE_CHANGED = 2;
const REASON_DRIVER_CHANGED = 3;
const REASON_AL_CONFIG_CHANGED = 4;
// GRAPHICS_SANITY_TEST_OS_SNAPSHOT histogram enumeration values
const SNAPSHOT_VIDEO_OK = 0;
@ -120,7 +125,7 @@ function verifyLayersRendering(ctx) {
return testPixel(ctx, 18, 18, 255, 0, 0, 255, 64);
}
function testCompositor(win, ctx) {
function testCompositor(test, win, ctx) {
takeWindowSnapshot(win, ctx);
var testPassed = true;
@ -131,8 +136,16 @@ function testCompositor(win, ctx) {
}
if (!verifyLayersRendering(ctx)) {
// Try disabling advanced layers if it was enabled. Also trgiger
// a device reset so the screen redraws.
if (Preferences.get(AL_ENABLED_PREF, false)) {
Preferences.set(AL_TEST_FAILED_PREF, true);
test.utils.triggerDeviceReset();
}
reportResult(TEST_FAILED_RENDER);
testPassed = false;
} else {
Preferences.set(AL_TEST_FAILED_PREF, false);
}
if (testPassed) {
@ -174,7 +187,7 @@ var listener = {
// Perform the compositor backbuffer test, which currently we use for
// actually deciding whether to enable hardware media decoding.
testCompositor(this.win, this.ctx);
testCompositor(this, this.win, this.ctx);
this.endTest();
},
@ -246,6 +259,7 @@ SanityTest.prototype = {
// gpu or drivers.
var buildId = Services.appinfo.platformBuildID;
var gfxinfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
var hasAL = Preferences.get(AL_ENABLED_PREF, false);
if (Preferences.get(RUNNING_PREF, false)) {
Preferences.set(DISABLE_VIDEO_PREF, true);
@ -269,7 +283,8 @@ SanityTest.prototype = {
// TODO: Handle dual GPU setups
if (checkPref(DRIVER_PREF, gfxinfo.adapterDriverVersion, REASON_DRIVER_CHANGED) &&
checkPref(DEVICE_PREF, gfxinfo.adapterDeviceID, REASON_DEVICE_CHANGED) &&
checkPref(VERSION_PREF, buildId, REASON_FIREFOX_CHANGED)) {
checkPref(VERSION_PREF, buildId, REASON_FIREFOX_CHANGED) &&
checkPref(ADVANCED_LAYERS_PREF, hasAL, REASON_AL_CONFIG_CHANGED)) {
return false;
}
@ -279,6 +294,7 @@ SanityTest.prototype = {
Preferences.set(DRIVER_PREF, gfxinfo.adapterDriverVersion);
Preferences.set(DEVICE_PREF, gfxinfo.adapterDeviceID);
Preferences.set(VERSION_PREF, buildId);
Preferences.set(ADVANCED_LAYERS_PREF, hasAL);
// Update the prefs so that this test doesn't run again until the next update.
Preferences.set(RUNNING_PREF, true);

Просмотреть файл

@ -25,7 +25,7 @@
<xul:button label="&print.label;" accesskey="&print.accesskey;"
oncommand="this.parentNode.print();" icon="print"/>
<xul:button label="&pageSetup.label;" accesskey="&pageSetup.accesskey;"
<xul:button anonid="pageSetup" label="&pageSetup.label;" accesskey="&pageSetup.accesskey;"
oncommand="this.parentNode.doPageSetup();"/>
<xul:vbox align="center" pack="center">
@ -104,9 +104,24 @@
<field name="mPrintButton">
document.getAnonymousNodes(this)[0]
</field>
<field name="mPageSetupButton">
document.getAnonymousElementByAttribute(this, "anonid", "pageSetup");
</field>
<field name="mNavigateHomeButton">
document.getAnonymousElementByAttribute(this, "anonid", "navigateHome");
</field>
<field name="mNavigatePreviousButton">
document.getAnonymousElementByAttribute(this, "anonid", "navigatePrevious");
</field>
<field name="mPageTextBox">
document.getAnonymousNodes(this)[5].childNodes[0]
</field>
<field name="mNavigateNextButton">
document.getAnonymousElementByAttribute(this, "anonid", "navigateNext");
</field>
<field name="mNavigateEndButton">
document.getAnonymousElementByAttribute(this, "anonid", "navigateEnd");
</field>
<field name="mTotalPages">
document.getAnonymousNodes(this)[5].childNodes[2]
</field>
@ -128,6 +143,9 @@
<field name="mSimplifyPageCheckbox">
document.getAnonymousNodes(this)[14]
</field>
<field name="mSimplifyPageNotAllowed">
this.mSimplifyPageCheckbox.disabled
</field>
<field name="mSimplifyPageToolbarSeparator">
document.getAnonymousNodes(this)[15]
</field>
@ -185,6 +203,25 @@
</body>
</method>
<method name="disableUpdateTriggers">
<parameter name="aDisabled"/>
<body>
<![CDATA[
this.mPrintButton.disabled = aDisabled;
this.mPageSetupButton.disabled = aDisabled;
this.mNavigateHomeButton.disabled = aDisabled;
this.mNavigatePreviousButton.disabled = aDisabled;
this.mPageTextBox.disabled = aDisabled;
this.mNavigateNextButton.disabled = aDisabled;
this.mNavigateEndButton.disabled = aDisabled;
this.mScaleCombobox.disabled = aDisabled;
this.mPortaitButton.disabled = aDisabled;
this.mLandscapeButton.disabled = aDisabled;
this.mSimplifyPageCheckbox.disabled = this.mSimplifyPageNotAllowed || aDisabled;
]]>
</body>
</method>
<method name="doPageSetup">
<body>
<![CDATA[
@ -355,6 +392,7 @@
<method name="enableSimplifyPage">
<body>
<![CDATA[
this.mSimplifyPageNotAllowed = false;
this.mSimplifyPageCheckbox.disabled = false;
this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
this.mSimplifyPageCheckbox.getAttribute("tooltiptext-enabled"));
@ -365,6 +403,7 @@
<method name="disableSimplifyPage">
<body>
<![CDATA[
this.mSimplifyPageNotAllowed = true;
this.mSimplifyPageCheckbox.disabled = true;
this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
this.mSimplifyPageCheckbox.getAttribute("tooltiptext-disabled"));
@ -389,8 +428,6 @@
}
this.mPageTextBox.value = 1;
this.mMessageManager.sendAsyncMessage("Printing:Preview:UpdatePageCount");
]]>
</body>
</method>

Просмотреть файл

@ -213,10 +213,10 @@ var PrintUtils = {
* to it will be used).
*/
printPreview(aListenerObj) {
// if we're already in PP mode, don't set the listener; chances
// are it is null because someone is calling printPreview() to
// get us to refresh the display.
if (!this.inPrintPreview) {
// If we already have a toolbar someone is calling printPreview() to get us
// to refresh the display and aListenerObj won't be passed.
let printPreviewTB = document.getElementById("print-preview-toolbar");
if (!printPreviewTB) {
this._listener = aListenerObj;
this._sourceBrowser = aListenerObj.getSourceBrowser();
this._originalTitle = this._sourceBrowser.contentTitle;
@ -225,6 +225,10 @@ var PrintUtils = {
// Here we log telemetry data for when the user enters print preview.
this.logTelemetry("PRINT_PREVIEW_OPENED_COUNT");
} else {
// Disable toolbar elements that can cause another update to be triggered
// during this update.
printPreviewTB.disableUpdateTriggers(true);
// collapse the browser here -- it will be shown in
// enterPrintPreview; this forces a reflow which fixes display
// issues in bug 267422.
@ -319,10 +323,6 @@ var PrintUtils = {
return this._currentPPBrowser.docShell.printPreview;
},
get inPrintPreview() {
return document.getElementById("print-preview-toolbar") != null;
},
// "private" methods and members. Don't use them.
_listener: null,
@ -445,6 +445,10 @@ var PrintUtils = {
// thrown. This should all get torn out once bug 1088061 is fixed.
mm.removeMessageListener("Printing:Preview:StateChange", this);
mm.removeMessageListener("Printing:Preview:ProgressChange", this);
// Enable toobar elements that we disabled during update.
let printPreviewTB = document.getElementById("print-preview-toolbar");
printPreviewTB.disableUpdateTriggers(false);
}
return listener.onStateChange(null, null,
@ -593,9 +597,11 @@ var PrintUtils = {
sendEnterPreviewMessage(this._sourceBrowser, false);
}
let waitForPrintProgressToEnableToolbar = false;
if (this._webProgressPP.value) {
mm.addMessageListener("Printing:Preview:StateChange", this);
mm.addMessageListener("Printing:Preview:ProgressChange", this);
waitForPrintProgressToEnableToolbar = true;
}
let onEntered = (message) => {
@ -618,8 +624,16 @@ var PrintUtils = {
if (message.data.changingBrowsers) {
printPreviewTB.destroy();
printPreviewTB.initialize(ppBrowser);
} else {
// printPreviewTB.initialize above already calls updateToolbar.
printPreviewTB.updateToolbar();
}
printPreviewTB.updateToolbar();
// If we don't have a progress listener to enable the toolbar do it now.
if (!waitForPrintProgressToEnableToolbar) {
printPreviewTB.disableUpdateTriggers(false);
}
ppBrowser.collapsed = false;
ppBrowser.focus();
return;
@ -646,6 +660,13 @@ var PrintUtils = {
navToolbox.parentNode.insertBefore(printPreviewTB, navToolbox);
printPreviewTB.initialize(ppBrowser);
// The print preview processing may not have fully completed, so if we
// have a progress listener, disable the toolbar elements that can trigger
// updates and it will enable them when completed.
if (waitForPrintProgressToEnableToolbar) {
printPreviewTB.disableUpdateTriggers(true);
}
// Enable simplify page checkbox when the page is an article
if (this._sourceBrowser.isArticle) {
printPreviewTB.enableSimplifyPage();

Просмотреть файл

@ -409,13 +409,13 @@ var Printing = {
"Printing:Preview:Exit",
"Printing:Preview:Navigate",
"Printing:Preview:ParseDocument",
"Printing:Preview:UpdatePageCount",
"Printing:Print",
],
init() {
this.MESSAGES.forEach(msgName => addMessageListener(msgName, this));
addEventListener("PrintingError", this, true);
addEventListener("printPreviewUpdate", this, true);
},
get shouldSavePrintSettings() {
@ -423,16 +423,49 @@ var Printing = {
Services.prefs.getBoolPref("print.save_print_settings");
},
printPreviewInitializingInfo: null,
handleEvent(event) {
if (event.type == "PrintingError") {
let win = event.target.defaultView;
let wbp = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebBrowserPrint);
let nsresult = event.detail;
sendAsyncMessage("Printing:Error", {
isPrinting: wbp.doingPrint,
nsresult,
});
switch (event.type) {
case "PrintingError": {
let win = event.target.defaultView;
let wbp = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebBrowserPrint);
let nsresult = event.detail;
sendAsyncMessage("Printing:Error", {
isPrinting: wbp.doingPrint,
nsresult,
});
break;
}
case "printPreviewUpdate": {
let info = this.printPreviewInitializingInfo;
if (!info) {
// If there is no printPreviewInitializingInfo then we did not
// initiate the preview so ignore this event.
return;
}
// Only send Printing:Preview:Entered message on first update, indicated
// by printPreviewInitializingInfo.entered not being set.
if (!info.entered) {
info.entered = true;
sendAsyncMessage("Printing:Preview:Entered", {
failed: false,
changingBrowsers: info.changingBrowsers
});
// If we have another request waiting, dispatch it now.
if (info.nextRequest) {
Services.tm.dispatchToMainThread(info.nextRequest);
}
}
// Always send page count update.
this.updatePageCount();
break;
}
}
},
@ -459,11 +492,6 @@ var Printing = {
break;
}
case "Printing:Preview:UpdatePageCount": {
this.updatePageCount();
break;
}
case "Printing:Print": {
this.print(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode, data.defaultPrinterName);
break;
@ -618,28 +646,6 @@ var Printing = {
},
enterPrintPreview(contentWindow, simplifiedMode, changingBrowsers, defaultPrinterName) {
// We'll call this whenever we've finished reflowing the document, or if
// we errored out while attempting to print preview (in which case, we'll
// notify the parent that we've failed).
let notifyEntered = (error) => {
removeEventListener("printPreviewUpdate", onPrintPreviewReady);
sendAsyncMessage("Printing:Preview:Entered", {
failed: !!error,
changingBrowsers,
});
};
let onPrintPreviewReady = () => {
notifyEntered();
};
// We have to wait for the print engine to finish reflowing all of the
// documents and subdocuments before we can tell the parent to flip to
// the print preview UI - otherwise, the print preview UI might ask for
// information (like the number of pages in the document) before we have
// our PresShells set up.
addEventListener("printPreviewUpdate", onPrintPreviewReady);
try {
let printSettings = this.getPrintSettings(defaultPrinterName);
@ -649,28 +655,42 @@ var Printing = {
if (printSettings && simplifiedMode)
printSettings.docURL = contentWindow.document.baseURI;
// The print preview docshell will be in a different TabGroup,
// so we run it in a separate runnable to avoid touching a
// different TabGroup in our own runnable.
Services.tm.dispatchToMainThread(() => {
// The print preview docshell will be in a different TabGroup, so
// printPreviewInitialize must be run in a separate runnable to avoid
// touching a different TabGroup in our own runnable.
let printPreviewInitialize = () => {
try {
this.printPreviewInitializingInfo = { changingBrowsers };
docShell.printPreview.printPreview(printSettings, contentWindow, this);
} catch (error) {
// This might fail if we, for example, attempt to print a XUL document.
// In that case, we inform the parent to bail out of print preview.
Components.utils.reportError(error);
notifyEntered(error);
this.printPreviewInitializingInfo = null;
sendAsyncMessage("Printing:Preview:Entered", { failed: true });
}
});
}
// If printPreviewInitializingInfo.entered is not set we are still in the
// initial setup of a previous preview request. We delay this one until
// that has finished because running them at the same time will almost
// certainly cause failures.
if (this.printPreviewInitializingInfo &&
!this.printPreviewInitializingInfo.entered) {
this.printPreviewInitializingInfo.nextRequest = printPreviewInitialize;
} else {
Services.tm.dispatchToMainThread(printPreviewInitialize);
}
} catch (error) {
// This might fail if we, for example, attempt to print a XUL document.
// In that case, we inform the parent to bail out of print preview.
Components.utils.reportError(error);
notifyEntered(error);
sendAsyncMessage("Printing:Preview:Entered", { failed: true });
}
},
exitPrintPreview() {
this.printPreviewInitializingInfo = null;
docShell.printPreview.exitPrintPreview();
},

Просмотреть файл

@ -286,8 +286,20 @@ JSONFile.prototype = {
* @rejects JavaScript exception.
*/
async _save() {
let json;
try {
json = JSON.stringify(this._data);
} catch (e) {
// If serialization fails, try fallback safe JSON converter.
if (typeof this._data.toJSONSafe == "function") {
json = JSON.stringify(this._data.toJSONSafe());
} else {
throw e;
}
}
// Create or overwrite the file.
let bytes = gTextEncoder.encode(JSON.stringify(this._data));
let bytes = gTextEncoder.encode(json);
if (this._beforeSave) {
await Promise.resolve(this._beforeSave());
}

Просмотреть файл

@ -86,32 +86,3 @@ ProfileBuffer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
return n;
}
#define DYNAMIC_MAX_STRING 8192
char*
ProfileBuffer::processEmbeddedString(int aReadAheadPos, int* aEntriesConsumed,
char* aStrBuf)
{
int strBufPos = 0;
// Read the string stored in mChars until the null character is seen.
bool seenNullByte = false;
while (aReadAheadPos != mWritePos && !seenNullByte) {
(*aEntriesConsumed)++;
ProfileBufferEntry readAheadEntry = mEntries[aReadAheadPos];
for (size_t pos = 0; pos < ProfileBufferEntry::kNumChars; pos++) {
aStrBuf[strBufPos] = readAheadEntry.u.mChars[pos];
if (aStrBuf[strBufPos] == '\0' || strBufPos == DYNAMIC_MAX_STRING-2) {
seenNullByte = true;
break;
}
strBufPos++;
}
if (!seenNullByte) {
aReadAheadPos = (aReadAheadPos + 1) % mEntrySize;
}
}
return aStrBuf;
}

Просмотреть файл

@ -42,6 +42,10 @@ public:
// record the resulting generation and index in |aLS| if it's non-null.
void addThreadIdEntry(int aThreadId, LastSample* aLS = nullptr);
// Maximum size of a dynamic string (including the terminating '\0' char)
// that we'll write to the ProfileBuffer.
static const size_t kMaxDynamicStringLength = 8192;
void StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
double aSinceTime, JSContext* cx,
UniqueStacks& aUniqueStacks);
@ -64,9 +68,7 @@ public:
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
protected:
char* processEmbeddedString(int aReadaheadPos, int* aEntriesConsumed,
char* aStrBuf);
private:
int FindLastSampleOfThread(int aThreadId, const LastSample& aLS);
public:

Просмотреть файл

@ -7,6 +7,7 @@
#include <ostream>
#include "platform.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/Sprintf.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
@ -533,7 +534,7 @@ void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame)
struct ProfileSample
{
uint32_t mStack;
Maybe<double> mTime;
double mTime;
Maybe<double> mResponsiveness;
Maybe<double> mRSS;
Maybe<double> mUSS;
@ -553,9 +554,7 @@ static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample)
writer.IntElement(STACK, aSample.mStack);
if (aSample.mTime.isSome()) {
writer.DoubleElement(TIME, *aSample.mTime);
}
writer.DoubleElement(TIME, aSample.mTime);
if (aSample.mResponsiveness.isSome()) {
writer.DoubleElement(RESPONSIVENESS, *aSample.mResponsiveness);
@ -570,146 +569,313 @@ static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample)
}
}
void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
double aSinceTime, JSContext* aContext,
UniqueStacks& aUniqueStacks)
class EntryGetter
{
Maybe<ProfileSample> sample;
int readPos = mReadPos;
int currentThreadID = -1;
Maybe<double> currentTime;
UniquePtr<char[]> strbuf = MakeUnique<char[]>(DYNAMIC_MAX_STRING);
public:
explicit EntryGetter(ProfileBuffer* aBuffer)
: mEntries(aBuffer->mEntries.get())
, mReadPos(aBuffer->mReadPos)
, mWritePos(aBuffer->mWritePos)
, mEntrySize(aBuffer->mEntrySize)
{}
while (readPos != mWritePos) {
ProfileBufferEntry entry = mEntries[readPos];
if (entry.isThreadId()) {
currentThreadID = entry.u.mInt;
currentTime.reset();
int readAheadPos = (readPos + 1) % mEntrySize;
if (readAheadPos != mWritePos) {
ProfileBufferEntry readAheadEntry = mEntries[readAheadPos];
if (readAheadEntry.isTime()) {
currentTime = Some(readAheadEntry.u.mDouble);
bool Has() const { return mReadPos != mWritePos; }
const ProfileBufferEntry& Get() const { return mEntries[mReadPos]; }
void Next() { mReadPos = (mReadPos + 1) % mEntrySize; }
private:
const ProfileBufferEntry* const mEntries;
int mReadPos;
const int mWritePos;
const int mEntrySize;
};
// Each sample is made up of multiple ProfileBuffer entries. The following
// grammar shows legal sequences.
//
// (
// ThreadId
// Time
// Sample
// ( NativeLeafAddr
// | CodeLocation EmbeddedString* LineNumber? Category?
// | JitReturnAddr
// )+
// Marker*
// Responsiveness?
// ResidentMemory?
// UnsharedMemory?
// )*
//
// The most complicated part is the stack entry sequence that begins with
// CodeLocation. Here are some examples.
//
// - PseudoStack entries without a dynamic string (or with privacy enabled so
// that the dynamic string is hidden):
//
// CodeLocation("js::RunScript")
// Category(ProfileEntry::Category::JS)
//
// CodeLocation("XREMain::XRE_main")
// LineNumber(4660)
// Category(ProfileEntry::Category::OTHER)
//
// CodeLocation("ElementRestyler::ComputeStyleChangeFor")
// LineNumber(3003)
// Category(ProfileEntry::Category::CSS)
//
// - PseudoStack entries with a dynamic string:
//
// CodeLocation("")
// EmbeddedString("nsObserv")
// EmbeddedString("erServic")
// EmbeddedString("e::Notif")
// EmbeddedString("yObserve")
// EmbeddedString("rs profi")
// EmbeddedString("ler-star")
// EmbeddedString "ted")
// LineNumber(291)
// Category(ProfileEntry::Category::OTHER)
//
// CodeLocation("")
// EmbeddedString("closeWin")
// EmbeddedString("dow (chr")
// EmbeddedString("ome://gl")
// EmbeddedString("obal/con")
// EmbeddedString("tent/glo")
// EmbeddedString("balOverl")
// EmbeddedString("ay.js:5)")
// EmbeddedString("") # this string holds the closing '\0'
// LineNumber(25)
// Category(ProfileEntry::Category::JS)
//
// CodeLocation("")
// EmbeddedString("bound (s")
// EmbeddedString("elf-host")
// EmbeddedString("ed:914)")
// LineNumber(945)
// Category(ProfileEntry::Category::JS)
//
// - A wasm JIT frame entry:
//
// CodeLocation("")
// EmbeddedString("wasm-fun")
// EmbeddedString("ction[87")
// EmbeddedString("36] (blo")
// EmbeddedString("b:http:/")
// EmbeddedString("/webasse")
// EmbeddedString("mbly.org")
// EmbeddedString("/3dc5759")
// EmbeddedString("4-ce58-4")
// EmbeddedString("626-975b")
// EmbeddedString("-08ad116")
// EmbeddedString("30bc1:38")
// EmbeddedString("29856)")
//
// - A JS frame entry in a synchronous sample:
//
// CodeLocation("")
// EmbeddedString("u (https")
// EmbeddedString("://perf-")
// EmbeddedString("html.io/")
// EmbeddedString("ac0da204")
// EmbeddedString("aaa44d75")
// EmbeddedString("a800.bun")
// EmbeddedString("dle.js:2")
// EmbeddedString("5)")
//
void
ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
double aSinceTime, JSContext* aContext,
UniqueStacks& aUniqueStacks)
{
UniquePtr<char[]> strbuf = MakeUnique<char[]>(kMaxDynamicStringLength);
// Because this is a format entirely internal to the Profiler, any parsing
// error indicates a bug in the ProfileBuffer writing or the parser itself,
// or possibly flaky hardware.
#define ERROR_AND_SKIP_TO_NEXT_SAMPLE(msg) \
do { \
fprintf(stderr, "ProfileBuffer parse error: %s", msg); \
MOZ_ASSERT(false, msg); \
goto skip_to_next_sample; \
} while (0)
EntryGetter e(this);
// This block skips entries until we find the start of the next sample. This
// is useful in two situations.
//
// - The circular buffer overwrites old entries, so when we start parsing we
// might be in the middle of a sample, and we must skip forward to the
// start of the next sample.
//
// - We skip samples that don't have an appropriate ThreadId or Time.
//
skip_to_next_sample:
while (e.Has()) {
if (e.Get().isThreadId()) {
break;
} else {
e.Next();
}
}
while (e.Has()) {
if (e.Get().isThreadId()) {
int threadId = e.Get().u.mInt;
e.Next();
// Ignore samples that are for the wrong thread.
if (threadId != aThreadId) {
goto skip_to_next_sample;
}
} else {
// Due to the skip_to_next_sample block above, if we have an entry here
// it must be a ThreadId entry.
MOZ_CRASH();
}
ProfileSample sample;
if (e.Has() && e.Get().isTime()) {
sample.mTime = e.Get().u.mDouble;
e.Next();
// Ignore samples that are too old.
if (sample.mTime < aSinceTime) {
goto skip_to_next_sample;
}
} else {
ERROR_AND_SKIP_TO_NEXT_SAMPLE("expected a Time entry");
}
if (e.Has() && e.Get().isSample()) {
e.Next();
} else {
ERROR_AND_SKIP_TO_NEXT_SAMPLE("expected a Sample entry");
}
UniqueStacks::Stack stack =
aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)"));
int numFrames = 0;
while (e.Has()) {
if (e.Get().isNativeLeafAddr()) {
numFrames++;
// Bug 753041: We need a double cast here to tell GCC that we don't
// want to sign extend 32-bit addresses starting with 0xFXXXXXX.
unsigned long long pc = (unsigned long long)(uintptr_t)e.Get().u.mPtr;
char buf[20];
SprintfLiteral(buf, "%#llx", pc);
stack.AppendFrame(UniqueStacks::OnStackFrameKey(buf));
e.Next();
} else if (e.Get().isCodeLocation()) {
numFrames++;
const char* string = e.Get().u.mString;
e.Next();
strbuf[0] = '\0';
char* p = &strbuf[0];
while (e.Has()) {
if (e.Get().isEmbeddedString()) {
if (p < &strbuf[kMaxDynamicStringLength]) {
// Copy 8 chars at a time. This won't overflow strbuf so long as
// its length is a multiple of 8.
static_assert(kMaxDynamicStringLength % 8 == 0, "bad strbuf sz");
memcpy(p, e.Get().u.mChars, ProfileBufferEntry::kNumChars);
p += ProfileBufferEntry::kNumChars;
}
e.Next();
} else {
break;
}
}
strbuf[kMaxDynamicStringLength - 1] = '\0';
if (strbuf[0] != '\0') {
// When we have EmbeddedStrings, the preceding CodeLocation's string
// is always empty.
MOZ_ASSERT(string[0] == '\0');
string = strbuf.get();
}
UniqueStacks::OnStackFrameKey frameKey(string);
if (e.Has() && e.Get().isLineNumber()) {
frameKey.mLine = Some(unsigned(e.Get().u.mInt));
e.Next();
}
if (e.Has() && e.Get().isCategory()) {
frameKey.mCategory = Some(unsigned(e.Get().u.mInt));
e.Next();
}
stack.AppendFrame(frameKey);
} else if (e.Get().isJitReturnAddr()) {
numFrames++;
// A JIT frame may expand to multiple frames due to inlining.
void* pc = e.Get().u.mPtr;
unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc);
if (depth == 0) {
StreamJSFramesOp framesOp(pc, stack);
MOZ_RELEASE_ASSERT(aContext);
JS::ForEachProfiledFrame(aContext, pc, framesOp);
aUniqueStacks.AddJITFrameDepth(pc, framesOp.depth());
} else {
for (unsigned i = 0; i < depth; i++) {
UniqueStacks::OnStackFrameKey inlineFrameKey(pc, i);
stack.AppendFrame(inlineFrameKey);
}
}
e.Next();
} else {
break;
}
}
if (currentThreadID == aThreadId && (currentTime.isNothing() || *currentTime >= aSinceTime)) {
switch (entry.kind()) {
case ProfileBufferEntry::Kind::Responsiveness:
if (sample.isSome()) {
sample->mResponsiveness = Some(entry.u.mDouble);
}
break;
case ProfileBufferEntry::Kind::ResidentMemory:
if (sample.isSome()) {
sample->mRSS = Some(entry.u.mDouble);
}
break;
case ProfileBufferEntry::Kind::UnsharedMemory:
if (sample.isSome()) {
sample->mUSS = Some(entry.u.mDouble);
}
break;
case ProfileBufferEntry::Kind::Sample:
{
// end the previous sample if there was one
if (sample.isSome()) {
WriteSample(aWriter, *sample);
sample.reset();
}
// begin the next sample
sample.emplace();
sample->mTime = currentTime;
// Process all the remaining entries within this sample.
UniqueStacks::Stack stack =
aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)"));
int entryPos = (readPos + 1) % mEntrySize;
ProfileBufferEntry entry = mEntries[entryPos];
while (entryPos != mWritePos && !entry.isSample() &&
!entry.isThreadId()) {
int incBy = 1;
entry = mEntries[entryPos];
// Read ahead to the next entry. If it's an EmbeddedString entry
// process it now.
const char* string = entry.u.mString;
int readAheadPos = (entryPos + 1) % mEntrySize;
// Make sure the string is always null terminated if it fills up
// DYNAMIC_MAX_STRING-2
strbuf[DYNAMIC_MAX_STRING-1] = '\0';
if (readAheadPos != mWritePos &&
mEntries[readAheadPos].isEmbeddedString()) {
string =
processEmbeddedString(readAheadPos, &incBy, strbuf.get());
}
// Write one entry. It can have either
// 1. only location - a NativeLeafAddr containing a memory address
// 2. location and line number - a CodeLocation followed by
// EmbeddedStrings, an optional LineNumber and an
// optional Category
// 3. a JitReturnAddress containing a native code address
if (entry.isNativeLeafAddr()) {
// Bug 753041
// We need a double cast here to tell GCC that we don't want to sign
// extend 32-bit addresses starting with 0xFXXXXXX.
unsigned long long pc =
(unsigned long long)(uintptr_t)entry.u.mPtr;
snprintf(strbuf.get(), DYNAMIC_MAX_STRING, "%#llx", pc);
stack.AppendFrame(UniqueStacks::OnStackFrameKey(strbuf.get()));
} else if (entry.isCodeLocation()) {
UniqueStacks::OnStackFrameKey frameKey(string);
readAheadPos = (entryPos + incBy) % mEntrySize;
if (readAheadPos != mWritePos &&
mEntries[readAheadPos].isLineNumber()) {
frameKey.mLine = Some((unsigned) mEntries[readAheadPos].u.mInt);
incBy++;
}
readAheadPos = (entryPos + incBy) % mEntrySize;
if (readAheadPos != mWritePos &&
mEntries[readAheadPos].isCategory()) {
frameKey.mCategory =
Some((unsigned) mEntries[readAheadPos].u.mInt);
incBy++;
}
stack.AppendFrame(frameKey);
} else if (entry.isJitReturnAddr()) {
// A JIT frame may expand to multiple frames due to inlining.
void* pc = entry.u.mPtr;
unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc);
if (depth == 0) {
StreamJSFramesOp framesOp(pc, stack);
MOZ_RELEASE_ASSERT(aContext);
JS::ForEachProfiledFrame(aContext, pc, framesOp);
aUniqueStacks.AddJITFrameDepth(pc, framesOp.depth());
} else {
for (unsigned i = 0; i < depth; i++) {
UniqueStacks::OnStackFrameKey inlineFrameKey(pc, i);
stack.AppendFrame(inlineFrameKey);
}
}
}
entryPos = (entryPos + incBy) % mEntrySize;
}
sample->mStack = stack.GetOrAddIndex();
break;
}
default:
break;
} /* switch (entry.kind()) */
if (numFrames == 0) {
ERROR_AND_SKIP_TO_NEXT_SAMPLE("expected one or more frame entries");
}
readPos = (readPos + 1) % mEntrySize;
}
if (sample.isSome()) {
WriteSample(aWriter, *sample);
sample.mStack = stack.GetOrAddIndex();
// Skip over the markers. We process them in StreamMarkersToJSON().
while (e.Has()) {
if (e.Get().isMarker()) {
e.Next();
} else {
break;
}
}
if (e.Has() && e.Get().isResponsiveness()) {
sample.mResponsiveness = Some(e.Get().u.mDouble);
e.Next();
}
if (e.Has() && e.Get().isResidentMemory()) {
sample.mRSS = Some(e.Get().u.mDouble);
e.Next();
}
if (e.Has() && e.Get().isUnsharedMemory()) {
sample.mUSS = Some(e.Get().u.mDouble);
e.Next();
}
WriteSample(aWriter, sample);
}
#undef ERROR_AND_SKIP_TO_NEXT_SAMPLE
}
void
@ -719,19 +885,22 @@ ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter,
double aSinceTime,
UniqueStacks& aUniqueStacks)
{
int readPos = mReadPos;
EntryGetter e(this);
int currentThreadID = -1;
while (readPos != mWritePos) {
ProfileBufferEntry entry = mEntries[readPos];
if (entry.isThreadId()) {
currentThreadID = entry.u.mInt;
} else if (currentThreadID == aThreadId && entry.isMarker()) {
const ProfilerMarker* marker = entry.u.mMarker;
// Stream all markers whose threadId matches aThreadId. All other entries are
// skipped, because we process them in StreamSamplesToJSON().
while (e.Has()) {
if (e.Get().isThreadId()) {
currentThreadID = e.Get().u.mInt;
} else if (currentThreadID == aThreadId && e.Get().isMarker()) {
const ProfilerMarker* marker = e.Get().u.mMarker;
if (marker->GetTime() >= aSinceTime) {
entry.u.mMarker->StreamJSON(aWriter, aProcessStartTime, aUniqueStacks);
marker->StreamJSON(aWriter, aProcessStartTime, aUniqueStacks);
}
}
readPos = (readPos + 1) % mEntrySize;
e.Next();
}
}

Просмотреть файл

@ -1241,6 +1241,9 @@ DoNativeBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
// Writes some components shared by periodic and synchronous profiles to
// ActivePS's ProfileBuffer. (This should only be called from DoSyncSample()
// and DoPeriodicSample().)
//
// The grammar for entry sequences is in a comment above
// ProfileBuffer::StreamSamplesToJSON.
static inline void
DoSharedSample(PSLockRef aLock, bool aIsSynchronous,
ThreadInfo& aThreadInfo, const TimeStamp& aNow,

Просмотреть файл

@ -1381,6 +1381,11 @@ GfxInfoBase::DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> aObj)
if (advancedLayers != FeatureStatus::Unused) {
InitFeatureObject(aCx, aObj, "advancedLayers", FEATURE_ADVANCED_LAYERS,
Some(advancedLayers), &obj);
if (gfxConfig::UseFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING)) {
JS::Rooted<JS::Value> trueVal(aCx, JS::BooleanValue(true));
JS_SetProperty(aCx, obj, "noConstantBufferOffsetting", trueVal);
}
}
}