layout/generic/TextOverflow.h
author Emilio Cobos Álvarez <emilio@crisal.io>
Mon, 18 Mar 2019 15:15:13 +0000
changeset 523637 94682e23ba118dcfab3843a3280618b2e1686ecb
parent 505383 6f3709b3878117466168c40affa7bca0b60cf75b
child 531981 c2250a23fd66e3c812cb023c2d12f6f5e8d5a2f2
permissions -rw-r--r--
Bug 1472637 - Don't display alt text while loading, to match other UAs. r=tnikkel Differential Revision: https://phabricator.services.mozilla.com/D18518

/* -*- 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/. */

#ifndef TextOverflow_h_
#define TextOverflow_h_

#include "nsDisplayList.h"
#include "nsTHashtable.h"
#include "mozilla/Attributes.h"
#include "mozilla/Likely.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WritingModes.h"
#include <algorithm>

class nsIScrollableFrame;
class nsLineBox;

namespace mozilla {
namespace css {

/**
 * A class for rendering CSS3 text-overflow.
 * Usage:
 *  1. allocate an object using WillProcessLines
 *  2. then call ProcessLine for each line you are building display lists for
 */
class TextOverflow final {
 public:
  /**
   * Allocate an object for text-overflow processing.
   * @return nullptr if no processing is necessary.  The caller owns the object.
   */
  static Maybe<TextOverflow> WillProcessLines(nsDisplayListBuilder* aBuilder,
                                              nsIFrame* aBlockFrame);

  /**
   * Constructor, which client code SHOULD NOT use directly. Instead, clients
   * should call WillProcessLines(), which is basically the factory function
   * for TextOverflow instances.
   */
  TextOverflow(nsDisplayListBuilder* aBuilder, nsIFrame* aBlockFrame);

  TextOverflow() = delete;
  ~TextOverflow() = default;
  TextOverflow(TextOverflow&&) = default;
  TextOverflow(const TextOverflow&) = delete;
  TextOverflow& operator=(TextOverflow&&) = default;
  TextOverflow& operator=(const TextOverflow&) = delete;

  /**
   * Analyze the display lists for text overflow and what kind of item is at
   * the content edges.  Add display items for text-overflow markers as needed
   * and remove or clip items that would overlap a marker.
   */
  void ProcessLine(const nsDisplayListSet& aLists, nsLineBox* aLine,
                   uint32_t aLineNumber);

  /**
   * Get the resulting text-overflow markers (the list may be empty).
   * @return a DisplayList containing any text-overflow markers.
   */
  nsDisplayList& GetMarkers() { return mMarkerList; }

  /**
   * @return true if aBlockFrmae has text-overflow:clip on both sides.
   */
  static bool HasClippedOverflow(nsIFrame* aBlockFrame);
  /**
   * @return true if aBlockFrame needs analysis for text overflow.
   */
  static bool CanHaveTextOverflow(nsIFrame* aBlockFrame);

  typedef nsTHashtable<nsPtrHashKey<nsIFrame>> FrameHashtable;

 private:
  typedef mozilla::WritingMode WritingMode;
  typedef mozilla::LogicalRect LogicalRect;

  struct AlignmentEdges {
    AlignmentEdges() : mIStart(0), mIEnd(0), mAssigned(false) {}
    void Accumulate(WritingMode aWM, const LogicalRect& aRect) {
      if (MOZ_LIKELY(mAssigned)) {
        mIStart = std::min(mIStart, aRect.IStart(aWM));
        mIEnd = std::max(mIEnd, aRect.IEnd(aWM));
      } else {
        mIStart = aRect.IStart(aWM);
        mIEnd = aRect.IEnd(aWM);
        mAssigned = true;
      }
    }
    nscoord ISize() { return mIEnd - mIStart; }
    nscoord mIStart;
    nscoord mIEnd;
    bool mAssigned;
  };

  struct InnerClipEdges {
    InnerClipEdges()
        : mIStart(0), mIEnd(0), mAssignedIStart(false), mAssignedIEnd(false) {}
    void AccumulateIStart(WritingMode aWM, const LogicalRect& aRect) {
      if (MOZ_LIKELY(mAssignedIStart)) {
        mIStart = std::max(mIStart, aRect.IStart(aWM));
      } else {
        mIStart = aRect.IStart(aWM);
        mAssignedIStart = true;
      }
    }
    void AccumulateIEnd(WritingMode aWM, const LogicalRect& aRect) {
      if (MOZ_LIKELY(mAssignedIEnd)) {
        mIEnd = std::min(mIEnd, aRect.IEnd(aWM));
      } else {
        mIEnd = aRect.IEnd(aWM);
        mAssignedIEnd = true;
      }
    }
    nscoord mIStart;
    nscoord mIEnd;
    bool mAssignedIStart;
    bool mAssignedIEnd;
  };

  LogicalRect GetLogicalScrollableOverflowRectRelativeToBlock(
      nsIFrame* aFrame) const {
    return LogicalRect(
        mBlockWM,
        aFrame->GetScrollableOverflowRect() + aFrame->GetOffsetTo(mBlock),
        mBlockSize);
  }

  /**
   * Examines frames on the line to determine whether we should draw a left
   * and/or right marker, and if so, which frames should be completely hidden
   * and the bounds of what will be displayed between the markers.
   * @param aLine the line we're processing
   * @param aFramesToHide frames that should have their display items removed
   * @param aAlignmentEdges the outermost edges of all text and atomic
   *   inline-level frames that are inside the area between the markers
   * @return the area inside which we should add any markers;
   *   this is the block's content area narrowed by any floats on this line.
   */
  LogicalRect ExamineLineFrames(nsLineBox* aLine, FrameHashtable* aFramesToHide,
                                AlignmentEdges* aAlignmentEdges);

  /**
   * LineHasOverflowingText calls this to analyze edges, both the block's
   * content edges and the hypothetical marker edges aligned at the block edges.
   * @param aFrame the descendant frame of mBlock that we're analyzing
   * @param aContentArea the block's content area
   * @param aInsideMarkersArea the rectangle between the markers
   * @param aFramesToHide frames that should have their display items removed
   * @param aAlignmentEdges the outermost edges of all text and atomic
   *   inline-level frames that are inside the area between the markers
   * @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
   *   inline-level frame is visible between the marker edges
   * @param aClippedMarkerEdges the innermost edges of all text and atomic
   *   inline-level frames that are clipped by the current marker width
   */
  void ExamineFrameSubtree(nsIFrame* aFrame, const LogicalRect& aContentArea,
                           const LogicalRect& aInsideMarkersArea,
                           FrameHashtable* aFramesToHide,
                           AlignmentEdges* aAlignmentEdges,
                           bool* aFoundVisibleTextOrAtomic,
                           InnerClipEdges* aClippedMarkerEdges);

  /**
   * ExamineFrameSubtree calls this to analyze a frame against the hypothetical
   * marker edges (aInsideMarkersArea) for text frames and atomic inline-level
   * elements.  A text frame adds its extent inside aInsideMarkersArea where
   * grapheme clusters are fully visible.  An atomic adds its border box if
   * it's fully inside aInsideMarkersArea, otherwise the frame is added to
   * aFramesToHide.
   * @param aFrame the descendant frame of mBlock that we're analyzing
   * @param aFrameType aFrame's frame type
   * @param aInsideMarkersArea the rectangle between the markers
   * @param aFramesToHide frames that should have their display items removed
   * @param aAlignmentEdges the outermost edges of all text and atomic
   *   inline-level frames that are inside the area between the markers
   *                       inside aInsideMarkersArea
   * @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
   *   inline-level frame is visible between the marker edges
   * @param aClippedMarkerEdges the innermost edges of all text and atomic
   *   inline-level frames that are clipped by the current marker width
   */
  void AnalyzeMarkerEdges(nsIFrame* aFrame, mozilla::LayoutFrameType aFrameType,
                          const LogicalRect& aInsideMarkersArea,
                          FrameHashtable* aFramesToHide,
                          AlignmentEdges* aAlignmentEdges,
                          bool* aFoundVisibleTextOrAtomic,
                          InnerClipEdges* aClippedMarkerEdges);

  /**
   * Clip or remove items given the final marker edges. ("clip" here just means
   * assigning mVisIStartEdge/mVisIEndEdge for any nsCharClipDisplayItem that
   * needs it; see nsDisplayList.h for a description of that item).
   * @param aFramesToHide remove display items for these frames
   * @param aInsideMarkersArea is the area inside the markers
   */
  void PruneDisplayListContents(nsDisplayList* aList,
                                const FrameHashtable& aFramesToHide,
                                const LogicalRect& aInsideMarkersArea);

  /**
   * ProcessLine calls this to create display items for the markers and insert
   * them into mMarkerList.
   * @param aLine the line we're processing
   * @param aCreateIStart if true, create a marker on the inline start side
   * @param aCreateIEnd if true, create a marker on the inline end side
   * @param aInsideMarkersArea is the area inside the markers
   * @param aContentArea is the area inside which we should add the markers;
   *   this is the block's content area narrowed by any floats on this line.
   */
  void CreateMarkers(const nsLineBox* aLine, bool aCreateIStart,
                     bool aCreateIEnd, const LogicalRect& aInsideMarkersArea,
                     const LogicalRect& aContentArea, uint32_t aLineNumber);

  LogicalRect mContentArea;
  nsDisplayListBuilder* mBuilder;
  nsIFrame* mBlock;
  nsIScrollableFrame* mScrollableFrame;
  nsDisplayList mMarkerList;
  nsSize mBlockSize;
  WritingMode mBlockWM;
  bool mCanHaveInlineAxisScrollbar;
  bool mAdjustForPixelSnapping;

  class Marker {
   public:
    void Init(const nsStyleTextOverflowSide& aStyle) {
      mInitialized = false;
      mISize = 0;
      mStyle = &aStyle;
      mIntrinsicISize = 0;
      mHasOverflow = false;
      mActive = false;
    }

    /**
     * Setup the marker string and calculate its size, if not done already.
     */
    void SetupString(nsIFrame* aFrame);

    bool IsNeeded() const { return mHasOverflow; }
    void Reset() { mHasOverflow = false; }

    // The current width of the marker, the range is [0 .. mIntrinsicISize].
    nscoord mISize;
    // The intrinsic width of the marker.
    nscoord mIntrinsicISize;
    // The style for this side.
    const nsStyleTextOverflowSide* mStyle;
    // True if there is visible overflowing inline content on this side.
    bool mHasOverflow;
    // True if mMarkerString and mWidth have been setup from style.
    bool mInitialized;
    // True if the style is text-overflow:clip on this side and the marker
    // won't cause the line to become empty.
    bool mActive;
  };

  Marker mIStart;  // the inline start marker
  Marker mIEnd;    // the inline end marker
};

}  // namespace css
}  // namespace mozilla

#endif /* !defined(TextOverflow_h_) */