зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to autoland
This commit is contained in:
Коммит
049436ef06
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче