gecko-dev/layout/generic/nsFrameSetFrame.cpp
Timothy Nikkel e7de0ed0e8 Bug 1933521. Fix hit testing of backface-visibility: hidden frames that are not transformed. r=layout-reviewers,emilio
preserve3d means that transforms on child frames are in a shared 3d transform space with their parent; rather than transforming the child in it's own 3d space and then rendering the 2d depiction of that and then the parent applies it's own transform to the flat representation of the child frame. And this can be extended down the DOM tree: each direct descendant with preserve3d then participates in the same shared 3d space. The tree of all frames that are all in one shared preserve3d space form a preserve3d context.

Setting preserve3d on a frame makes the frame considered to be transformed (whether it has an actual transform or not).

combines3d means that the parent frame is preserve3d AND this frame is either transformed or is backface-visibility: hidden.

A leaf in the preserve3d context is a frame that is combines3D but is not preserve3d itself. So it is the last frame in the preserve3d context. In other words, it's a frame that is not preserve3d but its parent is preserve3d, AND it's either transformed or backface-visibility: hidden.

To make this work in the display list, items get wrapped in transform items. Specifically, after we've built the display list for a preserve3d frame (preserve3d makes the frame transformed, so it's a stacking context) we determine if every item is a participant in that preserve3d context or a non-participant. A participant is a transform or perspective item for a frame that is in the same preserve3d context. A non-participant is anything else. Non-participants get wrapped in a new transform item whose frame is the current preserve3d frame and the transform item gets tagged as a transform seperator.

We defined preserve3d leaf frame above (the last frame that is in the preserve3d context). We define a preserve3d leaf transform display item as one where the frame is a preserve3d leaf, or the item is tagged as a transform seperator (note the small difference). This makes sense since the display item contained in the transform seperator item is not part of the preserve3d context.

Thus for every preserve3d context there is a set of leaf transform display items that contain all of the content to draw. Every other item in the context that is above these leaf transform items is a transform item of a frame in the preserve3d context that is not a leaf transform item. And below these leaf transform items is the display items for the content to draw. We don't need to worry about these non-leaf transform items because whenever we get the transform for a leaf transform item we accumulate the whole transform from the root of the preserve3d context down to the leaf item.

Hit testing is done by first finding this set of leaf transform items and then iterating over them in the usual way.

However there is a subtle issue with this: backface-visibility: hidden frames are combines3d (and thus part of the preserve3d context) but are not transformed, so they don't generate transform items. So when doing the wrapping process from above they are considered to be non-participants and they get wrapped with a transform seperator item, and that transform seperator item is what is considered the leaf. This setup works fine for most things but it does not work fine when hittesting the backface-visibility: hidden leaf. Because the backface-visibility: hidden check happens in the transform item hittest function, and there is no transform item for the backface-visibility: hidden frame. The transform item knows that the transform is showing the backface, but it's frame is not backface-visibility: hidden. And the item for the backface-visibility: hidden frame is not a transform item, so it doesn't know the current transform.

To fix this we put a little state in the hit testing state that tracks if the transform shows the backface. We reset this bit whenever we actually exit the preserve3d context (by checking if the item is no longer combines3d). And then when hittesting the item, the item can combine that state with its local backface-visibility to determine if it is hit or not.

Differential Revision: https://phabricator.services.mozilla.com/D252978
2025-06-12 10:21:07 +00:00

1529 lines
54 KiB
C++

/* -*- 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/. */
/* rendering object for HTML <frameset> elements */
#include "nsFrameSetFrame.h"
#include "gfxContext.h"
#include "gfxUtils.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/Likely.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "nsGenericHTMLElement.h"
#include "nsAttrValueInlines.h"
#include "nsLeafFrame.h"
#include "nsContainerFrame.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsIContentInlines.h"
#include "nsGkAtoms.h"
#include "nsStyleConsts.h"
#include "nsHTMLParts.h"
#include "nsNameSpaceManager.h"
#include "nsCSSAnonBoxes.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/ServoStyleSetInlines.h"
#include "mozilla/dom/Element.h"
#include "nsDisplayList.h"
#include "mozAutoDocUpdate.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/ChildIterator.h"
#include "mozilla/dom/HTMLFrameSetElement.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/MouseEvents.h"
#include "nsSubDocumentFrame.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
// masks for mEdgeVisibility
#define LEFT_VIS 0x0001
#define RIGHT_VIS 0x0002
#define TOP_VIS 0x0004
#define BOTTOM_VIS 0x0008
#define ALL_VIS 0x000F
#define NONE_VIS 0x0000
/*******************************************************************************
* nsFramesetDrag
******************************************************************************/
nsFramesetDrag::nsFramesetDrag() { UnSet(); }
void nsFramesetDrag::Reset(bool aVertical, int32_t aIndex, int32_t aChange,
nsHTMLFramesetFrame* aSource) {
mVertical = aVertical;
mIndex = aIndex;
mChange = aChange;
mSource = aSource;
}
void nsFramesetDrag::UnSet() {
mVertical = true;
mIndex = -1;
mChange = 0;
mSource = nullptr;
}
/*******************************************************************************
* nsHTMLFramesetBorderFrame
******************************************************************************/
class nsHTMLFramesetBorderFrame final : public nsLeafFrame {
public:
NS_DECL_FRAMEARENA_HELPERS(nsHTMLFramesetBorderFrame)
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const override;
#endif
virtual nsresult HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) override;
Cursor GetCursor(const nsPoint&) override;
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) override;
virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
bool GetVisibility() { return mVisibility; }
void SetVisibility(bool aVisibility);
void SetColor(nscolor aColor);
void PaintBorder(DrawTarget* aDrawTarget, nsPoint aPt);
protected:
nsHTMLFramesetBorderFrame(ComputedStyle*, nsPresContext*, int32_t aWidth,
bool aVertical, bool aVisible);
virtual ~nsHTMLFramesetBorderFrame();
// the prev and next neighbors are indexes into the row (for a horizontal
// border) or col (for a vertical border) of nsHTMLFramesetFrames or
// nsHTMLFrames
int32_t mPrevNeighbor;
int32_t mNextNeighbor;
nscolor mColor;
int32_t mWidth;
bool mVertical;
bool mVisibility;
bool mCanResize;
friend class nsHTMLFramesetFrame;
};
/*******************************************************************************
* nsHTMLFramesetBlankFrame
******************************************************************************/
class nsHTMLFramesetBlankFrame final : public nsLeafFrame {
public:
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS(nsHTMLFramesetBlankFrame)
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const override {
return MakeFrameName(u"FramesetBlank"_ns, aResult);
}
#endif
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) override;
virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
protected:
explicit nsHTMLFramesetBlankFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext)
: nsLeafFrame(aStyle, aPresContext, kClassID) {}
virtual ~nsHTMLFramesetBlankFrame();
friend class nsHTMLFramesetFrame;
friend class nsHTMLFrameset;
};
/*******************************************************************************
* nsHTMLFramesetFrame
******************************************************************************/
bool nsHTMLFramesetFrame::gDragInProgress = false;
#define DEFAULT_BORDER_WIDTH_PX 6
nsHTMLFramesetFrame::nsHTMLFramesetFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext)
: nsContainerFrame(aStyle, aPresContext, kClassID) {
mNumRows = 0;
mNumCols = 0;
mEdgeVisibility = 0;
mParentFrameborder = eFrameborder_Yes; // default
mParentBorderWidth = -1; // default not set
mParentBorderColor = NO_COLOR; // default not set
mFirstDragPoint.x = mFirstDragPoint.y = 0;
mMinDrag = nsPresContext::CSSPixelsToAppUnits(2);
mNonBorderChildCount = 0;
mNonBlankChildCount = 0;
mDragger = nullptr;
mChildCount = 0;
mTopLevelFrameset = nullptr;
mEdgeColors.Set(NO_COLOR);
}
nsHTMLFramesetFrame::~nsHTMLFramesetFrame() = default;
NS_QUERYFRAME_HEAD(nsHTMLFramesetFrame)
NS_QUERYFRAME_ENTRY(nsHTMLFramesetFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
void nsHTMLFramesetFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
nsIFrame* aPrevInFlow) {
nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
// find the highest ancestor that is a frameset
nsIFrame* parentFrame = GetParent();
mTopLevelFrameset = this;
while (parentFrame) {
nsHTMLFramesetFrame* frameset = do_QueryFrame(parentFrame);
if (frameset) {
mTopLevelFrameset = frameset;
parentFrame = parentFrame->GetParent();
} else {
break;
}
}
nsPresContext* presContext = PresContext();
mozilla::PresShell* presShell = presContext->PresShell();
nsFrameborder frameborder = GetFrameBorder();
int32_t borderWidth = GetBorderWidth(presContext, false);
nscolor borderColor = GetBorderColor();
// Get the rows= cols= data
HTMLFrameSetElement* ourContent = HTMLFrameSetElement::FromNode(mContent);
NS_ASSERTION(ourContent, "Someone gave us a broken frameset element!");
const nsFramesetSpec* rowSpecs = nullptr;
const nsFramesetSpec* colSpecs = nullptr;
// GetRowSpec and GetColSpec can fail, but when they do they set
// mNumRows and mNumCols respectively to 0, so we deal with it fine.
ourContent->GetRowSpec(&mNumRows, &rowSpecs);
ourContent->GetColSpec(&mNumCols, &colSpecs);
static_assert(
NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nscoord),
"Maximum value of mNumRows and mNumCols is NS_MAX_FRAMESET_SPEC_COUNT");
mRowSizes = MakeUnique<nscoord[]>(mNumRows);
mColSizes = MakeUnique<nscoord[]>(mNumCols);
static_assert(
NS_MAX_FRAMESET_SPEC_COUNT < INT32_MAX / NS_MAX_FRAMESET_SPEC_COUNT,
"Should not overflow numCells");
int32_t numCells = mNumRows * mNumCols;
static_assert(NS_MAX_FRAMESET_SPEC_COUNT <
UINT_MAX / sizeof(nsHTMLFramesetBorderFrame*),
"Should not overflow nsHTMLFramesetBorderFrame");
mVerBorders = MakeUnique<nsHTMLFramesetBorderFrame*[]>(
mNumCols); // 1 more than number of ver borders
for (int verX = 0; verX < mNumCols; verX++) {
mVerBorders[verX] = nullptr;
}
mHorBorders = MakeUnique<nsHTMLFramesetBorderFrame*[]>(
mNumRows); // 1 more than number of hor borders
for (int horX = 0; horX < mNumRows; horX++) {
mHorBorders[horX] = nullptr;
}
static_assert(NS_MAX_FRAMESET_SPEC_COUNT <
UINT_MAX / sizeof(int32_t) / NS_MAX_FRAMESET_SPEC_COUNT,
"Should not overflow numCells");
static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nsFrameborder) /
NS_MAX_FRAMESET_SPEC_COUNT,
"Should not overflow numCells");
static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nsBorderColor) /
NS_MAX_FRAMESET_SPEC_COUNT,
"Should not overflow numCells");
mChildFrameborder = MakeUnique<nsFrameborder[]>(numCells);
mChildBorderColors = MakeUnique<nsBorderColor[]>(numCells);
// create the children frames; skip content which isn't <frameset> or <frame>
mChildCount = 0; // number of <frame> or <frameset> children
FlattenedChildIterator children(mContent);
for (nsIContent* child = children.GetNextChild(); child;
child = children.GetNextChild()) {
if (mChildCount == numCells) {
// we have more <frame> or <frameset> than cells
// Clear the lazy bits in the remaining children. Also clear
// the restyle flags, like nsCSSFrameConstructor::ProcessChildren does.
for (; child; child = child->GetNextSibling()) {
child->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
}
break;
}
child->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
// IMPORTANT: This must match the conditions in
// nsCSSFrameConstructor::ContentAppended/Inserted/Removed
if (!child->IsAnyOfHTMLElements(nsGkAtoms::frameset, nsGkAtoms::frame)) {
continue;
}
// FIXME(emilio): This doesn't even respect display: none, but that matches
// other browsers ;_;
//
// Maybe we should change that though.
RefPtr<ComputedStyle> kidStyle =
ServoStyleSet::ResolveServoStyle(*child->AsElement());
nsIFrame* frame;
if (child->IsHTMLElement(nsGkAtoms::frameset)) {
frame = NS_NewHTMLFramesetFrame(presShell, kidStyle);
nsHTMLFramesetFrame* childFrame = (nsHTMLFramesetFrame*)frame;
childFrame->SetParentFrameborder(frameborder);
childFrame->SetParentBorderWidth(borderWidth);
childFrame->SetParentBorderColor(borderColor);
frame->Init(child, this, nullptr);
mChildBorderColors[mChildCount].Set(childFrame->GetBorderColor());
} else { // frame
frame = NS_NewSubDocumentFrame(presShell, kidStyle);
frame->Init(child, this, nullptr);
mChildFrameborder[mChildCount] = GetFrameBorder(child);
mChildBorderColors[mChildCount].Set(GetBorderColor(child));
}
child->SetPrimaryFrame(frame);
mFrames.AppendFrame(nullptr, frame);
mChildCount++;
}
mNonBlankChildCount = mChildCount;
// add blank frames for frameset cells that had no content provided
for (int blankX = mChildCount; blankX < numCells; blankX++) {
RefPtr<ComputedStyle> pseudoComputedStyle =
presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::framesetBlank);
// XXX the blank frame is using the content of its parent - at some point it
// should just have null content, if we support that
nsHTMLFramesetBlankFrame* blankFrame = new (presShell)
nsHTMLFramesetBlankFrame(pseudoComputedStyle, PresContext());
blankFrame->Init(mContent, this, nullptr);
mFrames.AppendFrame(nullptr, blankFrame);
mChildBorderColors[mChildCount].Set(NO_COLOR);
mChildCount++;
}
mNonBorderChildCount = mChildCount;
}
void nsHTMLFramesetFrame::SetInitialChildList(ChildListID aListID,
nsFrameList&& aChildList) {
// We do this weirdness where we create our child frames in Init(). On the
// other hand, we're going to get a SetInitialChildList() with an empty list
// and null list name after the frame constructor is done creating us. So
// just ignore that call.
if (aListID == FrameChildListID::Principal && aChildList.IsEmpty()) {
return;
}
nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
}
// XXX should this try to allocate twips based on an even pixel boundary?
void nsHTMLFramesetFrame::Scale(nscoord aDesired, int32_t aNumIndicies,
int32_t* aIndicies, int32_t aNumItems,
int32_t* aItems) {
int32_t actual = 0;
int32_t i, j;
// get the actual total
for (i = 0; i < aNumIndicies; i++) {
j = aIndicies[i];
actual += aItems[j];
}
if (actual > 0) {
float factor = (float)aDesired / (float)actual;
actual = 0;
// scale the items up or down
for (i = 0; i < aNumIndicies; i++) {
j = aIndicies[i];
aItems[j] = NSToCoordRound((float)aItems[j] * factor);
actual += aItems[j];
}
} else if (aNumIndicies != 0) {
// All the specs say zero width, but we have to fill up space
// somehow. Distribute it equally.
nscoord width = NSToCoordRound((float)aDesired / (float)aNumIndicies);
actual = width * aNumIndicies;
for (i = 0; i < aNumIndicies; i++) {
aItems[aIndicies[i]] = width;
}
}
if (aNumIndicies > 0 && aDesired != actual) {
int32_t unit = (aDesired > actual) ? 1 : -1;
for (i = 0; (i < aNumIndicies) && (aDesired != actual); i++) {
j = aIndicies[i];
if (j < aNumItems) {
aItems[j] += unit;
actual += unit;
}
}
}
}
/**
* Translate the rows/cols specs into an array of integer sizes for
* each cell in the frameset. Sizes are allocated based on the priorities of the
* specifier - fixed sizes have the highest priority, percentage sizes have the
* next highest priority and relative sizes have the lowest.
*/
void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext,
nscoord aSize, int32_t aNumSpecs,
const nsFramesetSpec* aSpecs,
nscoord* aValues) {
static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(int32_t),
"aNumSpecs maximum value is NS_MAX_FRAMESET_SPEC_COUNT");
int32_t fixedTotal = 0;
int32_t numFixed = 0;
auto fixed = MakeUnique<int32_t[]>(aNumSpecs);
int32_t numPercent = 0;
auto percent = MakeUnique<int32_t[]>(aNumSpecs);
int32_t relativeSums = 0;
int32_t numRelative = 0;
auto relative = MakeUnique<int32_t[]>(aNumSpecs);
if (MOZ_UNLIKELY(!fixed || !percent || !relative)) {
return; // NS_ERROR_OUT_OF_MEMORY
}
int32_t i, j;
// initialize the fixed, percent, relative indices, allocate the fixed sizes
// and zero the others
for (i = 0; i < aNumSpecs; i++) {
aValues[i] = 0;
switch (aSpecs[i].mUnit) {
case eFramesetUnit_Fixed:
aValues[i] = nsPresContext::CSSPixelsToAppUnits(aSpecs[i].mValue);
fixedTotal += aValues[i];
fixed[numFixed] = i;
numFixed++;
break;
case eFramesetUnit_Percent:
percent[numPercent] = i;
numPercent++;
break;
case eFramesetUnit_Relative:
relative[numRelative] = i;
numRelative++;
relativeSums += aSpecs[i].mValue;
break;
}
}
// scale the fixed sizes if they total too much (or too little and there
// aren't any percent or relative)
if ((fixedTotal > aSize) ||
((fixedTotal < aSize) && (0 == numPercent) && (0 == numRelative))) {
Scale(aSize, numFixed, fixed.get(), aNumSpecs, aValues);
return;
}
int32_t percentMax = aSize - fixedTotal;
int32_t percentTotal = 0;
// allocate the percentage sizes from what is left over from the fixed
// allocation
for (i = 0; i < numPercent; i++) {
j = percent[i];
aValues[j] =
NSToCoordRound((float)aSpecs[j].mValue * (float)aSize / 100.0f);
percentTotal += aValues[j];
}
// scale the percent sizes if they total too much (or too little and there
// aren't any relative)
if ((percentTotal > percentMax) ||
((percentTotal < percentMax) && (0 == numRelative))) {
Scale(percentMax, numPercent, percent.get(), aNumSpecs, aValues);
return;
}
int32_t relativeMax = percentMax - percentTotal;
int32_t relativeTotal = 0;
// allocate the relative sizes from what is left over from the percent
// allocation
for (i = 0; i < numRelative; i++) {
j = relative[i];
aValues[j] = NSToCoordRound((float)aSpecs[j].mValue * (float)relativeMax /
(float)relativeSums);
relativeTotal += aValues[j];
}
// scale the relative sizes if they take up too much or too little
if (relativeTotal != relativeMax) {
Scale(relativeMax, numRelative, relative.get(), aNumSpecs, aValues);
}
}
/**
* Translate the rows/cols integer sizes into an array of specs for
* each cell in the frameset. Reverse of CalculateRowCol() behaviour.
* This allows us to maintain the user size info through reflows.
*/
void nsHTMLFramesetFrame::GenerateRowCol(nsPresContext* aPresContext,
nscoord aSize, int32_t aNumSpecs,
const nsFramesetSpec* aSpecs,
nscoord* aValues, nsString& aNewAttr) {
int32_t i;
for (i = 0; i < aNumSpecs; i++) {
if (!aNewAttr.IsEmpty()) {
aNewAttr.Append(char16_t(','));
}
switch (aSpecs[i].mUnit) {
case eFramesetUnit_Fixed:
aNewAttr.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(aValues[i]));
break;
case eFramesetUnit_Percent: // XXX Only accurate to 1%, need 1 pixel
case eFramesetUnit_Relative:
// Add 0.5 to the percentage to make rounding work right.
aNewAttr.AppendInt(uint32_t((100.0 * aValues[i]) / aSize + 0.5));
aNewAttr.Append(char16_t('%'));
break;
}
}
}
int32_t nsHTMLFramesetFrame::GetBorderWidth(nsPresContext* aPresContext,
bool aTakeForcingIntoAccount) {
nsFrameborder frameborder = GetFrameBorder();
if (frameborder == eFrameborder_No) {
return 0;
}
nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(mContent);
if (content) {
const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::border);
if (attr) {
int32_t intVal = 0;
if (attr->Type() == nsAttrValue::eInteger) {
intVal = attr->GetIntegerValue();
if (intVal < 0) {
intVal = 0;
}
}
return nsPresContext::CSSPixelsToAppUnits(intVal);
}
}
if (mParentBorderWidth >= 0) {
return mParentBorderWidth;
}
return nsPresContext::CSSPixelsToAppUnits(DEFAULT_BORDER_WIDTH_PX);
}
void nsHTMLFramesetFrame::GetDesiredSize(nsPresContext* aPresContext,
const ReflowInput& aReflowInput,
ReflowOutput& aDesiredSize) {
WritingMode wm = aReflowInput.GetWritingMode();
LogicalSize desiredSize(wm);
nsHTMLFramesetFrame* framesetParent = do_QueryFrame(GetParent());
if (nullptr == framesetParent) {
if (aPresContext->IsPaginated()) {
// XXX This needs to be changed when framesets paginate properly
desiredSize.ISize(wm) = aReflowInput.AvailableISize();
desiredSize.BSize(wm) = aReflowInput.AvailableBSize();
} else {
LogicalSize area(wm, aPresContext->GetVisibleArea().Size());
desiredSize.ISize(wm) = area.ISize(wm);
desiredSize.BSize(wm) = area.BSize(wm);
}
} else {
LogicalSize size(wm);
framesetParent->GetSizeOfChild(this, wm, size);
desiredSize.ISize(wm) = size.ISize(wm);
desiredSize.BSize(wm) = size.BSize(wm);
}
aDesiredSize.SetSize(wm, desiredSize);
}
// only valid for non border children
void nsHTMLFramesetFrame::GetSizeOfChildAt(int32_t aIndexInParent,
WritingMode aWM, LogicalSize& aSize,
nsIntPoint& aCellIndex) {
int32_t row = aIndexInParent / mNumCols;
int32_t col = aIndexInParent -
(row * mNumCols); // remainder from dividing index by mNumCols
if ((row < mNumRows) && (col < mNumCols)) {
aSize.ISize(aWM) = mColSizes[col];
aSize.BSize(aWM) = mRowSizes[row];
aCellIndex.x = col;
aCellIndex.y = row;
} else {
aSize.SizeTo(aWM, 0, 0);
aCellIndex.x = aCellIndex.y = 0;
}
}
// only valid for non border children
void nsHTMLFramesetFrame::GetSizeOfChild(nsIFrame* aChild, WritingMode aWM,
LogicalSize& aSize) {
// Reflow only creates children frames for <frameset> and <frame> content.
// this assumption is used here
int i = 0;
for (nsIFrame* child : mFrames) {
if (aChild == child) {
nsIntPoint ignore;
GetSizeOfChildAt(i, aWM, aSize, ignore);
return;
}
i++;
}
aSize.SizeTo(aWM, 0, 0);
}
nsresult nsHTMLFramesetFrame::HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) {
NS_ENSURE_ARG_POINTER(aEventStatus);
if (mDragger) {
// the nsFramesetBorderFrame has captured NS_MOUSE_DOWN
switch (aEvent->mMessage) {
case eMouseMove:
MouseDrag(aPresContext, aEvent);
break;
case eMouseUp:
if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
EndMouseDrag(aPresContext);
}
break;
default:
break;
}
*aEventStatus = nsEventStatus_eConsumeNoDefault;
} else {
*aEventStatus = nsEventStatus_eIgnore;
}
return NS_OK;
}
nsIFrame::Cursor nsHTMLFramesetFrame::GetCursor(const nsPoint&) {
auto kind = StyleCursorKind::Default;
if (mDragger) {
kind = mDragger->mVertical ? StyleCursorKind::EwResize
: StyleCursorKind::NsResize;
}
return Cursor{kind, AllowCustomCursorImage::No};
}
void nsHTMLFramesetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) {
BuildDisplayListForInline(aBuilder, aLists);
if (mDragger && aBuilder->IsForEventDelivery()) {
aLists.Content()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
}
}
void nsHTMLFramesetFrame::ReflowPlaceChild(nsIFrame* aChild,
nsPresContext* aPresContext,
const ReflowInput& aReflowInput,
nsPoint& aOffset, nsSize& aSize,
nsIntPoint* aCellIndex) {
// reflow the child
ReflowInput reflowInput(aPresContext, aReflowInput, aChild,
LogicalSize(aChild->GetWritingMode(), aSize));
reflowInput.SetComputedWidth(std::max(
0,
aSize.width - reflowInput.ComputedPhysicalBorderPadding().LeftRight()));
reflowInput.SetComputedHeight(std::max(
0,
aSize.height - reflowInput.ComputedPhysicalBorderPadding().TopBottom()));
ReflowOutput reflowOutput(aReflowInput);
reflowOutput.Width() = aSize.width;
reflowOutput.Height() = aSize.height;
nsReflowStatus status;
ReflowChild(aChild, aPresContext, reflowOutput, reflowInput, aOffset.x,
aOffset.y, ReflowChildFlags::Default, status);
NS_ASSERTION(status.IsComplete(), "bad status");
// Place and size the child
reflowOutput.Width() = aSize.width;
reflowOutput.Height() = aSize.height;
FinishReflowChild(aChild, aPresContext, reflowOutput, &reflowInput, aOffset.x,
aOffset.y, ReflowChildFlags::Default);
}
static nsFrameborder GetFrameBorderHelper(nsGenericHTMLElement* aContent) {
if (nullptr != aContent) {
const nsAttrValue* attr = aContent->GetParsedAttr(nsGkAtoms::frameborder);
if (attr && attr->Type() == nsAttrValue::eEnum) {
switch (static_cast<FrameBorderProperty>(attr->GetEnumValue())) {
case FrameBorderProperty::Yes:
case FrameBorderProperty::One:
return eFrameborder_Yes;
case FrameBorderProperty::No:
case FrameBorderProperty::Zero:
return eFrameborder_No;
}
}
}
return eFrameborder_Notset;
}
nsFrameborder nsHTMLFramesetFrame::GetFrameBorder() {
nsFrameborder result = eFrameborder_Notset;
nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(mContent);
if (content) {
result = GetFrameBorderHelper(content);
}
if (eFrameborder_Notset == result) {
return mParentFrameborder;
}
return result;
}
nsFrameborder nsHTMLFramesetFrame::GetFrameBorder(nsIContent* aContent) {
nsFrameborder result = eFrameborder_Notset;
nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(aContent);
if (content) {
result = GetFrameBorderHelper(content);
}
if (eFrameborder_Notset == result) {
return GetFrameBorder();
}
return result;
}
nscolor nsHTMLFramesetFrame::GetBorderColor() {
nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(mContent);
if (content) {
const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::bordercolor);
if (attr) {
nscolor color;
if (attr->GetColorValue(color)) {
return color;
}
}
}
return mParentBorderColor;
}
nscolor nsHTMLFramesetFrame::GetBorderColor(nsIContent* aContent) {
nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(aContent);
if (content) {
const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::bordercolor);
if (attr) {
nscolor color;
if (attr->GetColorValue(color)) {
return color;
}
}
}
return GetBorderColor();
}
void nsHTMLFramesetFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsHTMLFramesetFrame");
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
mozilla::PresShell* presShell = aPresContext->PresShell();
ServoStyleSet* styleSet = presShell->StyleSet();
GetParent()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
// printf("FramesetFrame2::Reflow %X (%d,%d) \n", this,
// aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight());
// Always get the size so that the caller knows how big we are
GetDesiredSize(aPresContext, aReflowInput, aDesiredSize);
nscoord width = (aDesiredSize.Width() <= aReflowInput.AvailableWidth())
? aDesiredSize.Width()
: aReflowInput.AvailableWidth();
nscoord height = (aDesiredSize.Height() <= aReflowInput.AvailableHeight())
? aDesiredSize.Height()
: aReflowInput.AvailableHeight();
// We might be reflowed more than once with NS_FRAME_FIRST_REFLOW;
// that's allowed. (Though it will only happen for misuse of frameset
// that includes it within other content.) So measure firstTime by
// what we care about, which is whether we've processed the data we
// process below if firstTime is true.
MOZ_ASSERT(!mChildFrameborder == !mChildBorderColors);
bool firstTime = !!mChildFrameborder;
// subtract out the width of all of the potential borders. There are
// only borders between <frame>s. There are none on the edges (e.g the
// leftmost <frame> has no left border).
int32_t borderWidth = GetBorderWidth(aPresContext, true);
width -= (mNumCols - 1) * borderWidth;
if (width < 0) {
width = 0;
}
height -= (mNumRows - 1) * borderWidth;
if (height < 0) {
height = 0;
}
HTMLFrameSetElement* ourContent = HTMLFrameSetElement::FromNode(mContent);
NS_ASSERTION(ourContent, "Someone gave us a broken frameset element!");
const nsFramesetSpec* rowSpecs = nullptr;
const nsFramesetSpec* colSpecs = nullptr;
int32_t rows = 0;
int32_t cols = 0;
ourContent->GetRowSpec(&rows, &rowSpecs);
ourContent->GetColSpec(&cols, &colSpecs);
// If the number of cols or rows has changed, the frame for the frameset
// will be re-created.
if (mNumRows != rows || mNumCols != cols) {
mDrag.UnSet();
return;
}
CalculateRowCol(aPresContext, width, mNumCols, colSpecs, mColSizes.get());
CalculateRowCol(aPresContext, height, mNumRows, rowSpecs, mRowSizes.get());
UniquePtr<bool[]> verBordersVis; // vertical borders visibility
UniquePtr<nscolor[]> verBorderColors;
UniquePtr<bool[]> horBordersVis; // horizontal borders visibility
UniquePtr<nscolor[]> horBorderColors;
nscolor borderColor = GetBorderColor();
nsFrameborder frameborder = GetFrameBorder();
if (firstTime) {
// Check for overflow in memory allocations using mNumCols and mNumRows
// which have a maxium value of NS_MAX_FRAMESET_SPEC_COUNT.
static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(bool),
"Check for overflow");
static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nscolor),
"Check for overflow");
verBordersVis = MakeUnique<bool[]>(mNumCols);
verBorderColors = MakeUnique<nscolor[]>(mNumCols);
for (int verX = 0; verX < mNumCols; verX++) {
verBordersVis[verX] = false;
verBorderColors[verX] = NO_COLOR;
}
horBordersVis = MakeUnique<bool[]>(mNumRows);
horBorderColors = MakeUnique<nscolor[]>(mNumRows);
for (int horX = 0; horX < mNumRows; horX++) {
horBordersVis[horX] = false;
horBorderColors[horX] = NO_COLOR;
}
}
// reflow the children
int32_t lastRow = 0;
int32_t lastCol = 0;
int32_t borderChildX = mNonBorderChildCount; // index of border children
nsHTMLFramesetBorderFrame* borderFrame = nullptr;
nsPoint offset(0, 0);
nsSize size, lastSize;
WritingMode wm = GetWritingMode();
LogicalSize logicalSize(wm);
nsIFrame* child = mFrames.FirstChild();
for (int32_t childX = 0; childX < mNonBorderChildCount; childX++) {
nsIntPoint cellIndex;
GetSizeOfChildAt(childX, wm, logicalSize, cellIndex);
size = logicalSize.GetPhysicalSize(wm);
if (lastRow != cellIndex.y) { // changed to next row
offset.x = 0;
offset.y += lastSize.height;
if (firstTime) { // create horizontal border
RefPtr<ComputedStyle> pseudoComputedStyle;
pseudoComputedStyle = styleSet->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::horizontalFramesetBorder);
borderFrame = new (presShell) nsHTMLFramesetBorderFrame(
pseudoComputedStyle, PresContext(), borderWidth, false, false);
borderFrame->Init(mContent, this, nullptr);
mChildCount++;
mFrames.AppendFrame(nullptr, borderFrame);
mHorBorders[cellIndex.y - 1] = borderFrame;
// set the neighbors for determining drag boundaries
borderFrame->mPrevNeighbor = lastRow;
borderFrame->mNextNeighbor = cellIndex.y;
} else {
borderFrame = (nsHTMLFramesetBorderFrame*)mFrames.FrameAt(borderChildX);
borderFrame->mWidth = borderWidth;
borderChildX++;
}
nsSize borderSize(aDesiredSize.Width(), borderWidth);
ReflowPlaceChild(borderFrame, aPresContext, aReflowInput, offset,
borderSize);
borderFrame = nullptr;
offset.y += borderWidth;
} else {
if (cellIndex.x > 0) { // moved to next col in same row
if (0 == cellIndex.y) { // in 1st row
if (firstTime) { // create vertical border
RefPtr<ComputedStyle> pseudoComputedStyle;
pseudoComputedStyle =
styleSet->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::verticalFramesetBorder);
borderFrame = new (presShell) nsHTMLFramesetBorderFrame(
pseudoComputedStyle, PresContext(), borderWidth, true, false);
borderFrame->Init(mContent, this, nullptr);
mChildCount++;
mFrames.AppendFrame(nullptr, borderFrame);
mVerBorders[cellIndex.x - 1] = borderFrame;
// set the neighbors for determining drag boundaries
borderFrame->mPrevNeighbor = lastCol;
borderFrame->mNextNeighbor = cellIndex.x;
} else {
borderFrame =
(nsHTMLFramesetBorderFrame*)mFrames.FrameAt(borderChildX);
borderFrame->mWidth = borderWidth;
borderChildX++;
}
nsSize borderSize(borderWidth, aDesiredSize.Height());
ReflowPlaceChild(borderFrame, aPresContext, aReflowInput, offset,
borderSize);
borderFrame = nullptr;
}
offset.x += borderWidth;
}
}
ReflowPlaceChild(child, aPresContext, aReflowInput, offset, size,
&cellIndex);
if (firstTime) {
int32_t childVis;
nsHTMLFramesetFrame* framesetFrame = do_QueryFrame(child);
if (framesetFrame) {
childVis = framesetFrame->mEdgeVisibility;
mChildBorderColors[childX] = framesetFrame->mEdgeColors;
} else if (child->IsSubDocumentFrame()) {
if (eFrameborder_Yes == mChildFrameborder[childX]) {
childVis = ALL_VIS;
} else if (eFrameborder_No == mChildFrameborder[childX]) {
childVis = NONE_VIS;
} else { // notset
childVis = (eFrameborder_No == frameborder) ? NONE_VIS : ALL_VIS;
}
} else { // blank
#ifdef DEBUG
nsHTMLFramesetBlankFrame* blank = do_QueryFrame(child);
MOZ_ASSERT(blank, "unexpected child frame type");
#endif
childVis = NONE_VIS;
}
nsBorderColor childColors = mChildBorderColors[childX];
// set the visibility, color of our edge borders based on children
if (0 == cellIndex.x) {
if (!(mEdgeVisibility & LEFT_VIS)) {
mEdgeVisibility |= (LEFT_VIS & childVis);
}
if (NO_COLOR == mEdgeColors.mLeft) {
mEdgeColors.mLeft = childColors.mLeft;
}
}
if (0 == cellIndex.y) {
if (!(mEdgeVisibility & TOP_VIS)) {
mEdgeVisibility |= (TOP_VIS & childVis);
}
if (NO_COLOR == mEdgeColors.mTop) {
mEdgeColors.mTop = childColors.mTop;
}
}
if (mNumCols - 1 == cellIndex.x) {
if (!(mEdgeVisibility & RIGHT_VIS)) {
mEdgeVisibility |= (RIGHT_VIS & childVis);
}
if (NO_COLOR == mEdgeColors.mRight) {
mEdgeColors.mRight = childColors.mRight;
}
}
if (mNumRows - 1 == cellIndex.y) {
if (!(mEdgeVisibility & BOTTOM_VIS)) {
mEdgeVisibility |= (BOTTOM_VIS & childVis);
}
if (NO_COLOR == mEdgeColors.mBottom) {
mEdgeColors.mBottom = childColors.mBottom;
}
}
// set the visibility of borders that the child may affect
if (childVis & RIGHT_VIS) {
verBordersVis[cellIndex.x] = true;
}
if (childVis & BOTTOM_VIS) {
horBordersVis[cellIndex.y] = true;
}
if ((cellIndex.x > 0) && (childVis & LEFT_VIS)) {
verBordersVis[cellIndex.x - 1] = true;
}
if ((cellIndex.y > 0) && (childVis & TOP_VIS)) {
horBordersVis[cellIndex.y - 1] = true;
}
// set the colors of borders that the child may affect
if (NO_COLOR == verBorderColors[cellIndex.x]) {
verBorderColors[cellIndex.x] = mChildBorderColors[childX].mRight;
}
if (NO_COLOR == horBorderColors[cellIndex.y]) {
horBorderColors[cellIndex.y] = mChildBorderColors[childX].mBottom;
}
if ((cellIndex.x > 0) && (NO_COLOR == verBorderColors[cellIndex.x - 1])) {
verBorderColors[cellIndex.x - 1] = mChildBorderColors[childX].mLeft;
}
if ((cellIndex.y > 0) && (NO_COLOR == horBorderColors[cellIndex.y - 1])) {
horBorderColors[cellIndex.y - 1] = mChildBorderColors[childX].mTop;
}
}
lastRow = cellIndex.y;
lastCol = cellIndex.x;
lastSize = size;
offset.x += size.width;
child = child->GetNextSibling();
}
if (firstTime) {
nscolor childColor;
// set the visibility, color, mouse sensitivity of borders
for (int verX = 0; verX < mNumCols - 1; verX++) {
if (mVerBorders[verX]) {
mVerBorders[verX]->SetVisibility(verBordersVis[verX]);
SetBorderResize(mVerBorders[verX]);
childColor = (NO_COLOR == verBorderColors[verX])
? borderColor
: verBorderColors[verX];
mVerBorders[verX]->SetColor(childColor);
}
}
for (int horX = 0; horX < mNumRows - 1; horX++) {
if (mHorBorders[horX]) {
mHorBorders[horX]->SetVisibility(horBordersVis[horX]);
SetBorderResize(mHorBorders[horX]);
childColor = (NO_COLOR == horBorderColors[horX])
? borderColor
: horBorderColors[horX];
mHorBorders[horX]->SetColor(childColor);
}
}
mChildFrameborder.reset();
mChildBorderColors.reset();
}
mDrag.UnSet();
aDesiredSize.SetOverflowAreasToDesiredBounds();
FinishAndStoreOverflow(&aDesiredSize);
}
#ifdef DEBUG_FRAME_DUMP
nsresult nsHTMLFramesetFrame::GetFrameName(nsAString& aResult) const {
return MakeFrameName(u"Frameset"_ns, aResult);
}
#endif
bool nsHTMLFramesetFrame::CanResize(bool aVertical, bool aLeft) {
int32_t childX;
int32_t startX;
if (aVertical) {
startX = (aLeft) ? 0 : mNumCols - 1;
for (childX = startX; childX < mNonBorderChildCount; childX += mNumCols) {
if (!CanChildResize(aVertical, aLeft, childX)) {
return false;
}
}
} else {
startX = (aLeft) ? 0 : (mNumRows - 1) * mNumCols;
int32_t endX = startX + mNumCols;
for (childX = startX; childX < endX; childX++) {
if (!CanChildResize(aVertical, aLeft, childX)) {
return false;
}
}
}
return true;
}
bool nsHTMLFramesetFrame::GetNoResize(nsIFrame* aChildFrame) {
nsIContent* content = aChildFrame->GetContent();
return content && content->IsElement() &&
content->AsElement()->HasAttr(nsGkAtoms::noresize);
}
bool nsHTMLFramesetFrame::CanChildResize(bool aVertical, bool aLeft,
int32_t aChildX) {
nsIFrame* child = mFrames.FrameAt(aChildX);
nsHTMLFramesetFrame* frameset = do_QueryFrame(child);
return frameset ? frameset->CanResize(aVertical, aLeft) : !GetNoResize(child);
}
// This calculates and sets the resizability of all border frames
void nsHTMLFramesetFrame::RecalculateBorderResize() {
if (!mContent) {
return;
}
static_assert(
NS_MAX_FRAMESET_SPEC_COUNT < INT32_MAX / NS_MAX_FRAMESET_SPEC_COUNT,
"Check for overflow");
static_assert(NS_MAX_FRAMESET_SPEC_COUNT <
UINT_MAX / sizeof(int32_t) / NS_MAX_FRAMESET_SPEC_COUNT,
"Check for overflow");
// set the visibility and mouse sensitivity of borders
int32_t verX;
for (verX = 0; verX < mNumCols - 1; verX++) {
if (mVerBorders[verX]) {
mVerBorders[verX]->mCanResize = true;
SetBorderResize(mVerBorders[verX]);
}
}
int32_t horX;
for (horX = 0; horX < mNumRows - 1; horX++) {
if (mHorBorders[horX]) {
mHorBorders[horX]->mCanResize = true;
SetBorderResize(mHorBorders[horX]);
}
}
}
void nsHTMLFramesetFrame::SetBorderResize(
nsHTMLFramesetBorderFrame* aBorderFrame) {
if (aBorderFrame->mVertical) {
for (int rowX = 0; rowX < mNumRows; rowX++) {
int32_t childX = aBorderFrame->mPrevNeighbor + (rowX * mNumCols);
if (!CanChildResize(true, false, childX) ||
!CanChildResize(true, true, childX + 1)) {
aBorderFrame->mCanResize = false;
}
}
} else {
int32_t childX = aBorderFrame->mPrevNeighbor * mNumCols;
int32_t endX = childX + mNumCols;
for (; childX < endX; childX++) {
if (!CanChildResize(false, false, childX)) {
aBorderFrame->mCanResize = false;
}
}
endX = endX + mNumCols;
for (; childX < endX; childX++) {
if (!CanChildResize(false, true, childX)) {
aBorderFrame->mCanResize = false;
}
}
}
}
void nsHTMLFramesetFrame::StartMouseDrag(nsPresContext* aPresContext,
nsHTMLFramesetBorderFrame* aBorder,
WidgetGUIEvent* aEvent) {
#if 0
int32_t index;
IndexOf(aBorder, index);
NS_ASSERTION((nullptr != aBorder) && (index >= 0), "invalid dragger");
#endif
PresShell::SetCapturingContent(GetContent(),
CaptureFlags::IgnoreAllowedState);
mDragger = aBorder;
mFirstDragPoint = aEvent->mRefPoint;
// Store the original frame sizes
if (mDragger->mVertical) {
mPrevNeighborOrigSize = mColSizes[mDragger->mPrevNeighbor];
mNextNeighborOrigSize = mColSizes[mDragger->mNextNeighbor];
} else {
mPrevNeighborOrigSize = mRowSizes[mDragger->mPrevNeighbor];
mNextNeighborOrigSize = mRowSizes[mDragger->mNextNeighbor];
}
gDragInProgress = true;
}
void nsHTMLFramesetFrame::MouseDrag(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent) {
// if the capture ended, reset the drag state
if (PresShell::GetCapturingContent() != GetContent()) {
mDragger = nullptr;
gDragInProgress = false;
return;
}
int32_t change; // measured positive from left-to-right or top-to-bottom
AutoWeakFrame weakFrame(this);
if (mDragger->mVertical) {
change = aPresContext->DevPixelsToAppUnits(aEvent->mRefPoint.x -
mFirstDragPoint.x);
if (change > mNextNeighborOrigSize - mMinDrag) {
change = mNextNeighborOrigSize - mMinDrag;
} else if (change <= mMinDrag - mPrevNeighborOrigSize) {
change = mMinDrag - mPrevNeighborOrigSize;
}
mColSizes[mDragger->mPrevNeighbor] = mPrevNeighborOrigSize + change;
mColSizes[mDragger->mNextNeighbor] = mNextNeighborOrigSize - change;
if (change != 0) {
// Recompute the specs from the new sizes.
nscoord width =
mRect.width - (mNumCols - 1) * GetBorderWidth(aPresContext, true);
HTMLFrameSetElement* ourContent = HTMLFrameSetElement::FromNode(mContent);
NS_ASSERTION(ourContent, "Someone gave us a broken frameset element!");
const nsFramesetSpec* colSpecs = nullptr;
ourContent->GetColSpec(&mNumCols, &colSpecs);
nsAutoString newColAttr;
GenerateRowCol(aPresContext, width, mNumCols, colSpecs, mColSizes.get(),
newColAttr);
// Setting the attr will trigger a reflow
mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::cols,
newColAttr, true);
}
} else {
change = aPresContext->DevPixelsToAppUnits(aEvent->mRefPoint.y -
mFirstDragPoint.y);
if (change > mNextNeighborOrigSize - mMinDrag) {
change = mNextNeighborOrigSize - mMinDrag;
} else if (change <= mMinDrag - mPrevNeighborOrigSize) {
change = mMinDrag - mPrevNeighborOrigSize;
}
mRowSizes[mDragger->mPrevNeighbor] = mPrevNeighborOrigSize + change;
mRowSizes[mDragger->mNextNeighbor] = mNextNeighborOrigSize - change;
if (change != 0) {
// Recompute the specs from the new sizes.
nscoord height =
mRect.height - (mNumRows - 1) * GetBorderWidth(aPresContext, true);
HTMLFrameSetElement* ourContent = HTMLFrameSetElement::FromNode(mContent);
NS_ASSERTION(ourContent, "Someone gave us a broken frameset element!");
const nsFramesetSpec* rowSpecs = nullptr;
ourContent->GetRowSpec(&mNumRows, &rowSpecs);
nsAutoString newRowAttr;
GenerateRowCol(aPresContext, height, mNumRows, rowSpecs, mRowSizes.get(),
newRowAttr);
// Setting the attr will trigger a reflow
mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::rows,
newRowAttr, true);
}
}
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
if (change != 0) {
mDrag.Reset(mDragger->mVertical, mDragger->mPrevNeighbor, change, this);
}
}
void nsHTMLFramesetFrame::EndMouseDrag(nsPresContext* aPresContext) {
PresShell::ReleaseCapturingContent();
mDragger = nullptr;
gDragInProgress = false;
}
nsIFrame* NS_NewHTMLFramesetFrame(PresShell* aPresShell,
ComputedStyle* aStyle) {
#ifdef DEBUG
const nsStyleDisplay* disp = aStyle->StyleDisplay();
NS_ASSERTION(!disp->IsAbsolutelyPositionedStyle() && !disp->IsFloatingStyle(),
"Framesets should not be positioned and should not float");
#endif
return new (aPresShell)
nsHTMLFramesetFrame(aStyle, aPresShell->GetPresContext());
}
NS_IMPL_FRAMEARENA_HELPERS(nsHTMLFramesetFrame)
/*******************************************************************************
* nsHTMLFramesetBorderFrame
******************************************************************************/
nsHTMLFramesetBorderFrame::nsHTMLFramesetBorderFrame(
ComputedStyle* aStyle, nsPresContext* aPresContext, int32_t aWidth,
bool aVertical, bool aVisibility)
: nsLeafFrame(aStyle, aPresContext, kClassID),
mWidth(aWidth),
mVertical(aVertical),
mVisibility(aVisibility) {
mCanResize = true;
mColor = NO_COLOR;
mPrevNeighbor = 0;
mNextNeighbor = 0;
}
nsHTMLFramesetBorderFrame::~nsHTMLFramesetBorderFrame() {
// printf("nsHTMLFramesetBorderFrame destructor %p \n", this);
}
NS_IMPL_FRAMEARENA_HELPERS(nsHTMLFramesetBorderFrame)
void nsHTMLFramesetBorderFrame::SetVisibility(bool aVisibility) {
mVisibility = aVisibility;
}
void nsHTMLFramesetBorderFrame::SetColor(nscolor aColor) { mColor = aColor; }
void nsHTMLFramesetBorderFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
DO_GLOBAL_REFLOW_COUNT("nsHTMLFramesetBorderFrame");
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
// Override Reflow(), since we don't want to deal with what our
// computed values are. We just size to our available size.
aDesiredSize.SetSize(aReflowInput.GetWritingMode(),
aReflowInput.AvailableSize());
aDesiredSize.SetOverflowAreasToDesiredBounds();
FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
}
class nsDisplayFramesetBorder final : public nsPaintedDisplayItem {
public:
nsDisplayFramesetBorder(nsDisplayListBuilder* aBuilder,
nsHTMLFramesetBorderFrame* aFrame)
: nsPaintedDisplayItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayFramesetBorder);
}
MOZ_COUNTED_DTOR_FINAL(nsDisplayFramesetBorder)
// REVIEW: see old GetFrameForPoint
// Receives events in its bounds
void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override {
if (ShouldIgnoreForBackfaceHidden(aState)) {
return;
}
aOutFrames->AppendElement(mFrame);
}
virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
NS_DISPLAY_DECL_NAME("FramesetBorder", TYPE_FRAMESET_BORDER)
};
void nsDisplayFramesetBorder::Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx) {
static_cast<nsHTMLFramesetBorderFrame*>(mFrame)->PaintBorder(
aCtx->GetDrawTarget(), ToReferenceFrame());
}
void nsHTMLFramesetBorderFrame::BuildDisplayList(
nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
aLists.Content()->AppendNewToTop<nsDisplayFramesetBorder>(aBuilder, this);
}
void nsHTMLFramesetBorderFrame::PaintBorder(DrawTarget* aDrawTarget,
nsPoint aPt) {
nscoord widthInPixels = nsPresContext::AppUnitsToIntCSSPixels(mWidth);
nscoord pixelWidth = nsPresContext::CSSPixelsToAppUnits(1);
if (widthInPixels <= 0) {
return;
}
ColorPattern bgColor(ToDeviceColor(LookAndFeel::Color(
LookAndFeel::ColorID::Window, this, NS_RGB(200, 200, 200))));
ColorPattern fgColor(ToDeviceColor(LookAndFeel::Color(
LookAndFeel::ColorID::Windowtext, this, NS_RGB(0, 0, 0))));
ColorPattern hltColor(ToDeviceColor(LookAndFeel::Color(
LookAndFeel::ColorID::Threedhighlight, this, NS_RGB(255, 255, 255))));
ColorPattern sdwColor(ToDeviceColor(LookAndFeel::Color(
LookAndFeel::ColorID::Threedshadow, this, NS_RGB(128, 128, 128))));
ColorPattern color(ToDeviceColor(NS_RGB(255, 255, 255))); // default to white
if (mVisibility) {
color =
(NO_COLOR == mColor) ? bgColor : ColorPattern(ToDeviceColor(mColor));
}
int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
Point toRefFrame = NSPointToPoint(aPt, appUnitsPerDevPixel);
AutoRestoreTransform autoRestoreTransform(aDrawTarget);
aDrawTarget->SetTransform(
aDrawTarget->GetTransform().PreTranslate(toRefFrame));
nsPoint start(0, 0);
nsPoint end = mVertical ? nsPoint(0, mRect.height) : nsPoint(mRect.width, 0);
// draw grey or white first
for (int i = 0; i < widthInPixels; i++) {
StrokeLineWithSnapping(start, end, appUnitsPerDevPixel, *aDrawTarget,
color);
if (mVertical) {
start.x += pixelWidth;
end.x = start.x;
} else {
start.y += pixelWidth;
end.y = start.y;
}
}
if (!mVisibility) {
return;
}
if (widthInPixels >= 5) {
start.x = (mVertical) ? pixelWidth : 0;
start.y = (mVertical) ? 0 : pixelWidth;
end.x = (mVertical) ? start.x : mRect.width;
end.y = (mVertical) ? mRect.height : start.y;
StrokeLineWithSnapping(start, end, appUnitsPerDevPixel, *aDrawTarget,
hltColor);
}
if (widthInPixels >= 2) {
start.x = (mVertical) ? mRect.width - (2 * pixelWidth) : 0;
start.y = (mVertical) ? 0 : mRect.height - (2 * pixelWidth);
end.x = (mVertical) ? start.x : mRect.width;
end.y = (mVertical) ? mRect.height : start.y;
StrokeLineWithSnapping(start, end, appUnitsPerDevPixel, *aDrawTarget,
sdwColor);
}
if (widthInPixels >= 1) {
start.x = (mVertical) ? mRect.width - pixelWidth : 0;
start.y = (mVertical) ? 0 : mRect.height - pixelWidth;
end.x = (mVertical) ? start.x : mRect.width;
end.y = (mVertical) ? mRect.height : start.y;
StrokeLineWithSnapping(start, end, appUnitsPerDevPixel, *aDrawTarget,
fgColor);
}
}
nsresult nsHTMLFramesetBorderFrame::HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) {
NS_ENSURE_ARG_POINTER(aEventStatus);
*aEventStatus = nsEventStatus_eIgnore;
// XXX Mouse setting logic removed. The remaining logic should also move.
if (!mCanResize) {
return NS_OK;
}
if (aEvent->mMessage == eMouseDown &&
aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
nsHTMLFramesetFrame* parentFrame = do_QueryFrame(GetParent());
if (parentFrame) {
parentFrame->StartMouseDrag(aPresContext, this, aEvent);
*aEventStatus = nsEventStatus_eConsumeNoDefault;
}
}
return NS_OK;
}
nsIFrame::Cursor nsHTMLFramesetBorderFrame::GetCursor(const nsPoint&) {
auto kind = StyleCursorKind::Default;
if (mCanResize) {
kind = mVertical ? StyleCursorKind::EwResize : StyleCursorKind::NsResize;
}
return Cursor{kind, AllowCustomCursorImage::No};
}
#ifdef DEBUG_FRAME_DUMP
nsresult nsHTMLFramesetBorderFrame::GetFrameName(nsAString& aResult) const {
return MakeFrameName(u"FramesetBorder"_ns, aResult);
}
#endif
/*******************************************************************************
* nsHTMLFramesetBlankFrame
******************************************************************************/
NS_QUERYFRAME_HEAD(nsHTMLFramesetBlankFrame)
NS_QUERYFRAME_ENTRY(nsHTMLFramesetBlankFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsLeafFrame)
NS_IMPL_FRAMEARENA_HELPERS(nsHTMLFramesetBlankFrame)
nsHTMLFramesetBlankFrame::~nsHTMLFramesetBlankFrame() {
// printf("nsHTMLFramesetBlankFrame destructor %p \n", this);
}
void nsHTMLFramesetBlankFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
DO_GLOBAL_REFLOW_COUNT("nsHTMLFramesetBlankFrame");
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
// Override Reflow(), since we don't want to deal with what our
// computed values are. We just size to our available size.
aDesiredSize.SetSize(aReflowInput.GetWritingMode(),
aReflowInput.AvailableSize());
aDesiredSize.SetOverflowAreasToDesiredBounds();
FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
}
class nsDisplayFramesetBlank final : public nsPaintedDisplayItem {
public:
nsDisplayFramesetBlank(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
: nsPaintedDisplayItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayFramesetBlank);
}
MOZ_COUNTED_DTOR_FINAL(nsDisplayFramesetBlank)
virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
NS_DISPLAY_DECL_NAME("FramesetBlank", TYPE_FRAMESET_BLANK)
};
void nsDisplayFramesetBlank::Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx) {
DrawTarget* drawTarget = aCtx->GetDrawTarget();
int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
Rect rect = NSRectToSnappedRect(GetPaintRect(aBuilder, aCtx),
appUnitsPerDevPixel, *drawTarget);
ColorPattern white(ToDeviceColor(sRGBColor::OpaqueWhite()));
drawTarget->FillRect(rect, white);
}
void nsHTMLFramesetBlankFrame::BuildDisplayList(
nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
aLists.Content()->AppendNewToTop<nsDisplayFramesetBlank>(aBuilder, this);
}