gecko/gfx/layers/ipc/CompositorParent.cpp
Chris Lord 3bee7155b6 Bug 716403 - Offset fixed layers so the toolbar doesn't obscure them. r=nrc,kats
Offset fixed layers in the compositor so that the toolbar in Firefox for Android
doesn't obscure them. This does not affect layout, so input on the elements in
said layers will appear broken.
2013-03-06 16:55:59 +00:00

1463 lines
47 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=2 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <map>
#include "mozilla/DebugOnly.h"
#include "base/basictypes.h"
#include <algorithm>
#if defined(MOZ_WIDGET_ANDROID)
# include <android/log.h>
# include "AndroidBridge.h"
#endif
#include "AsyncPanZoomController.h"
#include "AutoOpenSurface.h"
#include "BasicLayers.h"
#include "CompositorParent.h"
#include "LayerManagerOGL.h"
#include "nsGkAtoms.h"
#include "nsIWidget.h"
#include "RenderTrace.h"
#include "ShadowLayersParent.h"
#include "BasicLayers.h"
#include "LayerManagerOGL.h"
#include "nsIWidget.h"
#include "nsGkAtoms.h"
#include "RenderTrace.h"
#include "nsStyleAnimation.h"
#include "nsDisplayList.h"
#include "AnimationCommon.h"
#include "nsAnimationManager.h"
#include "TiledLayerBuffer.h"
#include "gfxPlatform.h"
#include "mozilla/dom/ScreenOrientation.h"
#include "mozilla/AutoRestore.h"
using namespace base;
using namespace mozilla;
using namespace mozilla::ipc;
using namespace mozilla::dom;
using namespace std;
namespace mozilla {
namespace layers {
// FIXME/bug 774386: we're assuming that there's only one
// CompositorParent, but that's not always true. This assumption only
// affects CrossProcessCompositorParent below.
static CompositorParent* sCurrentCompositor;
static Thread* sCompositorThread = nullptr;
// manual reference count of the compositor thread.
static int sCompositorThreadRefCount = 0;
static MessageLoop* sMainLoop = nullptr;
// When ContentParent::StartUp() is called, we use the Thread global.
// When StartUpWithExistingThread() is used, we have to use the two
// duplicated globals, because there's no API to make a Thread from an
// existing thread.
static PlatformThreadId sCompositorThreadID = 0;
static MessageLoop* sCompositorLoop = nullptr;
struct LayerTreeState {
nsRefPtr<Layer> mRoot;
nsRefPtr<AsyncPanZoomController> mController;
TargetConfig mTargetConfig;
};
static uint8_t sPanZoomUserDataKey;
struct PanZoomUserData : public LayerUserData {
PanZoomUserData(AsyncPanZoomController* aController)
: mController(aController)
{ }
// We don't keep a strong ref here because PanZoomUserData is only
// set transiently, and APZC is thread-safe refcounted so
// AddRef/Release is expensive.
AsyncPanZoomController* mController;
};
/**
* Lookup the indirect shadow tree for |aId| and return it if it
* exists. Otherwise null is returned. This must only be called on
* the compositor thread.
*/
static const LayerTreeState* GetIndirectShadowTree(uint64_t aId);
static void DeferredDeleteCompositorParent(CompositorParent* aNowReadyToDie)
{
aNowReadyToDie->Release();
}
static void DeleteCompositorThread()
{
if (NS_IsMainThread()){
delete sCompositorThread;
sCompositorThread = nullptr;
sCompositorLoop = nullptr;
sCompositorThreadID = 0;
} else {
sMainLoop->PostTask(FROM_HERE, NewRunnableFunction(&DeleteCompositorThread));
}
}
static void ReleaseCompositorThread()
{
if(--sCompositorThreadRefCount == 0) {
DeleteCompositorThread();
}
}
void
CompositorParent::StartUpWithExistingThread(MessageLoop* aMsgLoop,
PlatformThreadId aThreadID)
{
MOZ_ASSERT(!sCompositorThread);
CreateCompositorMap();
sCompositorLoop = aMsgLoop;
sCompositorThreadID = aThreadID;
sMainLoop = MessageLoop::current();
sCompositorThreadRefCount = 1;
}
void CompositorParent::StartUp()
{
// Check if compositor started already with StartUpWithExistingThread
if (sCompositorThreadID) {
return;
}
MOZ_ASSERT(!sCompositorLoop);
CreateCompositorMap();
CreateThread();
sMainLoop = MessageLoop::current();
}
void CompositorParent::ShutDown()
{
DestroyThread();
DestroyCompositorMap();
}
bool CompositorParent::CreateThread()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
if (sCompositorThread || sCompositorLoop) {
return true;
}
sCompositorThreadRefCount = 1;
sCompositorThread = new Thread("Compositor");
if (!sCompositorThread->Start()) {
delete sCompositorThread;
sCompositorThread = nullptr;
return false;
}
return true;
}
void CompositorParent::DestroyThread()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
ReleaseCompositorThread();
}
MessageLoop* CompositorParent::CompositorLoop()
{
return sCompositorThread ? sCompositorThread->message_loop() : sCompositorLoop;
}
CompositorParent::CompositorParent(nsIWidget* aWidget,
bool aRenderToEGLSurface,
int aSurfaceWidth, int aSurfaceHeight)
: mWidget(aWidget)
, mCurrentCompositeTask(NULL)
, mPaused(false)
, mXScale(1.0)
, mYScale(1.0)
, mIsFirstPaint(false)
, mLayersUpdated(false)
, mRenderToEGLSurface(aRenderToEGLSurface)
, mEGLSurfaceSize(aSurfaceWidth, aSurfaceHeight)
, mPauseCompositionMonitor("PauseCompositionMonitor")
, mResumeCompositionMonitor("ResumeCompositionMonitor")
, mOverrideComposeReadiness(false)
, mForceCompositionTask(nullptr)
{
NS_ABORT_IF_FALSE(sCompositorThread != nullptr || sCompositorThreadID,
"The compositor thread must be Initialized before instanciating a COmpositorParent.");
MOZ_COUNT_CTOR(CompositorParent);
mCompositorID = 0;
// FIXME: This holds on the the fact that right now the only thing that
// can destroy this instance is initialized on the compositor thread after
// this task has been processed.
CompositorLoop()->PostTask(FROM_HERE, NewRunnableFunction(&AddCompositor,
this, &mCompositorID));
if (!sCurrentCompositor) {
sCurrentCompositor = this;
}
++sCompositorThreadRefCount;
}
PlatformThreadId
CompositorParent::CompositorThreadID()
{
return sCompositorThread ? sCompositorThread->thread_id() : sCompositorThreadID;
}
CompositorParent::~CompositorParent()
{
MOZ_COUNT_DTOR(CompositorParent);
if (this == sCurrentCompositor) {
sCurrentCompositor = NULL;
}
ReleaseCompositorThread();
}
void
CompositorParent::Destroy()
{
NS_ABORT_IF_FALSE(ManagedPLayersParent().Length() == 0,
"CompositorParent destroyed before managed PLayersParent");
// Ensure that the layer manager is destructed on the compositor thread.
mLayerManager = NULL;
}
static void
DispatchMemoryPressureToLayers(Layer* aLayer)
{
ShadowLayer* shadowLayer = aLayer->AsShadowLayer();
if (shadowLayer) {
TiledLayerComposer* tileComposer = shadowLayer->AsTiledLayerComposer();
if (tileComposer) {
tileComposer->MemoryPressure();
}
}
for (Layer* child = aLayer->GetFirstChild();
child; child = child->GetNextSibling()) {
DispatchMemoryPressureToLayers(child);
}
}
bool
CompositorParent::RecvMemoryPressure()
{
if (!mLayerManager)
return true;
Layer* layer = mLayerManager->GetRoot();
if (layer)
DispatchMemoryPressureToLayers(layer);
return true;
}
bool
CompositorParent::RecvWillStop()
{
mPaused = true;
RemoveCompositor(mCompositorID);
// Ensure that the layer manager is destroyed before CompositorChild.
mLayerManager->Destroy();
return true;
}
bool
CompositorParent::RecvStop()
{
Destroy();
// There are chances that the ref count reaches zero on the main thread shortly
// after this function returns while some ipdl code still needs to run on
// this thread.
// We must keep the compositor parent alive untill the code handling message
// reception is finished on this thread.
this->AddRef(); // Corresponds to DeferredDeleteCompositorParent's Release
CompositorLoop()->PostTask(FROM_HERE,
NewRunnableFunction(&DeferredDeleteCompositorParent,
this));
return true;
}
bool
CompositorParent::RecvPause()
{
PauseComposition();
return true;
}
bool
CompositorParent::RecvResume()
{
ResumeComposition();
return true;
}
bool
CompositorParent::RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
SurfaceDescriptor* aOutSnapshot)
{
AutoOpenSurface opener(OPEN_READ_WRITE, aInSnapshot);
nsRefPtr<gfxContext> target = new gfxContext(opener.Get());
ComposeToTarget(target);
*aOutSnapshot = aInSnapshot;
return true;
}
void
CompositorParent::ScheduleRenderOnCompositorThread()
{
CancelableTask *renderTask = NewRunnableMethod(this, &CompositorParent::ScheduleComposition);
CompositorLoop()->PostTask(FROM_HERE, renderTask);
}
void
CompositorParent::PauseComposition()
{
NS_ABORT_IF_FALSE(CompositorThreadID() == PlatformThread::CurrentId(),
"PauseComposition() can only be called on the compositor thread");
MonitorAutoLock lock(mPauseCompositionMonitor);
if (!mPaused) {
mPaused = true;
#ifdef MOZ_WIDGET_ANDROID
static_cast<LayerManagerOGL*>(mLayerManager.get())->gl()->ReleaseSurface();
#endif
}
// if anyone's waiting to make sure that composition really got paused, tell them
lock.NotifyAll();
}
void
CompositorParent::ResumeComposition()
{
NS_ABORT_IF_FALSE(CompositorThreadID() == PlatformThread::CurrentId(),
"ResumeComposition() can only be called on the compositor thread");
MonitorAutoLock lock(mResumeCompositionMonitor);
#ifdef MOZ_WIDGET_ANDROID
if (!static_cast<LayerManagerOGL*>(mLayerManager.get())->gl()->RenewSurface()) {
// We can't get a surface. This could be because the activity changed between
// the time resume was scheduled and now.
__android_log_print(ANDROID_LOG_INFO, "CompositorParent", "Unable to renew compositor surface; remaining in paused state");
lock.NotifyAll();
return;
}
#endif
mPaused = false;
Composite();
// if anyone's waiting to make sure that composition really got resumed, tell them
lock.NotifyAll();
}
void
CompositorParent::ForceComposition()
{
// Cancel the orientation changed state to force composition
mForceCompositionTask = nullptr;
ScheduleRenderOnCompositorThread();
}
void
CompositorParent::SetEGLSurfaceSize(int width, int height)
{
NS_ASSERTION(mRenderToEGLSurface, "Compositor created without RenderToEGLSurface ar provided");
mEGLSurfaceSize.SizeTo(width, height);
if (mLayerManager) {
static_cast<LayerManagerOGL*>(mLayerManager.get())->SetSurfaceSize(mEGLSurfaceSize.width, mEGLSurfaceSize.height);
}
}
void
CompositorParent::ResumeCompositionAndResize(int width, int height)
{
SetEGLSurfaceSize(width, height);
ResumeComposition();
}
/*
* This will execute a pause synchronously, waiting to make sure that the compositor
* really is paused.
*/
void
CompositorParent::SchedulePauseOnCompositorThread()
{
MonitorAutoLock lock(mPauseCompositionMonitor);
CancelableTask *pauseTask = NewRunnableMethod(this,
&CompositorParent::PauseComposition);
CompositorLoop()->PostTask(FROM_HERE, pauseTask);
// Wait until the pause has actually been processed by the compositor thread
lock.Wait();
}
bool
CompositorParent::ScheduleResumeOnCompositorThread(int width, int height)
{
MonitorAutoLock lock(mResumeCompositionMonitor);
CancelableTask *resumeTask =
NewRunnableMethod(this, &CompositorParent::ResumeCompositionAndResize, width, height);
CompositorLoop()->PostTask(FROM_HERE, resumeTask);
// Wait until the resume has actually been processed by the compositor thread
lock.Wait();
return !mPaused;
}
void
CompositorParent::ScheduleTask(CancelableTask* task, int time)
{
if (time == 0) {
MessageLoop::current()->PostTask(FROM_HERE, task);
} else {
MessageLoop::current()->PostDelayedTask(FROM_HERE, task, time);
}
}
void
CompositorParent::NotifyShadowTreeTransaction()
{
if (mLayerManager) {
ShadowLayerManager *shadow = mLayerManager->AsShadowManager();
if (shadow) {
shadow->NotifyShadowTreeTransaction();
}
}
ScheduleComposition();
}
void
CompositorParent::ScheduleComposition()
{
if (mCurrentCompositeTask) {
return;
}
bool initialComposition = mLastCompose.IsNull();
TimeDuration delta;
if (!initialComposition)
delta = TimeStamp::Now() - mLastCompose;
#ifdef COMPOSITOR_PERFORMANCE_WARNING
mExpectedComposeTime = TimeStamp::Now() + TimeDuration::FromMilliseconds(15);
#endif
mCurrentCompositeTask = NewRunnableMethod(this, &CompositorParent::Composite);
// Since 60 fps is the maximum frame rate we can acheive, scheduling composition
// events less than 15 ms apart wastes computation..
if (!initialComposition && delta.ToMilliseconds() < 15) {
#ifdef COMPOSITOR_PERFORMANCE_WARNING
mExpectedComposeTime = TimeStamp::Now() + TimeDuration::FromMilliseconds(15 - delta.ToMilliseconds());
#endif
ScheduleTask(mCurrentCompositeTask, 15 - delta.ToMilliseconds());
} else {
ScheduleTask(mCurrentCompositeTask, 0);
}
}
void
CompositorParent::SetTransformation(float aScale, nsIntPoint aScrollOffset)
{
mXScale = aScale;
mYScale = aScale;
mScrollOffset = aScrollOffset;
}
/**
* DRAWING PHASE ONLY
*
* For reach RefLayer in |aRoot|, look up its referent and connect it
* to the layer tree, if found. On exiting scope, detaches all
* resolved referents.
*/
class NS_STACK_CLASS AutoResolveRefLayers {
public:
/**
* |aRoot| must remain valid in the scope of this, which should be
* guaranteed by this helper only being used during the drawing
* phase.
*/
AutoResolveRefLayers(Layer* aRoot, const TargetConfig& aConfig) : mRoot(aRoot), mTargetConfig(aConfig), mReadyForCompose(true)
{ WalkTheTree<Resolve>(mRoot, nullptr); }
~AutoResolveRefLayers()
{ WalkTheTree<Detach>(mRoot, nullptr); }
bool IsReadyForCompose()
{ return mReadyForCompose; }
private:
enum Op { Resolve, Detach };
template<Op OP>
void WalkTheTree(Layer* aLayer, Layer* aParent)
{
if (RefLayer* ref = aLayer->AsRefLayer()) {
if (const LayerTreeState* state = GetIndirectShadowTree(ref->GetReferentId())) {
if (Layer* referent = state->mRoot) {
if (!ref->GetVisibleRegion().IsEmpty()) {
ScreenOrientation chromeOrientation = mTargetConfig.orientation();
ScreenOrientation contentOrientation = state->mTargetConfig.orientation();
if (!IsSameDimension(chromeOrientation, contentOrientation) &&
ContentMightReflowOnOrientationChange(mTargetConfig.clientBounds())) {
mReadyForCompose = false;
}
}
if (OP == Resolve) {
ref->ConnectReferentLayer(referent);
if (AsyncPanZoomController* apzc = state->mController) {
referent->SetUserData(&sPanZoomUserDataKey,
new PanZoomUserData(apzc));
}
} else {
ref->DetachReferentLayer(referent);
referent->RemoveUserData(&sPanZoomUserDataKey);
}
}
}
}
for (Layer* child = aLayer->GetFirstChild();
child; child = child->GetNextSibling()) {
WalkTheTree<OP>(child, aLayer);
}
}
bool IsSameDimension(ScreenOrientation o1, ScreenOrientation o2) {
bool isO1portrait = (o1 == eScreenOrientation_PortraitPrimary || o1 == eScreenOrientation_PortraitSecondary);
bool isO2portrait = (o2 == eScreenOrientation_PortraitPrimary || o2 == eScreenOrientation_PortraitSecondary);
return !(isO1portrait ^ isO2portrait);
}
bool ContentMightReflowOnOrientationChange(nsIntRect& rect) {
return rect.width != rect.height;
}
Layer* mRoot;
TargetConfig mTargetConfig;
bool mReadyForCompose;
AutoResolveRefLayers(const AutoResolveRefLayers&) MOZ_DELETE;
AutoResolveRefLayers& operator=(const AutoResolveRefLayers&) MOZ_DELETE;
};
void
CompositorParent::Composite()
{
NS_ABORT_IF_FALSE(CompositorThreadID() == PlatformThread::CurrentId(),
"Composite can only be called on the compositor thread");
mCurrentCompositeTask = nullptr;
mLastCompose = TimeStamp::Now();
if (!CanComposite()) {
return;
}
Layer* layer = mLayerManager->GetRoot();
AutoResolveRefLayers resolve(layer, mTargetConfig);
if (mForceCompositionTask && !mOverrideComposeReadiness) {
if (!resolve.IsReadyForCompose()) {
return;
} else {
mForceCompositionTask->Cancel();
mForceCompositionTask = nullptr;
}
}
bool requestNextFrame = TransformShadowTree(mLastCompose);
if (requestNextFrame) {
ScheduleComposition();
}
RenderTraceLayers(layer, "0000");
if (LAYERS_OPENGL == mLayerManager->GetBackendType() &&
!mTargetConfig.naturalBounds().IsEmpty()) {
LayerManagerOGL* lm = static_cast<LayerManagerOGL*>(mLayerManager.get());
lm->SetWorldTransform(
ComputeGLTransformForRotation(mTargetConfig.naturalBounds(),
mTargetConfig.rotation()));
}
mLayerManager->EndEmptyTransaction();
#ifdef COMPOSITOR_PERFORMANCE_WARNING
if (mExpectedComposeTime + TimeDuration::FromMilliseconds(15) < TimeStamp::Now()) {
printf_stderr("Compositor: Composite took %i ms.\n",
15 + (int)(TimeStamp::Now() - mExpectedComposeTime).ToMilliseconds());
}
#endif
}
void
CompositorParent::ComposeToTarget(gfxContext* aTarget)
{
AutoRestore<bool> override(mOverrideComposeReadiness);
mOverrideComposeReadiness = true;
if (!CanComposite()) {
return;
}
mLayerManager->BeginTransactionWithTarget(aTarget);
// Since CanComposite() is true, Composite() must end the layers txn
// we opened above.
Composite();
}
bool
CompositorParent::CanComposite()
{
return !(mPaused || !mLayerManager || !mLayerManager->GetRoot());
}
// Do a breadth-first search to find the first layer in the tree that is
// scrollable.
static void
Translate2D(gfx3DMatrix& aTransform, const gfxPoint& aOffset)
{
aTransform._41 += aOffset.x;
aTransform._42 += aOffset.y;
}
void
CompositorParent::TransformFixedLayers(Layer* aLayer,
const gfxPoint& aTranslation,
const gfxSize& aScaleDiff,
const gfx::Margin& aFixedLayerMargins)
{
if (aLayer->GetIsFixedPosition() &&
!aLayer->GetParent()->GetIsFixedPosition()) {
// When a scale has been applied to a layer, it focuses around (0,0).
// The anchor position is used here as a scale focus point (assuming that
// aScaleDiff has already been applied) to re-focus the scale.
const gfxPoint& anchor = aLayer->GetFixedPositionAnchor();
gfxPoint translation(aTranslation - (anchor - anchor / aScaleDiff));
// Offset this translation by the fixed layer margins, depending on what
// side of the viewport the layer is anchored to.
if (anchor.x > 0) {
translation.x -= aFixedLayerMargins.right;
} else {
translation.x += aFixedLayerMargins.left;
}
if (anchor.y > 0) {
translation.y -= aFixedLayerMargins.bottom;
} else {
translation.y += aFixedLayerMargins.top;
}
// The transform already takes the resolution scale into account. Since we
// will apply the resolution scale again when computing the effective
// transform, we must apply the inverse resolution scale here.
gfx3DMatrix layerTransform = aLayer->GetTransform();
Translate2D(layerTransform, translation);
if (ContainerLayer* c = aLayer->AsContainerLayer()) {
layerTransform.Scale(1.0f/c->GetPreXScale(),
1.0f/c->GetPreYScale(),
1);
}
layerTransform.ScalePost(1.0f/aLayer->GetPostXScale(),
1.0f/aLayer->GetPostYScale(),
1);
ShadowLayer* shadow = aLayer->AsShadowLayer();
shadow->SetShadowTransform(layerTransform);
const nsIntRect* clipRect = aLayer->GetClipRect();
if (clipRect) {
nsIntRect transformedClipRect(*clipRect);
transformedClipRect.MoveBy(translation.x, translation.y);
shadow->SetShadowClipRect(&transformedClipRect);
}
}
for (Layer* child = aLayer->GetFirstChild();
child; child = child->GetNextSibling()) {
TransformFixedLayers(child, aTranslation, aScaleDiff, aFixedLayerMargins);
}
}
// Go down shadow layer tree, setting properties to match their non-shadow
// counterparts.
static void
SetShadowProperties(Layer* aLayer)
{
// FIXME: Bug 717688 -- Do these updates in ShadowLayersParent::RecvUpdate.
ShadowLayer* shadow = aLayer->AsShadowLayer();
// Set the shadow's base transform to the layer's base transform.
shadow->SetShadowTransform(aLayer->GetBaseTransform());
shadow->SetShadowVisibleRegion(aLayer->GetVisibleRegion());
shadow->SetShadowClipRect(aLayer->GetClipRect());
shadow->SetShadowOpacity(aLayer->GetOpacity());
for (Layer* child = aLayer->GetFirstChild();
child; child = child->GetNextSibling()) {
SetShadowProperties(child);
}
}
static void
SampleValue(float aPortion, Animation& aAnimation, nsStyleAnimation::Value& aStart,
nsStyleAnimation::Value& aEnd, Animatable* aValue)
{
nsStyleAnimation::Value interpolatedValue;
NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() ||
aStart.GetUnit() == nsStyleAnimation::eUnit_None ||
aEnd.GetUnit() == nsStyleAnimation::eUnit_None, "Must have same unit");
nsStyleAnimation::Interpolate(aAnimation.property(), aStart, aEnd,
aPortion, interpolatedValue);
if (aAnimation.property() == eCSSProperty_opacity) {
*aValue = interpolatedValue.GetFloatValue();
return;
}
nsCSSValueList* interpolatedList = interpolatedValue.GetCSSValueListValue();
TransformData& data = aAnimation.data().get_TransformData();
nsPoint origin = data.origin();
// we expect all our transform data to arrive in css pixels, so here we must
// adjust to dev pixels.
double cssPerDev = double(nsDeviceContext::AppUnitsPerCSSPixel())
/ double(data.appUnitsPerDevPixel());
gfxPoint3D mozOrigin = data.mozOrigin();
mozOrigin.x = mozOrigin.x * cssPerDev;
mozOrigin.y = mozOrigin.y * cssPerDev;
gfxPoint3D perspectiveOrigin = data.perspectiveOrigin();
perspectiveOrigin.x = perspectiveOrigin.x * cssPerDev;
perspectiveOrigin.y = perspectiveOrigin.y * cssPerDev;
nsDisplayTransform::FrameTransformProperties props(interpolatedList,
mozOrigin,
perspectiveOrigin,
data.perspective());
gfx3DMatrix transform =
nsDisplayTransform::GetResultingTransformMatrix(props, origin,
data.appUnitsPerDevPixel(),
&data.bounds());
gfxPoint3D scaledOrigin =
gfxPoint3D(NS_round(NSAppUnitsToFloatPixels(origin.x, data.appUnitsPerDevPixel())),
NS_round(NSAppUnitsToFloatPixels(origin.y, data.appUnitsPerDevPixel())),
0.0f);
transform.Translate(scaledOrigin);
InfallibleTArray<TransformFunction> functions;
functions.AppendElement(TransformMatrix(transform));
*aValue = functions;
}
static bool
SampleAnimations(Layer* aLayer, TimeStamp aPoint)
{
AnimationArray& animations = aLayer->GetAnimations();
InfallibleTArray<AnimData>& animationData = aLayer->GetAnimationData();
bool activeAnimations = false;
for (uint32_t i = animations.Length(); i-- !=0; ) {
Animation& animation = animations[i];
AnimData& animData = animationData[i];
double numIterations = animation.numIterations() != -1 ?
animation.numIterations() : NS_IEEEPositiveInfinity();
double positionInIteration =
ElementAnimations::GetPositionInIteration(aPoint - animation.startTime(),
animation.duration(),
numIterations,
animation.direction());
NS_ABORT_IF_FALSE(0.0 <= positionInIteration &&
positionInIteration <= 1.0,
"position should be in [0-1]");
int segmentIndex = 0;
AnimationSegment* segment = animation.segments().Elements();
while (segment->endPortion() < positionInIteration) {
++segment;
++segmentIndex;
}
double positionInSegment = (positionInIteration - segment->startPortion()) /
(segment->endPortion() - segment->startPortion());
double portion = animData.mFunctions[segmentIndex]->GetValue(positionInSegment);
activeAnimations = true;
// interpolate the property
Animatable interpolatedValue;
SampleValue(portion, animation, animData.mStartValues[segmentIndex],
animData.mEndValues[segmentIndex], &interpolatedValue);
ShadowLayer* shadow = aLayer->AsShadowLayer();
switch (animation.property()) {
case eCSSProperty_opacity:
{
shadow->SetShadowOpacity(interpolatedValue.get_float());
break;
}
case eCSSProperty_transform:
{
gfx3DMatrix matrix = interpolatedValue.get_ArrayOfTransformFunction()[0].get_TransformMatrix().value();
if (ContainerLayer* c = aLayer->AsContainerLayer()) {
matrix.ScalePost(c->GetInheritedXScale(),
c->GetInheritedYScale(),
1);
}
NS_ASSERTION(!aLayer->GetIsFixedPosition(), "Can't animate transforms on fixed-position layers");
shadow->SetShadowTransform(matrix);
break;
}
default:
NS_WARNING("Unhandled animated property");
}
}
for (Layer* child = aLayer->GetFirstChild(); child;
child = child->GetNextSibling()) {
activeAnimations |= SampleAnimations(child, aPoint);
}
return activeAnimations;
}
bool
CompositorParent::ApplyAsyncContentTransformToTree(TimeStamp aCurrentFrame,
Layer *aLayer,
bool* aWantNextFrame)
{
bool appliedTransform = false;
for (Layer* child = aLayer->GetFirstChild();
child; child = child->GetNextSibling()) {
appliedTransform |=
ApplyAsyncContentTransformToTree(aCurrentFrame, child, aWantNextFrame);
}
ContainerLayer* container = aLayer->AsContainerLayer();
if (!container) {
return appliedTransform;
}
AsyncPanZoomController* controller = nullptr;
// Check if an AsyncPanZoomController is attached to this layer.
if (LayerUserData* data = aLayer->GetUserData(&sPanZoomUserDataKey)) {
controller = static_cast<PanZoomUserData*>(data)->mController;
} else {
// Check if a derived implementation provides a default AsyncPanZoomController.
controller = GetDefaultPanZoomController();
}
if (controller) {
ShadowLayer* shadow = aLayer->AsShadowLayer();
ViewTransform treeTransform;
*aWantNextFrame |=
controller->SampleContentTransformForFrame(aCurrentFrame,
container,
&treeTransform);
gfx3DMatrix transform(gfx3DMatrix(treeTransform) * aLayer->GetTransform());
// The transform already takes the resolution scale into account. Since we
// will apply the resolution scale again when computing the effective
// transform, we must apply the inverse resolution scale here.
transform.Scale(1.0f/container->GetPreXScale(),
1.0f/container->GetPreYScale(),
1);
transform.ScalePost(1.0f/aLayer->GetPostXScale(),
1.0f/aLayer->GetPostYScale(),
1);
shadow->SetShadowTransform(transform);
gfx::Margin fixedLayerMargins(0, 0, 0, 0);
TransformFixedLayers(
aLayer,
-treeTransform.mTranslation / treeTransform.mScale,
treeTransform.mScale,
fixedLayerMargins);
appliedTransform = true;
}
return appliedTransform;
}
void
CompositorParent::TransformScrollableLayer(Layer* aLayer, const gfx3DMatrix& aRootTransform)
{
ShadowLayer* shadow = aLayer->AsShadowLayer();
ContainerLayer* container = aLayer->AsContainerLayer();
const FrameMetrics& metrics = container->GetFrameMetrics();
// We must apply the resolution scale before a pan/zoom transform, so we call
// GetTransform here.
const gfx3DMatrix& currentTransform = aLayer->GetTransform();
gfx3DMatrix treeTransform;
// Translate fixed position layers so that they stay in the correct position
// when mScrollOffset and metricsScrollOffset differ.
gfxPoint offset;
gfxSize scaleDiff;
float rootScaleX = aRootTransform.GetXScale(),
rootScaleY = aRootTransform.GetYScale();
// The ratio of layers pixels to device pixels. The Java
// compositor wants to see values in units of device pixels, so we
// map our FrameMetrics values to that space. This is not exposed
// as a FrameMetrics helper because it's a deprecated conversion.
float devPixelRatioX = 1 / rootScaleX, devPixelRatioY = 1 / rootScaleY;
gfxPoint scrollOffsetLayersPixels(metrics.GetScrollOffsetInLayerPixels());
nsIntPoint scrollOffsetDevPixels(
NS_lround(scrollOffsetLayersPixels.x * devPixelRatioX),
NS_lround(scrollOffsetLayersPixels.y * devPixelRatioY));
if (mIsFirstPaint) {
mContentRect = metrics.mContentRect;
SetFirstPaintViewport(scrollOffsetDevPixels,
1/rootScaleX,
mContentRect,
metrics.mScrollableRect);
mIsFirstPaint = false;
} else if (!metrics.mContentRect.IsEqualEdges(mContentRect)) {
mContentRect = metrics.mContentRect;
SetPageRect(metrics.mScrollableRect);
}
// We synchronise the viewport information with Java after sending the above
// notifications, so that Java can take these into account in its response.
// Calculate the absolute display port to send to Java
gfx::Rect displayPortLayersPixels(metrics.mCriticalDisplayPort.IsEmpty() ?
metrics.mDisplayPort : metrics.mCriticalDisplayPort);
nsIntRect displayPortDevPixels(
NS_lround(displayPortLayersPixels.x * devPixelRatioX),
NS_lround(displayPortLayersPixels.y * devPixelRatioY),
NS_lround(displayPortLayersPixels.width * devPixelRatioX),
NS_lround(displayPortLayersPixels.height * devPixelRatioY));
displayPortDevPixels.x += scrollOffsetDevPixels.x;
displayPortDevPixels.y += scrollOffsetDevPixels.y;
gfx::Margin fixedLayerMargins(0, 0, 0, 0);
SyncViewportInfo(displayPortDevPixels, 1/rootScaleX, mLayersUpdated,
mScrollOffset, mXScale, mYScale, fixedLayerMargins);
mLayersUpdated = false;
// Handle transformations for asynchronous panning and zooming. We determine the
// zoom used by Gecko from the transformation set on the root layer, and we
// determine the scroll offset used by Gecko from the frame metrics of the
// primary scrollable layer. We compare this to the desired zoom and scroll
// offset in the view transform we obtained from Java in order to compute the
// transformation we need to apply.
float tempScaleDiffX = rootScaleX * mXScale;
float tempScaleDiffY = rootScaleY * mYScale;
nsIntPoint metricsScrollOffset(0, 0);
if (metrics.IsScrollable()) {
metricsScrollOffset = scrollOffsetDevPixels;
}
nsIntPoint scrollCompensation(
(mScrollOffset.x / tempScaleDiffX - metricsScrollOffset.x) * mXScale,
(mScrollOffset.y / tempScaleDiffY - metricsScrollOffset.y) * mYScale);
treeTransform = gfx3DMatrix(ViewTransform(-scrollCompensation,
gfxSize(mXScale, mYScale)));
// If the contents can fit entirely within the widget area on a particular
// dimenson, we need to translate and scale so that the fixed layers remain
// within the page boundaries.
if (mContentRect.width * tempScaleDiffX < metrics.mCompositionBounds.width) {
offset.x = -metricsScrollOffset.x;
scaleDiff.width = std::min(1.0f, metrics.mCompositionBounds.width / (float)mContentRect.width);
} else {
offset.x = clamped(mScrollOffset.x / tempScaleDiffX, (float)mContentRect.x,
mContentRect.XMost() - metrics.mCompositionBounds.width / tempScaleDiffX) -
metricsScrollOffset.x;
scaleDiff.width = tempScaleDiffX;
}
if (mContentRect.height * tempScaleDiffY < metrics.mCompositionBounds.height) {
offset.y = -metricsScrollOffset.y;
scaleDiff.height = std::min(1.0f, metrics.mCompositionBounds.height / (float)mContentRect.height);
} else {
offset.y = clamped(mScrollOffset.y / tempScaleDiffY, (float)mContentRect.y,
mContentRect.YMost() - metrics.mCompositionBounds.height / tempScaleDiffY) -
metricsScrollOffset.y;
scaleDiff.height = tempScaleDiffY;
}
// The transform already takes the resolution scale into account. Since we
// will apply the resolution scale again when computing the effective
// transform, we must apply the inverse resolution scale here.
gfx3DMatrix computedTransform = treeTransform * currentTransform;
computedTransform.Scale(1.0f/container->GetPreXScale(),
1.0f/container->GetPreYScale(),
1);
computedTransform.ScalePost(1.0f/container->GetPostXScale(),
1.0f/container->GetPostYScale(),
1);
shadow->SetShadowTransform(computedTransform);
TransformFixedLayers(aLayer, offset, scaleDiff, fixedLayerMargins);
}
bool
CompositorParent::TransformShadowTree(TimeStamp aCurrentFrame)
{
bool wantNextFrame = false;
Layer* root = mLayerManager->GetRoot();
// NB: we must sample animations *before* sampling pan/zoom
// transforms.
wantNextFrame |= SampleAnimations(root, aCurrentFrame);
const gfx3DMatrix& rootTransform = root->GetTransform();
// FIXME/bug 775437: unify this interface with the ~native-fennec
// derived code
//
// Attempt to apply an async content transform to any layer that has
// an async pan zoom controller (which means that it is rendered
// async using Gecko). If this fails, fall back to transforming the
// primary scrollable layer. "Failing" here means that we don't
// find a frame that is async scrollable. Note that the fallback
// code also includes Fennec which is rendered async. Fennec uses
// its own platform-specific async rendering that is done partially
// in Gecko and partially in Java.
if (!ApplyAsyncContentTransformToTree(aCurrentFrame, root, &wantNextFrame)) {
nsAutoTArray<Layer*,1> scrollableLayers;
#ifdef MOZ_WIDGET_ANDROID
scrollableLayers.AppendElement(mLayerManager->GetPrimaryScrollableLayer());
#else
mLayerManager->GetScrollableLayers(scrollableLayers);
#endif
for (uint32_t i = 0; i < scrollableLayers.Length(); i++) {
if (scrollableLayers[i]) {
TransformScrollableLayer(scrollableLayers[i], rootTransform);
}
}
}
return wantNextFrame;
}
void
CompositorParent::SetFirstPaintViewport(const nsIntPoint& aOffset, float aZoom,
const nsIntRect& aPageRect, const gfx::Rect& aCssPageRect)
{
#ifdef MOZ_WIDGET_ANDROID
AndroidBridge::Bridge()->SetFirstPaintViewport(aOffset, aZoom, aPageRect, aCssPageRect);
#endif
}
void
CompositorParent::SetPageRect(const gfx::Rect& aCssPageRect)
{
#ifdef MOZ_WIDGET_ANDROID
AndroidBridge::Bridge()->SetPageRect(aCssPageRect);
#endif
}
void
CompositorParent::SyncViewportInfo(const nsIntRect& aDisplayPort,
float aDisplayResolution, bool aLayersUpdated,
nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
gfx::Margin& aFixedLayerMargins)
{
#ifdef MOZ_WIDGET_ANDROID
AndroidBridge::Bridge()->SyncViewportInfo(aDisplayPort, aDisplayResolution, aLayersUpdated,
aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins);
#endif
}
void
CompositorParent::ShadowLayersUpdated(ShadowLayersParent* aLayerTree,
const TargetConfig& aTargetConfig,
bool isFirstPaint)
{
if (!isFirstPaint && !mIsFirstPaint && mTargetConfig.orientation() != aTargetConfig.orientation()) {
if (mForceCompositionTask != NULL) {
mForceCompositionTask->Cancel();
}
mForceCompositionTask = NewRunnableMethod(this, &CompositorParent::ForceComposition);
ScheduleTask(mForceCompositionTask, gfxPlatform::GetPlatform()->GetOrientationSyncMillis());
}
// Instruct the LayerManager to update its render bounds now. Since all the orientation
// change, dimension change would be done at the stage, update the size here is free of
// race condition.
if (LAYERS_OPENGL == mLayerManager->GetBackendType()) {
LayerManagerOGL* lm = static_cast<LayerManagerOGL*>(mLayerManager.get());
lm->UpdateRenderBounds(aTargetConfig.clientBounds());
}
mTargetConfig = aTargetConfig;
mIsFirstPaint = mIsFirstPaint || isFirstPaint;
mLayersUpdated = true;
Layer* root = aLayerTree->GetRoot();
mLayerManager->SetRoot(root);
if (root) {
SetShadowProperties(root);
}
ScheduleComposition();
ShadowLayerManager *shadow = mLayerManager->AsShadowManager();
if (shadow) {
shadow->NotifyShadowTreeTransaction();
}
}
PLayersParent*
CompositorParent::AllocPLayers(const LayersBackend& aBackendHint,
const uint64_t& aId,
LayersBackend* aBackend,
int32_t* aMaxTextureSize)
{
MOZ_ASSERT(aId == 0);
// mWidget doesn't belong to the compositor thread, so it should be set to
// NULL before returning from this method, to avoid accessing it elsewhere.
nsIntRect rect;
mWidget->GetClientBounds(rect);
*aBackend = aBackendHint;
if (aBackendHint == mozilla::layers::LAYERS_OPENGL) {
nsRefPtr<LayerManagerOGL> layerManager;
layerManager =
new LayerManagerOGL(mWidget, mEGLSurfaceSize.width, mEGLSurfaceSize.height, mRenderToEGLSurface);
mWidget = NULL;
mLayerManager = layerManager;
ShadowLayerManager* shadowManager = layerManager->AsShadowManager();
if (shadowManager) {
shadowManager->SetCompositorID(mCompositorID);
}
if (!layerManager->Initialize()) {
NS_ERROR("Failed to init OGL Layers");
return NULL;
}
ShadowLayerManager* slm = layerManager->AsShadowManager();
if (!slm) {
return NULL;
}
*aMaxTextureSize = layerManager->GetMaxTextureSize();
return new ShadowLayersParent(slm, this, 0);
} else if (aBackendHint == mozilla::layers::LAYERS_BASIC) {
nsRefPtr<LayerManager> layerManager = new BasicShadowLayerManager(mWidget);
mWidget = NULL;
mLayerManager = layerManager;
ShadowLayerManager* slm = layerManager->AsShadowManager();
if (!slm) {
return NULL;
}
*aMaxTextureSize = layerManager->GetMaxTextureSize();
return new ShadowLayersParent(slm, this, 0);
} else {
NS_ERROR("Unsupported backend selected for Async Compositor");
return NULL;
}
}
bool
CompositorParent::DeallocPLayers(PLayersParent* actor)
{
delete actor;
return true;
}
typedef map<uint64_t,CompositorParent*> CompositorMap;
static CompositorMap* sCompositorMap;
void CompositorParent::CreateCompositorMap()
{
if (sCompositorMap == nullptr) {
sCompositorMap = new CompositorMap;
}
}
void CompositorParent::DestroyCompositorMap()
{
if (sCompositorMap != nullptr) {
NS_ASSERTION(sCompositorMap->empty(),
"The Compositor map should be empty when destroyed>");
delete sCompositorMap;
sCompositorMap = nullptr;
}
}
CompositorParent* CompositorParent::GetCompositor(uint64_t id)
{
CompositorMap::iterator it = sCompositorMap->find(id);
return it != sCompositorMap->end() ? it->second : nullptr;
}
void CompositorParent::AddCompositor(CompositorParent* compositor, uint64_t* outID)
{
static uint64_t sNextID = 1;
++sNextID;
(*sCompositorMap)[sNextID] = compositor;
*outID = sNextID;
}
CompositorParent* CompositorParent::RemoveCompositor(uint64_t id)
{
CompositorMap::iterator it = sCompositorMap->find(id);
if (it == sCompositorMap->end()) {
return nullptr;
}
sCompositorMap->erase(it);
return it->second;
}
typedef map<uint64_t, LayerTreeState> LayerTreeMap;
static LayerTreeMap sIndirectLayerTrees;
/*static*/ uint64_t
CompositorParent::AllocateLayerTreeId()
{
MOZ_ASSERT(CompositorLoop());
MOZ_ASSERT(NS_IsMainThread());
static uint64_t ids;
return ++ids;
}
static void
EraseLayerState(uint64_t aId)
{
sIndirectLayerTrees.erase(aId);
}
/*static*/ void
CompositorParent::DeallocateLayerTreeId(uint64_t aId)
{
MOZ_ASSERT(NS_IsMainThread());
CompositorLoop()->PostTask(FROM_HERE,
NewRunnableFunction(&EraseLayerState, aId));
}
static void
UpdateControllerForLayersId(uint64_t aLayersId,
AsyncPanZoomController* aController)
{
// Adopt ref given to us by SetPanZoomControllerForLayerTree()
sIndirectLayerTrees[aLayersId].mController =
already_AddRefed<AsyncPanZoomController>(aController);
// Notify the AsyncPanZoomController about the current compositor so that it
// can request composites off the compositor thread.
aController->SetCompositorParent(sCurrentCompositor);
}
/*static*/ void
CompositorParent::SetPanZoomControllerForLayerTree(uint64_t aLayersId,
AsyncPanZoomController* aController)
{
// This ref is adopted by UpdateControllerForLayersId().
aController->AddRef();
CompositorLoop()->PostTask(FROM_HERE,
NewRunnableFunction(&UpdateControllerForLayersId,
aLayersId,
aController));
}
/**
* This class handles layer updates pushed directly from child
* processes to the compositor thread. It's associated with a
* CompositorParent on the compositor thread. While it uses the
* PCompositor protocol to manage these updates, it doesn't actually
* drive compositing itself. For that it hands off work to the
* CompositorParent it's associated with.
*/
class CrossProcessCompositorParent : public PCompositorParent,
public ShadowLayersManager
{
friend class CompositorParent;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CrossProcessCompositorParent)
public:
CrossProcessCompositorParent(Transport* aTransport)
: mTransport(aTransport)
{}
virtual ~CrossProcessCompositorParent();
virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
// FIXME/bug 774388: work out what shutdown protocol we need.
virtual bool RecvWillStop() MOZ_OVERRIDE { return true; }
virtual bool RecvStop() MOZ_OVERRIDE { return true; }
virtual bool RecvPause() MOZ_OVERRIDE { return true; }
virtual bool RecvResume() MOZ_OVERRIDE { return true; }
virtual bool RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
SurfaceDescriptor* aOutSnapshot)
{ return true; }
virtual PLayersParent* AllocPLayers(const LayersBackend& aBackendType,
const uint64_t& aId,
LayersBackend* aBackend,
int32_t* aMaxTextureSize) MOZ_OVERRIDE;
virtual bool DeallocPLayers(PLayersParent* aLayers) MOZ_OVERRIDE;
virtual void ShadowLayersUpdated(ShadowLayersParent* aLayerTree,
const TargetConfig& aTargetConfig,
bool isFirstPaint) MOZ_OVERRIDE;
virtual PGrallocBufferParent* AllocPGrallocBuffer(
const gfxIntSize&, const uint32_t&, const uint32_t&,
MaybeMagicGrallocBufferHandle*) MOZ_OVERRIDE
{ return nullptr; }
virtual bool DeallocPGrallocBuffer(PGrallocBufferParent*)
{ return false; }
virtual bool RecvMemoryPressure()
{ return true; }
private:
void DeferredDestroy();
// There can be many CPCPs, and IPDL-generated code doesn't hold a
// reference to top-level actors. So we hold a reference to
// ourself. This is released (deferred) in ActorDestroy().
nsRefPtr<CrossProcessCompositorParent> mSelfRef;
Transport* mTransport;
};
static void
OpenCompositor(CrossProcessCompositorParent* aCompositor,
Transport* aTransport, ProcessHandle aHandle,
MessageLoop* aIOLoop)
{
DebugOnly<bool> ok = aCompositor->Open(aTransport, aHandle, aIOLoop);
MOZ_ASSERT(ok);
}
/*static*/ PCompositorParent*
CompositorParent::Create(Transport* aTransport, ProcessId aOtherProcess)
{
nsRefPtr<CrossProcessCompositorParent> cpcp =
new CrossProcessCompositorParent(aTransport);
ProcessHandle handle;
if (!base::OpenProcessHandle(aOtherProcess, &handle)) {
// XXX need to kill |aOtherProcess|, it's boned
return nullptr;
}
cpcp->mSelfRef = cpcp;
CompositorLoop()->PostTask(
FROM_HERE,
NewRunnableFunction(OpenCompositor, cpcp.get(),
aTransport, handle, XRE_GetIOMessageLoop()));
// The return value is just compared to null for success checking,
// we're not sharing a ref.
return cpcp.get();
}
static void
UpdateIndirectTree(uint64_t aId, Layer* aRoot, const TargetConfig& aTargetConfig, bool isFirstPaint)
{
sIndirectLayerTrees[aId].mRoot = aRoot;
sIndirectLayerTrees[aId].mTargetConfig = aTargetConfig;
if (ContainerLayer* root = aRoot->AsContainerLayer()) {
if (AsyncPanZoomController* apzc = sIndirectLayerTrees[aId].mController) {
apzc->NotifyLayersUpdated(root->GetFrameMetrics(), isFirstPaint);
}
}
}
static const LayerTreeState*
GetIndirectShadowTree(uint64_t aId)
{
LayerTreeMap::const_iterator cit = sIndirectLayerTrees.find(aId);
if (sIndirectLayerTrees.end() == cit) {
return nullptr;
}
return &cit->second;
}
static void
RemoveIndirectTree(uint64_t aId)
{
sIndirectLayerTrees.erase(aId);
}
void
CrossProcessCompositorParent::ActorDestroy(ActorDestroyReason aWhy)
{
MessageLoop::current()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &CrossProcessCompositorParent::DeferredDestroy));
}
PLayersParent*
CrossProcessCompositorParent::AllocPLayers(const LayersBackend& aBackendType,
const uint64_t& aId,
LayersBackend* aBackend,
int32_t* aMaxTextureSize)
{
MOZ_ASSERT(aId != 0);
nsRefPtr<LayerManager> lm = sCurrentCompositor->GetLayerManager();
*aBackend = lm->GetBackendType();
*aMaxTextureSize = lm->GetMaxTextureSize();
return new ShadowLayersParent(lm->AsShadowManager(), this, aId);
}
bool
CrossProcessCompositorParent::DeallocPLayers(PLayersParent* aLayers)
{
ShadowLayersParent* slp = static_cast<ShadowLayersParent*>(aLayers);
RemoveIndirectTree(slp->GetId());
delete aLayers;
return true;
}
void
CrossProcessCompositorParent::ShadowLayersUpdated(
ShadowLayersParent* aLayerTree,
const TargetConfig& aTargetConfig,
bool isFirstPaint)
{
uint64_t id = aLayerTree->GetId();
MOZ_ASSERT(id != 0);
Layer* shadowRoot = aLayerTree->GetRoot();
if (shadowRoot) {
SetShadowProperties(shadowRoot);
}
UpdateIndirectTree(id, shadowRoot, aTargetConfig, isFirstPaint);
sCurrentCompositor->NotifyShadowTreeTransaction();
}
void
CrossProcessCompositorParent::DeferredDestroy()
{
mSelfRef = nullptr;
// |this| was just destroyed, hands off
}
CrossProcessCompositorParent::~CrossProcessCompositorParent()
{
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
new DeleteTask<Transport>(mTransport));
}
} // namespace layers
} // namespace mozilla