зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to mozilla-central a=merge
This commit is contained in:
Коммит
8ed1d04be2
|
@ -5439,13 +5439,7 @@ CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
|
|||
|
||||
// Check only if we have a canvas element; if we were created with a docshell,
|
||||
// then it's special internal use.
|
||||
if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
|
||||
// We could ask bindings for the caller type, but they already hand us a
|
||||
// JSContext, and we're at least _somewhat_ perf-sensitive (so may not
|
||||
// want to compute the caller type in the common non-write-only case), so
|
||||
// let's just use what we have.
|
||||
!nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission))
|
||||
{
|
||||
if (mCanvasElement && !mCanvasElement->CallerCanRead(aCx)) {
|
||||
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
|
||||
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return nullptr;
|
||||
|
|
|
@ -233,8 +233,9 @@ DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement,
|
|||
return;
|
||||
}
|
||||
|
||||
if (aCanvasElement->IsWriteOnly())
|
||||
if (aCanvasElement->IsWriteOnly() && !aCanvasElement->mExpandedReader) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we explicitly set WriteOnly just do it and get out
|
||||
if (forceWriteOnly) {
|
||||
|
@ -253,6 +254,25 @@ DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement,
|
|||
return;
|
||||
}
|
||||
|
||||
if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
|
||||
// This is a resource from an extension content script principal.
|
||||
|
||||
if (aCanvasElement->mExpandedReader &&
|
||||
aCanvasElement->mExpandedReader->Subsumes(aPrincipal)) {
|
||||
// This canvas already allows reading from this principal.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aCanvasElement->mExpandedReader) {
|
||||
// Allow future reads from this same princial only.
|
||||
aCanvasElement->SetWriteOnly(aPrincipal);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got here, this must be the *second* extension tainting
|
||||
// the canvas. Fall through to mark it WriteOnly for everyone.
|
||||
}
|
||||
|
||||
aCanvasElement->SetWriteOnly();
|
||||
}
|
||||
|
||||
|
|
|
@ -670,9 +670,8 @@ HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType,
|
|||
nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
// do a trust check if this is a write-only canvas
|
||||
if (mWriteOnly &&
|
||||
!nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission)) {
|
||||
// mWriteOnly check is redundant, but optimizes for the common case.
|
||||
if (mWriteOnly && !CallerCanRead(aCx)) {
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
}
|
||||
|
@ -881,9 +880,8 @@ HTMLCanvasElement::ToBlob(JSContext* aCx,
|
|||
nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
// do a trust check if this is a write-only canvas
|
||||
if (mWriteOnly &&
|
||||
!nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission)) {
|
||||
// mWriteOnly check is redundant, but optimizes for the common case.
|
||||
if (mWriteOnly && !CallerCanRead(aCx)) {
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
}
|
||||
|
@ -1093,9 +1091,36 @@ HTMLCanvasElement::IsWriteOnly()
|
|||
void
|
||||
HTMLCanvasElement::SetWriteOnly()
|
||||
{
|
||||
mExpandedReader = nullptr;
|
||||
mWriteOnly = true;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::SetWriteOnly(nsIPrincipal* aExpandedReader)
|
||||
{
|
||||
mExpandedReader = aExpandedReader;
|
||||
mWriteOnly = true;
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLCanvasElement::CallerCanRead(JSContext* aCx)
|
||||
{
|
||||
if (!mWriteOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsIPrincipal* prin = nsContentUtils::SubjectPrincipal(aCx);
|
||||
|
||||
// If mExpandedReader is set, this canvas was tainted only by
|
||||
// mExpandedReader's resources. So allow reading if the subject
|
||||
// principal subsumes mExpandedReader.
|
||||
if (mExpandedReader && prin->Subsumes(mExpandedReader)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nsContentUtils::PrincipalHasPermission(prin, nsGkAtoms::all_urlsPermission);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect* damageRect)
|
||||
{
|
||||
|
|
|
@ -230,6 +230,12 @@ public:
|
|||
*/
|
||||
void SetWriteOnly();
|
||||
|
||||
/**
|
||||
* Force the canvas to be write-only, except for readers from
|
||||
* a specific extension's content script expanded principal.
|
||||
*/
|
||||
void SetWriteOnly(nsIPrincipal* aExpandedReader);
|
||||
|
||||
/**
|
||||
* Notify that some canvas content has changed and the window may
|
||||
* need to be updated. aDamageRect is in canvas coordinates.
|
||||
|
@ -395,8 +401,15 @@ public:
|
|||
// We set this when script paints an image from a different origin.
|
||||
// We also transitively set it when script paints a canvas which
|
||||
// is itself write-only.
|
||||
bool mWriteOnly;
|
||||
bool mWriteOnly;
|
||||
|
||||
// When this canvas is (only) tainted by an image from an extension
|
||||
// content script, allow reads from the same extension afterwards.
|
||||
RefPtr<nsIPrincipal> mExpandedReader;
|
||||
|
||||
// Determines if the caller should be able to read the content.
|
||||
bool CallerCanRead(JSContext* aCx);
|
||||
|
||||
bool IsPrintCallbackDone();
|
||||
|
||||
void HandlePrintCallback(nsPresContext::nsPresContextType aType);
|
||||
|
|
|
@ -23,12 +23,6 @@ worker.onmessage = function(event) {
|
|||
|
||||
} else if (event.data.type == 'status') {
|
||||
ok(event.data.status, event.data.msg);
|
||||
|
||||
} else if (event.data.type == 'getOSCPU') {
|
||||
worker.postMessage({
|
||||
type: 'returnOSCPU',
|
||||
result: navigator.oscpu
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,39 +7,22 @@ function workerTestDone() {
|
|||
postMessage({ type: 'finish' });
|
||||
}
|
||||
|
||||
function workerTestGetOSCPU(cb) {
|
||||
addEventListener('message', function workerTestGetOSCPUCB(e) {
|
||||
if (e.data.type !== 'returnOSCPU') {
|
||||
return;
|
||||
}
|
||||
removeEventListener('message', workerTestGetOSCPUCB);
|
||||
cb(e.data.result);
|
||||
});
|
||||
postMessage({
|
||||
type: 'getOSCPU'
|
||||
});
|
||||
}
|
||||
|
||||
ok(self.performance, "Performance object should exist.");
|
||||
ok(typeof self.performance.now == 'function', "Performance object should have a 'now' method.");
|
||||
var n = self.performance.now(), d = Date.now();
|
||||
ok(n >= 0, "The value of now() should be equal to or greater than 0.");
|
||||
ok(self.performance.now() >= n, "The value of now() should monotonically increase.");
|
||||
|
||||
// The spec says performance.now() should have micro-second resolution, but allows 1ms if the platform doesn't support it.
|
||||
// Our implementation does provide micro-second resolution, except for windows XP combined with some HW properties
|
||||
// where we can't use QueryPerformanceCounters (see comments at mozilla-central/xpcom/ds/TimeStamp_windows.cpp).
|
||||
// This XP-low-res case results in about 15ms resolutions, and can be identified when perf.now() returns only integers.
|
||||
//
|
||||
// Since setTimeout might return too early/late, our goal is that perf.now() changed within 2ms
|
||||
// (or 25ms for XP-low-res), rather than specific number of setTimeout(N) invocations.
|
||||
// Spin on setTimeout() until performance.now() increases. Due to recent
|
||||
// security developments, the hr-time working group has not yet reached
|
||||
// consensus on what the recommend minimum clock resolution should be:
|
||||
// https://w3c.github.io/hr-time/#clock-resolution
|
||||
// Since setTimeout might return too early/late, our goal is for
|
||||
// performance.now() to increase before a 2 ms deadline rather than specific
|
||||
// number of setTimeout(N) invocations.
|
||||
// See bug 749894 (intermittent failures of this test)
|
||||
var platformPossiblyLowRes;
|
||||
workerTestGetOSCPU(function(oscpu) {
|
||||
platformPossiblyLowRes = oscpu.indexOf("Windows NT 5.1") == 0; // XP only
|
||||
setTimeout(checkAfterTimeout, 1);
|
||||
});
|
||||
var allInts = (n % 1) == 0; // Indicator of limited HW resolution.
|
||||
setTimeout(checkAfterTimeout, 1);
|
||||
|
||||
var checks = 0;
|
||||
|
||||
function checkAfterTimeout() {
|
||||
|
@ -47,30 +30,28 @@ function checkAfterTimeout() {
|
|||
var d2 = Date.now();
|
||||
var n2 = self.performance.now();
|
||||
|
||||
allInts = allInts && (n2 % 1) == 0;
|
||||
var lowResCounter = platformPossiblyLowRes && allInts;
|
||||
|
||||
if ( n2 == n && checks < 50 && // 50 is just a failsafe. Our real goals are 2ms or 25ms.
|
||||
( (d2 - d) < 2 // The spec allows 1ms resolution. We allow up to measured 2ms to ellapse.
|
||||
||
|
||||
lowResCounter &&
|
||||
(d2 - d) < 25
|
||||
)
|
||||
) {
|
||||
// Spin on setTimeout() until performance.now() increases. Abort the test
|
||||
// if it runs for more than 2 ms or 50 timeouts.
|
||||
let elapsedTime = d2 - d;
|
||||
let elapsedPerf = n2 - n;
|
||||
if (elapsedPerf == 0 && elapsedTime < 2 && checks < 50) {
|
||||
setTimeout(checkAfterTimeout, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Loose spec: 1ms resolution, or 15ms resolution for the XP-low-res case.
|
||||
// We shouldn't test that dt is actually within 2/25ms since the iterations break if it isn't, and timeout could be late.
|
||||
ok(n2 > n, "Loose - the value of now() should increase within 2ms (or 25ms if low-res counter) (delta now(): " + (n2 - n) + " ms).");
|
||||
// Our implementation provides 1 ms resolution (bug 1451790), but we
|
||||
// can't assert that elapsedPerf >= 1 ms because this worker test runs with
|
||||
// "privacy.reduceTimerPrecision" == false so performance.now() is not
|
||||
// limited to 1 ms resolution.
|
||||
ok(elapsedPerf > 0,
|
||||
`Loose - the value of now() should increase after 2ms. ` +
|
||||
`delta now(): ${elapsedPerf} ms`);
|
||||
|
||||
// If we need more than 1 iteration, then either performance.now() resolution
|
||||
// is shorter than 1 ms or setTimeout() is returning too early.
|
||||
ok(checks == 1,
|
||||
`Strict - the value of now() should increase after one setTimeout. ` +
|
||||
`iters: ${checks}, dt: ${elapsedTime}, now(): ${n2}`);
|
||||
|
||||
// Strict spec: if it's not the XP-low-res case, while the spec allows 1ms resolution, it prefers microseconds, which we provide.
|
||||
// Since the fastest setTimeout return which I observed was ~500 microseconds, a microseconds counter should change in 1 iteretion.
|
||||
ok(n2 > n && (lowResCounter || checks == 1),
|
||||
"Strict - [if high-res counter] the value of now() should increase after one setTimeout (hi-res: " + (!lowResCounter) +
|
||||
", iters: " + checks +
|
||||
", dt: " + (d2 - d) +
|
||||
", now(): " + n2 + ").");
|
||||
workerTestDone();
|
||||
};
|
||||
|
|
|
@ -13,21 +13,18 @@
|
|||
var n = window.performance.now(), d = Date.now();
|
||||
ok(n >= 0, "The value of now() should be equal to or greater than 0.");
|
||||
ok(window.performance.now() >= n, "The value of now() should monotonically increase.");
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduceTimerPrecision");
|
||||
SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
|
||||
SimpleTest.requestFlakyTimeout("untriaged");
|
||||
|
||||
// The spec says performance.now() should have micro-second resolution, but allows 1ms if the platform doesn't support it.
|
||||
// Our implementation does provide micro-second resolution, except for windows XP combined with some HW properties
|
||||
// where we can't use QueryPerformanceCounters (see comments at mozilla-central/xpcom/ds/TimeStamp_windows.cpp).
|
||||
// This XP-low-res case results in about 15ms resolutions, and can be identified when perf.now() returns only integers.
|
||||
//
|
||||
// Since setTimeout might return too early/late, our goal is that perf.now() changed within 2ms
|
||||
// (or 25ms for XP-low-res), rather than specific number of setTimeout(N) invocations.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestFlakyTimeout("using setTimeout() to measure performance.now()");
|
||||
|
||||
// Spin on setTimeout() until performance.now() increases. Due to recent
|
||||
// security developments, the hr-time working group has not yet reached
|
||||
// consensus on what the recommend minimum clock resolution should be:
|
||||
// https://w3c.github.io/hr-time/#clock-resolution
|
||||
// Since setTimeout might return too early/late, our goal is for
|
||||
// performance.now() to increase before a 2 ms deadline rather than specific
|
||||
// number of setTimeout(N) invocations.
|
||||
// See bug 749894 (intermittent failures of this test)
|
||||
var platformPossiblyLowRes = navigator.oscpu.indexOf("Windows NT 5.1") == 0; // XP only
|
||||
var allInts = (n % 1) == 0; // Indicator of limited HW resolution.
|
||||
var checks = 0;
|
||||
|
||||
function checkAfterTimeout() {
|
||||
|
@ -35,32 +32,26 @@
|
|||
var d2 = Date.now();
|
||||
var n2 = window.performance.now();
|
||||
|
||||
allInts = allInts && (n2 % 1) == 0;
|
||||
var lowResCounter = platformPossiblyLowRes && allInts;
|
||||
|
||||
if ( n2 == n && checks < 50 && // 50 is just a failsafe. Our real goals are 2ms or 25ms.
|
||||
( (d2 - d) < 2 // The spec allows 1ms resolution. We allow up to measured 2ms to ellapse.
|
||||
||
|
||||
lowResCounter &&
|
||||
(d2 - d) < 25
|
||||
)
|
||||
) {
|
||||
// Spin on setTimeout() until performance.now() increases. Abort the
|
||||
// test if it runs for more than 2 ms or 50 timeouts.
|
||||
let elapsedTime = d2 - d;
|
||||
let elapsedPerf = n2 - n;
|
||||
if (elapsedPerf == 0 && elapsedTime < 2 && checks < 50) {
|
||||
setTimeout(checkAfterTimeout, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Loose spec: 1ms resolution, or 15ms resolution for the XP-low-res case.
|
||||
// We shouldn't test that dt is actually within 2/25ms since the iterations break if it isn't, and timeout could be late.
|
||||
ok(n2 > n, "Loose - the value of now() should increase within 2ms (or 25ms if low-res counter) (delta now(): " + (n2 - n) + " ms).");
|
||||
// Our implementation provides 1 ms resolution (bug 1451790).
|
||||
ok(elapsedPerf >= 1,
|
||||
`Loose - the value of now() should increase by no less than 1 ms ` +
|
||||
`after 2 ms. delta now(): ${elapsedPerf} ms`);
|
||||
|
||||
// If we need more than 1 iteration, then either performance.now()
|
||||
// resolution is shorter than 1 ms or setTimeout() is returning too early.
|
||||
ok(checks == 1,
|
||||
`Strict - the value of now() should increase after one setTimeout. ` +
|
||||
`iters: ${checks}, dt: ${elapsedTime}, now(): ${n2}`);
|
||||
|
||||
// Strict spec: if it's not the XP-low-res case, while the spec allows 1ms resolution, it prefers microseconds, which we provide.
|
||||
// Since the fastest setTimeout return which I observed was ~500 microseconds, a microseconds counter should change in 1 iteretion.
|
||||
ok(n2 > n && (lowResCounter || checks == 1),
|
||||
"Strict - [if high-res counter] the value of now() should increase after one setTimeout (hi-res: " + (!lowResCounter) +
|
||||
", iters: " + checks +
|
||||
", dt: " + (d2 - d) +
|
||||
", now(): " + n2 + ").");
|
||||
SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
|
||||
SimpleTest.finish();
|
||||
};
|
||||
setTimeout(checkAfterTimeout, 1);
|
||||
|
|
|
@ -139,7 +139,7 @@ struct OpUpdateBlobImage {
|
|||
};
|
||||
|
||||
struct OpSetImageVisibleArea {
|
||||
Rect area;
|
||||
ImageIntRect area;
|
||||
ImageKey key;
|
||||
};
|
||||
|
||||
|
|
|
@ -328,7 +328,8 @@ IpcResourceUpdateQueue::UpdateExternalImage(wr::ExternalImageId aExtId,
|
|||
}
|
||||
|
||||
void
|
||||
IpcResourceUpdateQueue::SetImageVisibleArea(ImageKey aKey, const gfx::Rect& aArea)
|
||||
IpcResourceUpdateQueue::SetImageVisibleArea(ImageKey aKey,
|
||||
const ImageIntRect& aArea)
|
||||
{
|
||||
mUpdates.AppendElement(layers::OpSetImageVisibleArea(aArea, aKey));
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ public:
|
|||
ImageKey aKey,
|
||||
ImageIntRect aDirtyRect);
|
||||
|
||||
void SetImageVisibleArea(ImageKey aKey, const gfx::Rect& aArea);
|
||||
void SetImageVisibleArea(ImageKey aKey, const ImageIntRect& aArea);
|
||||
|
||||
void DeleteImage(wr::ImageKey aKey);
|
||||
|
||||
|
|
|
@ -343,7 +343,7 @@ WebRenderBridgeParent::UpdateResources(const nsTArray<OpUpdateResource>& aResour
|
|||
}
|
||||
case OpUpdateResource::TOpSetImageVisibleArea: {
|
||||
const auto& op = cmd.get_OpSetImageVisibleArea();
|
||||
wr::NormalizedRect area;
|
||||
wr::DeviceUintRect area;
|
||||
area.origin.x = op.area().x;
|
||||
area.origin.y = op.area().y;
|
||||
area.size.width = op.area().width;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "BasicLayers.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
#include "mozilla/gfx/Types.h"
|
||||
#include "mozilla/layers/ClipManager.h"
|
||||
#include "mozilla/layers/ImageClient.h"
|
||||
|
@ -63,23 +64,6 @@ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(BlobGroupDataProperty,
|
|||
nsTArray<BlobItemData*>,
|
||||
DestroyBlobGroupDataProperty);
|
||||
|
||||
static void
|
||||
SetBlobImageVisibleArea(wr::IpcResourceUpdateQueue& aResources,
|
||||
wr::ImageKey aImageKey,
|
||||
const LayoutDeviceRect& aImageRect,
|
||||
const LayoutDeviceRect& aPaintRect)
|
||||
{
|
||||
LayoutDeviceRect visibleRect = aImageRect.Intersect(aPaintRect);
|
||||
// Send the visible rect in normalized coordinates.
|
||||
Rect visibleArea = Rect((visibleRect.x - aImageRect.x) / aImageRect.width,
|
||||
(visibleRect.y - aImageRect.y) / aImageRect.height,
|
||||
visibleRect.width / aImageRect.width,
|
||||
visibleRect.height / aImageRect.height);
|
||||
|
||||
aResources.SetImageVisibleArea(aImageKey, visibleArea);
|
||||
}
|
||||
|
||||
|
||||
// These are currently manually allocated and ownership is help by the mDisplayItems
|
||||
// hash table in DIGroup
|
||||
struct BlobItemData
|
||||
|
@ -348,7 +332,7 @@ struct DIGroup
|
|||
nsPoint mLastAnimatedGeometryRootOrigin;
|
||||
IntRect mInvalidRect;
|
||||
nsRect mGroupBounds;
|
||||
LayoutDeviceRect mPaintRect;
|
||||
LayerIntRect mPaintRect;
|
||||
int32_t mAppUnitsPerDevPixel;
|
||||
gfx::Size mScale;
|
||||
FrameMetrics::ViewID mScrollId;
|
||||
|
@ -631,7 +615,9 @@ struct DIGroup
|
|||
GP("Not repainting group because it's empty\n");
|
||||
GP("End EndGroup\n");
|
||||
if (mKey) {
|
||||
SetBlobImageVisibleArea(aResources, mKey.value(), bounds, mPaintRect);
|
||||
aResources.SetImageVisibleArea(
|
||||
mKey.value(),
|
||||
ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
|
||||
PushImage(aBuilder, bounds);
|
||||
}
|
||||
return;
|
||||
|
@ -703,7 +689,9 @@ struct DIGroup
|
|||
}
|
||||
mFonts = std::move(fonts);
|
||||
mInvalidRect.SetEmpty();
|
||||
SetBlobImageVisibleArea(aResources, mKey.value(), mPaintRect, bounds);
|
||||
aResources.SetImageVisibleArea(
|
||||
mKey.value(),
|
||||
ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
|
||||
PushImage(aBuilder, bounds);
|
||||
GP("End EndGroup\n\n");
|
||||
}
|
||||
|
@ -759,6 +747,8 @@ struct DIGroup
|
|||
data->mInvalid = false;
|
||||
} else {
|
||||
BlobItemData* data = GetBlobItemData(item);
|
||||
if (data->mInvalid)
|
||||
gfxCriticalError() << "DisplayItem" << item->Name() << "should be invalid";
|
||||
// if the item is invalid it needs to be fully contained
|
||||
MOZ_RELEASE_ASSERT(!data->mInvalid);
|
||||
}
|
||||
|
@ -1232,16 +1222,23 @@ WebRenderCommandBuilder::DoGroupingForDisplayList(nsDisplayList* aList,
|
|||
g.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
|
||||
group.mResidualOffset = residualOffset;
|
||||
group.mGroupBounds = groupBounds;
|
||||
group.mPaintRect = LayoutDeviceRect::FromAppUnits(
|
||||
aWrappingItem->GetPaintRect(),
|
||||
appUnitsPerDevPixel
|
||||
);
|
||||
group.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
|
||||
group.mLayerBounds = LayerIntRect::FromUnknownRect(ScaleToOutsidePixelsOffset(group.mGroupBounds,
|
||||
scale.width,
|
||||
scale.height,
|
||||
group.mAppUnitsPerDevPixel,
|
||||
residualOffset));
|
||||
group.mPaintRect = LayerIntRect::FromUnknownRect(
|
||||
ScaleToOutsidePixelsOffset(aWrappingItem->GetPaintRect(),
|
||||
scale.width,
|
||||
scale.height,
|
||||
group.mAppUnitsPerDevPixel,
|
||||
residualOffset))
|
||||
.Intersect(group.mLayerBounds);
|
||||
// XXX: Make the paint rect relative to the layer bounds. After we include
|
||||
// mLayerBounds.TopLeft() in the blob image we want to stop doing this
|
||||
// adjustment.
|
||||
group.mPaintRect = group.mPaintRect - group.mLayerBounds.TopLeft();
|
||||
g.mTransform = Matrix::Scaling(scale.width, scale.height)
|
||||
.PostTranslate(residualOffset.x, residualOffset.y);
|
||||
group.mScale = scale;
|
||||
|
|
|
@ -422,20 +422,20 @@ impl ClipStore {
|
|||
&self.clip_node_instances[(node_range.first + index) as usize]
|
||||
}
|
||||
|
||||
// Notify the clip store that a new rasterization root has been created.
|
||||
// Notify the clip store that a new surface has been created.
|
||||
// This means any clips from an earlier root should be collected rather
|
||||
// than applied on the primitive itself.
|
||||
pub fn push_raster_root(
|
||||
pub fn push_surface(
|
||||
&mut self,
|
||||
raster_spatial_node_index: SpatialNodeIndex,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
) {
|
||||
self.clip_node_collectors.push(
|
||||
ClipNodeCollector::new(raster_spatial_node_index),
|
||||
ClipNodeCollector::new(spatial_node_index),
|
||||
);
|
||||
}
|
||||
|
||||
// Mark the end of a rasterization root.
|
||||
pub fn pop_raster_root(
|
||||
// Mark the end of a rendering surface.
|
||||
pub fn pop_surface(
|
||||
&mut self,
|
||||
) -> ClipNodeCollector {
|
||||
self.clip_node_collectors.pop().unwrap()
|
||||
|
@ -474,7 +474,7 @@ impl ClipStore {
|
|||
// Check if any clip node index should actually be
|
||||
// handled during compositing of a rasterization root.
|
||||
match self.clip_node_collectors.iter_mut().find(|c| {
|
||||
clip_chain_node.spatial_node_index < c.raster_root
|
||||
clip_chain_node.spatial_node_index < c.spatial_node_index
|
||||
}) {
|
||||
Some(collector) => {
|
||||
collector.insert(current_clip_chain_id);
|
||||
|
@ -1186,16 +1186,16 @@ pub fn project_inner_rect(
|
|||
// root at the end of primitive preparation.
|
||||
#[derive(Debug)]
|
||||
pub struct ClipNodeCollector {
|
||||
raster_root: SpatialNodeIndex,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
clips: FastHashSet<ClipChainId>,
|
||||
}
|
||||
|
||||
impl ClipNodeCollector {
|
||||
pub fn new(
|
||||
raster_root: SpatialNodeIndex,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
) -> Self {
|
||||
ClipNodeCollector {
|
||||
raster_root,
|
||||
spatial_node_index,
|
||||
clips: FastHashSet::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, Pipeline
|
|||
use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
|
||||
use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
|
||||
use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
|
||||
use clip::{ClipDataInterner, ClipChainId, ClipRegion, ClipItemKey, ClipStore};
|
||||
use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
|
||||
use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
|
||||
use euclid::vec2;
|
||||
use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
|
||||
|
@ -31,18 +31,12 @@ use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
|
|||
use render_backend::{DocumentView};
|
||||
use resource_cache::{FontInstanceMap, ImageRequest};
|
||||
use scene::{Scene, ScenePipeline, StackingContextHelpers};
|
||||
use scene_builder::DocumentResources;
|
||||
use spatial_node::{SpatialNodeType, StickyFrameInfo};
|
||||
use std::{f32, mem};
|
||||
use tiling::{CompositeOps, ScrollbarPrimitive};
|
||||
use tiling::{CompositeOps};
|
||||
use util::{MaxRect, RectHelpers};
|
||||
|
||||
static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
|
||||
r: 0.3,
|
||||
g: 0.3,
|
||||
b: 0.3,
|
||||
a: 0.6,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct ClipNode {
|
||||
id: ClipChainId,
|
||||
|
@ -144,9 +138,6 @@ pub struct DisplayListFlattener<'a> {
|
|||
/// The stack keeping track of the root clip chains associated with pipelines.
|
||||
pipeline_clip_chain_stack: Vec<ClipChainId>,
|
||||
|
||||
/// A list of scrollbar primitives.
|
||||
pub scrollbar_prims: Vec<ScrollbarPrimitive>,
|
||||
|
||||
/// The store of primitives.
|
||||
pub prim_store: PrimitiveStore,
|
||||
|
||||
|
@ -160,8 +151,9 @@ pub struct DisplayListFlattener<'a> {
|
|||
/// order to determine the default font.
|
||||
pub config: FrameBuilderConfig,
|
||||
|
||||
/// Reference to the clip interner for this document.
|
||||
clip_interner: &'a mut ClipDataInterner,
|
||||
/// Reference to the document resources, which contains
|
||||
/// shared (interned) data between display lists.
|
||||
resources: &'a mut DocumentResources,
|
||||
|
||||
/// The estimated count of primtives we expect to encounter during flattening.
|
||||
prim_count_estimate: usize,
|
||||
|
@ -178,7 +170,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
new_scene: &mut Scene,
|
||||
scene_id: u64,
|
||||
picture_id_generator: &mut PictureIdGenerator,
|
||||
clip_interner: &mut ClipDataInterner,
|
||||
resources: &mut DocumentResources,
|
||||
) -> FrameBuilder {
|
||||
// We checked that the root pipeline is available on the render backend.
|
||||
let root_pipeline_id = scene.root_pipeline_id.unwrap();
|
||||
|
@ -196,14 +188,13 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
output_pipelines,
|
||||
id_to_index_mapper: ClipIdToIndexMapper::default(),
|
||||
hit_testing_runs: Vec::new(),
|
||||
scrollbar_prims: Vec::new(),
|
||||
shadow_stack: Vec::new(),
|
||||
sc_stack: Vec::new(),
|
||||
pipeline_clip_chain_stack: vec![ClipChainId::NONE],
|
||||
prim_store: PrimitiveStore::new(),
|
||||
clip_store: ClipStore::new(),
|
||||
picture_id_generator,
|
||||
clip_interner,
|
||||
resources,
|
||||
prim_count_estimate: 0,
|
||||
};
|
||||
|
||||
|
@ -259,7 +250,6 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
);
|
||||
|
||||
let root_scroll_node = ClipId::root_scroll_node(pipeline_id);
|
||||
let scroll_frame_info = self.simple_scroll_and_clip_chain(&root_scroll_node);
|
||||
|
||||
self.push_stacking_context(
|
||||
pipeline_id,
|
||||
|
@ -295,17 +285,6 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
|
||||
self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayoutVector2D::zero());
|
||||
|
||||
if self.config.enable_scrollbars {
|
||||
let scrollbar_rect = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(10.0, 70.0));
|
||||
let container_rect = LayoutRect::new(LayoutPoint::zero(), *frame_size);
|
||||
self.add_scroll_bar(
|
||||
reference_frame_info.spatial_node_index,
|
||||
&LayoutPrimitiveInfo::new(scrollbar_rect),
|
||||
DEFAULT_SCROLLBAR_COLOR,
|
||||
ScrollbarInfo(scroll_frame_info.spatial_node_index, container_rect),
|
||||
);
|
||||
}
|
||||
|
||||
self.pop_stacking_context();
|
||||
}
|
||||
|
||||
|
@ -819,7 +798,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
for item in clip_items {
|
||||
// Intern this clip item, and store the handle
|
||||
// in the clip chain node.
|
||||
let handle = self.clip_interner.intern(&item);
|
||||
let handle = self.resources.clip_interner.intern(&item);
|
||||
|
||||
clip_chain_id = self.clip_store
|
||||
.add_clip_chain_node(
|
||||
|
@ -1337,6 +1316,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
|
||||
// Build the clip sources from the supplied region.
|
||||
let handle = self
|
||||
.resources
|
||||
.clip_interner
|
||||
.intern(&ClipItemKey::rectangle(clip_region.main, ClipMode::Clip));
|
||||
|
||||
|
@ -1351,6 +1331,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
|
||||
if let Some(ref image_mask) = clip_region.image_mask {
|
||||
let handle = self
|
||||
.resources
|
||||
.clip_interner
|
||||
.intern(&ClipItemKey::image_mask(image_mask));
|
||||
|
||||
|
@ -1366,6 +1347,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
|
||||
for region in clip_region.complex_clips {
|
||||
let handle = self
|
||||
.resources
|
||||
.clip_interner
|
||||
.intern(&ClipItemKey::rounded_rect(region.rect, region.radii, region.mode));
|
||||
|
||||
|
@ -1447,9 +1429,10 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
// blur radius is 0, the code in Picture::prepare_for_render will
|
||||
// detect this and mark the picture to be drawn directly into the
|
||||
// parent picture, which avoids an intermediate surface and blur.
|
||||
let blur_filter = FilterOp::Blur(std_deviation).sanitize();
|
||||
let shadow_pic = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
Some(PictureCompositeMode::Filter(FilterOp::Blur(std_deviation))),
|
||||
Some(PictureCompositeMode::Filter(blur_filter)),
|
||||
false,
|
||||
pipeline_id,
|
||||
None,
|
||||
|
@ -1528,40 +1511,6 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn add_scroll_bar(
|
||||
&mut self,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
info: &LayoutPrimitiveInfo,
|
||||
color: ColorF,
|
||||
scrollbar_info: ScrollbarInfo,
|
||||
) {
|
||||
if color.a == 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let prim = BrushPrimitive::new(
|
||||
BrushKind::new_solid(color),
|
||||
None,
|
||||
);
|
||||
|
||||
let prim_index = self.create_primitive(
|
||||
info,
|
||||
ClipChainId::NONE,
|
||||
spatial_node_index,
|
||||
PrimitiveContainer::Brush(prim),
|
||||
);
|
||||
|
||||
self.add_primitive_to_draw_list(
|
||||
prim_index,
|
||||
);
|
||||
|
||||
self.scrollbar_prims.push(ScrollbarPrimitive {
|
||||
prim_index,
|
||||
scroll_frame_index: scrollbar_info.0,
|
||||
frame_rect: scrollbar_info.1,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_line(
|
||||
&mut self,
|
||||
clip_and_scroll: ScrollNodeAndClipChain,
|
||||
|
@ -2117,6 +2066,3 @@ struct FlattenedStackingContext {
|
|||
participating_in_3d_context: bool,
|
||||
has_mix_blend_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScrollbarInfo(pub SpatialNodeIndex, pub LayoutRect);
|
||||
|
|
|
@ -15,7 +15,7 @@ use internal_types::{FastHashMap};
|
|||
use picture::{PictureCompositeMode, PictureSurface, RasterConfig};
|
||||
use prim_store::{PrimitiveIndex, PrimitiveStore, SpaceMapper};
|
||||
use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
|
||||
use render_backend::FrameId;
|
||||
use render_backend::{FrameResources, FrameId};
|
||||
use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
|
||||
use resource_cache::{ResourceCache};
|
||||
use scene::{ScenePipeline, SceneProperties};
|
||||
|
@ -24,8 +24,7 @@ use spatial_node::SpatialNode;
|
|||
use std::f32;
|
||||
use std::sync::Arc;
|
||||
use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
|
||||
use tiling::{ScrollbarPrimitive, SpecialRenderPasses};
|
||||
use util;
|
||||
use tiling::{SpecialRenderPasses};
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
@ -47,7 +46,6 @@ impl Default for ChasePrimitive {
|
|||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct FrameBuilderConfig {
|
||||
pub enable_scrollbars: bool,
|
||||
pub default_font_render_mode: FontRenderMode,
|
||||
pub dual_source_blending_is_supported: bool,
|
||||
pub dual_source_blending_is_enabled: bool,
|
||||
|
@ -64,7 +62,6 @@ pub struct FrameBuilder {
|
|||
pub clip_store: ClipStore,
|
||||
pub hit_testing_runs: Vec<HitTestingRun>,
|
||||
pub config: FrameBuilderConfig,
|
||||
pub scrollbar_prims: Vec<ScrollbarPrimitive>,
|
||||
}
|
||||
|
||||
pub struct FrameBuildingContext<'a> {
|
||||
|
@ -85,7 +82,7 @@ pub struct FrameBuildingState<'a> {
|
|||
pub gpu_cache: &'a mut GpuCache,
|
||||
pub special_render_passes: &'a mut SpecialRenderPasses,
|
||||
pub transforms: &'a mut TransformPalette,
|
||||
pub clip_data_store: &'a mut ClipDataStore,
|
||||
pub resources: &'a mut FrameResources,
|
||||
pub segment_builder: SegmentBuilder,
|
||||
}
|
||||
|
||||
|
@ -95,7 +92,6 @@ pub struct PictureContext {
|
|||
pub inflation_factor: f32,
|
||||
pub allow_subpixel_aa: bool,
|
||||
pub is_passthrough: bool,
|
||||
pub establishes_raster_root: bool,
|
||||
pub raster_space: RasterSpace,
|
||||
}
|
||||
|
||||
|
@ -135,7 +131,6 @@ impl FrameBuilder {
|
|||
pub fn empty() -> Self {
|
||||
FrameBuilder {
|
||||
hit_testing_runs: Vec::new(),
|
||||
scrollbar_prims: Vec::new(),
|
||||
prim_store: PrimitiveStore::new(),
|
||||
clip_store: ClipStore::new(),
|
||||
screen_rect: DeviceUintRect::zero(),
|
||||
|
@ -143,7 +138,6 @@ impl FrameBuilder {
|
|||
background_color: None,
|
||||
scene_id: 0,
|
||||
config: FrameBuilderConfig {
|
||||
enable_scrollbars: false,
|
||||
default_font_render_mode: FontRenderMode::Mono,
|
||||
dual_source_blending_is_enabled: true,
|
||||
dual_source_blending_is_supported: false,
|
||||
|
@ -161,7 +155,6 @@ impl FrameBuilder {
|
|||
) -> Self {
|
||||
FrameBuilder {
|
||||
hit_testing_runs: flattener.hit_testing_runs,
|
||||
scrollbar_prims: flattener.scrollbar_prims,
|
||||
prim_store: flattener.prim_store,
|
||||
clip_store: flattener.clip_store,
|
||||
screen_rect,
|
||||
|
@ -186,7 +179,7 @@ impl FrameBuilder {
|
|||
device_pixel_scale: DevicePixelScale,
|
||||
scene_properties: &SceneProperties,
|
||||
transform_palette: &mut TransformPalette,
|
||||
clip_data_store: &mut ClipDataStore,
|
||||
resources: &mut FrameResources,
|
||||
) -> Option<RenderTaskId> {
|
||||
profile_scope!("cull");
|
||||
|
||||
|
@ -224,7 +217,7 @@ impl FrameBuilder {
|
|||
gpu_cache,
|
||||
special_render_passes,
|
||||
transforms: transform_palette,
|
||||
clip_data_store,
|
||||
resources,
|
||||
segment_builder: SegmentBuilder::new(),
|
||||
};
|
||||
|
||||
|
@ -290,35 +283,6 @@ impl FrameBuilder {
|
|||
Some(render_task_id)
|
||||
}
|
||||
|
||||
fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
|
||||
static SCROLLBAR_PADDING: f32 = 8.0;
|
||||
|
||||
for scrollbar_prim in &self.scrollbar_prims {
|
||||
let metadata = &mut self.prim_store.primitives[scrollbar_prim.prim_index.0].metadata;
|
||||
let scroll_frame = &clip_scroll_tree.spatial_nodes[scrollbar_prim.scroll_frame_index.0];
|
||||
|
||||
// Invalidate what's in the cache so it will get rebuilt.
|
||||
gpu_cache.invalidate(&metadata.gpu_location);
|
||||
|
||||
let scrollable_distance = scroll_frame.scrollable_size().height;
|
||||
if scrollable_distance <= 0.0 {
|
||||
metadata.local_clip_rect.size = LayoutSize::zero();
|
||||
continue;
|
||||
}
|
||||
let amount_scrolled = -scroll_frame.scroll_offset().y / scrollable_distance;
|
||||
|
||||
let frame_rect = scrollbar_prim.frame_rect;
|
||||
let min_y = frame_rect.origin.y + SCROLLBAR_PADDING;
|
||||
let max_y = frame_rect.origin.y + frame_rect.size.height -
|
||||
(SCROLLBAR_PADDING + metadata.local_rect.size.height);
|
||||
|
||||
metadata.local_rect.origin.x = frame_rect.origin.x + frame_rect.size.width -
|
||||
(metadata.local_rect.size.width + SCROLLBAR_PADDING);
|
||||
metadata.local_rect.origin.y = util::lerp(min_y, max_y, amount_scrolled);
|
||||
metadata.local_clip_rect = metadata.local_rect;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
&mut self,
|
||||
resource_cache: &mut ResourceCache,
|
||||
|
@ -332,7 +296,7 @@ impl FrameBuilder {
|
|||
texture_cache_profile: &mut TextureCacheProfileCounters,
|
||||
gpu_cache_profile: &mut GpuCacheProfileCounters,
|
||||
scene_properties: &SceneProperties,
|
||||
clip_data_store: &mut ClipDataStore,
|
||||
resources: &mut FrameResources,
|
||||
) -> Frame {
|
||||
profile_scope!("build");
|
||||
debug_assert!(
|
||||
|
@ -355,8 +319,6 @@ impl FrameBuilder {
|
|||
Some(&mut transform_palette),
|
||||
);
|
||||
|
||||
self.update_scroll_bars(clip_scroll_tree, gpu_cache);
|
||||
|
||||
let mut render_tasks = RenderTaskTree::new(frame_id);
|
||||
|
||||
let screen_size = self.screen_rect.size.to_i32();
|
||||
|
@ -373,7 +335,7 @@ impl FrameBuilder {
|
|||
device_pixel_scale,
|
||||
scene_properties,
|
||||
&mut transform_palette,
|
||||
clip_data_store,
|
||||
resources,
|
||||
);
|
||||
|
||||
resource_cache.block_until_all_resources_added(gpu_cache,
|
||||
|
@ -417,7 +379,7 @@ impl FrameBuilder {
|
|||
resource_cache,
|
||||
use_dual_source_blending,
|
||||
clip_scroll_tree,
|
||||
clip_data_store,
|
||||
resources,
|
||||
};
|
||||
|
||||
pass.build(
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use api::{TileOffset, TileRange, LayoutRect, LayoutSize, LayoutPoint};
|
||||
use api::{DeviceUintSize, NormalizedRect};
|
||||
use api::{DeviceUintSize, DeviceUintRect};
|
||||
use euclid::{vec2, point2};
|
||||
use prim_store::EdgeAaSegmentMask;
|
||||
|
||||
|
@ -227,22 +227,21 @@ pub fn for_each_tile(
|
|||
}
|
||||
|
||||
pub fn compute_tile_range(
|
||||
visible_area: &NormalizedRect,
|
||||
image_size: &DeviceUintSize,
|
||||
visible_area: &DeviceUintRect,
|
||||
tile_size: u16,
|
||||
) -> TileRange {
|
||||
// Tile dimensions in normalized coordinates.
|
||||
let tw = (image_size.width as f32) / (tile_size as f32);
|
||||
let th = (image_size.height as f32) / (tile_size as f32);
|
||||
let tw = 1. / (tile_size as f32);
|
||||
let th = 1. / (tile_size as f32);
|
||||
|
||||
let t0 = point2(
|
||||
f32::floor(visible_area.origin.x * tw),
|
||||
f32::floor(visible_area.origin.y * th),
|
||||
f32::floor(visible_area.origin.x as f32 * tw),
|
||||
f32::floor(visible_area.origin.y as f32 * th),
|
||||
).cast::<u16>();
|
||||
|
||||
let t1 = point2(
|
||||
f32::ceil(visible_area.max_x() * tw),
|
||||
f32::ceil(visible_area.max_y() * th),
|
||||
f32::ceil(visible_area.max_x() as f32 * tw),
|
||||
f32::ceil(visible_area.max_y() as f32 * th),
|
||||
).cast::<u16>();
|
||||
|
||||
TileRange {
|
||||
|
@ -323,4 +322,4 @@ mod tests {
|
|||
);
|
||||
assert_eq!(count, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -292,9 +292,9 @@ impl PicturePrimitive {
|
|||
raster_spatial_node_index
|
||||
};
|
||||
|
||||
if establishes_raster_root {
|
||||
if has_surface {
|
||||
frame_state.clip_store
|
||||
.push_raster_root(raster_spatial_node_index);
|
||||
.push_surface(surface_spatial_node_index);
|
||||
}
|
||||
|
||||
let map_pic_to_world = SpaceMapper::new_with_target(
|
||||
|
@ -361,7 +361,6 @@ impl PicturePrimitive {
|
|||
inflation_factor,
|
||||
allow_subpixel_aa,
|
||||
is_passthrough: self.raster_config.is_none(),
|
||||
establishes_raster_root,
|
||||
raster_space,
|
||||
};
|
||||
|
||||
|
@ -425,10 +424,10 @@ impl PicturePrimitive {
|
|||
}
|
||||
};
|
||||
|
||||
let clip_node_collector = if context.establishes_raster_root {
|
||||
Some(frame_state.clip_store.pop_raster_root())
|
||||
} else {
|
||||
let clip_node_collector = if context.is_passthrough {
|
||||
None
|
||||
} else {
|
||||
Some(frame_state.clip_store.pop_surface())
|
||||
};
|
||||
|
||||
(local_rect, clip_node_collector)
|
||||
|
|
|
@ -1733,7 +1733,7 @@ impl PrimitiveStore {
|
|||
frame_context.device_pixel_scale,
|
||||
&frame_context.world_rect,
|
||||
&clip_node_collector,
|
||||
frame_state.clip_data_store,
|
||||
&mut frame_state.resources.clip_data_store,
|
||||
);
|
||||
|
||||
let clip_chain = match clip_chain {
|
||||
|
@ -2087,7 +2087,7 @@ fn write_brush_segment_description(
|
|||
let clip_instance = frame_state
|
||||
.clip_store
|
||||
.get_instance_from_range(&clip_chain.clips_range, i);
|
||||
let clip_node = &frame_state.clip_data_store[clip_instance.handle];
|
||||
let clip_node = &frame_state.resources.clip_data_store[clip_instance.handle];
|
||||
|
||||
// If this clip item is positioned by another positioning node, its relative position
|
||||
// could change during scrolling. This means that we would need to resegment. Instead
|
||||
|
@ -2254,7 +2254,7 @@ impl Primitive {
|
|||
frame_context.device_pixel_scale,
|
||||
&frame_context.world_rect,
|
||||
clip_node_collector,
|
||||
frame_state.clip_data_store,
|
||||
&mut frame_state.resources.clip_data_store,
|
||||
);
|
||||
|
||||
match segment_clip_chain {
|
||||
|
@ -2287,7 +2287,7 @@ impl Primitive {
|
|||
frame_state.gpu_cache,
|
||||
frame_state.resource_cache,
|
||||
frame_state.render_tasks,
|
||||
frame_state.clip_data_store,
|
||||
&mut frame_state.resources.clip_data_store,
|
||||
);
|
||||
|
||||
let clip_task_id = frame_state.render_tasks.add(clip_task);
|
||||
|
@ -2828,7 +2828,7 @@ impl Primitive {
|
|||
frame_state.gpu_cache,
|
||||
frame_state.resource_cache,
|
||||
frame_state.render_tasks,
|
||||
frame_state.clip_data_store,
|
||||
&mut frame_state.resources.clip_data_store,
|
||||
);
|
||||
|
||||
let clip_task_id = frame_state.render_tasks.add(clip_task);
|
||||
|
|
|
@ -16,9 +16,7 @@ use api::channel::{MsgReceiver, Payload};
|
|||
use api::CaptureBits;
|
||||
#[cfg(feature = "replay")]
|
||||
use api::CapturedDocument;
|
||||
#[cfg(feature = "replay")]
|
||||
use clip::ClipDataInterner;
|
||||
use clip::{ClipDataUpdateList, ClipDataStore};
|
||||
use clip::ClipDataStore;
|
||||
use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
|
||||
#[cfg(feature = "debugger")]
|
||||
use debug_server;
|
||||
|
@ -80,6 +78,24 @@ impl DocumentView {
|
|||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct FrameId(pub u32);
|
||||
|
||||
// A collection of resources that are shared by clips, primitives
|
||||
// between display lists.
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct FrameResources {
|
||||
// The store of currently active / available clip nodes. This is kept
|
||||
// in sync with the clip interner in the scene builder for each document.
|
||||
pub clip_data_store: ClipDataStore,
|
||||
}
|
||||
|
||||
impl FrameResources {
|
||||
fn new() -> Self {
|
||||
FrameResources {
|
||||
clip_data_store: ClipDataStore::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Document {
|
||||
// The latest built scene, usable to build frames.
|
||||
// received from the scene builder thread.
|
||||
|
@ -118,9 +134,7 @@ struct Document {
|
|||
frame_is_valid: bool,
|
||||
hit_tester_is_valid: bool,
|
||||
|
||||
// The store of currently active / available clip nodes. This is kept
|
||||
// in sync with the clip interner in the scene builder for each document.
|
||||
clip_data_store: ClipDataStore,
|
||||
resources: FrameResources,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
|
@ -149,7 +163,7 @@ impl Document {
|
|||
dynamic_properties: SceneProperties::new(),
|
||||
frame_is_valid: false,
|
||||
hit_tester_is_valid: false,
|
||||
clip_data_store: ClipDataStore::new(),
|
||||
resources: FrameResources::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,11 +292,11 @@ impl Document {
|
|||
&mut resource_profile.texture_cache,
|
||||
&mut resource_profile.gpu_cache,
|
||||
&self.dynamic_properties,
|
||||
&mut self.clip_data_store,
|
||||
&mut self.resources,
|
||||
);
|
||||
self.hit_tester = Some(frame_builder.create_hit_tester(
|
||||
&self.clip_scroll_tree,
|
||||
&self.clip_data_store,
|
||||
&self.resources.clip_data_store,
|
||||
));
|
||||
frame
|
||||
};
|
||||
|
@ -309,7 +323,7 @@ impl Document {
|
|||
|
||||
self.hit_tester = Some(frame_builder.create_hit_tester(
|
||||
&self.clip_scroll_tree,
|
||||
&self.clip_data_store,
|
||||
&self.resources.clip_data_store,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -419,6 +433,7 @@ pub struct RenderBackend {
|
|||
recorder: Option<Box<ApiRecordingReceiver>>,
|
||||
sampler: Option<Box<AsyncPropertySampler + Send>>,
|
||||
size_of_op: Option<VoidPtrToSizeFn>,
|
||||
namespace_alloc_by_client: bool,
|
||||
|
||||
last_scene_id: u64,
|
||||
}
|
||||
|
@ -438,6 +453,7 @@ impl RenderBackend {
|
|||
recorder: Option<Box<ApiRecordingReceiver>>,
|
||||
sampler: Option<Box<AsyncPropertySampler + Send>>,
|
||||
size_of_op: Option<VoidPtrToSizeFn>,
|
||||
namespace_alloc_by_client: bool,
|
||||
) -> RenderBackend {
|
||||
RenderBackend {
|
||||
api_rx,
|
||||
|
@ -457,6 +473,7 @@ impl RenderBackend {
|
|||
sampler,
|
||||
size_of_op,
|
||||
last_scene_id: 0,
|
||||
namespace_alloc_by_client,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -629,7 +646,7 @@ impl RenderBackend {
|
|||
self.update_document(
|
||||
txn.document_id,
|
||||
replace(&mut txn.resource_updates, Vec::new()),
|
||||
txn.clip_updates.take(),
|
||||
txn.doc_resource_updates.take(),
|
||||
replace(&mut txn.frame_ops, Vec::new()),
|
||||
replace(&mut txn.notifications, Vec::new()),
|
||||
txn.render_frame,
|
||||
|
@ -729,8 +746,13 @@ impl RenderBackend {
|
|||
tx.send(glyph_indices).unwrap();
|
||||
}
|
||||
ApiMsg::CloneApi(sender) => {
|
||||
assert!(!self.namespace_alloc_by_client);
|
||||
sender.send(self.next_namespace_id()).unwrap();
|
||||
}
|
||||
ApiMsg::CloneApiByClient(namespace_id) => {
|
||||
assert!(self.namespace_alloc_by_client);
|
||||
debug_assert!(!self.documents.iter().any(|(did, _doc)| did.0 == namespace_id));
|
||||
}
|
||||
ApiMsg::AddDocument(document_id, initial_size, layer) => {
|
||||
let document = Document::new(
|
||||
initial_size,
|
||||
|
@ -973,7 +995,7 @@ impl RenderBackend {
|
|||
&mut self,
|
||||
document_id: DocumentId,
|
||||
resource_updates: Vec<ResourceUpdate>,
|
||||
clip_updates: Option<ClipDataUpdateList>,
|
||||
doc_resource_updates: Option<DocumentResourceUpdates>,
|
||||
mut frame_ops: Vec<FrameMsg>,
|
||||
mut notifications: Vec<NotificationRequest>,
|
||||
mut render_frame: bool,
|
||||
|
@ -998,8 +1020,8 @@ impl RenderBackend {
|
|||
|
||||
// If there are any additions or removals of clip modes
|
||||
// during the scene build, apply them to the data store now.
|
||||
if let Some(clip_updates) = clip_updates {
|
||||
doc.clip_data_store.apply_updates(clip_updates);
|
||||
if let Some(updates) = doc_resource_updates {
|
||||
doc.resources.clip_data_store.apply_updates(updates.clip_updates);
|
||||
}
|
||||
|
||||
// TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
|
||||
|
@ -1316,8 +1338,8 @@ impl RenderBackend {
|
|||
config.serialize(&rendered_document.frame, file_name);
|
||||
}
|
||||
|
||||
let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1);
|
||||
config.serialize(&doc.clip_data_store, clip_data_name);
|
||||
let frame_resources_name = format!("frame-resources-{}-{}", (id.0).0, id.1);
|
||||
config.serialize(&doc.resources, frame_resources_name);
|
||||
}
|
||||
|
||||
debug!("\tscene builder");
|
||||
|
@ -1403,13 +1425,13 @@ impl RenderBackend {
|
|||
let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
|
||||
.expect(&format!("Unable to open {}.ron", scene_name));
|
||||
|
||||
let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1);
|
||||
let clip_interner = CaptureConfig::deserialize::<ClipDataInterner, _>(root, &clip_interner_name)
|
||||
.expect(&format!("Unable to open {}.ron", clip_interner_name));
|
||||
let doc_resources_name = format!("doc-resources-{}-{}", (id.0).0, id.1);
|
||||
let doc_resources = CaptureConfig::deserialize::<DocumentResources, _>(root, &doc_resources_name)
|
||||
.expect(&format!("Unable to open {}.ron", doc_resources_name));
|
||||
|
||||
let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1);
|
||||
let clip_data_store = CaptureConfig::deserialize::<ClipDataStore, _>(root, &clip_data_name)
|
||||
.expect(&format!("Unable to open {}.ron", clip_data_name));
|
||||
let frame_resources_name = format!("frame-resources-{}-{}", (id.0).0, id.1);
|
||||
let frame_resources = CaptureConfig::deserialize::<FrameResources, _>(root, &frame_resources_name)
|
||||
.expect(&format!("Unable to open {}.ron", frame_resources_name));
|
||||
|
||||
let mut doc = Document {
|
||||
scene: scene.clone(),
|
||||
|
@ -1423,7 +1445,7 @@ impl RenderBackend {
|
|||
hit_tester: None,
|
||||
frame_is_valid: false,
|
||||
hit_tester_is_valid: false,
|
||||
clip_data_store,
|
||||
resources: frame_resources,
|
||||
};
|
||||
|
||||
let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
|
||||
|
@ -1464,7 +1486,7 @@ impl RenderBackend {
|
|||
font_instances: self.resource_cache.get_font_instances(),
|
||||
scene_id: last_scene_id,
|
||||
build_frame,
|
||||
clip_interner,
|
||||
doc_resources,
|
||||
});
|
||||
|
||||
self.documents.insert(id, doc);
|
||||
|
|
|
@ -1721,7 +1721,6 @@ impl Renderer {
|
|||
};
|
||||
|
||||
let config = FrameBuilderConfig {
|
||||
enable_scrollbars: options.enable_scrollbars,
|
||||
default_font_render_mode,
|
||||
dual_source_blending_is_enabled: true,
|
||||
dual_source_blending_is_supported: ext_dual_source_blending,
|
||||
|
@ -1759,6 +1758,7 @@ impl Renderer {
|
|||
});
|
||||
let sampler = options.sampler;
|
||||
let size_of_op = options.size_of_op;
|
||||
let namespace_alloc_by_client = options.namespace_alloc_by_client;
|
||||
|
||||
let blob_image_handler = options.blob_image_handler.take();
|
||||
let thread_listener_for_render_backend = thread_listener.clone();
|
||||
|
@ -1842,6 +1842,7 @@ impl Renderer {
|
|||
recorder,
|
||||
sampler,
|
||||
size_of_op,
|
||||
namespace_alloc_by_client,
|
||||
);
|
||||
backend.run(backend_profile_counters);
|
||||
if let Some(ref thread_listener) = *thread_listener_for_render_backend {
|
||||
|
@ -4400,7 +4401,6 @@ pub struct RendererOptions {
|
|||
pub enable_aa: bool,
|
||||
pub enable_dithering: bool,
|
||||
pub max_recorded_profiles: usize,
|
||||
pub enable_scrollbars: bool,
|
||||
pub precache_shaders: bool,
|
||||
pub renderer_kind: RendererKind,
|
||||
pub enable_subpixel_aa: bool,
|
||||
|
@ -4422,6 +4422,7 @@ pub struct RendererOptions {
|
|||
pub sampler: Option<Box<AsyncPropertySampler + Send>>,
|
||||
pub chase_primitive: ChasePrimitive,
|
||||
pub support_low_priority_transactions: bool,
|
||||
pub namespace_alloc_by_client: bool,
|
||||
}
|
||||
|
||||
impl Default for RendererOptions {
|
||||
|
@ -4433,7 +4434,6 @@ impl Default for RendererOptions {
|
|||
enable_dithering: true,
|
||||
debug_flags: DebugFlags::empty(),
|
||||
max_recorded_profiles: 0,
|
||||
enable_scrollbars: false,
|
||||
precache_shaders: false,
|
||||
renderer_kind: RendererKind::Native,
|
||||
enable_subpixel_aa: false,
|
||||
|
@ -4457,6 +4457,7 @@ impl Default for RendererOptions {
|
|||
sampler: None,
|
||||
chase_primitive: ChasePrimitive::Nothing,
|
||||
support_low_priority_transactions: false,
|
||||
namespace_alloc_by_client: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use api::{FontInstanceData, FontInstanceOptions, FontInstancePlatformOptions, Fo
|
|||
use api::{GlyphDimensions, IdNamespace};
|
||||
use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
|
||||
use api::{MemoryReport, VoidPtrToSizeFn};
|
||||
use api::{TileOffset, TileSize, TileRange, NormalizedRect, BlobImageData};
|
||||
use api::{TileOffset, TileSize, TileRange, BlobImageData};
|
||||
use app_units::Au;
|
||||
#[cfg(feature = "capture")]
|
||||
use capture::ExternalCaptureImage;
|
||||
|
@ -555,7 +555,6 @@ impl ResourceCache {
|
|||
if let Some(tile_size) = template.tiling {
|
||||
template.viewport_tiles = Some(compute_tile_range(
|
||||
&area,
|
||||
&template.descriptor.size,
|
||||
tile_size,
|
||||
));
|
||||
}
|
||||
|
@ -1040,31 +1039,28 @@ impl ResourceCache {
|
|||
let mut tiles = template.viewport_tiles.unwrap_or_else(|| {
|
||||
// Default to requesting the full range of tiles.
|
||||
compute_tile_range(
|
||||
&NormalizedRect {
|
||||
origin: point2(0.0, 0.0),
|
||||
size: size2(1.0, 1.0),
|
||||
&DeviceUintRect {
|
||||
origin: point2(0, 0),
|
||||
size: template.descriptor.size,
|
||||
},
|
||||
&template.descriptor.size,
|
||||
tile_size,
|
||||
)
|
||||
});
|
||||
|
||||
// Don't request tiles that weren't invalidated.
|
||||
if let Some(dirty_rect) = template.dirty_rect {
|
||||
let f32_size = template.descriptor.size.to_f32();
|
||||
let normalized_dirty_rect = NormalizedRect {
|
||||
let dirty_rect = DeviceUintRect {
|
||||
origin: point2(
|
||||
dirty_rect.origin.x as f32 / f32_size.width,
|
||||
dirty_rect.origin.y as f32 / f32_size.height,
|
||||
dirty_rect.origin.x,
|
||||
dirty_rect.origin.y,
|
||||
),
|
||||
size: size2(
|
||||
dirty_rect.size.width as f32 / f32_size.width,
|
||||
dirty_rect.size.height as f32 / f32_size.height,
|
||||
dirty_rect.size.width,
|
||||
dirty_rect.size.height,
|
||||
),
|
||||
};
|
||||
let dirty_tiles = compute_tile_range(
|
||||
&normalized_dirty_rect,
|
||||
&template.descriptor.size,
|
||||
&dirty_rect,
|
||||
tile_size,
|
||||
);
|
||||
|
||||
|
@ -1173,7 +1169,7 @@ impl ResourceCache {
|
|||
fn discard_tiles_outside_visible_area(
|
||||
&mut self,
|
||||
key: ImageKey,
|
||||
area: &NormalizedRect
|
||||
area: &DeviceUintRect
|
||||
) {
|
||||
let template = match self.blob_image_templates.get(&key) {
|
||||
Some(template) => template,
|
||||
|
@ -1194,7 +1190,6 @@ impl ResourceCache {
|
|||
|
||||
let tile_range = compute_tile_range(
|
||||
&area,
|
||||
&template.descriptor.size,
|
||||
tile_size,
|
||||
);
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@ use util::drain_filter;
|
|||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct DocumentResourceUpdates {
|
||||
pub clip_updates: ClipDataUpdateList,
|
||||
}
|
||||
|
||||
/// Represents the work associated to a transaction before scene building.
|
||||
pub struct Transaction {
|
||||
pub document_id: DocumentId,
|
||||
|
@ -69,7 +73,7 @@ pub struct BuiltTransaction {
|
|||
pub frame_ops: Vec<FrameMsg>,
|
||||
pub removed_pipelines: Vec<PipelineId>,
|
||||
pub notifications: Vec<NotificationRequest>,
|
||||
pub clip_updates: Option<ClipDataUpdateList>,
|
||||
pub doc_resource_updates: Option<DocumentResourceUpdates>,
|
||||
pub scene_build_start_time: u64,
|
||||
pub scene_build_end_time: u64,
|
||||
pub render_frame: bool,
|
||||
|
@ -102,7 +106,7 @@ pub struct LoadScene {
|
|||
pub view: DocumentView,
|
||||
pub config: FrameBuilderConfig,
|
||||
pub build_frame: bool,
|
||||
pub clip_interner: ClipDataInterner,
|
||||
pub doc_resources: DocumentResources,
|
||||
}
|
||||
|
||||
pub struct BuiltScene {
|
||||
|
@ -144,20 +148,40 @@ pub enum SceneSwapResult {
|
|||
Aborted,
|
||||
}
|
||||
|
||||
// This struct contains all items that can be shared between
|
||||
// display lists. We want to intern and share the same clips,
|
||||
// primitives and other things between display lists so that:
|
||||
// - GPU cache handles remain valid, reducing GPU cache updates.
|
||||
// - Comparison of primitives and pictures between two
|
||||
// display lists is (a) fast (b) done during scene building.
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct DocumentResources {
|
||||
pub clip_interner: ClipDataInterner,
|
||||
}
|
||||
|
||||
impl DocumentResources {
|
||||
fn new() -> Self {
|
||||
DocumentResources {
|
||||
clip_interner: ClipDataInterner::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A document in the scene builder contains the current scene,
|
||||
// as well as a persistent clip interner. This allows clips
|
||||
// to be de-duplicated, and persisted in the GPU cache between
|
||||
// display lists.
|
||||
struct Document {
|
||||
scene: Scene,
|
||||
clip_interner: ClipDataInterner,
|
||||
resources: DocumentResources,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
fn new(scene: Scene) -> Self {
|
||||
Document {
|
||||
scene,
|
||||
clip_interner: ClipDataInterner::new(),
|
||||
resources: DocumentResources::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,8 +284,8 @@ impl SceneBuilder {
|
|||
#[cfg(feature = "capture")]
|
||||
fn save_scene(&mut self, config: CaptureConfig) {
|
||||
for (id, doc) in &self.documents {
|
||||
let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1);
|
||||
config.serialize(&doc.clip_interner, clip_interner_name);
|
||||
let doc_resources_name = format!("doc-resources-{}-{}", (id.0).0, id.1);
|
||||
config.serialize(&doc.resources, doc_resources_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,7 +297,7 @@ impl SceneBuilder {
|
|||
let scene_build_start_time = precise_time_ns();
|
||||
|
||||
let mut built_scene = None;
|
||||
let mut clip_updates = None;
|
||||
let mut doc_resource_updates = None;
|
||||
|
||||
if item.scene.has_root_pipeline() {
|
||||
let mut clip_scroll_tree = ClipScrollTree::new();
|
||||
|
@ -289,10 +313,19 @@ impl SceneBuilder {
|
|||
&mut new_scene,
|
||||
item.scene_id,
|
||||
&mut self.picture_id_generator,
|
||||
&mut item.clip_interner,
|
||||
&mut item.doc_resources,
|
||||
);
|
||||
|
||||
clip_updates = Some(item.clip_interner.end_frame_and_get_pending_updates());
|
||||
let clip_updates = item
|
||||
.doc_resources
|
||||
.clip_interner
|
||||
.end_frame_and_get_pending_updates();
|
||||
|
||||
doc_resource_updates = Some(
|
||||
DocumentResourceUpdates {
|
||||
clip_updates,
|
||||
}
|
||||
);
|
||||
|
||||
built_scene = Some(BuiltScene {
|
||||
scene: new_scene,
|
||||
|
@ -305,7 +338,7 @@ impl SceneBuilder {
|
|||
item.document_id,
|
||||
Document {
|
||||
scene: item.scene,
|
||||
clip_interner: item.clip_interner,
|
||||
resources: item.doc_resources,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -321,7 +354,7 @@ impl SceneBuilder {
|
|||
notifications: Vec::new(),
|
||||
scene_build_start_time,
|
||||
scene_build_end_time: precise_time_ns(),
|
||||
clip_updates,
|
||||
doc_resource_updates,
|
||||
});
|
||||
|
||||
self.forward_built_transaction(txn);
|
||||
|
@ -362,7 +395,7 @@ impl SceneBuilder {
|
|||
}
|
||||
|
||||
let mut built_scene = None;
|
||||
let mut clip_updates = None;
|
||||
let mut doc_resource_updates = None;
|
||||
if scene.has_root_pipeline() {
|
||||
if let Some(request) = txn.request_scene_build.take() {
|
||||
let mut clip_scroll_tree = ClipScrollTree::new();
|
||||
|
@ -378,11 +411,20 @@ impl SceneBuilder {
|
|||
&mut new_scene,
|
||||
request.scene_id,
|
||||
&mut self.picture_id_generator,
|
||||
&mut doc.clip_interner,
|
||||
&mut doc.resources,
|
||||
);
|
||||
|
||||
// Retrieve the list of updates from the clip interner.
|
||||
clip_updates = Some(doc.clip_interner.end_frame_and_get_pending_updates());
|
||||
let clip_updates = doc
|
||||
.resources
|
||||
.clip_interner
|
||||
.end_frame_and_get_pending_updates();
|
||||
|
||||
doc_resource_updates = Some(
|
||||
DocumentResourceUpdates {
|
||||
clip_updates,
|
||||
}
|
||||
);
|
||||
|
||||
built_scene = Some(BuiltScene {
|
||||
scene: new_scene,
|
||||
|
@ -419,7 +461,7 @@ impl SceneBuilder {
|
|||
frame_ops: replace(&mut txn.frame_ops, Vec::new()),
|
||||
removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()),
|
||||
notifications: replace(&mut txn.notifications, Vec::new()),
|
||||
clip_updates,
|
||||
doc_resource_updates,
|
||||
scene_build_start_time,
|
||||
scene_build_end_time: precise_time_ns(),
|
||||
})
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
use api::{ColorF, BorderStyle, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
|
||||
use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat};
|
||||
use api::{LayoutRect, MixBlendMode, PipelineId};
|
||||
use api::{MixBlendMode, PipelineId};
|
||||
use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
|
||||
use clip::{ClipDataStore, ClipStore};
|
||||
use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
|
||||
use clip::ClipStore;
|
||||
use clip_scroll_tree::{ClipScrollTree};
|
||||
use device::{FrameId, Texture};
|
||||
#[cfg(feature = "pathfinder")]
|
||||
use euclid::{TypedPoint2D, TypedVector2D};
|
||||
|
@ -17,8 +17,9 @@ use gpu_types::{TransformData, TransformPalette};
|
|||
use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
|
||||
#[cfg(feature = "pathfinder")]
|
||||
use pathfinder_partitioner::mesh::Mesh;
|
||||
use prim_store::{PrimitiveIndex, PrimitiveStore, DeferredResolve};
|
||||
use prim_store::{PrimitiveStore, DeferredResolve};
|
||||
use profiler::FrameProfileCounters;
|
||||
use render_backend::FrameResources;
|
||||
use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
|
||||
use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
|
||||
use resource_cache::ResourceCache;
|
||||
|
@ -31,13 +32,6 @@ const MIN_TARGET_SIZE: u32 = 2048;
|
|||
const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16);
|
||||
const STYLE_MASK: i32 = 0x00FF_FF00;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScrollbarPrimitive {
|
||||
pub scroll_frame_index: SpatialNodeIndex,
|
||||
pub prim_index: PrimitiveIndex,
|
||||
pub frame_rect: LayoutRect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
|
@ -49,7 +43,7 @@ pub struct RenderTargetContext<'a, 'rc> {
|
|||
pub resource_cache: &'rc mut ResourceCache,
|
||||
pub use_dual_source_blending: bool,
|
||||
pub clip_scroll_tree: &'a ClipScrollTree,
|
||||
pub clip_data_store: &'a ClipDataStore,
|
||||
pub resources: &'a FrameResources,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
|
@ -594,7 +588,7 @@ impl RenderTarget for AlphaRenderTarget {
|
|||
clip_store,
|
||||
ctx.clip_scroll_tree,
|
||||
transforms,
|
||||
ctx.clip_data_store,
|
||||
&ctx.resources.clip_data_store,
|
||||
);
|
||||
}
|
||||
RenderTaskKind::ClipRegion(ref task) => {
|
||||
|
|
|
@ -17,7 +17,7 @@ use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, Devic
|
|||
use {DeviceUintSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions};
|
||||
use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphIndex, ImageData};
|
||||
use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
|
||||
use {NativeFontHandle, WorldPoint, NormalizedRect};
|
||||
use {NativeFontHandle, WorldPoint};
|
||||
|
||||
pub type TileSize = u16;
|
||||
/// Documents are rendered in the ascending order of their associated layer values.
|
||||
|
@ -28,7 +28,7 @@ pub enum ResourceUpdate {
|
|||
AddImage(AddImage),
|
||||
UpdateImage(UpdateImage),
|
||||
DeleteImage(ImageKey),
|
||||
SetImageVisibleArea(ImageKey, NormalizedRect),
|
||||
SetImageVisibleArea(ImageKey, DeviceUintRect),
|
||||
AddFont(AddFont),
|
||||
DeleteFont(FontKey),
|
||||
AddFontInstance(AddFontInstance),
|
||||
|
@ -321,7 +321,7 @@ impl Transaction {
|
|||
self.resource_updates.push(ResourceUpdate::DeleteImage(key));
|
||||
}
|
||||
|
||||
pub fn set_image_visible_area(&mut self, key: ImageKey, area: NormalizedRect) {
|
||||
pub fn set_image_visible_area(&mut self, key: ImageKey, area: DeviceUintRect) {
|
||||
self.resource_updates.push(ResourceUpdate::SetImageVisibleArea(key, area))
|
||||
}
|
||||
|
||||
|
@ -662,6 +662,8 @@ pub enum ApiMsg {
|
|||
GetGlyphIndices(FontKey, String, MsgSender<Vec<Option<u32>>>),
|
||||
/// Adds a new document namespace.
|
||||
CloneApi(MsgSender<IdNamespace>),
|
||||
/// Adds a new document namespace.
|
||||
CloneApiByClient(IdNamespace),
|
||||
/// Adds a new document with given initial size.
|
||||
AddDocument(DocumentId, DeviceUintSize, DocumentLayer),
|
||||
/// A message targeted at a particular document.
|
||||
|
@ -695,6 +697,7 @@ impl fmt::Debug for ApiMsg {
|
|||
ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
|
||||
ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices",
|
||||
ApiMsg::CloneApi(..) => "ApiMsg::CloneApi",
|
||||
ApiMsg::CloneApiByClient(..) => "ApiMsg::CloneApiByClient",
|
||||
ApiMsg::AddDocument(..) => "ApiMsg::AddDocument",
|
||||
ApiMsg::UpdateDocument(..) => "ApiMsg::UpdateDocument",
|
||||
ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument",
|
||||
|
@ -864,6 +867,23 @@ impl RenderApiSender {
|
|||
next_id: Cell::new(ResourceId(0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new resource API object with a dedicated namespace.
|
||||
/// Namespace id is allocated by client.
|
||||
///
|
||||
/// The function could be used only when RendererOptions::namespace_alloc_by_client is true.
|
||||
/// When the option is true, create_api() could not be used to prevent namespace id conflict.
|
||||
pub fn create_api_by_client(&self, namespace_id: IdNamespace) -> RenderApi {
|
||||
let msg = ApiMsg::CloneApiByClient(namespace_id);
|
||||
self.api_sender.send(msg).expect("Failed to send CloneApiByClient message");
|
||||
RenderApi {
|
||||
api_sender: self.api_sender.clone(),
|
||||
payload_sender: self.payload_sender.clone(),
|
||||
namespace_id,
|
||||
next_id: Cell::new(ResourceId(0)),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct RenderApi {
|
||||
|
|
|
@ -123,12 +123,6 @@ pub type LayoutRectAu = TypedRect<Au, LayoutPixel>;
|
|||
pub type LayoutSizeAu = TypedSize2D<Au, LayoutPixel>;
|
||||
pub type LayoutVector2DAu = TypedVector2D<Au, LayoutPixel>;
|
||||
|
||||
/// Coordinates in normalized space (between zero and one).
|
||||
#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct NormalizedCoordinates;
|
||||
|
||||
pub type NormalizedRect = TypedRect<f32, NormalizedCoordinates>;
|
||||
|
||||
/// Stores two coordinates in texel space. The coordinates
|
||||
/// are stored in texel coordinates because the texture atlas
|
||||
/// may grow. Storing them as texel coords and normalizing
|
||||
|
|
|
@ -702,7 +702,9 @@ TransactionBuilder::UpdateExternalImageWithDirtyRect(ImageKey aKey,
|
|||
aDirtyRect);
|
||||
}
|
||||
|
||||
void TransactionBuilder::SetImageVisibleArea(ImageKey aKey, const wr::NormalizedRect& aArea)
|
||||
void
|
||||
TransactionBuilder::SetImageVisibleArea(ImageKey aKey,
|
||||
const wr::DeviceUintRect& aArea)
|
||||
{
|
||||
wr_resource_updates_set_image_visible_area(mTxn, aKey, &aArea);
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ public:
|
|||
const wr::DeviceUintRect& aDirtyRect,
|
||||
uint8_t aChannelIndex = 0);
|
||||
|
||||
void SetImageVisibleArea(ImageKey aKey, const wr::NormalizedRect& aArea);
|
||||
void SetImageVisibleArea(ImageKey aKey, const wr::DeviceUintRect& aArea);
|
||||
|
||||
void DeleteImage(wr::ImageKey aKey);
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
0d2e9611c07e04ac830277f16a3b46bf125b9e7c
|
||||
d7a6d081384ce0da9dd359b0cf4b9f758aab1b67
|
||||
|
|
|
@ -1359,7 +1359,7 @@ pub extern "C" fn wr_resource_updates_update_image(
|
|||
pub extern "C" fn wr_resource_updates_set_image_visible_area(
|
||||
txn: &mut Transaction,
|
||||
key: WrImageKey,
|
||||
area: &NormalizedRect,
|
||||
area: &DeviceUintRect,
|
||||
) {
|
||||
txn.set_image_visible_area(key, *area);
|
||||
}
|
||||
|
|
|
@ -277,9 +277,6 @@ struct DocumentHandle;
|
|||
// Geometry in a stacking context's local coordinate space (logical pixels).
|
||||
struct LayoutPixel;
|
||||
|
||||
// Coordinates in normalized space (between zero and one).
|
||||
struct NormalizedCoordinates;
|
||||
|
||||
// The renderer is responsible for submitting to the GPU the work prepared by the
|
||||
// RenderBackend.
|
||||
struct Renderer;
|
||||
|
@ -1052,8 +1049,6 @@ struct WrImageDescriptor {
|
|||
}
|
||||
};
|
||||
|
||||
using NormalizedRect = TypedRect<float, NormalizedCoordinates>;
|
||||
|
||||
struct WrTransformProperty {
|
||||
uint64_t id;
|
||||
LayoutTransform transform;
|
||||
|
@ -1715,7 +1710,7 @@ WR_FUNC;
|
|||
WR_INLINE
|
||||
void wr_resource_updates_set_image_visible_area(Transaction *aTxn,
|
||||
WrImageKey aKey,
|
||||
const NormalizedRect *aArea)
|
||||
const DeviceUintRect *aArea)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
|
|
|
@ -185,9 +185,9 @@ impl<'a> RawtestHarness<'a> {
|
|||
);
|
||||
txn.set_image_visible_area(
|
||||
blob_img,
|
||||
NormalizedRect {
|
||||
origin: point2(0.0, 0.03),
|
||||
size: size2(1.0, 0.03),
|
||||
DeviceUintRect {
|
||||
origin: point2(0, 111256 / 30),
|
||||
size: size2(1510, 111256 / 30),
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -310,9 +310,9 @@ impl<'a> RawtestHarness<'a> {
|
|||
);
|
||||
// Set a visible rectangle that is too small.
|
||||
// This will force sync rasterization of the missing tiles during frame building.
|
||||
txn.set_image_visible_area(blob_img2, NormalizedRect {
|
||||
origin: point2(0.25, 0.25),
|
||||
size: size2(0.1, 0.1),
|
||||
txn.set_image_visible_area(blob_img2, DeviceUintRect {
|
||||
origin: point2(200, 200),
|
||||
size: size2(80, 80),
|
||||
});
|
||||
|
||||
builder.push_image(
|
||||
|
|
|
@ -1587,11 +1587,9 @@ RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList)
|
|||
hint &= ~nsChangeHint_UpdateTransformLayer;
|
||||
}
|
||||
|
||||
if (hint & nsChangeHint_UpdateEffects) {
|
||||
for (nsIFrame* cont = frame; cont;
|
||||
cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
|
||||
SVGObserverUtils::UpdateEffects(cont);
|
||||
}
|
||||
if ((hint & nsChangeHint_UpdateEffects) &&
|
||||
frame == nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame)) {
|
||||
SVGObserverUtils::UpdateEffects(frame);
|
||||
}
|
||||
if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
|
||||
((hint & nsChangeHint_UpdateOpacityLayer) &&
|
||||
|
|
|
@ -21,14 +21,16 @@ namespace mozilla {
|
|||
// Feel free to add more justifications to PixelCastJustification, along with
|
||||
// a comment that explains under what circumstances it is appropriate to use.
|
||||
|
||||
enum class PixelCastJustification : uint8_t {
|
||||
enum class PixelCastJustification : uint8_t
|
||||
{
|
||||
// For the root layer, Screen Pixel = Parent Layer Pixel.
|
||||
ScreenIsParentLayerForRoot,
|
||||
// On the layout side, Screen Pixel = LayoutDevice at the outer-window level.
|
||||
LayoutDeviceIsScreenForBounds,
|
||||
// For the root layer, Render Target Pixel = Parent Layer Pixel.
|
||||
RenderTargetIsParentLayerForRoot,
|
||||
// For the root composition size we want to view it as layer pixels in any layer
|
||||
// For the root composition size we want to view it as layer pixels in any
|
||||
// layer
|
||||
ParentLayerToLayerForRootComposition,
|
||||
// The Layer coordinate space for one layer is the ParentLayer coordinate
|
||||
// space for its children
|
||||
|
@ -55,7 +57,9 @@ enum class PixelCastJustification : uint8_t {
|
|||
MultipleAsyncTransforms,
|
||||
// We have reason to believe a layer doesn't have a local transform.
|
||||
// Should only be used if we've already checked or asserted this.
|
||||
NoTransformOnLayer
|
||||
NoTransformOnLayer,
|
||||
// LayerPixels are ImagePixels
|
||||
LayerIsImage,
|
||||
};
|
||||
|
||||
template <class TargetUnits, class SourceUnits>
|
||||
|
|
|
@ -2717,10 +2717,11 @@ ComputeClipForMaskItem(nsDisplayListBuilder* aBuilder, nsIFrame* aMaskedFrame,
|
|||
// case, and we handle that specially.
|
||||
nsRect dirtyRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
|
||||
|
||||
nsIFrame* firstFrame = nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame);
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(firstFrame);
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
|
||||
nsIFrame* firstFrame =
|
||||
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame);
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames;
|
||||
// XXX check return value?
|
||||
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
||||
|
||||
for (uint32_t i = 0; i < maskFrames.Length(); ++i) {
|
||||
gfxRect clipArea;
|
||||
|
|
|
@ -9572,9 +9572,9 @@ ComputeMaskGeometry(PaintFramesParams& aParams)
|
|||
|
||||
const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
|
||||
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(firstFrame);
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames;
|
||||
// XXX check return value?
|
||||
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
||||
|
||||
if (maskFrames.Length() == 0) {
|
||||
return;
|
||||
|
@ -9715,12 +9715,11 @@ nsDisplayMasksAndClipPaths::IsValidMask() {
|
|||
|
||||
nsIFrame* firstFrame =
|
||||
nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(firstFrame);
|
||||
|
||||
if (SVGObserverUtils::GetAndObserveClipPath(firstFrame, nullptr) ==
|
||||
SVGObserverUtils::eHasRefsSomeInvalid ||
|
||||
effectProperties.HasInvalidMask()) {
|
||||
SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) ==
|
||||
SVGObserverUtils::eHasRefsSomeInvalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -10035,8 +10034,6 @@ nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo)
|
|||
{
|
||||
nsIFrame* firstFrame =
|
||||
nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(firstFrame);
|
||||
bool first = true;
|
||||
aTo += " effects=(";
|
||||
if (mFrame->StyleEffects()->mOpacity != 1.0f && mHandleOpacity) {
|
||||
|
@ -10061,7 +10058,9 @@ nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo)
|
|||
first = false;
|
||||
}
|
||||
|
||||
nsTArray<nsSVGMaskFrame*> masks = effectProperties.GetMaskFrames();
|
||||
nsTArray<nsSVGMaskFrame*> masks;
|
||||
// XXX check return value?
|
||||
SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks);
|
||||
if (!masks.IsEmpty() && masks[0]) {
|
||||
if (!first) {
|
||||
aTo += ", ";
|
||||
|
|
|
@ -566,20 +566,6 @@ nsSVGPaintingProperty::OnRenderingChange()
|
|||
}
|
||||
}
|
||||
|
||||
static SVGMaskObserverList*
|
||||
GetOrCreateMaskProperty(nsIFrame* aFrame)
|
||||
{
|
||||
SVGMaskObserverList *prop =
|
||||
aFrame->GetProperty(SVGObserverUtils::MaskProperty());
|
||||
if (prop)
|
||||
return prop;
|
||||
|
||||
prop = new SVGMaskObserverList(aFrame);
|
||||
NS_ADDREF(prop);
|
||||
aFrame->SetProperty(SVGObserverUtils::MaskProperty(), prop);
|
||||
return prop;
|
||||
}
|
||||
|
||||
static already_AddRefed<URLAndReferrerInfo>
|
||||
ResolveURLUsingLocalRef(nsIFrame* aFrame, const css::URLValueData* aURL)
|
||||
{
|
||||
|
@ -673,6 +659,8 @@ SVGObserverUtils::GetMarkerFrames(nsIFrame* aMarkedFrame,
|
|||
static SVGFilterObserverListForCSSProp*
|
||||
GetOrCreateFilterObserverListForCSS(nsIFrame* aFrame)
|
||||
{
|
||||
MOZ_ASSERT(!aFrame->GetPrevContinuation(), "Require first continuation");
|
||||
|
||||
const nsStyleEffects* effects = aFrame->StyleEffects();
|
||||
if (!effects->HasFilters()) {
|
||||
return nullptr;
|
||||
|
@ -757,6 +745,8 @@ SVGObserverUtils::DetachFromCanvasContext(nsISupports* aAutoObserver)
|
|||
static nsSVGPaintingProperty*
|
||||
GetOrCreateClipPathObserver(nsIFrame* aClippedFrame)
|
||||
{
|
||||
MOZ_ASSERT(!aClippedFrame->GetPrevContinuation(), "Require first continuation");
|
||||
|
||||
const nsStyleSVGReset* svgStyleReset = aClippedFrame->StyleSVGReset();
|
||||
if (svgStyleReset->mClipPath.GetType() != StyleShapeSourceType::URL) {
|
||||
return nullptr;
|
||||
|
@ -793,6 +783,71 @@ SVGObserverUtils::GetAndObserveClipPath(nsIFrame* aClippedFrame,
|
|||
return frame ? eHasRefsAllValid : eHasNoRefs;
|
||||
}
|
||||
|
||||
static SVGMaskObserverList*
|
||||
GetOrCreateMaskObserverList(nsIFrame* aMaskedFrame)
|
||||
{
|
||||
MOZ_ASSERT(!aMaskedFrame->GetPrevContinuation(), "Require first continuation");
|
||||
|
||||
const nsStyleSVGReset* style = aMaskedFrame->StyleSVGReset();
|
||||
if (!style->HasMask()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(style->mMask.mImageCount > 0);
|
||||
|
||||
SVGMaskObserverList* prop =
|
||||
aMaskedFrame->GetProperty(SVGObserverUtils::MaskProperty());
|
||||
if (prop) {
|
||||
return prop;
|
||||
}
|
||||
prop = new SVGMaskObserverList(aMaskedFrame);
|
||||
NS_ADDREF(prop);
|
||||
aMaskedFrame->SetProperty(SVGObserverUtils::MaskProperty(), prop);
|
||||
return prop;
|
||||
}
|
||||
|
||||
SVGObserverUtils::ReferenceState
|
||||
SVGObserverUtils::GetAndObserveMasks(nsIFrame* aMaskedFrame,
|
||||
nsTArray<nsSVGMaskFrame*>* aMaskFrames)
|
||||
{
|
||||
SVGMaskObserverList* observerList = GetOrCreateMaskObserverList(aMaskedFrame);
|
||||
if (!observerList) {
|
||||
return eHasNoRefs;
|
||||
}
|
||||
|
||||
const nsTArray<RefPtr<nsSVGPaintingProperty>>& observers =
|
||||
observerList->GetObservers();
|
||||
if (observers.IsEmpty()) {
|
||||
return eHasNoRefs;
|
||||
}
|
||||
|
||||
ReferenceState state = eHasRefsAllValid;
|
||||
|
||||
for (size_t i = 0; i < observers.Length(); i++) {
|
||||
bool frameTypeOK = true;
|
||||
nsSVGMaskFrame* maskFrame = static_cast<nsSVGMaskFrame*>(
|
||||
observers[i]->GetAndObserveReferencedFrame(LayoutFrameType::SVGMask,
|
||||
&frameTypeOK));
|
||||
MOZ_ASSERT(!maskFrame || frameTypeOK);
|
||||
// XXXjwatt: this looks fishy
|
||||
if (!frameTypeOK) {
|
||||
// We can not find the specific SVG mask resource in the downloaded SVG
|
||||
// document. There are two possibilities:
|
||||
// 1. The given resource id is invalid.
|
||||
// 2. The given resource id refers to a viewbox.
|
||||
//
|
||||
// Hand it over to the style image.
|
||||
observerList->ResolveImage(i);
|
||||
state = eHasRefsSomeInvalid;
|
||||
}
|
||||
if (aMaskFrames) {
|
||||
aMaskFrames->AppendElement(maskFrame);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
SVGGeometryElement*
|
||||
SVGObserverUtils::GetTextPathsReferencedPath(nsIFrame* aTextPathFrame)
|
||||
{
|
||||
|
@ -838,7 +893,7 @@ SVGObserverUtils::InitiateResourceDocLoads(nsIFrame* aFrame)
|
|||
// make aFrame start observing the referenced frames.
|
||||
Unused << GetOrCreateFilterObserverListForCSS(aFrame);
|
||||
Unused << GetOrCreateClipPathObserver(aFrame);
|
||||
Unused << GetEffectProperties(aFrame);
|
||||
Unused << GetOrCreateMaskObserverList(aFrame);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -919,21 +974,6 @@ SVGObserverUtils::GetPaintingPropertyForURI(URLAndReferrerInfo* aURI,
|
|||
return prop;
|
||||
}
|
||||
|
||||
SVGObserverUtils::EffectProperties
|
||||
SVGObserverUtils::GetEffectProperties(nsIFrame* aFrame)
|
||||
{
|
||||
NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
|
||||
|
||||
EffectProperties result;
|
||||
const nsStyleSVGReset *style = aFrame->StyleSVGReset();
|
||||
|
||||
MOZ_ASSERT(style->mMask.mImageCount > 0);
|
||||
result.mMaskObservers = style->HasMask()
|
||||
? GetOrCreateMaskProperty(aFrame) : nullptr;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
nsSVGPaintServerFrame *
|
||||
SVGObserverUtils::GetPaintServer(nsIFrame* aTargetFrame,
|
||||
nsStyleSVGPaint nsStyleSVG::* aPaint)
|
||||
|
@ -978,60 +1018,6 @@ SVGObserverUtils::GetPaintServer(nsIFrame* aTargetFrame,
|
|||
return static_cast<nsSVGPaintServerFrame*>(result);
|
||||
}
|
||||
|
||||
nsTArray<nsSVGMaskFrame *>
|
||||
SVGObserverUtils::EffectProperties::GetMaskFrames()
|
||||
{
|
||||
nsTArray<nsSVGMaskFrame *> result;
|
||||
if (!mMaskObservers) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
const nsTArray<RefPtr<nsSVGPaintingProperty>>& observers =
|
||||
mMaskObservers->GetObservers();
|
||||
for (size_t i = 0; i < observers.Length(); i++) {
|
||||
nsSVGMaskFrame* maskFrame = static_cast<nsSVGMaskFrame*>(
|
||||
observers[i]->GetAndObserveReferencedFrame(LayoutFrameType::SVGMask, &ok));
|
||||
MOZ_ASSERT(!maskFrame || ok);
|
||||
if (!ok) {
|
||||
// We can not find the specific SVG mask resource in the downloaded SVG
|
||||
// document. There are two possibilities:
|
||||
// 1. The given resource id is invalid.
|
||||
// 2. The given resource id refers to a viewbox.
|
||||
//
|
||||
// Hand it over to the style image.
|
||||
mMaskObservers->ResolveImage(i);
|
||||
}
|
||||
result.AppendElement(maskFrame);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
SVGObserverUtils::EffectProperties::HasNoOrValidEffects()
|
||||
{
|
||||
return HasNoOrValidMask();
|
||||
}
|
||||
|
||||
bool
|
||||
SVGObserverUtils::EffectProperties::HasNoOrValidMask()
|
||||
{
|
||||
if (mMaskObservers) {
|
||||
bool ok = true;
|
||||
const nsTArray<RefPtr<nsSVGPaintingProperty>>& observers =
|
||||
mMaskObservers->GetObservers();
|
||||
for (size_t i = 0; i < observers.Length(); i++) {
|
||||
observers[i]->GetAndObserveReferencedFrame(LayoutFrameType::SVGMask, &ok);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SVGObserverUtils::UpdateEffects(nsIFrame* aFrame)
|
||||
{
|
||||
|
|
|
@ -581,46 +581,6 @@ public:
|
|||
NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty,
|
||||
URIObserverHashtable)
|
||||
|
||||
struct EffectProperties {
|
||||
SVGMaskObserverList* mMaskObservers;
|
||||
|
||||
/**
|
||||
* @return an array which contains all SVG mask frames.
|
||||
*/
|
||||
nsTArray<nsSVGMaskFrame*> GetMaskFrames();
|
||||
|
||||
/*
|
||||
* @return true if all effects we have are valid or we have no effect
|
||||
* at all.
|
||||
*/
|
||||
bool HasNoOrValidEffects();
|
||||
|
||||
/*
|
||||
* @return true if we have any invalid effect.
|
||||
*/
|
||||
bool HasInvalidEffects() {
|
||||
return !HasNoOrValidEffects();
|
||||
}
|
||||
|
||||
/*
|
||||
* @return true if we either do not have mask or all masks we have
|
||||
* are valid.
|
||||
*/
|
||||
bool HasNoOrValidMask();
|
||||
|
||||
/*
|
||||
* @return true if we have an invalid mask.
|
||||
*/
|
||||
bool HasInvalidMask() {
|
||||
return !HasNoOrValidMask();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param aFrame should be the first continuation
|
||||
*/
|
||||
static EffectProperties GetEffectProperties(nsIFrame* aFrame);
|
||||
|
||||
/**
|
||||
* Ensures that that if the given frame requires any resources that are in
|
||||
* SVG resource documents that the loading of those documents is initiated.
|
||||
|
@ -795,6 +755,22 @@ public:
|
|||
GetAndObserveClipPath(nsIFrame* aClippedFrame,
|
||||
nsSVGClipPathFrame** aClipPathFrame);
|
||||
|
||||
/**
|
||||
* If masking is applied to aMaskedFrame, gets an array of any SVG masks
|
||||
* that are referenced, setting up aMaskFrames as a rendering observer of
|
||||
* those masks (if any).
|
||||
*
|
||||
* NOTE! A return value of eHasNoRefs does NOT mean that there are no masks
|
||||
* to be applied, only that there are no references to SVG mask elements.
|
||||
*
|
||||
* Note that, unlike for filters, a reference to an ID that doesn't exist
|
||||
* is not invalid for clip-path or mask. We will return eHasNoRefs in that
|
||||
* case.
|
||||
*/
|
||||
static ReferenceState
|
||||
GetAndObserveMasks(nsIFrame* aMaskedFrame,
|
||||
nsTArray<nsSVGMaskFrame*>* aMaskFrames);
|
||||
|
||||
/**
|
||||
* Get the SVGGeometryElement that is referenced by aTextPathFrame, and make
|
||||
* aTextPathFrame start observing rendering changes to that element.
|
||||
|
|
|
@ -303,7 +303,7 @@ nsRect
|
|||
// GetPostFilterBounds below! See bug 1494263.
|
||||
// TODO: we should really return an empty rect for eHasRefsSomeInvalid since
|
||||
// in that case we disable painting of the element.
|
||||
if (SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
|
||||
if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
|
||||
SVGObserverUtils::eHasRefsSomeInvalid) {
|
||||
return aPreEffectsOverflowRect;
|
||||
}
|
||||
|
@ -747,9 +747,10 @@ nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame)
|
|||
{
|
||||
nsIFrame* firstFrame =
|
||||
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(firstFrame);
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames;
|
||||
// XXX check return value?
|
||||
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
||||
|
||||
const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
|
||||
|
||||
for (uint32_t i = 0; i < maskFrames.Length(); i++) {
|
||||
|
@ -803,11 +804,6 @@ nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
|
|||
}
|
||||
|
||||
gfxContext& ctx = aParams.ctx;
|
||||
nsIFrame* firstFrame =
|
||||
nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(firstFrame);
|
||||
|
||||
RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget();
|
||||
|
||||
if (maskUsage.shouldGenerateMaskLayer &&
|
||||
|
@ -823,7 +819,12 @@ nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
|
|||
SurfaceFormat::A8);
|
||||
}
|
||||
|
||||
nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
|
||||
nsIFrame* firstFrame =
|
||||
nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames;
|
||||
// XXX check return value?
|
||||
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
||||
|
||||
AutoPopGroup autoPop;
|
||||
bool shouldPushOpacity = (maskUsage.opacity != 1.0) &&
|
||||
(maskFrames.Length() != 1);
|
||||
|
@ -924,19 +925,18 @@ void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams, const T& aPa
|
|||
gfxContext& context = aParams.ctx;
|
||||
gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
|
||||
|
||||
/* Properties are added lazily and may have been removed by a restyle,
|
||||
so make sure all applicable ones are set again. */
|
||||
nsIFrame* firstFrame =
|
||||
nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(firstFrame);
|
||||
|
||||
nsSVGClipPathFrame* clipPathFrame;
|
||||
// XXX check return value?
|
||||
SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
|
||||
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames;
|
||||
// XXX check return value?
|
||||
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
||||
|
||||
gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
|
||||
|
||||
bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
|
||||
maskUsage.shouldGenerateClipMaskLayer ||
|
||||
|
|
|
@ -487,12 +487,11 @@ nsSVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
|
|||
nsIFrame* firstFrame =
|
||||
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
|
||||
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(firstFrame);
|
||||
const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
|
||||
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
|
||||
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames;
|
||||
// XXX check return value?
|
||||
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
||||
aUsage.shouldGenerateMaskLayer = (maskFrames.Length() > 0);
|
||||
|
||||
nsSVGClipPathFrame* clipPathFrame;
|
||||
|
@ -714,22 +713,21 @@ nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame,
|
|||
/* Properties are added lazily and may have been removed by a restyle,
|
||||
so make sure all applicable ones are set again. */
|
||||
nsSVGClipPathFrame* clipPathFrame;
|
||||
SVGObserverUtils::EffectProperties effectProperties =
|
||||
SVGObserverUtils::GetEffectProperties(aFrame);
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames;
|
||||
// TODO: We currently pass nullptr instead of an nsTArray* here, but we
|
||||
// actually should get the filter frames and then pass them into
|
||||
// PaintFilteredFrame below! See bug 1494263.
|
||||
if (effectProperties.HasInvalidEffects() ||
|
||||
SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
|
||||
if (SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
|
||||
SVGObserverUtils::eHasRefsSomeInvalid ||
|
||||
SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
|
||||
SVGObserverUtils::eHasRefsSomeInvalid ||
|
||||
SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames) ==
|
||||
SVGObserverUtils::eHasRefsSomeInvalid) {
|
||||
// Some resource is invalid. We shouldn't paint anything.
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<nsSVGMaskFrame*> masks = effectProperties.GetMaskFrames();
|
||||
nsSVGMaskFrame *maskFrame = masks.IsEmpty() ? nullptr : masks[0];
|
||||
nsSVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0];
|
||||
|
||||
MixModeBlender blender(aFrame, &aContext);
|
||||
gfxContext* target = blender.ShouldCreateDrawTargetForBlend()
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 35 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 35 B |
|
@ -0,0 +1,86 @@
|
|||
"use strict";
|
||||
|
||||
const server = createHttpServer({hosts: ["green.example.com", "red.example.com"]});
|
||||
|
||||
server.registerDirectory("/data/", do_get_file("data"));
|
||||
|
||||
server.registerPathHandler("/pixel.html", (request, response) => {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.write(`<!DOCTYPE html>
|
||||
<script>
|
||||
function readByWeb() {
|
||||
let ctx = document.querySelector("canvas").getContext("2d");
|
||||
let {data} = ctx.getImageData(0, 0, 1, 1);
|
||||
return data.slice(0, 3).join();
|
||||
}
|
||||
</script>
|
||||
`);
|
||||
});
|
||||
|
||||
add_task(async function test_contentscript_canvas_tainting() {
|
||||
async function contentScript() {
|
||||
let canvas = document.createElement("canvas");
|
||||
let ctx = canvas.getContext("2d");
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
function draw(url) {
|
||||
return new Promise(resolve => {
|
||||
let img = document.createElement("img");
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, 0, 0, 1, 1);
|
||||
resolve();
|
||||
};
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function readByExt() {
|
||||
let {data} = ctx.getImageData(0, 0, 1, 1);
|
||||
return data.slice(0, 3).join();
|
||||
}
|
||||
|
||||
let readByWeb = window.wrappedJSObject.readByWeb;
|
||||
|
||||
// Test reading after drawing an image from the same origin as the web page.
|
||||
await draw("http://green.example.com/data/pixel_green.gif");
|
||||
browser.test.assertEq(readByWeb(), "0,255,0", "Content can read same-origin image");
|
||||
browser.test.assertEq(readByExt(), "0,255,0", "Extension can read same-origin image");
|
||||
|
||||
// Test reading after drawing a blue pixel data URI from extension content script.
|
||||
await draw("data:image/gif;base64,R0lGODlhAQABAIABAAAA/wAAACwAAAAAAQABAAACAkQBADs=");
|
||||
browser.test.assertThrows(readByWeb, /operation is insecure/, "Content can't read extension's image");
|
||||
browser.test.assertEq(readByExt(), "0,0,255", "Extension can read its own image");
|
||||
|
||||
// Test after tainting the canvas with an image from a third party domain.
|
||||
await draw("http://red.example.com/data/pixel_red.gif");
|
||||
browser.test.assertThrows(readByWeb, /operation is insecure/, "Content can't read third party image");
|
||||
browser.test.assertThrows(readByExt, /operation is insecure/, "Extension can't read fully tainted");
|
||||
|
||||
// Test canvas is still fully tainted after drawing extension's data: image again.
|
||||
await draw("data:image/gif;base64,R0lGODlhAQABAIABAAAA/wAAACwAAAAAAQABAAACAkQBADs=");
|
||||
browser.test.assertThrows(readByWeb, /operation is insecure/, "Canvas still fully tainted for content");
|
||||
browser.test.assertThrows(readByExt, /operation is insecure/, "Canvas still fully tainted for extension");
|
||||
|
||||
browser.test.sendMessage("done");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
content_scripts: [{
|
||||
"matches": ["http://green.example.com/pixel.html"],
|
||||
"js": ["cs.js"],
|
||||
}],
|
||||
},
|
||||
files: {
|
||||
"cs.js": contentScript,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
let contentPage = await ExtensionTestUtils.loadContentPage("http://green.example.com/pixel.html");
|
||||
await extension.awaitMessage("done");
|
||||
|
||||
await contentPage.close();
|
||||
await extension.unload();
|
||||
});
|
|
@ -3,6 +3,7 @@ skip-if = os == "android" || (os == "win" && debug) || (os == "linux")
|
|||
[test_ext_i18n_css.js]
|
||||
[test_ext_contentscript.js]
|
||||
[test_ext_contentscript_about_blank_start.js]
|
||||
[test_ext_contentscript_canvas_tainting.js]
|
||||
[test_ext_contentscript_scriptCreated.js]
|
||||
[test_ext_contentscript_triggeringPrincipal.js]
|
||||
skip-if = (os == "android" && debug) || (os == "win" && debug) # Windows: Bug 1438796
|
||||
|
|
|
@ -446,13 +446,6 @@ nsChildView::Create(nsIWidget* aParent,
|
|||
if (!gChildViewMethodsSwizzled) {
|
||||
nsToolkit::SwizzleMethods([NSView class], @selector(mouseDownCanMoveWindow),
|
||||
@selector(nsChildView_NSView_mouseDownCanMoveWindow));
|
||||
#ifdef __LP64__
|
||||
nsToolkit::SwizzleMethods([NSEvent class], @selector(addLocalMonitorForEventsMatchingMask:handler:),
|
||||
@selector(nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:handler:),
|
||||
true);
|
||||
nsToolkit::SwizzleMethods([NSEvent class], @selector(removeMonitor:),
|
||||
@selector(nsChildView_NSEvent_removeMonitor:), true);
|
||||
#endif
|
||||
gChildViewMethodsSwizzled = true;
|
||||
}
|
||||
|
||||
|
@ -7274,48 +7267,3 @@ static const CGEventField kCGWindowNumberField = (const CGEventField) 51;
|
|||
}
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __LP64__
|
||||
// When using blocks, at least on OS X 10.7, the OS sometimes calls
|
||||
// +[NSEvent removeMonitor:] more than once on a single event monitor, which
|
||||
// causes crashes. See bug 678607. We hook these methods to work around
|
||||
// the problem.
|
||||
@interface NSEvent (MethodSwizzling)
|
||||
+ (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block;
|
||||
+ (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor;
|
||||
@end
|
||||
|
||||
// This is a local copy of the AppKit frameworks sEventObservers hashtable.
|
||||
// It only stores "local monitors". We use it to ensure that +[NSEvent
|
||||
// removeMonitor:] is never called more than once on the same local monitor.
|
||||
static NSHashTable *sLocalEventObservers = nil;
|
||||
|
||||
@implementation NSEvent (MethodSwizzling)
|
||||
|
||||
+ (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block
|
||||
{
|
||||
if (!sLocalEventObservers) {
|
||||
sLocalEventObservers = [[NSHashTable hashTableWithOptions:
|
||||
NSHashTableStrongMemory | NSHashTableObjectPointerPersonality] retain];
|
||||
}
|
||||
id retval =
|
||||
[self nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:mask handler:block];
|
||||
if (sLocalEventObservers && retval && ![sLocalEventObservers containsObject:retval]) {
|
||||
[sLocalEventObservers addObject:retval];
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
+ (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor
|
||||
{
|
||||
if (sLocalEventObservers && [eventMonitor isKindOfClass: ::NSClassFromString(@"_NSLocalEventObserver")]) {
|
||||
if (![sLocalEventObservers containsObject:eventMonitor]) {
|
||||
return;
|
||||
}
|
||||
[sLocalEventObservers removeObject:eventMonitor];
|
||||
}
|
||||
[self nsChildView_NSEvent_removeMonitor:eventMonitor];
|
||||
}
|
||||
|
||||
@end
|
||||
#endif // #ifdef __LP64__
|
||||
|
|
Загрузка…
Ссылка в новой задаче