/* -*- 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
 * 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 "nsPageContentFrame.h"

#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_layout.h"

#include "nsContentUtils.h"
#include "nsCSSFrameConstructor.h"
#include "nsPresContext.h"
#include "nsGkAtoms.h"
#include "nsPageSequenceFrame.h"

using namespace mozilla;

nsPageContentFrame* NS_NewPageContentFrame(PresShell* aPresShell,
                                           ComputedStyle* aStyle) {
  return new (aPresShell)
      nsPageContentFrame(aStyle, aPresShell->GetPresContext());
}

NS_IMPL_FRAMEARENA_HELPERS(nsPageContentFrame)

void nsPageContentFrame::Reflow(nsPresContext* aPresContext,
                                ReflowOutput& aReflowOutput,
                                const ReflowInput& aReflowInput,
                                nsReflowStatus& aStatus) {
  MarkInReflow();
  DO_GLOBAL_REFLOW_COUNT("nsPageContentFrame");
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
  MOZ_ASSERT(mPD, "Need a pointer to nsSharedPageData before reflow starts");

  if (GetPrevInFlow() && HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    nsresult rv =
        aPresContext->PresShell()->FrameConstructor()->ReplicateFixedFrames(
            this);
    if (NS_FAILED(rv)) {
      return;
    }
  }

  // Set our size up front, since some parts of reflow depend on it
  // being already set.  Note that the computed height may be
  // unconstrained; that's ok.  Consumers should watch out for that.
  nsSize maxSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight());
  SetSize(maxSize);

  WritingMode wm = aReflowInput.GetWritingMode();
  aReflowOutput.ISize(wm) = aReflowInput.ComputedISize();
  if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
    aReflowOutput.BSize(wm) = aReflowInput.ComputedBSize();
  }
  aReflowOutput.SetOverflowAreasToDesiredBounds();

  // A PageContentFrame must always have one child: the canvas frame.
  // Resize our frame allowing it only to be as big as we are
  // XXX Pay attention to the page's border and padding...
  if (mFrames.NotEmpty()) {
    nsIFrame* frame = mFrames.FirstChild();
    WritingMode wm = frame->GetWritingMode();
    LogicalSize logicalSize(wm, maxSize);
    ReflowInput kidReflowInput(aPresContext, aReflowInput, frame, logicalSize);
    kidReflowInput.SetComputedBSize(logicalSize.BSize(wm));
    ReflowOutput kidReflowOutput(kidReflowInput);
    ReflowChild(frame, aPresContext, kidReflowOutput, kidReflowInput, 0, 0,
                ReflowChildFlags::Default, aStatus);

    // The document element's background should cover the entire canvas, so
    // take into account the combined area and any space taken up by
    // absolutely positioned elements
    nsMargin padding(0, 0, 0, 0);

    // XXXbz this screws up percentage padding (sets padding to zero
    // in the percentage padding case)
    kidReflowInput.mStylePadding->GetPadding(padding);

    // This is for shrink-to-fit, and therefore we want to use the
    // scrollable overflow, since the purpose of shrink to fit is to
    // make the content that ought to be reachable (represented by the
    // scrollable overflow) fit in the page.
    if (frame->HasOverflowAreas()) {
      // The background covers the content area and padding area, so check
      // for children sticking outside the child frame's padding edge
      nscoord xmost = kidReflowOutput.ScrollableOverflow().XMost();
      if (xmost > kidReflowOutput.Width()) {
        nscoord widthToFit =
            xmost + padding.right +
            kidReflowInput.mStyleBorder->GetComputedBorderWidth(eSideRight);
        float ratio = float(maxSize.width) / widthToFit;
        NS_ASSERTION(ratio >= 0.0 && ratio < 1.0,
                     "invalid shrink-to-fit ratio");
        mPD->mShrinkToFitRatio = std::min(mPD->mShrinkToFitRatio, ratio);
      }
      // In the case of pdf.js documents, we also want to consider the height,
      // so that we don't clip the page in either axis if the aspect ratio of
      // the PDF doesn't match the destination.
      if (nsContentUtils::IsPDFJS(PresContext()->Document()->GetPrincipal())) {
        nscoord ymost = kidReflowOutput.ScrollableOverflow().YMost();
        if (ymost > kidReflowOutput.Height()) {
          nscoord heightToFit =
              ymost + padding.bottom +
              kidReflowInput.mStyleBorder->GetComputedBorderWidth(eSideBottom);
          float ratio = float(maxSize.height) / heightToFit;
          MOZ_ASSERT(ratio >= 0.0 && ratio < 1.0);
          mPD->mShrinkToFitRatio = std::min(mPD->mShrinkToFitRatio, ratio);
        }

        // pdf.js pages should never overflow given the scaling above.
        // nsPrintJob::SetupToPrintContent ignores some ratios close to 1.0
        // though and doesn't reflow us again in that case, so we need to clear
        // the overflow area here in case that happens. (bug 1689789)
        frame->ClearOverflowRects();
        kidReflowOutput.mOverflowAreas = aReflowOutput.mOverflowAreas;
      }
    }

    // Place and size the child
    FinishReflowChild(frame, aPresContext, kidReflowOutput, &kidReflowInput, 0,
                      0, ReflowChildFlags::Default);

    NS_ASSERTION(aPresContext->IsDynamic() || !aStatus.IsFullyComplete() ||
                     !frame->GetNextInFlow(),
                 "bad child flow list");

    aReflowOutput.mOverflowAreas.UnionWith(kidReflowOutput.mOverflowAreas);
  }

  FinishAndStoreOverflow(&aReflowOutput);

  // Reflow our fixed frames
  nsReflowStatus fixedStatus;
  ReflowAbsoluteFrames(aPresContext, aReflowOutput, aReflowInput, fixedStatus);
  NS_ASSERTION(fixedStatus.IsComplete(),
               "fixed frames can be truncated, but not incomplete");

  if (StaticPrefs::layout_display_list_improve_fragmentation() &&
      mFrames.NotEmpty()) {
    auto* previous = static_cast<nsPageContentFrame*>(GetPrevContinuation());
    const nscoord previousPageOverflow =
        previous ? previous->mRemainingOverflow : 0;
    const nsSize containerSize(aReflowInput.AvailableWidth(),
                               aReflowInput.AvailableHeight());
    const nscoord pageBSize = GetLogicalRect(containerSize).BSize(wm);
    const nscoord overflowBSize =
        LogicalRect(wm, ScrollableOverflowRect(), GetSize()).BEnd(wm);
    const nscoord currentPageOverflow = overflowBSize - pageBSize;
    nscoord remainingOverflow =
        std::max(currentPageOverflow, previousPageOverflow - pageBSize);

    if (aStatus.IsFullyComplete() && remainingOverflow > 0) {
      // If we have ScrollableOverflow off the end of our page, then we report
      // ourselves as overflow-incomplete in order to produce an additional
      // content-less page, which we expect to draw our overflow on our behalf.
      aStatus.SetOverflowIncomplete();
    }

    mRemainingOverflow = std::max(remainingOverflow, 0);
  }

  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aReflowOutput);
}

void nsPageContentFrame::AppendDirectlyOwnedAnonBoxes(
    nsTArray<OwnedAnonBox>& aResult) {
  MOZ_ASSERT(mFrames.FirstChild(),
             "pageContentFrame must have a canvasFrame child");
  aResult.AppendElement(mFrames.FirstChild());
}

#ifdef DEBUG_FRAME_DUMP
nsresult nsPageContentFrame::GetFrameName(nsAString& aResult) const {
  return MakeFrameName(u"PageContent"_ns, aResult);
}
#endif