text: /**
   * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
   *           (C) 2000 Simon Hausmann <hausmann@kde.org>
   *           (C) 2000 Stefan Schimanski (1Stein@gmx.de)
   * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Library General Public
   * License as published by the Free Software Foundation; either
   * version 2 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Library General Public License for more details.
   *
   * You should have received a copy of the GNU Library General Public License
   * along with this library; see the file COPYING.LIB.  If not, write to
   * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   * Boston, MA 02110-1301, USA.
   *
   */
  
  #include "third_party/blink/renderer/core/layout/layout_frame_set.h"
  
  #include "third_party/blink/renderer/core/dom/document.h"
  #include "third_party/blink/renderer/core/events/mouse_event.h"
  #include "third_party/blink/renderer/core/frame/local_frame.h"
  #include "third_party/blink/renderer/core/html/html_dimension.h"
  #include "third_party/blink/renderer/core/html/html_frame_set_element.h"
  #include "third_party/blink/renderer/core/input/event_handler.h"
  #include "third_party/blink/renderer/core/layout/layout_frame.h"
  #include "third_party/blink/renderer/core/layout/layout_view.h"
  #include "third_party/blink/renderer/core/paint/frame_set_painter.h"
  #include "third_party/blink/renderer/platform/cursors.h"
  #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
  
  namespace blink {
  
  // Adjusts proportionally the size with remaining size.
  static int AdjustSizeToRemainingSize(int current,
                                       int remaining,
                                       int64_t total) {
    // Performs the math operations step by step to avoid the overflow.
    base::CheckedNumeric<int64_t> temp_product = current;
    temp_product *= remaining;
    temp_product /= total;
    return base::checked_cast<int>(temp_product.ValueOrDie());
  }
  
  LayoutFrameSet::LayoutFrameSet(HTMLFrameSetElement* frame_set)
      : LayoutBox(frame_set), is_resizing_(false) {
    SetInline(false);
  }
  
  LayoutFrameSet::~LayoutFrameSet() = default;
  
  LayoutFrameSet::GridAxis::GridAxis() : split_being_resized_(kNoSplit) {}
  
  HTMLFrameSetElement* LayoutFrameSet::FrameSet() const {
    NOT_DESTROYED();
    return To<HTMLFrameSetElement>(GetNode());
  }
  
  void LayoutFrameSet::Paint(const PaintInfo& paint_info) const {
    NOT_DESTROYED();
    FrameSetPainter(*this).Paint(paint_info);
  }
  
  void LayoutFrameSet::GridAxis::Resize(int size) {
    sizes_.resize(size);
    deltas_.resize(size);
    deltas_.Fill(0);
  
    // To track edges for resizability and borders, we need to be (size + 1). This
    // is because a parent frameset may ask us for information about our left/top/
    // right/bottom edges in order to make its own decisions about what to do. We
    // are capable of tainting that parent frameset's borders, so we have to cache
    // this info.
    prevent_resize_.resize(size + 1);
    allow_border_.resize(size + 1);
  }
  
  void LayoutFrameSet::LayOutAxis(GridAxis& axis,
                                  const Vector<HTMLDimension>& grid,
                                  int available_len) {
    NOT_DESTROYED();
    available_len = max(available_len, 0);
  
    int* grid_layout = axis.sizes_.data();
  
    if (grid.IsEmpty()) {
      grid_layout[0] = available_len;
      return;
    }
  
    int grid_len = axis.sizes_.size();
    DCHECK(grid_len);
  
    int64_t total_relative = 0;
    int64_t total_fixed = 0;
    int64_t total_percent = 0;
    int count_relative = 0;
    int count_fixed = 0;
    int count_percent = 0;
  
    float effective_zoom = StyleRef().EffectiveZoom();
  
    // First we need to investigate how many columns of each type we have and
    // how much space these columns are going to require.
    for (int i = 0; i < grid_len; ++i) {
      // Count the total length of all of the fixed columns/rows -> totalFixed.
      // Count the number of columns/rows which are fixed -> countFixed.
      if (grid[i].IsAbsolute()) {
        grid_layout[i] = clampTo<int>(max(grid[i].Value() * effective_zoom, 0.0));
        total_fixed += grid_layout[i];
        count_fixed++;
      }
  
      // Count the total percentage of all of the percentage columns/rows ->
      // totalPercent. Count the number of columns/rows which are percentages ->
      // countPercent.
      if (grid[i].IsPercentage()) {
        grid_layout[i] =
            clampTo<int>(max(grid[i].Value() * available_len / 100., 0.0));
        total_percent += grid_layout[i];
        count_percent++;
      }
  
      // Count the total relative of all the relative columns/rows ->
      // totalRelative. Count the number of columns/rows which are relative ->
      // countRelative.
      if (grid[i].IsRelative()) {
        total_relative += clampTo<int>(max(grid[i].Value(), 1.0));
        count_relative++;
      }
    }
  
    int remaining_len = available_len;
  
    // Fixed columns/rows are our first priority. If there is not enough space to
    // fit all fixed columns/rows we need to proportionally adjust their size.
    if (total_fixed > remaining_len) {
      int remaining_fixed = remaining_len;
  
      for (int i = 0; i < grid_len; ++i) {
        if (grid[i].IsAbsolute()) {
          grid_layout[i] = AdjustSizeToRemainingSize(
              grid_layout[i], remaining_fixed, total_fixed);
          remaining_len -= grid_layout[i];
        }
      }
    } else {
      remaining_len -= total_fixed;
    }
  
    // Percentage columns/rows are our second priority. Divide the remaining space
    // proportionally over all percentage columns/rows.
    // NOTE: the size of each column/row is not relative to 100%, but to the total
    // percentage. For example, if there are three columns, each of 75%, and the
    // available space is 300px, each column will become 100px in width.
    if (total_percent > remaining_len) {
      int remaining_percent = remaining_len;
  
      for (int i = 0; i < grid_len; ++i) {
        if (grid[i].IsPercentage()) {
          grid_layout[i] = AdjustSizeToRemainingSize(
              grid_layout[i], remaining_percent, total_percent);
          remaining_len -= grid_layout[i];
        }
      }
    } else {
      remaining_len -= total_percent;
    }
  
    // Relative columns/rows are our last priority. Divide the remaining space
    // proportionally over all relative columns/rows.
    // NOTE: the relative value of 0* is treated as 1*.
    if (count_relative) {
      int last_relative = 0;
      int remaining_relative = remaining_len;
  
      for (int i = 0; i < grid_len; ++i) {
        if (grid[i].IsRelative()) {
          grid_layout[i] =
              (max(grid[i].Value(), 1.) * remaining_relative) / total_relative;
          remaining_len -= grid_layout[i];
          last_relative = i;
        }
      }
  
      // If we could not evenly distribute the available space of all of the
      // relative columns/rows, the remainder will be added to the last column/
      // row. For example: if we have a space of 100px and three columns (*,*,*),
      // the remainder will be 1px and will be added to the last column: 33px,
      // 33px, 34px.
      if (remaining_len) {
        grid_layout[last_relative] += remaining_len;
        remaining_len = 0;
      }
    }
  
    // If we still have some left over space we need to divide it over the already
    // existing columns/rows
    if (remaining_len) {
      // Our first priority is to spread if over the percentage columns. The
      // remaining space is spread evenly, for example: if we have a space of
      // 100px, the columns definition of 25%,25% used to result in two columns of
      // 25px. After this the columns will each be 50px in width.
      if (count_percent && total_percent) {
        int remaining_percent = remaining_len;
        int change_percent = 0;
  
        for (int i = 0; i < grid_len; ++i) {
          if (grid[i].IsPercentage()) {
            change_percent = AdjustSizeToRemainingSize(
                grid_layout[i], remaining_percent, total_percent);
            grid_layout[i] += change_percent;
            remaining_len -= change_percent;
          }
        }
      } else if (total_fixed) {
        // Our last priority is to spread the remaining space over the fixed
        // columns. For example if we have 100px of space and two column of each
        // 40px, both columns will become exactly 50px.
        int remaining_fixed = remaining_len;
        int change_fixed = 0;
  
        for (int i = 0; i < grid_len; ++i) {
          if (grid[i].IsAbsolute()) {
            change_fixed = AdjustSizeToRemainingSize(
                grid_layout[i], remaining_fixed, total_fixed);
            grid_layout[i] += change_fixed;
            remaining_len -= change_fixed;
          }
        }
      }
    }
  
    // If we still have some left over space we probably ended up with a remainder
    // of a division. We cannot spread it evenly anymore. If we have any
    // percentage columns/rows simply spread the remainder equally over all
    // available percentage columns, regardless of their size.
    if (remaining_len && count_percent) {
      int remaining_percent = remaining_len;
      int change_percent = 0;
  
      for (int i = 0; i < grid_len; ++i) {
        if (grid[i].IsPercentage()) {
          change_percent = remaining_percent / count_percent;
          grid_layout[i] += change_percent;
          remaining_len -= change_percent;
        }
      }
    } else if (remaining_len && count_fixed) {
      // If we don't have any percentage columns/rows we only have fixed columns.
      // Spread the remainder equally over all fixed columns/rows.
      int remaining_fixed = remaining_len;
      int change_fixed = 0;
  
      for (int i = 0; i < grid_len; ++i) {
        if (grid[i].IsAbsolute()) {
          change_fixed = remaining_fixed / count_fixed;
          grid_layout[i] += change_fixed;
          remaining_len -= change_fixed;
        }
      }
    }
  
    // Still some left over. Add it to the last column, because it is impossible
    // spread it evenly or equally.
    if (remaining_len)
      grid_layout[grid_len - 1] += remaining_len;
  
    // now we have the final layout, distribute the delta over it
    bool worked = true;
    int* grid_delta = axis.deltas_.data();
    for (int i = 0; i < grid_len; ++i) {
      if (grid_layout[i] && grid_layout[i] + grid_delta[i] <= 0)
        worked = false;
      grid_layout[i] += grid_delta[i];
    }
    // if the deltas broke something, undo them
    if (!worked) {
      for (int i = 0; i < grid_len; ++i)
        grid_layout[i] -= grid_delta[i];
      axis.deltas_.Fill(0);
    }
  }
  
  void LayoutFrameSet::NotifyFrameEdgeInfoChanged() {
    NOT_DESTROYED();
    if (NeedsLayout())
      return;
    // FIXME: We should only recompute the edge info with respect to the frame
    // that changed and its adjacent frame(s) instead of recomputing the edge info
    // for the entire frameset.
    ComputeEdgeInfo();
  }
  
  void LayoutFrameSet::FillFromEdgeInfo(const FrameEdgeInfo& edge_info,
                                        int r,
                                        int c) {
    NOT_DESTROYED();
    if (edge_info.AllowBorder(kLeftFrameEdge))
      cols_.allow_border_[c] = true;
    if (edge_info.AllowBorder(kRightFrameEdge))
      cols_.allow_border_[c + 1] = true;
    if (edge_info.PreventResize(kLeftFrameEdge))
      cols_.prevent_resize_[c] = true;
    if (edge_info.PreventResize(kRightFrameEdge))
      cols_.prevent_resize_[c + 1] = true;
  
    if (edge_info.AllowBorder(kTopFrameEdge))
      rows_.allow_border_[r] = true;
    if (edge_info.AllowBorder(kBottomFrameEdge))
      rows_.allow_border_[r + 1] = true;
    if (edge_info.PreventResize(kTopFrameEdge))
      rows_.prevent_resize_[r] = true;
    if (edge_info.PreventResize(kBottomFrameEdge))
      rows_.prevent_resize_[r + 1] = true;
  }
  
  void LayoutFrameSet::ComputeEdgeInfo() {
    NOT_DESTROYED();
    rows_.prevent_resize_.Fill(FrameSet()->NoResize());
    rows_.allow_border_.Fill(false);
    cols_.prevent_resize_.Fill(FrameSet()->NoResize());
    cols_.allow_border_.Fill(false);
  
    LayoutObject* child = FirstChild();
    if (!child)
      return;
  
    size_t rows = rows_.sizes_.size();
    size_t cols = cols_.sizes_.size();
    for (size_t r = 0; r < rows; ++r) {
      for (size_t c = 0; c < cols; ++c) {
        FrameEdgeInfo edge_info;
        if (child->IsFrameSet())
          edge_info = To<LayoutFrameSet>(child)->EdgeInfo();
        else
          edge_info = To<LayoutFrame>(child)->EdgeInfo();
        FillFromEdgeInfo(edge_info, r, c);
        child = child->NextSibling();
        if (!child)
          return;
      }
    }
  }
  
  FrameEdgeInfo LayoutFrameSet::EdgeInfo() const {
    NOT_DESTROYED();
    FrameEdgeInfo result(FrameSet()->NoResize(), true);
  
    int rows = FrameSet()->TotalRows();
    int cols = FrameSet()->TotalCols();
    if (rows && cols) {
      result.SetPreventResize(kLeftFrameEdge, cols_.prevent_resize_[0]);
      result.SetAllowBorder(kLeftFrameEdge, cols_.allow_border_[0]);
      result.SetPreventResize(kRightFrameEdge, cols_.prevent_resize_[cols]);
      result.SetAllowBorder(kRightFrameEdge, cols_.allow_border_[cols]);
      result.SetPreventResize(kTopFrameEdge, rows_.prevent_resize_[0]);
      result.SetAllowBorder(kTopFrameEdge, rows_.allow_border_[0]);
      result.SetPreventResize(kBottomFrameEdge, rows_.prevent_resize_[rows]);
      result.SetAllowBorder(kBottomFrameEdge, rows_.allow_border_[rows]);
    }
  
    return result;
  }
  
  void LayoutFrameSet::UpdateLayout() {
    NOT_DESTROYED();
    DCHECK(NeedsLayout());
  
    if (!Parent()->IsFrameSet() && !GetDocument().Printing()) {
      SetWidth(LayoutUnit(View()->ViewWidth()));
      SetHeight(LayoutUnit(View()->ViewHeight()));
    }
  
    unsigned cols = FrameSet()->TotalCols();
    unsigned rows = FrameSet()->TotalRows();
  
    if (rows_.sizes_.size() != rows || cols_.sizes_.size() != cols) {
      rows_.Resize(rows);
      cols_.Resize(cols);
    }
  
    LayoutUnit border_thickness(FrameSet()->Border());
    LayOutAxis(rows_, FrameSet()->RowLengths(),
               (Size().Height() - (rows - 1) * border_thickness).ToInt());
    LayOutAxis(cols_, FrameSet()->ColLengths(),
               (Size().Width() - (cols - 1) * border_thickness).ToInt());
  
    PositionFrames();
  
    LayoutBox::UpdateLayout();
  
    ComputeEdgeInfo();
  
    UpdateAfterLayout();
  
    ClearNeedsLayout();
  }
  
  static void ClearNeedsLayoutOnHiddenFrames(LayoutBox* frame) {
    for (; frame; frame = frame->NextSiblingBox()) {
      frame->SetWidth(LayoutUnit());
      frame->SetHeight(LayoutUnit());
      frame->ClearNeedsLayout();
      ClearNeedsLayoutOnHiddenFrames(frame->FirstChildBox());
    }
  }
  
  void LayoutFrameSet::PositionFrames() {
    NOT_DESTROYED();
    LayoutBox* child = FirstChildBox();
    if (!child)
      return;
  
    int rows = FrameSet()->TotalRows();
    int cols = FrameSet()->TotalCols();
  
    int border_thickness = FrameSet()->Border();
    LayoutSize size;
    LayoutPoint position;
    for (int r = 0; r < rows; r++) {
      position.SetX(LayoutUnit());
      size.SetHeight(LayoutUnit(rows_.sizes_[r]));
      for (int c = 0; c < cols; c++) {
        child->SetLocation(position);
        size.SetWidth(LayoutUnit(cols_.sizes_[c]));
  
        // If we have a new size, we need to resize and layout the child. If the
        // size is 0x0 we also need to lay out, since this may mean that we're
        // dealing with a child frameset that wasn't previously initialized
        // properly, because it was previously hidden, but no longer is, because
        // rows * cols may have increased.
        if (size != child->Size() || size.IsEmpty()) {
          child->SetSize(size);
          child->SetNeedsLayoutAndFullPaintInvalidation(
              layout_invalidation_reason::kSizeChanged);
          child->UpdateLayout();
        }
  
        position.SetX(position.X() + size.Width() + border_thickness);
  
        child = child->NextSiblingBox();
        if (!child)
          return;
      }
      position.SetY(position.Y() + size.Height() + border_thickness);
    }
  
    // All the remaining frames are hidden to avoid ugly spurious unflowed frames.
    ClearNeedsLayoutOnHiddenFrames(child);
  }
  
  void LayoutFrameSet::StartResizing(GridAxis& axis, int position) {
    NOT_DESTROYED();
    int split = HitTestSplit(axis, position);
    if (split == kNoSplit || axis.prevent_resize_[split]) {
      axis.split_being_resized_ = kNoSplit;
      return;
    }
    axis.split_being_resized_ = split;
    axis.split_resize_offset_ = position - SplitPosition(axis, split);
  }
  
  void LayoutFrameSet::ContinueResizing(GridAxis& axis, int position) {
    NOT_DESTROYED();
    if (NeedsLayout())
      return;
    if (axis.split_being_resized_ == kNoSplit)
      return;
    int current_split_position = SplitPosition(axis, axis.split_being_resized_);
    int delta = (position - current_split_position) - axis.split_resize_offset_;
    if (!delta)
      return;
    axis.deltas_[axis.split_being_resized_ - 1] += delta;
    axis.deltas_[axis.split_being_resized_] -= delta;
    SetNeedsLayoutAndFullPaintInvalidation(
        layout_invalidation_reason::kSizeChanged);
  }
  
  bool LayoutFrameSet::UserResize(const MouseEvent& evt) {
    NOT_DESTROYED();
    if (!is_resizing_) {
      if (NeedsLayout())
        return false;
      if (evt.type() == event_type_names::kMousedown &&
          evt.button() ==
              static_cast<int16_t>(WebPointerProperties::Button::kLeft)) {
        FloatPoint local_pos =
            AbsoluteToLocalFloatPoint(FloatPoint(evt.AbsoluteLocation()));
        StartResizing(cols_, local_pos.X());
        StartResizing(rows_, local_pos.Y());
        if (cols_.split_being_resized_ != kNoSplit ||
            rows_.split_being_resized_ != kNoSplit) {
          SetIsResizing(true);
          return true;
        }
      }
    } else {
      if (evt.type() == event_type_names::kMousemove ||
          (evt.type() == event_type_names::kMouseup &&
           evt.button() ==
               static_cast<int16_t>(WebPointerProperties::Button::kLeft))) {
        FloatPoint local_pos =
            AbsoluteToLocalFloatPoint(FloatPoint(evt.AbsoluteLocation()));
        ContinueResizing(cols_, local_pos.X());
        ContinueResizing(rows_, local_pos.Y());
        if (evt.type() == event_type_names::kMouseup &&
            evt.button() ==
                static_cast<int16_t>(WebPointerProperties::Button::kLeft)) {
          SetIsResizing(false);
          return true;
        }
      }
    }
  
    return false;
  }
  
  void LayoutFrameSet::SetIsResizing(bool is_resizing) {
    NOT_DESTROYED();
    is_resizing_ = is_resizing;
    if (LocalFrame* frame = GetFrame()) {
      frame->GetEventHandler().SetResizingFrameSet(is_resizing ? FrameSet()
                                                               : nullptr);
    }
  }
  
  bool LayoutFrameSet::CanResizeRow(const IntPoint& p) const {
    NOT_DESTROYED();
    int r = HitTestSplit(rows_, p.Y());
    return r != kNoSplit && !rows_.prevent_resize_[r];
  }
  
  bool LayoutFrameSet::CanResizeColumn(const IntPoint& p) const {
    NOT_DESTROYED();
    int c = HitTestSplit(cols_, p.X());
    return c != kNoSplit && !cols_.prevent_resize_[c];
  }
  
  int LayoutFrameSet::SplitPosition(const GridAxis& axis, int split) const {
    NOT_DESTROYED();
    if (NeedsLayout())
      return 0;
  
    int border_thickness = FrameSet()->Border();
  
    int size = axis.sizes_.size();
    if (!size)
      return 0;
  
    int position = 0;
    for (int i = 0; i < split && i < size; ++i)
      position += axis.sizes_[i] + border_thickness;
    return position - border_thickness;
  }
  
  int LayoutFrameSet::HitTestSplit(const GridAxis& axis, int position) const {
    NOT_DESTROYED();
    if (NeedsLayout())
      return kNoSplit;
  
    int border_thickness = FrameSet()->Border();
    if (border_thickness <= 0)
      return kNoSplit;
  
    size_t size = axis.sizes_.size();
    if (!size)
      return kNoSplit;
  
    int split_position = axis.sizes_[0];
    for (size_t i = 1; i < size; ++i) {
      if (position >= split_position &&
          position < split_position + border_thickness)
        return i;
      split_position += border_thickness + axis.sizes_[i];
    }
    return kNoSplit;
  }
  
  bool LayoutFrameSet::IsChildAllowed(LayoutObject* child,
                                      const ComputedStyle&) const {
    NOT_DESTROYED();
    return child->IsFrame() || child->IsFrameSet();
  }
  
  CursorDirective LayoutFrameSet::GetCursor(const PhysicalOffset& point,
                                            ui::Cursor& cursor) const {
    NOT_DESTROYED();
    IntPoint rounded_point = RoundedIntPoint(point);
    if (CanResizeRow(rounded_point)) {
      cursor = RowResizeCursor();
      return kSetCursor;
    }
    if (CanResizeColumn(rounded_point)) {
      cursor = ColumnResizeCursor();
      return kSetCursor;
    }
    return LayoutBox::GetCursor(point, cursor);
  }
  
  }  // namespace blink
  