/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: sw=2 ts=8 et : */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at: * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Code. * * The Initial Developer of the Original Code is * The Mozilla Foundation * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Chris Jones * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "mozilla/layers/ShadowLayersParent.h" #include "BasicLayers.h" #include "LayerManagerOGL.h" #include "RenderFrameParent.h" #include "gfx3DMatrix.h" #include "nsFrameLoader.h" #include "nsViewportFrame.h" #include "nsSubDocumentFrame.h" typedef nsContentView::ViewConfig ViewConfig; using namespace mozilla::layers; namespace mozilla { namespace layout { typedef FrameMetrics::ViewID ViewID; typedef RenderFrameParent::ViewMap ViewMap; // Represents (affine) transforms that are calculated from a content view. struct ViewTransform { ViewTransform(nsIntPoint aTranslation, float aXScale, float aYScale) : mTranslation(aTranslation) , mXScale(aXScale) , mYScale(aYScale) {} operator gfx3DMatrix() const { return gfx3DMatrix::Scale(mXScale, mYScale, 1) * gfx3DMatrix::Translation(mTranslation.x, mTranslation.y, 0); } nsIntPoint mTranslation; float mXScale; float mYScale; }; // Matrix helpers // For our simple purposes, these helpers apply to 2D affine transformations // that can be represented by a scale and a translation. This makes the math // much easier because we only expect the diagonals and the translation // coordinates of the matrix to be non-zero. static double GetXScale(const gfx3DMatrix& aTransform) { return aTransform._11; } static double GetYScale(const gfx3DMatrix& aTransform) { return aTransform._22; } static void Scale(gfx3DMatrix& aTransform, double aXScale, double aYScale) { aTransform._11 *= aXScale; aTransform._22 *= aYScale; } static void Translate(gfx3DMatrix& aTransform, nsIntPoint aTranslate) { aTransform._41 += aTranslate.x; aTransform._42 += aTranslate.y; } static void ApplyTransform(nsRect& aRect, gfx3DMatrix& aTransform, nscoord auPerDevPixel) { aRect.x = aRect.x * aTransform._11 + aTransform._41 * auPerDevPixel; aRect.y = aRect.y * aTransform._22 + aTransform._42 * auPerDevPixel; aRect.width = aRect.width * aTransform._11; aRect.height = aRect.height * aTransform._22; } static void AssertInTopLevelChromeDoc(ContainerLayer* aContainer, nsIFrame* aContainedFrame) { NS_ASSERTION( (aContainer->Manager()->GetBackendType() != LayerManager::LAYERS_BASIC) || (aContainedFrame->GetNearestWidget() == static_cast(aContainer->Manager())->GetRetainerWidget()), "Expected frame to be in top-level chrome document"); } // Return view for given ID in aArray, NULL if not found. static nsContentView* FindViewForId(const ViewMap& aMap, ViewID aId) { ViewMap::const_iterator iter = aMap.find(aId); return iter != aMap.end() ? iter->second : NULL; } static void AssertValidContainerOfShadowTree(ContainerLayer* aContainer, Layer* aShadowRoot) { NS_ABORT_IF_FALSE( !aContainer || (aShadowRoot && aShadowRoot == aContainer->GetFirstChild() && nsnull == aShadowRoot->GetNextSibling()), "container of shadow tree may only be null or have 1 child that is the shadow root"); } static const FrameMetrics* GetFrameMetrics(Layer* aLayer) { // Children are not container layers, so they don't have frame metrics. Give // them a blank metric. if (!aLayer->GetFirstChild()) return NULL; ContainerLayer* container = static_cast(aLayer); return &container->GetFrameMetrics(); } static nsIntPoint GetRootFrameOffset(nsIFrame* aContainerFrame, nsDisplayListBuilder* aBuilder) { nscoord auPerDevPixel = aContainerFrame->PresContext()->AppUnitsPerDevPixel(); // Offset to the content rect in case we have borders or padding nsPoint frameOffset = (aBuilder->ToReferenceFrame(aContainerFrame->GetParent()) + aContainerFrame->GetContentRect().TopLeft()); return frameOffset.ToNearestPixels(auPerDevPixel); } // Compute the transform of the shadow tree contained by // |aContainerFrame| to widget space. We transform because the // subprocess layer manager renders to a different top-left than where // the shadow tree is drawn here and because a scale can be set on the // shadow tree. static ViewTransform ComputeShadowTreeTransform(nsIFrame* aContainerFrame, nsFrameLoader* aRootFrameLoader, const FrameMetrics* aMetrics, const ViewConfig& aConfig, nsDisplayListBuilder* aBuilder, float aInverseScaleX, float aInverseScaleY) { // |aMetrics->mViewportScrollOffset| The frame's scroll offset when it was // painted, in content document pixels. // |aConfig.mScrollOffset| What our user expects, or wants, the // frame scroll offset to be in chrome // document app units. // // So we set a compensating translation that moves the content document // pixels to where the user wants them to be. // nscoord auPerDevPixel = aContainerFrame->PresContext()->AppUnitsPerDevPixel(); nsIntPoint scrollOffset = aConfig.mScrollOffset.ToNearestPixels(auPerDevPixel); nsIntPoint metricsScrollOffset = aMetrics->mViewportScrollOffset; if (aRootFrameLoader->AsyncScrollEnabled()) { nsIntPoint scrollCompensation( scrollOffset.x * aInverseScaleX - metricsScrollOffset.x * aConfig.mXScale, scrollOffset.y * aInverseScaleY - metricsScrollOffset.y * aConfig.mYScale); return ViewTransform(-scrollCompensation, aConfig.mXScale, aConfig.mYScale); } else { return ViewTransform(nsIntPoint(0, 0), 1, 1); } } // Use shadow layer tree to build display list for the browser's frame. static void BuildListForLayer(Layer* aLayer, nsFrameLoader* aRootFrameLoader, gfx3DMatrix aTransform, nsDisplayListBuilder* aBuilder, nsDisplayList& aShadowTree, nsIFrame* aSubdocFrame) { const FrameMetrics* metrics = GetFrameMetrics(aLayer); gfx3DMatrix transform; if (metrics && metrics->IsScrollable()) { const ViewID scrollId = metrics->mScrollId; // We need to figure out the bounds of the scrollable region using the // shadow layer tree from the remote process. The metrics viewport is // defined based on all the transformations of its parent layers and // the scale of the current layer. // Calculate transform for this layer. nsContentView* view = aRootFrameLoader->GetCurrentRemoteFrame()->GetContentView(scrollId); gfx3DMatrix applyTransform = ComputeShadowTreeTransform( aSubdocFrame, aRootFrameLoader, metrics, view->GetViewConfig(), aBuilder, 1 / GetXScale(aTransform), 1 / GetYScale(aTransform)); transform = applyTransform * aLayer->GetTransform() * aTransform; // As mentioned above, bounds calculation also depends on the scale // of this layer. Scale(aTransform, GetXScale(applyTransform), GetYScale(applyTransform)); // Calculate rect for this layer based on aTransform. nsRect bounds; { nscoord auPerDevPixel = aSubdocFrame->PresContext()->AppUnitsPerDevPixel(); bounds = metrics->mViewport.ToAppUnits(auPerDevPixel); ApplyTransform(bounds, aTransform, auPerDevPixel); } aShadowTree.AppendToTop( new (aBuilder) nsDisplayRemoteShadow(aBuilder, aSubdocFrame, bounds, scrollId)); } else { transform = aLayer->GetTransform() * aTransform; } for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { BuildListForLayer(child, aRootFrameLoader, transform, aBuilder, aShadowTree, aSubdocFrame); } } // Go down shadow layer tree and apply transformations for scrollable layers. static void TransformShadowTree(nsDisplayListBuilder* aBuilder, nsFrameLoader* aFrameLoader, nsIFrame* aFrame, Layer* aLayer, float aXScale = 1, float aYScale = 1) { ShadowLayer* shadow = aLayer->AsShadowLayer(); shadow->SetShadowClipRect(aLayer->GetClipRect()); shadow->SetShadowVisibleRegion(aLayer->GetVisibleRegion()); const FrameMetrics* metrics = GetFrameMetrics(aLayer); gfx3DMatrix shadowTransform; if (metrics && metrics->IsScrollable()) { const ViewID scrollId = metrics->mScrollId; const nsContentView* view = aFrameLoader->GetCurrentRemoteFrame()->GetContentView(scrollId); NS_ABORT_IF_FALSE(view, "Array of views should be consistent with layer tree"); ViewTransform viewTransform = ComputeShadowTreeTransform( aFrame, aFrameLoader, metrics, view->GetViewConfig(), aBuilder, 1 / aXScale, 1 / aYScale ); if (metrics->IsRootScrollable()) { viewTransform.mTranslation += GetRootFrameOffset(aFrame, aBuilder); } shadowTransform = gfx3DMatrix(viewTransform) * aLayer->GetTransform(); } else { shadowTransform = aLayer->GetTransform(); } shadow->SetShadowTransform(shadowTransform); aXScale *= GetXScale(shadowTransform); aYScale *= GetYScale(shadowTransform); for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { TransformShadowTree(aBuilder, aFrameLoader, aFrame, child, aXScale, aYScale); } } static Layer* ShadowRootOf(ContainerLayer* aContainer) { NS_ABORT_IF_FALSE(aContainer, "need a non-null container"); Layer* shadowRoot = aContainer->GetFirstChild(); NS_ABORT_IF_FALSE(!shadowRoot || nsnull == shadowRoot->GetNextSibling(), "shadow root container may only have 0 or 1 children"); return shadowRoot; } // Return true iff |aManager| is a "temporary layer manager". They're // used for small software rendering tasks, like drawWindow. That's // currently implemented by a BasicLayerManager without a backing // widget, and hence in non-retained mode. static PRBool IsTempLayerManager(LayerManager* aManager) { return (LayerManager::LAYERS_BASIC == aManager->GetBackendType() && !static_cast(aManager)->IsRetained()); } // Recursively create a new array of scrollables, preserving any scrollables // that are still in the layer tree. // // aXScale and aYScale are used to calculate any values that need to be in // chrome-document CSS pixels and aren't part of the rendering loop, such as // the initial scroll offset for a new view. static void BuildViewMap(ViewMap& oldContentViews, ViewMap& newContentViews, nsFrameLoader* aFrameLoader, Layer* aLayer, float aXScale = 1, float aYScale = 1) { if (!aLayer->GetFirstChild()) return; ContainerLayer* container = static_cast(aLayer); const FrameMetrics metrics = container->GetFrameMetrics(); const ViewID scrollId = metrics.mScrollId; if (metrics.IsScrollable()) { nscoord auPerDevPixel = aFrameLoader->GetPrimaryFrameOfOwningContent() ->PresContext()->AppUnitsPerDevPixel(); nsContentView* view = FindViewForId(oldContentViews, scrollId); if (view) { // View already exists. Be sure to propagate scales for any values // that need to be calculated something in chrome-doc CSS pixels. ViewConfig config = view->GetViewConfig(); aXScale *= config.mXScale; aYScale *= config.mYScale; view->mOwnerContent = aFrameLoader->GetOwnerContent(); } else { // View doesn't exist, so generate one. We start the view scroll offset at // the same position as the framemetric's scroll offset from the layer. // The default scale is 1, so no need to propagate scale down. ViewConfig config; config.mScrollOffset = nsPoint( NSIntPixelsToAppUnits(metrics.mViewportScrollOffset.x, auPerDevPixel) * aXScale, NSIntPixelsToAppUnits(metrics.mViewportScrollOffset.y, auPerDevPixel) * aYScale); view = new nsContentView(aFrameLoader->GetOwnerContent(), scrollId, config); } view->mViewportSize = nsSize( NSIntPixelsToAppUnits(metrics.mViewport.width, auPerDevPixel) * aXScale, NSIntPixelsToAppUnits(metrics.mViewport.height, auPerDevPixel) * aYScale); view->mContentSize = nsSize( NSIntPixelsToAppUnits(metrics.mContentSize.width, auPerDevPixel) * aXScale, NSIntPixelsToAppUnits(metrics.mContentSize.height, auPerDevPixel) * aYScale); newContentViews.insert(ViewMap::value_type(scrollId, view)); } for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { const gfx3DMatrix transform = aLayer->GetTransform(); aXScale *= GetXScale(transform); aYScale *= GetYScale(transform); BuildViewMap(oldContentViews, newContentViews, aFrameLoader, child, aXScale, aYScale); } } RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader) : mFrameLoader(aFrameLoader) { NS_ABORT_IF_FALSE(aFrameLoader, "Need a frameloader here"); mContentViews.insert(ViewMap::value_type( FrameMetrics::ROOT_SCROLL_ID, new nsContentView(aFrameLoader->GetOwnerContent(), FrameMetrics::ROOT_SCROLL_ID) )); } RenderFrameParent::~RenderFrameParent() {} void RenderFrameParent::Destroy() { size_t numChildren = ManagedPLayersParent().Length(); NS_ABORT_IF_FALSE(0 == numChildren || 1 == numChildren, "render frame must only have 0 or 1 layer manager"); if (numChildren) { ShadowLayersParent* layers = static_cast(ManagedPLayersParent()[0]); layers->Destroy(); } } nsContentView* RenderFrameParent::GetContentView(ViewID aId) { return FindViewForId(mContentViews, aId); } void RenderFrameParent::ShadowLayersUpdated() { mFrameLoader->SetCurrentRemoteFrame(this); // View map must only contain views that are associated with the current // shadow layer tree. We must always update the map when shadow layers // are updated. BuildViewMap(); nsIFrame* docFrame = mFrameLoader->GetPrimaryFrameOfOwningContent(); if (!docFrame) { // Bad, but nothing we can do about it (XXX/cjones: or is there? // maybe bug 589337?). When the new frame is created, we'll // probably still be the current render frame and will get to draw // our content then. Or, we're shutting down and this update goes // to /dev/null. return; } // FIXME/cjones: we should collect the rects/regions updated for // Painted*Layer() calls and pass that region to here, then only // invalidate that rect // // We pass INVALIDATE_NO_THEBES_LAYERS here because we're // invalidating the on behalf of its counterpart in the // content process. Not only do we not need to invalidate the // shadow layers, things would just break if we did --- we have no // way to repaint shadow layers from this process. nsRect rect = nsRect(nsPoint(0, 0), docFrame->GetRect().Size()); docFrame->InvalidateWithFlags(rect, nsIFrame::INVALIDATE_NO_THEBES_LAYERS); } already_AddRefed RenderFrameParent::BuildLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, LayerManager* aManager, const nsIntRect& aVisibleRect) { NS_ABORT_IF_FALSE(aFrame, "makes no sense to have a shadow tree without a frame"); NS_ABORT_IF_FALSE(!mContainer || IsTempLayerManager(aManager) || mContainer->Manager() == aManager, "retaining manager changed out from under us ... HELP!"); if (mContainer && mContainer->Manager() != aManager) { // This can happen if aManager is a "temporary" manager, or if the // widget's layer manager changed out from under us. We need to // FIXME handle the former case somehow, probably with an API to // draw a manager's subtree. The latter is bad bad bad, but the // the NS_ABORT_IF_FALSE() above will flag it. Returning NULL // here will just cause the shadow subtree not to be rendered. return nsnull; } Layer* containerShadowRoot = mContainer ? ShadowRootOf(mContainer) : nsnull; ContainerLayer* shadowRoot = GetRootLayer(); NS_ABORT_IF_FALSE(!shadowRoot || shadowRoot->Manager() == aManager, "retaining manager changed out from under us ... HELP!"); if (mContainer && shadowRoot != containerShadowRoot) { // Shadow root changed. Remove it from the container, if it // existed. if (containerShadowRoot) { mContainer->RemoveChild(containerShadowRoot); } } if (!shadowRoot) { // No shadow layer tree at the moment. mContainer = nsnull; } else if (shadowRoot != containerShadowRoot) { // Wrap the shadow layer tree in mContainer. if (!mContainer) { mContainer = aManager->CreateContainerLayer(); } NS_ABORT_IF_FALSE(!mContainer->GetFirstChild(), "container of shadow tree shouldn't have a 'root' here"); mContainer->InsertAfter(shadowRoot, nsnull); } if (mContainer) { AssertInTopLevelChromeDoc(mContainer, aFrame); TransformShadowTree(aBuilder, mFrameLoader, aFrame, shadowRoot); mContainer->SetClipRect(nsnull); } AssertValidContainerOfShadowTree(mContainer, shadowRoot); return nsRefPtr(mContainer).forget(); } void RenderFrameParent::OwnerContentChanged(nsIContent* aContent) { NS_ABORT_IF_FALSE(mFrameLoader->GetOwnerContent() == aContent, "Don't build new map if owner is same!"); BuildViewMap(); } void RenderFrameParent::ActorDestroy(ActorDestroyReason why) { if (mFrameLoader->GetCurrentRemoteFrame() == this) { // XXX this might cause some weird issues ... we'll just not // redraw the part of the window covered by this until the "next" // remote frame has a layer-tree transaction. For // why==NormalShutdown, we'll definitely want to do something // better, especially as nothing guarantees another Update() from // the "next" remote layer tree. mFrameLoader->SetCurrentRemoteFrame(nsnull); } mFrameLoader = nsnull; } PLayersParent* RenderFrameParent::AllocPLayers() { LayerManager* lm = GetLayerManager(); switch (lm->GetBackendType()) { case LayerManager::LAYERS_BASIC: { BasicShadowLayerManager* bslm = static_cast(lm); return new ShadowLayersParent(bslm); } case LayerManager::LAYERS_OPENGL: { LayerManagerOGL* lmo = static_cast(lm); return new ShadowLayersParent(lmo); } default: { NS_WARNING("shadow layers no sprechen D3D backend yet"); return nsnull; } } } bool RenderFrameParent::DeallocPLayers(PLayersParent* aLayers) { delete aLayers; return true; } void RenderFrameParent::BuildViewMap() { ViewMap newContentViews; // BuildViewMap assumes we have a primary frame, which may not be the case. if (GetRootLayer() && mFrameLoader->GetPrimaryFrameOfOwningContent()) { // Some of the content views in our hash map may no longer be active. To // tag them as inactive and to remove any chance of them using a dangling // pointer, we set mContentView to NULL. // // BuildViewMap will restore mOwnerContent if the content view is still // in our hash table. for (ViewMap::const_iterator iter = mContentViews.begin(); iter != mContentViews.end(); ++iter) { iter->second->mOwnerContent = NULL; } mozilla::layout::BuildViewMap(mContentViews, newContentViews, mFrameLoader, GetRootLayer()); } // Here, we guarantee that *only* the root view is preserved in // case we couldn't build a new view map above. This is important because // the content view map should only contain the root view and content // views that are present in the layer tree. if (newContentViews.empty()) { newContentViews.insert(ViewMap::value_type( FrameMetrics::ROOT_SCROLL_ID, FindViewForId(mContentViews, FrameMetrics::ROOT_SCROLL_ID) )); } mContentViews = newContentViews; } LayerManager* RenderFrameParent::GetLayerManager() const { nsIDocument* doc = mFrameLoader->GetOwnerDoc(); return doc->GetShell()->GetLayerManager(); } ShadowLayersParent* RenderFrameParent::GetShadowLayers() const { const nsTArray& shadowParents = ManagedPLayersParent(); NS_ABORT_IF_FALSE(shadowParents.Length() <= 1, "can only support at most 1 ShadowLayersParent"); return (shadowParents.Length() == 1) ? static_cast(shadowParents[0]) : nsnull; } ContainerLayer* RenderFrameParent::GetRootLayer() const { ShadowLayersParent* shadowLayers = GetShadowLayers(); return shadowLayers ? shadowLayers->GetRoot() : nsnull; } NS_IMETHODIMP RenderFrameParent::BuildDisplayList(nsDisplayListBuilder* aBuilder, nsSubDocumentFrame* aFrame, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { // We're the subdoc for and it has // painted content. Display its shadow layer tree. nsDisplayList shadowTree; if (aBuilder->IsForEventDelivery()) { nsRect bounds = aFrame->EnsureInnerView()->GetBounds(); ViewTransform offset = ViewTransform(GetRootFrameOffset(aFrame, aBuilder), 1, 1); BuildListForLayer(GetRootLayer(), mFrameLoader, offset, aBuilder, shadowTree, aFrame); } else { shadowTree.AppendToTop( new (aBuilder) nsDisplayRemote(aBuilder, aFrame, this)); } // Clip the shadow layers to subdoc bounds nsPoint offset = aFrame->GetOffsetToCrossDoc(aBuilder->ReferenceFrame()); nsRect bounds = aFrame->EnsureInnerView()->GetBounds() + offset; return aLists.Content()->AppendNewToTop( new (aBuilder) nsDisplayClip(aBuilder, aFrame, &shadowTree, bounds)); } } // namespace layout } // namespace mozilla already_AddRefed nsDisplayRemote::BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager) { PRInt32 appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); nsIntRect visibleRect = GetVisibleRect().ToNearestPixels(appUnitsPerDevPixel); nsRefPtr layer = mRemoteFrame->BuildLayer(aBuilder, mFrame, aManager, visibleRect); return layer.forget(); } void nsDisplayRemoteShadow::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, HitTestState* aState, nsTArray *aOutFrames) { // If we are here, then rects have intersected. // // XXX I think iframes and divs can be rounded like anything else but we don't // cover that case here. // if (aState->mShadows) { aState->mShadows->AppendElement(mId); } }