2017-10-27 20:33:53 +03:00
|
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
2017-10-18 06:09:07 +03:00
|
|
|
|
* 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 "RetainedDisplayListBuilder.h"
|
2018-01-12 20:02:14 +03:00
|
|
|
|
#include "nsPlaceholderFrame.h"
|
2017-10-18 06:09:07 +03:00
|
|
|
|
#include "nsSubDocumentFrame.h"
|
2017-11-25 02:02:20 +03:00
|
|
|
|
#include "nsViewManager.h"
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Code for doing display list building for a modified subset of the window,
|
|
|
|
|
* and then merging it into the existing display list (for the full window).
|
|
|
|
|
*
|
|
|
|
|
* The approach primarily hinges on the observation that the ‘true’ ordering of
|
|
|
|
|
* display items is represented by a DAG (only items that intersect in 2d space
|
|
|
|
|
* have a defined ordering). Our display list is just one of a many possible linear
|
|
|
|
|
* representations of this ordering.
|
|
|
|
|
*
|
|
|
|
|
* Each time a frame changes (gets a new style context, or has a size/position
|
|
|
|
|
* change), we schedule a paint (as we do currently), but also reord the frame that
|
|
|
|
|
* changed.
|
|
|
|
|
*
|
|
|
|
|
* When the next paint occurs we union the overflow areas (in screen space) of the
|
|
|
|
|
* changed frames, and compute a rect/region that contains all changed items. We
|
|
|
|
|
* then build a display list just for this subset of the screen and merge it into
|
|
|
|
|
* the display list from last paint.
|
|
|
|
|
*
|
|
|
|
|
* Any items that exist in one list and not the other must not have a defined
|
|
|
|
|
* ordering in the DAG, since they need to intersect to have an ordering and
|
|
|
|
|
* we would have built both in the new list if they intersected. Given that, we
|
|
|
|
|
* can align items that appear in both lists, and any items that appear between
|
|
|
|
|
* matched items can be inserted into the merged list in any order.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
using namespace mozilla;
|
|
|
|
|
|
|
|
|
|
void MarkFramesWithItemsAndImagesModified(nsDisplayList* aList)
|
|
|
|
|
{
|
|
|
|
|
for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) {
|
|
|
|
|
if (!i->HasDeletedFrame() && i->CanBeReused() && !i->Frame()->IsFrameModified()) {
|
|
|
|
|
// If we have existing cached geometry for this item, then check that for
|
|
|
|
|
// whether we need to invalidate for a sync decode. If we don't, then
|
|
|
|
|
// use the item's flags.
|
|
|
|
|
DisplayItemData* data = FrameLayerBuilder::GetOldDataFor(i);
|
|
|
|
|
bool invalidate = false;
|
|
|
|
|
if (data &&
|
|
|
|
|
data->GetGeometry()) {
|
|
|
|
|
invalidate = data->GetGeometry()->InvalidateForSyncDecodeImages();
|
|
|
|
|
} else if (!(i->GetFlags() & TYPE_RENDERS_NO_IMAGES)) {
|
|
|
|
|
invalidate = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (invalidate) {
|
|
|
|
|
i->FrameForInvalidation()->MarkNeedsDisplayItemRebuild();
|
2017-10-27 09:00:52 +03:00
|
|
|
|
if (i->GetDependentFrame()) {
|
|
|
|
|
i->GetDependentFrame()->MarkNeedsDisplayItemRebuild();
|
|
|
|
|
}
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (i->GetChildren()) {
|
|
|
|
|
MarkFramesWithItemsAndImagesModified(i->GetChildren());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsAnyAncestorModified(nsIFrame* aFrame)
|
|
|
|
|
{
|
|
|
|
|
nsIFrame* f = aFrame;
|
|
|
|
|
while (f) {
|
|
|
|
|
if (f->IsFrameModified()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
f = nsLayoutUtils::GetCrossDocParentFrame(f);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 20:27:42 +03:00
|
|
|
|
static AnimatedGeometryRoot*
|
|
|
|
|
SelectAGRForFrame(nsIFrame* aFrame, AnimatedGeometryRoot* aParentAGR)
|
|
|
|
|
{
|
|
|
|
|
if (!aFrame->IsStackingContext()) {
|
|
|
|
|
return aParentAGR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!aFrame->HasOverrideDirtyRegion()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsDisplayListBuilder::DisplayListBuildingData* data =
|
|
|
|
|
aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
|
|
|
|
|
|
|
|
|
|
return data && data->mModifiedAGR ? data->mModifiedAGR.get()
|
|
|
|
|
: nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
// Removes any display items that belonged to a frame that was deleted,
|
|
|
|
|
// and mark frames that belong to a different AGR so that get their
|
|
|
|
|
// items built again.
|
|
|
|
|
// TODO: We currently descend into all children even if we don't have an AGR
|
|
|
|
|
// to mark, as child stacking contexts might. It would be nice if we could
|
|
|
|
|
// jump into those immediately rather than walking the entire thing.
|
|
|
|
|
void
|
|
|
|
|
RetainedDisplayListBuilder::PreProcessDisplayList(nsDisplayList* aList,
|
|
|
|
|
AnimatedGeometryRoot* aAGR)
|
|
|
|
|
{
|
2017-11-16 06:09:28 +03:00
|
|
|
|
nsDisplayList saved;
|
2017-10-18 06:09:07 +03:00
|
|
|
|
while (nsDisplayItem* i = aList->RemoveBottom()) {
|
|
|
|
|
if (i->HasDeletedFrame() || !i->CanBeReused()) {
|
|
|
|
|
i->Destroy(&mBuilder);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsIFrame* f = i->Frame();
|
|
|
|
|
|
|
|
|
|
if (i->GetChildren()) {
|
2018-01-17 20:27:42 +03:00
|
|
|
|
PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR));
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: We should be able to check the clipped bounds relative
|
|
|
|
|
// to the common AGR (of both the existing item and the invalidated
|
|
|
|
|
// frame) and determine if they can ever intersect.
|
|
|
|
|
if (aAGR && i->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) {
|
2017-10-25 13:10:04 +03:00
|
|
|
|
mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame());
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: This is here because we sometimes reuse the previous display list
|
|
|
|
|
// completely. For optimization, we could only restore the state for reused
|
|
|
|
|
// display items.
|
|
|
|
|
i->RestoreState();
|
|
|
|
|
|
|
|
|
|
saved.AppendToTop(i);
|
|
|
|
|
}
|
|
|
|
|
aList->AppendToTop(&saved);
|
|
|
|
|
aList->RestoreState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsSameItem(nsDisplayItem* aFirst, nsDisplayItem* aSecond)
|
|
|
|
|
{
|
|
|
|
|
return aFirst->Frame() == aSecond->Frame() &&
|
|
|
|
|
aFirst->GetPerFrameKey() == aSecond->GetPerFrameKey();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DisplayItemKey
|
|
|
|
|
{
|
|
|
|
|
bool operator ==(const DisplayItemKey& aOther) const {
|
|
|
|
|
return mFrame == aOther.mFrame &&
|
|
|
|
|
mPerFrameKey == aOther.mPerFrameKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsIFrame* mFrame;
|
|
|
|
|
uint32_t mPerFrameKey;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class DisplayItemHashEntry : public PLDHashEntryHdr
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
typedef DisplayItemKey KeyType;
|
|
|
|
|
typedef const DisplayItemKey* KeyTypePointer;
|
|
|
|
|
|
|
|
|
|
explicit DisplayItemHashEntry(KeyTypePointer aKey)
|
|
|
|
|
: mKey(*aKey) {}
|
|
|
|
|
explicit DisplayItemHashEntry(const DisplayItemHashEntry& aCopy)=default;
|
|
|
|
|
|
|
|
|
|
~DisplayItemHashEntry() = default;
|
|
|
|
|
|
|
|
|
|
KeyType GetKey() const { return mKey; }
|
|
|
|
|
bool KeyEquals(KeyTypePointer aKey) const
|
|
|
|
|
{
|
|
|
|
|
return mKey == *aKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
|
|
|
|
|
static PLDHashNumber HashKey(KeyTypePointer aKey)
|
|
|
|
|
{
|
|
|
|
|
if (!aKey)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey);
|
|
|
|
|
}
|
|
|
|
|
enum { ALLOW_MEMMOVE = true };
|
|
|
|
|
|
|
|
|
|
DisplayItemKey mKey;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
void SwapAndRemove(nsTArray<T>& aArray, uint32_t aIndex)
|
|
|
|
|
{
|
|
|
|
|
if (aIndex != (aArray.Length() - 1)) {
|
|
|
|
|
T last = aArray.LastElement();
|
|
|
|
|
aArray.LastElement() = aArray[aIndex];
|
|
|
|
|
aArray[aIndex] = last;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
aArray.RemoveElementAt(aArray.Length() - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
MergeFrameRects(nsDisplayLayerEventRegions* aOldItem,
|
|
|
|
|
nsDisplayLayerEventRegions* aNewItem,
|
|
|
|
|
nsDisplayLayerEventRegions::FrameRects nsDisplayLayerEventRegions::*aRectList,
|
|
|
|
|
nsTArray<nsIFrame*>& aAddedFrames)
|
|
|
|
|
{
|
|
|
|
|
// Go through the old item's rect list and remove any rectangles
|
|
|
|
|
// belonging to invalidated frames (deleted frames should
|
|
|
|
|
// already be gone at this point)
|
|
|
|
|
nsDisplayLayerEventRegions::FrameRects& oldRects = aOldItem->*aRectList;
|
|
|
|
|
uint32_t i = 0;
|
|
|
|
|
while (i < oldRects.mFrames.Length()) {
|
|
|
|
|
// TODO: As mentioned in nsDisplayLayerEventRegions, this
|
|
|
|
|
// operation might perform really poorly on a vector.
|
|
|
|
|
nsIFrame* f = oldRects.mFrames[i];
|
|
|
|
|
if (IsAnyAncestorModified(f)) {
|
|
|
|
|
MOZ_ASSERT(f != aOldItem->Frame());
|
|
|
|
|
f->RemoveDisplayItem(aOldItem);
|
|
|
|
|
SwapAndRemove(oldRects.mFrames, i);
|
|
|
|
|
SwapAndRemove(oldRects.mBoxes, i);
|
|
|
|
|
} else {
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!aNewItem) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy items from the source list to the dest list, but
|
|
|
|
|
// only if the dest doesn't already include them.
|
|
|
|
|
nsDisplayItem* destItem = aOldItem;
|
|
|
|
|
nsDisplayLayerEventRegions::FrameRects* destRects = &(aOldItem->*aRectList);
|
|
|
|
|
nsDisplayLayerEventRegions::FrameRects* srcRects = &(aNewItem->*aRectList);
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < srcRects->mFrames.Length(); i++) {
|
|
|
|
|
nsIFrame* f = srcRects->mFrames[i];
|
|
|
|
|
if (!f->HasDisplayItem(destItem)) {
|
|
|
|
|
// If this frame isn't already in the destination item,
|
|
|
|
|
// then add it!
|
|
|
|
|
destRects->Add(f, srcRects->mBoxes[i]);
|
|
|
|
|
|
|
|
|
|
// We also need to update RealDisplayItemData for 'f',
|
|
|
|
|
// but that'll mess up this check for the following
|
|
|
|
|
// FrameRects lists, so defer that until the end.
|
|
|
|
|
aAddedFrames.AppendElement(f);
|
|
|
|
|
MOZ_ASSERT(f != aOldItem->Frame());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MergeLayerEventRegions(nsDisplayItem* aOldItem,
|
|
|
|
|
nsDisplayItem* aNewItem)
|
|
|
|
|
{
|
|
|
|
|
nsDisplayLayerEventRegions* oldItem =
|
|
|
|
|
static_cast<nsDisplayLayerEventRegions*>(aOldItem);
|
|
|
|
|
nsDisplayLayerEventRegions* newItem =
|
|
|
|
|
static_cast<nsDisplayLayerEventRegions*>(aNewItem);
|
|
|
|
|
|
|
|
|
|
nsTArray<nsIFrame*> addedFrames;
|
|
|
|
|
|
|
|
|
|
MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHitRegion, addedFrames);
|
|
|
|
|
MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mMaybeHitRegion, addedFrames);
|
|
|
|
|
MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mDispatchToContentHitRegion, addedFrames);
|
|
|
|
|
MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mNoActionRegion, addedFrames);
|
|
|
|
|
MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHorizontalPanRegion, addedFrames);
|
|
|
|
|
MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mVerticalPanRegion, addedFrames);
|
|
|
|
|
|
|
|
|
|
// MergeFrameRects deferred updating the display item data list during
|
|
|
|
|
// processing so that earlier calls didn't change the result of later
|
|
|
|
|
// ones. Fix that up now.
|
|
|
|
|
for (nsIFrame* f : addedFrames) {
|
|
|
|
|
if (!f->HasDisplayItem(aOldItem)) {
|
|
|
|
|
f->AddDisplayItem(aOldItem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem)
|
|
|
|
|
{
|
|
|
|
|
MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT);
|
|
|
|
|
|
|
|
|
|
nsSubDocumentFrame* subDocFrame =
|
|
|
|
|
static_cast<nsDisplaySubDocument*>(aItem)->SubDocumentFrame();
|
|
|
|
|
MOZ_ASSERT(subDocFrame);
|
|
|
|
|
|
|
|
|
|
nsIPresShell* presShell = subDocFrame->GetSubdocumentPresShellForPainting(0);
|
|
|
|
|
MOZ_ASSERT(presShell);
|
|
|
|
|
|
|
|
|
|
mBuilder.IncrementPresShellPaintCount(presShell);
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-10 03:16:55 +03:00
|
|
|
|
void UpdateASR(nsDisplayItem* aItem,
|
2017-11-12 23:02:48 +03:00
|
|
|
|
Maybe<const ActiveScrolledRoot*>& aContainerASR)
|
2017-11-10 03:16:55 +03:00
|
|
|
|
{
|
2017-11-12 23:02:48 +03:00
|
|
|
|
if (!aContainerASR) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-10 03:16:55 +03:00
|
|
|
|
nsDisplayWrapList* wrapList = aItem->AsDisplayWrapList();
|
|
|
|
|
if (!wrapList) {
|
2017-11-12 23:02:48 +03:00
|
|
|
|
aItem->SetActiveScrolledRoot(aContainerASR.value());
|
2017-11-10 03:16:55 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wrapList->SetActiveScrolledRoot(
|
|
|
|
|
ActiveScrolledRoot::PickAncestor(wrapList->GetFrameActiveScrolledRoot(),
|
2017-11-12 23:02:48 +03:00
|
|
|
|
aContainerASR.value()));
|
2017-11-10 03:16:55 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
/**
|
|
|
|
|
* Takes two display lists and merges them into an output list.
|
|
|
|
|
*
|
|
|
|
|
* The basic algorithm is:
|
|
|
|
|
*
|
2018-01-17 02:07:42 +03:00
|
|
|
|
* For-each item i in the new list:
|
2017-10-18 06:09:07 +03:00
|
|
|
|
* If the item has a matching item in the old list:
|
2018-01-17 02:07:42 +03:00
|
|
|
|
* Remove items from the start of the old list up until we reach an item that also exists in the new list (leaving the matched item in place):
|
2017-10-18 06:09:07 +03:00
|
|
|
|
* Add valid items to the merged list, destroy invalid items.
|
2018-01-17 02:07:42 +03:00
|
|
|
|
* Add i into the merged list.
|
|
|
|
|
* If the start of the old list matches i, remove and destroy it, otherwise mark the old version of i as used.
|
|
|
|
|
* Add all remaining valid items from the old list into the merged list, skipping over (and destroying) any that are marked as used.
|
2017-10-18 06:09:07 +03:00
|
|
|
|
*
|
|
|
|
|
* If any item has a child display list, then we recurse into the merge
|
|
|
|
|
* algorithm once we match up the new/old versions (if present).
|
|
|
|
|
*
|
|
|
|
|
* Example 1:
|
|
|
|
|
*
|
|
|
|
|
* Old List: A,B,C,D
|
2018-01-17 02:07:42 +03:00
|
|
|
|
* Modified List: A,D
|
2017-10-18 06:09:07 +03:00
|
|
|
|
* Invalidations: C,D
|
|
|
|
|
*
|
|
|
|
|
* We first match the A items, and add the new one to the merged list.
|
|
|
|
|
* We then match the D items, copy B into the merged list, but not C
|
|
|
|
|
* (since it's invalid). We then add the new D to the list and we're
|
|
|
|
|
* finished.
|
|
|
|
|
*
|
|
|
|
|
* Merged List: A,B,D
|
|
|
|
|
*
|
2018-01-17 02:07:42 +03:00
|
|
|
|
* Example 2 (layout/reftests/retained-dl-zindex-1.html):
|
2017-10-18 06:09:07 +03:00
|
|
|
|
*
|
|
|
|
|
* Old List: A, B
|
2018-01-17 02:07:42 +03:00
|
|
|
|
* Modified List: B, A
|
|
|
|
|
* Invalidations: A
|
|
|
|
|
*
|
|
|
|
|
* In this example A has been explicitly moved to the back.
|
|
|
|
|
*
|
|
|
|
|
* We match the B items, but don't copy A since it's invalid, and then add the
|
|
|
|
|
* new B into the merged list. We then add A, and we're done.
|
|
|
|
|
*
|
|
|
|
|
* Merged List: B, A
|
|
|
|
|
*
|
|
|
|
|
* Example 3:
|
|
|
|
|
*
|
|
|
|
|
* Old List: A, B
|
|
|
|
|
* Modified List: B, A
|
2017-10-18 06:09:07 +03:00
|
|
|
|
* Invalidations: -
|
|
|
|
|
*
|
|
|
|
|
* This can happen because a prior merge might have changed the ordering
|
|
|
|
|
* for non-intersecting items.
|
|
|
|
|
*
|
2017-11-22 05:10:31 +03:00
|
|
|
|
* We match the B items, but don't copy A since it's also present in the new list
|
|
|
|
|
* and then add the new B into the merged list. We then add A, and we're done.
|
2017-10-18 06:09:07 +03:00
|
|
|
|
*
|
2017-11-22 05:10:31 +03:00
|
|
|
|
* Merged List: B, A
|
2018-01-17 02:07:42 +03:00
|
|
|
|
*
|
|
|
|
|
* Example 4 (layout/reftests/retained-dl-zindex-2.html):
|
|
|
|
|
*
|
|
|
|
|
* Element A has two elements covering it (B and C), that don't intersect each
|
|
|
|
|
* other. We then move C to the back.
|
|
|
|
|
*
|
|
|
|
|
* The correct initial ordering has B and C after A, in any order.
|
|
|
|
|
*
|
|
|
|
|
* Old List: A, B, C
|
|
|
|
|
* Modified List: C, A
|
|
|
|
|
* Invalidations: C
|
|
|
|
|
*
|
|
|
|
|
* We match the C items, but don't add anything from the old list because A is present
|
|
|
|
|
* in both lists. We add C to the merged list, and mark the old version of C as reused.
|
|
|
|
|
*
|
|
|
|
|
* We then match A, add the new version the merged list and delete the old version.
|
|
|
|
|
*
|
|
|
|
|
* We then process the remainder of the old list, B is added (since it is valid,
|
|
|
|
|
* and hasn't been mark as reused), C is destroyed since it's marked as reused and
|
|
|
|
|
* is already present in the merged list.
|
|
|
|
|
*
|
|
|
|
|
* Merged List: C, A, B
|
2017-10-18 06:09:07 +03:00
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList,
|
|
|
|
|
nsDisplayList* aOldList,
|
2017-11-10 03:16:55 +03:00
|
|
|
|
nsDisplayList* aOutList,
|
2017-11-12 23:02:48 +03:00
|
|
|
|
Maybe<const ActiveScrolledRoot*>& aOutContainerASR)
|
2017-10-18 06:09:07 +03:00
|
|
|
|
{
|
2017-11-16 06:09:28 +03:00
|
|
|
|
nsDisplayList merged;
|
2017-11-11 05:46:48 +03:00
|
|
|
|
const auto UseItem = [&](nsDisplayItem* aItem) {
|
2017-11-10 03:16:55 +03:00
|
|
|
|
const ActiveScrolledRoot* itemClipASR =
|
|
|
|
|
aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr;
|
|
|
|
|
|
|
|
|
|
const ActiveScrolledRoot* finiteBoundsASR = ActiveScrolledRoot::PickDescendant(
|
|
|
|
|
itemClipASR, aItem->GetActiveScrolledRoot());
|
2017-11-12 23:02:48 +03:00
|
|
|
|
if (!aOutContainerASR) {
|
|
|
|
|
aOutContainerASR = Some(finiteBoundsASR);
|
2017-11-10 03:16:55 +03:00
|
|
|
|
} else {
|
2017-11-12 23:02:48 +03:00
|
|
|
|
aOutContainerASR =
|
|
|
|
|
Some(ActiveScrolledRoot::PickAncestor(aOutContainerASR.value(), finiteBoundsASR));
|
2017-11-10 03:16:55 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
merged.AppendToTop(aItem);
|
2017-11-11 05:46:48 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto ReuseItem = [&](nsDisplayItem* aItem) {
|
|
|
|
|
UseItem(aItem);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
aItem->SetReused(true);
|
|
|
|
|
|
|
|
|
|
if (aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) {
|
|
|
|
|
IncrementSubDocPresShellPaintCount(aItem);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2018-01-15 02:15:50 +03:00
|
|
|
|
const bool newListIsEmpty = aNewList->IsEmpty();
|
|
|
|
|
if (!newListIsEmpty) {
|
|
|
|
|
// Build a hashtable of items in the old list so we can look for them quickly.
|
|
|
|
|
// We have similar data in the nsIFrame DisplayItems() property, but it doesn't
|
|
|
|
|
// know which display list items are in, and we only want to match items in
|
|
|
|
|
// this list.
|
|
|
|
|
nsDataHashtable<DisplayItemHashEntry, nsDisplayItem*> oldListLookup(aOldList->Count());
|
|
|
|
|
|
|
|
|
|
for (nsDisplayItem* i = aOldList->GetBottom(); i != nullptr; i = i->GetAbove()) {
|
|
|
|
|
i->SetReused(false);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
oldListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i);
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-15 02:15:50 +03:00
|
|
|
|
nsDataHashtable<DisplayItemHashEntry, nsDisplayItem*> newListLookup(aNewList->Count());
|
|
|
|
|
for (nsDisplayItem* i = aNewList->GetBottom(); i != nullptr; i = i->GetAbove()) {
|
2017-11-22 05:10:31 +03:00
|
|
|
|
#ifdef DEBUG
|
2018-01-15 02:15:50 +03:00
|
|
|
|
if (newListLookup.Get({ i->Frame(), i->GetPerFrameKey() }, nullptr)) {
|
|
|
|
|
MOZ_CRASH_UNSAFE_PRINTF("Duplicate display items detected!: %s(0x%p) type=%d key=%d",
|
|
|
|
|
i->Name(), i->Frame(),
|
|
|
|
|
static_cast<int>(i->GetType()), i->GetPerFrameKey());
|
|
|
|
|
}
|
2017-11-22 05:10:31 +03:00
|
|
|
|
#endif
|
2018-01-15 02:15:50 +03:00
|
|
|
|
newListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i);
|
|
|
|
|
}
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
2018-01-15 02:15:50 +03:00
|
|
|
|
while (nsDisplayItem* newItem = aNewList->RemoveBottom()) {
|
|
|
|
|
if (nsDisplayItem* oldItem = oldListLookup.Get({ newItem->Frame(), newItem->GetPerFrameKey() })) {
|
|
|
|
|
// The new item has a matching counterpart in the old list that we haven't yet reached,
|
|
|
|
|
// so copy all valid items from the old list into the merged list until we get to the
|
|
|
|
|
// matched item.
|
|
|
|
|
nsDisplayItem* old = nullptr;
|
|
|
|
|
while ((old = aOldList->GetBottom()) && old != oldItem) {
|
|
|
|
|
if (IsAnyAncestorModified(old->FrameForInvalidation())) {
|
|
|
|
|
// The old item is invalid, discard it.
|
|
|
|
|
oldListLookup.Remove({ old->Frame(), old->GetPerFrameKey() });
|
|
|
|
|
aOldList->RemoveBottom();
|
|
|
|
|
old->Destroy(&mBuilder);
|
|
|
|
|
} else if (newListLookup.Get({ old->Frame(), old->GetPerFrameKey() })) {
|
|
|
|
|
// This old item is also in the new list, but we haven't got to it yet.
|
|
|
|
|
// Stop now, and we'll deal with it when we get to the new entry.
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
// Recurse into the child list (without a matching new list) to
|
|
|
|
|
// ensure that we find and remove any invalidated items.
|
|
|
|
|
if (old->GetChildren()) {
|
|
|
|
|
nsDisplayList empty;
|
|
|
|
|
Maybe<const ActiveScrolledRoot*> containerASRForChildren;
|
|
|
|
|
MergeDisplayLists(&empty, old->GetChildren(),
|
|
|
|
|
old->GetChildren(), containerASRForChildren);
|
|
|
|
|
UpdateASR(old, containerASRForChildren);
|
|
|
|
|
old->UpdateBounds(&mBuilder);
|
|
|
|
|
}
|
|
|
|
|
aOldList->RemoveBottom();
|
|
|
|
|
ReuseItem(old);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
2018-01-15 02:15:50 +03:00
|
|
|
|
}
|
|
|
|
|
bool destroy = false;
|
|
|
|
|
if (old == oldItem) {
|
|
|
|
|
// If we advanced the old list until the matching item then we can pop
|
|
|
|
|
// the matching item off the old list and make sure we clean it up.
|
2018-01-17 02:07:42 +03:00
|
|
|
|
aOldList->RemoveBottom();
|
2018-01-15 02:15:50 +03:00
|
|
|
|
destroy = true;
|
|
|
|
|
} else {
|
|
|
|
|
// If we didn't get to the matching item, then mark the old item
|
|
|
|
|
// as being reused (since we're adding the new version to the new
|
|
|
|
|
// list now) so that we don't add it twice at the end.
|
|
|
|
|
oldItem->SetReused(true);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-15 02:15:50 +03:00
|
|
|
|
// Recursively merge any child lists, destroy the old item and add
|
|
|
|
|
// the new one to the list.
|
|
|
|
|
if (destroy &&
|
|
|
|
|
oldItem->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS &&
|
|
|
|
|
!IsAnyAncestorModified(oldItem->FrameForInvalidation())) {
|
|
|
|
|
// Event regions items don't have anything interesting other than
|
|
|
|
|
// the lists of regions and frames, so we have no need to use the
|
|
|
|
|
// newer item. Always use the old item instead since we assume it's
|
|
|
|
|
// likely to have the bigger lists and merging will be quicker.
|
|
|
|
|
MergeLayerEventRegions(oldItem, newItem);
|
|
|
|
|
ReuseItem(oldItem);
|
|
|
|
|
newItem->Destroy(&mBuilder);
|
|
|
|
|
} else {
|
|
|
|
|
if (!IsAnyAncestorModified(oldItem->FrameForInvalidation()) &&
|
|
|
|
|
oldItem->GetChildren()) {
|
|
|
|
|
MOZ_ASSERT(newItem->GetChildren());
|
|
|
|
|
Maybe<const ActiveScrolledRoot*> containerASRForChildren;
|
|
|
|
|
MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(),
|
|
|
|
|
newItem->GetChildren(), containerASRForChildren);
|
|
|
|
|
UpdateASR(newItem, containerASRForChildren);
|
|
|
|
|
newItem->UpdateBounds(&mBuilder);
|
|
|
|
|
}
|
2017-11-22 05:10:31 +03:00
|
|
|
|
|
2018-01-15 02:15:50 +03:00
|
|
|
|
if (destroy) {
|
|
|
|
|
oldItem->Destroy(&mBuilder);
|
|
|
|
|
}
|
|
|
|
|
UseItem(newItem);
|
2018-01-17 02:07:42 +03:00
|
|
|
|
}
|
2018-01-15 02:15:50 +03:00
|
|
|
|
} else {
|
|
|
|
|
// If there was no matching item in the old list, then we only need to
|
|
|
|
|
// add the new item to the merged list.
|
2017-11-22 05:10:31 +03:00
|
|
|
|
UseItem(newItem);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reuse the remaining valid items from the old display list.
|
|
|
|
|
while (nsDisplayItem* old = aOldList->RemoveBottom()) {
|
2018-01-17 02:07:42 +03:00
|
|
|
|
if (!IsAnyAncestorModified(old->FrameForInvalidation()) &&
|
2018-01-15 02:15:50 +03:00
|
|
|
|
(!old->IsReused() || newListIsEmpty)) {
|
2017-10-18 06:09:07 +03:00
|
|
|
|
if (old->GetChildren()) {
|
|
|
|
|
// We are calling MergeDisplayLists() to ensure that the display items
|
|
|
|
|
// with modified or deleted children will be correctly handled.
|
|
|
|
|
// Passing an empty new display list as an argument skips the merging
|
|
|
|
|
// loop above and jumps back here.
|
2017-11-16 06:09:28 +03:00
|
|
|
|
nsDisplayList empty;
|
2017-11-12 23:02:48 +03:00
|
|
|
|
Maybe<const ActiveScrolledRoot*> containerASRForChildren;
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
2017-11-10 03:16:55 +03:00
|
|
|
|
MergeDisplayLists(&empty, old->GetChildren(),
|
2017-11-12 23:02:48 +03:00
|
|
|
|
old->GetChildren(), containerASRForChildren);
|
2017-11-10 03:16:55 +03:00
|
|
|
|
UpdateASR(old, containerASRForChildren);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
old->UpdateBounds(&mBuilder);
|
|
|
|
|
}
|
|
|
|
|
if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
|
|
|
|
|
MergeLayerEventRegions(old, nullptr);
|
|
|
|
|
}
|
2017-11-10 03:16:55 +03:00
|
|
|
|
ReuseItem(old);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
} else {
|
|
|
|
|
old->Destroy(&mBuilder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
aOutList->AppendToTop(&merged);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2017-10-23 19:19:39 +03:00
|
|
|
|
TakeAndAddModifiedFramesFromRootFrame(nsTArray<nsIFrame*>& aFrames,
|
2017-10-18 06:09:07 +03:00
|
|
|
|
nsIFrame* aRootFrame)
|
|
|
|
|
{
|
|
|
|
|
MOZ_ASSERT(aRootFrame);
|
|
|
|
|
|
2017-11-08 05:25:44 +03:00
|
|
|
|
nsTArray<nsIFrame*>* frames =
|
2017-10-18 06:09:07 +03:00
|
|
|
|
aRootFrame->GetProperty(nsIFrame::ModifiedFrameList());
|
|
|
|
|
|
2017-10-23 19:19:39 +03:00
|
|
|
|
if (!frames) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
2017-11-08 05:25:44 +03:00
|
|
|
|
for (nsIFrame* f : *frames) {
|
2017-10-23 19:19:39 +03:00
|
|
|
|
if (f) {
|
|
|
|
|
aFrames.AppendElement(f);
|
|
|
|
|
}
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
2017-10-23 19:19:39 +03:00
|
|
|
|
|
2017-11-08 05:25:44 +03:00
|
|
|
|
frames->Clear();
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-25 02:02:20 +03:00
|
|
|
|
struct CbData {
|
|
|
|
|
nsDisplayListBuilder* builder;
|
|
|
|
|
nsTArray<nsIFrame*> modifiedFrames;
|
|
|
|
|
};
|
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
static bool
|
|
|
|
|
SubDocEnumCb(nsIDocument* aDocument, void* aData)
|
|
|
|
|
{
|
|
|
|
|
MOZ_ASSERT(aDocument);
|
|
|
|
|
MOZ_ASSERT(aData);
|
|
|
|
|
|
2017-11-25 02:02:20 +03:00
|
|
|
|
CbData* data = static_cast<CbData*>(aData);
|
|
|
|
|
|
|
|
|
|
// Although this is the actual subdocument, it might not be
|
|
|
|
|
// what painting uses. Walk up to the nsSubDocumentFrame owning
|
|
|
|
|
// us, and then ask that which subdoc it's going to paint.
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
nsIPresShell* presShell = aDocument->GetShell();
|
2017-11-25 02:02:20 +03:00
|
|
|
|
if (presShell) {
|
|
|
|
|
nsView* rootView = presShell->GetViewManager()->GetRootView();
|
|
|
|
|
MOZ_ASSERT(rootView);
|
|
|
|
|
|
|
|
|
|
// There should be an anonymous inner view between the root view
|
|
|
|
|
// of the subdoc, and the view for the nsSubDocumentFrame.
|
|
|
|
|
nsView* innerView = rootView->GetParent();
|
|
|
|
|
MOZ_ASSERT(innerView);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
2017-11-25 02:02:20 +03:00
|
|
|
|
nsView* subDocView = innerView->GetParent();
|
|
|
|
|
MOZ_ASSERT(subDocView);
|
|
|
|
|
|
|
|
|
|
nsIFrame* subDocFrame = subDocView->GetFrame();
|
2018-01-17 02:04:51 +03:00
|
|
|
|
if (subDocFrame) {
|
|
|
|
|
nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(subDocFrame);
|
|
|
|
|
MOZ_ASSERT(subdocumentFrame);
|
2017-11-25 02:02:20 +03:00
|
|
|
|
|
2018-01-17 02:04:51 +03:00
|
|
|
|
presShell = subdocumentFrame->GetSubdocumentPresShellForPainting(
|
|
|
|
|
data->builder->IsIgnoringPaintSuppression() ? nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION : 0);
|
|
|
|
|
nsIFrame* rootFrame = presShell ? presShell->GetRootFrame() : nullptr;
|
2017-11-25 02:02:20 +03:00
|
|
|
|
|
2018-01-17 02:04:51 +03:00
|
|
|
|
if (rootFrame) {
|
|
|
|
|
TakeAndAddModifiedFramesFromRootFrame(data->modifiedFrames, rootFrame);
|
|
|
|
|
}
|
2017-11-25 02:02:20 +03:00
|
|
|
|
}
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
aDocument->EnumerateSubDocuments(SubDocEnumCb, aData);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-23 19:19:39 +03:00
|
|
|
|
static nsTArray<nsIFrame*>
|
2017-11-25 02:02:20 +03:00
|
|
|
|
GetModifiedFrames(nsDisplayListBuilder* aBuilder)
|
2017-10-18 06:09:07 +03:00
|
|
|
|
{
|
2017-11-25 02:02:20 +03:00
|
|
|
|
MOZ_ASSERT(aBuilder->RootReferenceFrame());
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
2017-11-25 02:02:20 +03:00
|
|
|
|
CbData data;
|
|
|
|
|
data.builder = aBuilder;
|
|
|
|
|
TakeAndAddModifiedFramesFromRootFrame(data.modifiedFrames, aBuilder->RootReferenceFrame());
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
2017-11-25 02:02:20 +03:00
|
|
|
|
nsIDocument* rootdoc = aBuilder->RootReferenceFrame()->PresContext()->Document();
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
if (rootdoc) {
|
2017-11-25 02:02:20 +03:00
|
|
|
|
rootdoc->EnumerateSubDocuments(SubDocEnumCb, &data);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-25 02:02:20 +03:00
|
|
|
|
return Move(data.modifiedFrames);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ComputeRebuildRegion debugging
|
|
|
|
|
// #define CRR_DEBUG 1
|
|
|
|
|
#if CRR_DEBUG
|
|
|
|
|
# define CRR_LOG(...) printf_stderr(__VA_ARGS__)
|
|
|
|
|
#else
|
|
|
|
|
# define CRR_LOG(...)
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-01-17 20:27:42 +03:00
|
|
|
|
static nsIFrame*
|
|
|
|
|
HandlePreserve3D(nsIFrame* aFrame, nsRect& aOverflow)
|
|
|
|
|
{
|
|
|
|
|
// Preserve-3d frames don't have valid overflow areas, and they might
|
|
|
|
|
// have singular transforms (despite still being visible when combined
|
|
|
|
|
// with their ancestors). If we're at one, jump up to the root of the
|
|
|
|
|
// preserve-3d context and use the whole overflow area.
|
|
|
|
|
nsIFrame* last = aFrame;
|
|
|
|
|
while (aFrame->Extend3DContext() ||
|
|
|
|
|
aFrame->Combines3DTransformWithAncestors()) {
|
|
|
|
|
last = aFrame;
|
|
|
|
|
aFrame = aFrame->GetParent();
|
|
|
|
|
}
|
|
|
|
|
if (last != aFrame) {
|
|
|
|
|
aOverflow = last->GetVisualOverflowRectRelativeToParent();
|
2018-01-23 22:11:34 +03:00
|
|
|
|
CRR_LOG("HandlePreserve3D() Updated overflow rect to: %d %d %d %d\n",
|
|
|
|
|
aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
|
2018-01-17 20:27:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return aFrame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder& aBuilder,
|
|
|
|
|
AnimatedGeometryRoot** aAGR, nsRect& aOverflow,
|
|
|
|
|
nsIFrame* aStopAtFrame, nsTArray<nsIFrame*>& aOutFramesWithProps,
|
2018-01-12 20:02:14 +03:00
|
|
|
|
const bool aStopAtStackingContext)
|
2018-01-17 20:27:42 +03:00
|
|
|
|
{
|
|
|
|
|
nsIFrame* currentFrame = aFrame;
|
|
|
|
|
|
|
|
|
|
while (currentFrame != aStopAtFrame) {
|
2018-01-23 22:11:34 +03:00
|
|
|
|
CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n",
|
|
|
|
|
currentFrame, !aStopAtStackingContext,
|
|
|
|
|
aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
|
2018-01-17 20:27:42 +03:00
|
|
|
|
|
2018-01-23 22:11:34 +03:00
|
|
|
|
currentFrame = HandlePreserve3D(currentFrame, aOverflow);
|
2018-01-17 20:27:42 +03:00
|
|
|
|
|
2018-01-12 20:02:14 +03:00
|
|
|
|
// If the current frame is an OOF frame, DisplayListBuildingData needs to be
|
|
|
|
|
// set on all the ancestor stacking contexts of the placeholder frame, up
|
|
|
|
|
// to the containing block of the OOF frame. This is done to ensure that the
|
|
|
|
|
// content that might be behind the OOF frame is built for merging.
|
2018-01-23 22:11:34 +03:00
|
|
|
|
nsIFrame* placeholder = currentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
|
|
|
|
|
? currentFrame->GetPlaceholderFrame()
|
2018-01-12 20:02:14 +03:00
|
|
|
|
: nullptr;
|
|
|
|
|
|
|
|
|
|
if (placeholder) {
|
2018-01-23 22:11:34 +03:00
|
|
|
|
// The rect aOverflow is in the coordinate space of the containing block.
|
|
|
|
|
// Convert it to a coordinate space of the placeholder frame.
|
2018-01-12 20:02:14 +03:00
|
|
|
|
nsRect placeholderOverflow =
|
2018-01-23 22:11:34 +03:00
|
|
|
|
aOverflow + currentFrame->GetOffsetTo(placeholder);
|
2018-01-12 20:02:14 +03:00
|
|
|
|
|
|
|
|
|
CRR_LOG("Processing placeholder %p for OOF frame %p\n",
|
2018-01-23 22:11:34 +03:00
|
|
|
|
placeholder, currentFrame);
|
2018-01-12 20:02:14 +03:00
|
|
|
|
|
|
|
|
|
CRR_LOG("OOF frame draw area: %d %d %d %d\n",
|
|
|
|
|
placeholderOverflow.x, placeholderOverflow.y,
|
|
|
|
|
placeholderOverflow.width, placeholderOverflow.height);
|
|
|
|
|
|
|
|
|
|
// Tracking AGRs for the placeholder processing is not necessary, as the
|
|
|
|
|
// goal is to only modify the DisplayListBuildingData rect.
|
|
|
|
|
AnimatedGeometryRoot* dummyAGR = nullptr;
|
|
|
|
|
|
|
|
|
|
// Find a common ancestor frame to handle frame continuations.
|
|
|
|
|
// TODO: It might be possible to write a more specific and efficient
|
|
|
|
|
// function for this.
|
|
|
|
|
nsIFrame* ancestor =
|
2018-01-23 22:11:34 +03:00
|
|
|
|
nsLayoutUtils::FindNearestCommonAncestorFrame(currentFrame->GetParent(),
|
2018-01-12 20:02:14 +03:00
|
|
|
|
placeholder->GetParent());
|
|
|
|
|
|
|
|
|
|
ProcessFrame(placeholder, aBuilder, &dummyAGR, placeholderOverflow,
|
|
|
|
|
ancestor, aOutFramesWithProps, false);
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-23 22:11:34 +03:00
|
|
|
|
// Convert 'aOverflow' into the coordinate space of the nearest stacking context
|
|
|
|
|
// or display port ancestor and update 'currentFrame' to point to that frame.
|
|
|
|
|
aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, aOverflow, aStopAtFrame,
|
|
|
|
|
nullptr, nullptr,
|
|
|
|
|
/* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
|
|
|
|
|
¤tFrame);
|
|
|
|
|
MOZ_ASSERT(currentFrame);
|
|
|
|
|
|
2018-01-17 20:27:42 +03:00
|
|
|
|
if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) {
|
|
|
|
|
CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
|
|
|
|
|
nsIScrollableFrame* sf = do_QueryFrame(currentFrame);
|
|
|
|
|
MOZ_ASSERT(sf);
|
|
|
|
|
nsRect displayPort;
|
|
|
|
|
DebugOnly<bool> hasDisplayPort =
|
|
|
|
|
nsLayoutUtils::GetDisplayPort(currentFrame->GetContent(), &displayPort,
|
|
|
|
|
RelativeTo::ScrollPort);
|
|
|
|
|
MOZ_ASSERT(hasDisplayPort);
|
|
|
|
|
// get it relative to the scrollport (from the scrollframe)
|
|
|
|
|
nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft();
|
|
|
|
|
r.IntersectRect(r, displayPort);
|
|
|
|
|
if (!r.IsEmpty()) {
|
|
|
|
|
nsRect* rect =
|
|
|
|
|
currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
|
|
|
|
|
if (!rect) {
|
|
|
|
|
rect = new nsRect();
|
|
|
|
|
currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
|
|
|
|
|
currentFrame->SetHasOverrideDirtyRegion(true);
|
|
|
|
|
aOutFramesWithProps.AppendElement(currentFrame);
|
|
|
|
|
}
|
|
|
|
|
rect->UnionRect(*rect, r);
|
2018-01-12 20:02:14 +03:00
|
|
|
|
CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n",
|
|
|
|
|
r.x, r.y, r.width, r.height);
|
2018-01-17 20:27:42 +03:00
|
|
|
|
|
|
|
|
|
// TODO: Can we just use MarkFrameForDisplayIfVisible, plus MarkFramesForDifferentAGR to
|
|
|
|
|
// ensure that this displayport, plus any items that move relative to it get rebuilt,
|
|
|
|
|
// and then not contribute to the root dirty area?
|
|
|
|
|
aOverflow = sf->GetScrollPortRect();
|
|
|
|
|
} else {
|
|
|
|
|
// Don't contribute to the root dirty area at all.
|
|
|
|
|
aOverflow.SetEmpty();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (currentFrame->IsStackingContext()) {
|
|
|
|
|
CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
|
|
|
|
|
// If we found an intermediate stacking context with an existing display item
|
|
|
|
|
// then we can store the dirty rect there and stop. If we couldn't find one then
|
|
|
|
|
// we need to keep bubbling up to the next stacking context.
|
2018-01-12 20:02:14 +03:00
|
|
|
|
if (currentFrame == aBuilder.RootReferenceFrame() ||
|
|
|
|
|
!currentFrame->HasDisplayItems()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-01-17 20:27:42 +03:00
|
|
|
|
|
2018-01-12 20:02:14 +03:00
|
|
|
|
aBuilder.MarkFrameForDisplayIfVisible(currentFrame,
|
|
|
|
|
aBuilder.RootReferenceFrame());
|
|
|
|
|
|
|
|
|
|
// Store the stacking context relative dirty area such
|
|
|
|
|
// that display list building will pick it up when it
|
|
|
|
|
// gets to it.
|
|
|
|
|
nsDisplayListBuilder::DisplayListBuildingData* data =
|
|
|
|
|
currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
|
|
|
|
|
if (!data) {
|
|
|
|
|
data = new nsDisplayListBuilder::DisplayListBuildingData();
|
|
|
|
|
currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingRect(), data);
|
|
|
|
|
currentFrame->SetHasOverrideDirtyRegion(true);
|
|
|
|
|
aOutFramesWithProps.AppendElement(currentFrame);
|
|
|
|
|
}
|
|
|
|
|
CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
|
|
|
|
|
aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
|
|
|
|
|
data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow);
|
|
|
|
|
|
|
|
|
|
if (!aStopAtStackingContext) {
|
|
|
|
|
// Continue ascending the frame tree until we reach aStopAtFrame.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!data->mModifiedAGR) {
|
|
|
|
|
data->mModifiedAGR = *aAGR;
|
|
|
|
|
} else if (data->mModifiedAGR != *aAGR) {
|
|
|
|
|
data->mDirtyRect = currentFrame->GetVisualOverflowRectRelativeToSelf();
|
|
|
|
|
CRR_LOG("Found multiple modified AGRs within this stacking context, giving up\n");
|
2018-01-17 20:27:42 +03:00
|
|
|
|
}
|
2018-01-12 20:02:14 +03:00
|
|
|
|
|
|
|
|
|
// Don't contribute to the root dirty area at all.
|
|
|
|
|
aOverflow.SetEmpty();
|
|
|
|
|
*aAGR = nullptr;
|
|
|
|
|
|
|
|
|
|
break;
|
2018-01-17 20:27:42 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-12 20:02:14 +03:00
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
/**
|
|
|
|
|
* Given a list of frames that has been modified, computes the region that we need to
|
|
|
|
|
* do display list building for in order to build all modified display items.
|
|
|
|
|
*
|
|
|
|
|
* When a modified frame is within a stacking context (with an existing display item),
|
|
|
|
|
* then we only contribute to the build area within the stacking context, as well as forcing
|
|
|
|
|
* display list building to descend to the stacking context. We don't need to add build
|
|
|
|
|
* area outside of the stacking context (and force items above/below the stacking context
|
|
|
|
|
* container item to be built), since just matching the position of the stacking context
|
|
|
|
|
* container item is sufficient to ensure correct ordering during merging.
|
|
|
|
|
*
|
|
|
|
|
* We need to rebuild all items that might intersect with the modified frame, both now
|
|
|
|
|
* and during async changes on the compositor. We do this by rebuilding the area covered
|
|
|
|
|
* by the changed frame, as well as rebuilding all items that have a different (async)
|
|
|
|
|
* AGR to the changed frame. If we have changes to multiple AGRs (within a stacking
|
|
|
|
|
* context), then we rebuild that stacking context entirely.
|
|
|
|
|
*
|
|
|
|
|
* @param aModifiedFrames The list of modified frames.
|
|
|
|
|
* @param aOutDirty The result region to use for display list building.
|
|
|
|
|
* @param aOutModifiedAGR The modified AGR for the root stacking context.
|
|
|
|
|
* @param aOutFramesWithProps The list of frames to which we attached partial build
|
|
|
|
|
* data so that it can be cleaned up.
|
|
|
|
|
*
|
|
|
|
|
* @return true if we succesfully computed a partial rebuild region, false if a full
|
|
|
|
|
* build is required.
|
|
|
|
|
*/
|
|
|
|
|
bool
|
2017-10-23 19:19:39 +03:00
|
|
|
|
RetainedDisplayListBuilder::ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames,
|
2017-10-18 06:09:07 +03:00
|
|
|
|
nsRect* aOutDirty,
|
|
|
|
|
AnimatedGeometryRoot** aOutModifiedAGR,
|
2018-01-17 20:27:42 +03:00
|
|
|
|
nsTArray<nsIFrame*>& aOutFramesWithProps)
|
2017-10-18 06:09:07 +03:00
|
|
|
|
{
|
2018-01-17 20:27:42 +03:00
|
|
|
|
CRR_LOG("Computing rebuild regions for %zu frames:\n", aModifiedFrames.Length());
|
2017-10-18 06:09:07 +03:00
|
|
|
|
for (nsIFrame* f : aModifiedFrames) {
|
2017-10-23 19:19:39 +03:00
|
|
|
|
MOZ_ASSERT(f);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
if (f->HasOverrideDirtyRegion()) {
|
2018-01-17 20:27:42 +03:00
|
|
|
|
aOutFramesWithProps.AppendElement(f);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-03 08:31:31 +03:00
|
|
|
|
if (f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
// TODO: There is almost certainly a faster way of doing this, probably can be combined with the ancestor
|
|
|
|
|
// walk for TransformFrameRectToAncestor.
|
|
|
|
|
AnimatedGeometryRoot* agr = mBuilder.FindAnimatedGeometryRootFor(f)->GetAsyncAGR();
|
|
|
|
|
|
|
|
|
|
CRR_LOG("Processing frame %p with agr %p\n", f, agr->mFrame);
|
|
|
|
|
|
|
|
|
|
// Convert the frame's overflow rect into the coordinate space
|
|
|
|
|
// of the nearest stacking context that has an existing display item.
|
|
|
|
|
// We store that as a dirty rect on that stacking context so that we build
|
|
|
|
|
// all items that intersect the changed frame within the stacking context,
|
|
|
|
|
// and then we use MarkFrameForDisplayIfVisible to make sure the stacking
|
|
|
|
|
// context itself gets built. We don't need to build items that intersect outside
|
|
|
|
|
// of the stacking context, since we know the stacking context item exists in
|
|
|
|
|
// the old list, so we can trivially merge without needing other items.
|
|
|
|
|
nsRect overflow = f->GetVisualOverflowRectRelativeToSelf();
|
2018-01-18 22:03:23 +03:00
|
|
|
|
|
2018-01-19 20:08:27 +03:00
|
|
|
|
// If the modified frame is also a caret frame, include the caret area.
|
|
|
|
|
// This is needed because some frames (for example text frames without text)
|
|
|
|
|
// might have an empty overflow rect.
|
|
|
|
|
if (f == mBuilder.GetCaretFrame()) {
|
|
|
|
|
overflow.UnionRect(overflow, mBuilder.GetCaretRect());
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 20:27:42 +03:00
|
|
|
|
ProcessFrame(f, mBuilder, &agr, overflow, mBuilder.RootReferenceFrame(),
|
|
|
|
|
aOutFramesWithProps, true);
|
2017-11-18 00:59:23 +03:00
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
aOutDirty->UnionRect(*aOutDirty, overflow);
|
2018-01-17 20:27:42 +03:00
|
|
|
|
CRR_LOG("Adding area to root draw area: %d %d %d %d\n",
|
|
|
|
|
overflow.x, overflow.y, overflow.width, overflow.height);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
// If we get changed frames from multiple AGRS, then just give up as it gets really complex to
|
|
|
|
|
// track which items would need to be marked in MarkFramesForDifferentAGR.
|
|
|
|
|
if (!*aOutModifiedAGR) {
|
2018-01-17 20:27:42 +03:00
|
|
|
|
CRR_LOG("Setting %p as root stacking context AGR\n", agr);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
*aOutModifiedAGR = agr;
|
|
|
|
|
} else if (agr && *aOutModifiedAGR != agr) {
|
|
|
|
|
CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-24 15:54:16 +03:00
|
|
|
|
/*
|
|
|
|
|
* A simple early exit heuristic to avoid slow partial display list rebuilds.
|
|
|
|
|
*/
|
|
|
|
|
static bool
|
|
|
|
|
ShouldBuildPartial(nsTArray<nsIFrame*>& aModifiedFrames)
|
|
|
|
|
{
|
|
|
|
|
if (aModifiedFrames.Length() > gfxPrefs::LayoutRebuildFrameLimit()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (nsIFrame* f : aModifiedFrames) {
|
|
|
|
|
MOZ_ASSERT(f);
|
|
|
|
|
|
|
|
|
|
const LayoutFrameType type = f->Type();
|
|
|
|
|
|
|
|
|
|
// If we have any modified frames of the following types, it is likely that
|
|
|
|
|
// doing a partial rebuild of the display list will be slower than doing a
|
|
|
|
|
// full rebuild.
|
|
|
|
|
// This is because these frames either intersect or may intersect with most
|
|
|
|
|
// of the page content. This is either due to display port size or different
|
|
|
|
|
// async AGR.
|
|
|
|
|
if (type == LayoutFrameType::Viewport ||
|
|
|
|
|
type == LayoutFrameType::PageContent ||
|
|
|
|
|
type == LayoutFrameType::Canvas ||
|
|
|
|
|
type == LayoutFrameType::Scrollbar) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
2017-11-10 03:32:29 +03:00
|
|
|
|
static void
|
|
|
|
|
ClearFrameProps(nsTArray<nsIFrame*>& aFrames)
|
|
|
|
|
{
|
|
|
|
|
for (nsIFrame* f : aFrames) {
|
|
|
|
|
if (f->HasOverrideDirtyRegion()) {
|
|
|
|
|
f->SetHasOverrideDirtyRegion(false);
|
|
|
|
|
f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect());
|
|
|
|
|
f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f->SetFrameIsModified(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-09 18:10:21 +03:00
|
|
|
|
void
|
|
|
|
|
RetainedDisplayListBuilder::ClearModifiedFrameProps()
|
|
|
|
|
{
|
|
|
|
|
nsTArray<nsIFrame*> modifiedFrames =
|
2017-11-25 02:02:20 +03:00
|
|
|
|
GetModifiedFrames(&mBuilder);
|
2017-11-09 18:10:21 +03:00
|
|
|
|
|
|
|
|
|
ClearFrameProps(modifiedFrames);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
bool
|
|
|
|
|
RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop)
|
|
|
|
|
{
|
|
|
|
|
mBuilder.RemoveModifiedWindowDraggingRegion();
|
|
|
|
|
if (mBuilder.ShouldSyncDecodeImages()) {
|
|
|
|
|
MarkFramesWithItemsAndImagesModified(&mList);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mBuilder.EnterPresShell(mBuilder.RootReferenceFrame());
|
|
|
|
|
|
2017-10-23 19:19:39 +03:00
|
|
|
|
nsTArray<nsIFrame*> modifiedFrames =
|
2017-11-25 02:02:20 +03:00
|
|
|
|
GetModifiedFrames(&mBuilder);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
2017-11-10 03:32:29 +03:00
|
|
|
|
// Do not allow partial builds if the retained display list is empty, or if
|
|
|
|
|
// ShouldBuildPartial heuristic fails.
|
2017-12-15 02:38:21 +03:00
|
|
|
|
const bool shouldBuildPartial = !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames);
|
2017-10-24 15:54:16 +03:00
|
|
|
|
|
2017-10-18 06:09:07 +03:00
|
|
|
|
if (mPreviousCaret != mBuilder.GetCaretFrame()) {
|
|
|
|
|
if (mPreviousCaret) {
|
2017-10-26 12:02:05 +03:00
|
|
|
|
if (mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret)) {
|
|
|
|
|
modifiedFrames.AppendElement(mPreviousCaret);
|
|
|
|
|
}
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mBuilder.GetCaretFrame()) {
|
2017-10-26 12:02:05 +03:00
|
|
|
|
if (mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame())) {
|
|
|
|
|
modifiedFrames.AppendElement(mBuilder.GetCaretFrame());
|
|
|
|
|
}
|
2017-10-18 06:09:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mPreviousCaret = mBuilder.GetCaretFrame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsRect modifiedDirty;
|
|
|
|
|
AnimatedGeometryRoot* modifiedAGR = nullptr;
|
|
|
|
|
nsTArray<nsIFrame*> framesWithProps;
|
|
|
|
|
bool merged = false;
|
2017-11-10 03:32:29 +03:00
|
|
|
|
if (shouldBuildPartial &&
|
|
|
|
|
ComputeRebuildRegion(modifiedFrames, &modifiedDirty,
|
2018-01-17 20:27:42 +03:00
|
|
|
|
&modifiedAGR, framesWithProps)) {
|
2017-10-18 06:09:07 +03:00
|
|
|
|
modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf());
|
|
|
|
|
|
|
|
|
|
PreProcessDisplayList(&mList, modifiedAGR);
|
|
|
|
|
|
2017-11-16 06:09:28 +03:00
|
|
|
|
nsDisplayList modifiedDL;
|
2017-10-18 06:09:07 +03:00
|
|
|
|
if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) {
|
|
|
|
|
mBuilder.SetDirtyRect(modifiedDirty);
|
|
|
|
|
mBuilder.SetPartialUpdate(true);
|
|
|
|
|
mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL);
|
|
|
|
|
nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(),
|
|
|
|
|
nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
|
|
|
|
|
mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(),
|
|
|
|
|
aBackstop);
|
|
|
|
|
mBuilder.SetPartialUpdate(false);
|
|
|
|
|
|
|
|
|
|
//printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
|
|
|
|
|
// modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height);
|
2017-10-26 12:02:05 +03:00
|
|
|
|
//nsFrame::PrintDisplayList(&mBuilder, modifiedDL);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// TODO: We can also skip layer building and painting if
|
|
|
|
|
// PreProcessDisplayList didn't end up changing anything
|
|
|
|
|
// Invariant: display items should have their original state here.
|
|
|
|
|
// printf_stderr("Skipping display list building since nothing needed to be done\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// |modifiedDL| can sometimes be empty here. We still perform the
|
|
|
|
|
// display list merging to prune unused items (for example, items that
|
|
|
|
|
// are not visible anymore) from the old list.
|
|
|
|
|
// TODO: Optimization opportunity. In this case, MergeDisplayLists()
|
|
|
|
|
// unnecessarily creates a hashtable of the old items.
|
2017-11-12 23:02:48 +03:00
|
|
|
|
Maybe<const ActiveScrolledRoot*> dummy;
|
|
|
|
|
MergeDisplayLists(&modifiedDL, &mList, &mList, dummy);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
//printf_stderr("Painting --- Merged list:\n");
|
2017-10-26 12:02:05 +03:00
|
|
|
|
//nsFrame::PrintDisplayList(&mBuilder, mList);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
merged = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList);
|
|
|
|
|
|
2017-11-10 03:32:29 +03:00
|
|
|
|
// We set the override dirty regions during ComputeRebuildRegion or in
|
|
|
|
|
// nsLayoutUtils::InvalidateForDisplayPortChange. The display port change also
|
|
|
|
|
// marks the frame modified, so those regions are cleared here as well.
|
|
|
|
|
ClearFrameProps(modifiedFrames);
|
|
|
|
|
ClearFrameProps(framesWithProps);
|
2017-10-18 06:09:07 +03:00
|
|
|
|
|
|
|
|
|
return merged;
|
|
|
|
|
}
|