Bug 1740366 - Implement paginated fragmentation on page-name boundaries r=dholbert

This has a TODO about empty page names on previous sibling frames, to match
Chrome's behavior of page break coalescing we should be looking to the frame
before that sibling and check for page breaks there instead.

Differential Revision: https://phabricator.services.mozilla.com/D140423
This commit is contained in:
Emily McDonough 2022-04-28 00:03:19 +00:00
Родитель 94e2a597f6
Коммит f7af34aa62
13 изменённых файлов: 339 добавлений и 48 удалений

Просмотреть файл

@ -654,6 +654,10 @@ class MOZ_STACK_CLASS nsFrameConstructorState {
AbsoluteFrameList mTopLayerFixedList;
AbsoluteFrameList mTopLayerAbsoluteList;
// What `page: auto` resolves to. This is the used page-name of the parent
// frame. Updated by AutoFrameConstructionPageName.
const nsAtom* mAutoPageNameValue;
nsCOMPtr<nsILayoutHistoryState> mFrameState;
// These bits will be added to the state bits of any frame we construct
// using this state.
@ -842,6 +846,8 @@ nsFrameConstructorState::nsFrameConstructorState(
static_cast<nsContainerFrame*>(mFrameManager->GetRootFrame())),
mTopLayerAbsoluteList(
aPresShell->FrameConstructor()->GetDocElementContainingBlock()),
// Will be set by AutoFrameConstructionPageName
mAutoPageNameValue(nullptr),
// See PushAbsoluteContaningBlock below
mFrameState(aHistoryState),
mAdditionalStateBits(nsFrameState(0)),
@ -1406,6 +1412,95 @@ static void MoveChildrenTo(nsIFrame* aOldParent, nsContainerFrame* aNewParent,
}
}
static bool MaybeApplyPageName(nsFrameConstructorState& aState,
const StylePageName& aPageName) {
if (aPageName.IsPageName()) {
aState.mAutoPageNameValue = aPageName.AsPageName().AsAtom();
return true;
}
MOZ_ASSERT(aPageName.IsAuto(), "Impossible page name");
return false;
}
static void EnsureAutoPageName(nsFrameConstructorState& aState,
const nsContainerFrame* const aFrame) {
// Check if we need to figure out our used page name.
// When building the entire document, this should only happen for the
// root, which will mean the loop will immediately end. Either way, this will
// only happen once for each time the frame constructor is run.
for (const nsContainerFrame* frame = aFrame; frame;
frame = frame->GetParent()) {
const StylePageName& pageName = frame->StylePage()->mPage;
if (MaybeApplyPageName(aState, pageName)) {
return;
}
}
// Ensure that a root with `page: auto` gets an empty page name
// https://drafts.csswg.org/css-page-3/#using-named-pages
aState.mAutoPageNameValue = nsGkAtoms::_empty;
}
nsCSSFrameConstructor::AutoFrameConstructionPageName::
AutoFrameConstructionPageName(nsCSSFrameConstructor& aFCtor,
nsFrameConstructorState& aState,
FrameConstructionItemList& aItems,
nsIFrame* const aFrame)
: mFCtor(aFCtor),
mState(aState),
mItems(aItems),
mFrame(aFrame),
mNameToRestore(nullptr) {
if (!(aState.mPresContext->IsPaginated() &&
StaticPrefs::layout_css_named_pages_enabled())) {
return;
}
EnsureAutoPageName(aState, aFrame->GetParent());
mNameToRestore = aState.mAutoPageNameValue;
MOZ_ASSERT(mNameToRestore,
"Page name should have been found by EnsureAutoPageName");
MaybeApplyPageName(mState, aFrame->StylePage()->mPage);
// Ensure that the PageValuesProperty field has been created.
// Before layout.css.named_pages.enabled is prefed on by default, we should
// investigate making this property optional, and have a missing property
// indicate a default root page-name or an auto page-name.
//
// When we make this property optional, we should add a debug-only flag on
// nsIFrame to indicate it was visited by this logic, as currently we assert
// the existence of this property to validate that.
nsIFrame::PageValues* pageValues =
aFrame->GetProperty(nsIFrame::PageValuesProperty());
if (!pageValues) {
pageValues = new nsIFrame::PageValues();
aFrame->AddProperty(nsIFrame::PageValuesProperty(), pageValues);
}
}
nsCSSFrameConstructor::AutoFrameConstructionPageName::
~AutoFrameConstructionPageName() {
if (!(mState.mPresContext->IsPaginated() &&
StaticPrefs::layout_css_named_pages_enabled())) {
return;
}
nsIFrame::PageValues* const pageValues =
mFrame->GetProperty(nsIFrame::PageValuesProperty());
MOZ_ASSERT(!!pageValues->mStartPageValue == !!pageValues->mEndPageValue,
"Both or neither of the child page names should have been set.");
if (!pageValues->mStartPageValue && !mItems.IsEmpty()) {
pageValues->mStartPageValue = mState.mAutoPageNameValue;
pageValues->mEndPageValue = mState.mAutoPageNameValue;
}
if (const nsIFrame* const prevSibling = mFrame->GetPrevSibling()) {
if (const nsIFrame::PageValues* const prevPageValues =
prevSibling->GetProperty(nsIFrame::PageValuesProperty())) {
if (prevPageValues->mEndPageValue != pageValues->mStartPageValue) {
mFCtor.PrependPageBreakItem(mFrame->GetContent(), mItems);
}
}
}
mState.mAutoPageNameValue = mNameToRestore;
}
//----------------------------------------------------------------------
nsCSSFrameConstructor::nsCSSFrameConstructor(Document* aDocument,
@ -3768,6 +3863,8 @@ void nsCSSFrameConstructor::ConstructFrameFromItemInternal(
aState.MaybePushFloatContainingBlock(newFrameAsContainer, floatSaveState);
if (bits & FCDATA_USE_CHILD_ITEMS) {
AutoFrameConstructionPageName pageName(*this, aState, aItem.mChildItems,
newFrame);
ConstructFramesFromItemList(
aState, aItem.mChildItems, newFrameAsContainer,
bits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
@ -5048,8 +5145,9 @@ nsCSSFrameConstructor::FindSVGData(const Element& aElement,
return data;
}
void nsCSSFrameConstructor::AddPageBreakItem(
nsIContent* aContent, FrameConstructionItemList& aItems) {
void nsCSSFrameConstructor::InsertPageBreakItem(
nsIContent* aContent, FrameConstructionItemList& aItems,
InsertPageBreakLocation location) {
RefPtr<ComputedStyle> pseudoStyle =
mPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::pageBreak);
@ -5059,8 +5157,13 @@ void nsCSSFrameConstructor::AddPageBreakItem(
static constexpr FrameConstructionData sPageBreakData(NS_NewPageBreakFrame,
FCDATA_SKIP_FRAMESET);
aItems.AppendItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
true);
if (location == InsertPageBreakLocation::eBefore) {
aItems.PrependItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
true);
} else {
aItems.AppendItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
true);
}
}
bool nsCSSFrameConstructor::ShouldCreateItemsForChild(
@ -5387,6 +5490,55 @@ void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
return;
}
if (aState.mPresContext->IsPaginated() &&
StaticPrefs::layout_css_named_pages_enabled()) {
// TODO: This is slightly incorrect! See Bug 1764437
// We should be waiting all of our descendent frames to be constructed.
//
// Alternatively, we could propagate this back up the frame tree after
// constructing this frame's first child, inspecting the parent frames and
// rewriting their first child page-name.
const StylePageName& pageName = aComputedStyle->StylePage()->mPage;
const nsAtom* pageNameAtom;
if (pageName.IsPageName()) {
pageNameAtom = pageName.AsPageName().AsAtom();
} else {
// Resolve auto against the parent frame's used page name.
MOZ_ASSERT(pageName.IsAuto(), "Impossible page name");
pageNameAtom = aState.mAutoPageNameValue;
}
// Check if we are the first child of our parent. If so, propagate this
// child's page name up the frame tree for every frame while our ancestor
// is the first child of its parent.
//
// TODO: Bug 1766685
// We should consider if this frame can create a class A page break or not,
// and only propagate the page property if it can. Otherwise, the page
// property should be ignored in our computed style.
nsIFrame::PageValues* const framePageValues =
aParentFrame->GetProperty(nsIFrame::PageValuesProperty());
MOZ_ASSERT(framePageValues,
"child box page names should have been created by "
"AutoFrameConstructionPageName");
if (!framePageValues->mStartPageValue) {
framePageValues->mStartPageValue = pageNameAtom;
// Propagate the start page value back up the frame tree.
// If the frame already has mStartPageValue set, then we are not a
// descendant of the frame's first child.
for (nsContainerFrame* frame = aParentFrame->GetParent(); frame;
frame = frame->GetParent()) {
nsIFrame::PageValues* const parentPageValues =
frame->GetProperty(nsIFrame::PageValuesProperty());
if (!parentPageValues || parentPageValues->mStartPageValue) {
break;
}
parentPageValues->mStartPageValue = pageNameAtom;
}
}
framePageValues->mEndPageValue = pageNameAtom;
}
const bool canHavePageBreak =
aFlags.contains(ItemFlag::AllowPageBreak) &&
aState.mPresContext->IsPaginated() &&
@ -5395,7 +5547,7 @@ void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
!(bits & FCDATA_IS_TABLE_PART) && !(bits & FCDATA_IS_SVG_TEXT);
if (canHavePageBreak && display.BreakBefore()) {
AddPageBreakItem(aContent, aItems);
AppendPageBreakItem(aContent, aItems);
}
if (details && details->Open()) {
@ -5430,7 +5582,7 @@ void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
item->mIsPopup = isPopup;
if (canHavePageBreak && display.BreakAfter()) {
AddPageBreakItem(aContent, aItems);
AppendPageBreakItem(aContent, aItems);
}
if (bits & FCDATA_IS_INLINE) {
@ -9472,6 +9624,8 @@ void nsCSSFrameConstructor::AddFCItemsForAnonymousContent(
nsFrameConstructorState& aState, nsContainerFrame* aFrame,
const nsTArray<nsIAnonymousContentCreator::ContentInfo>& aAnonymousItems,
FrameConstructionItemList& aItemsToConstruct, ItemFlags aExtraFlags) {
AutoFrameConstructionPageName pageName(*this, aState, aItemsToConstruct,
aFrame);
for (const auto& info : aAnonymousItems) {
nsIContent* content = info.mContent;
// Gecko-styled nodes should have no pending restyle flags.
@ -9532,6 +9686,8 @@ void nsCSSFrameConstructor::ProcessChildren(
}
AutoFrameConstructionItemList itemsToConstruct(this);
AutoFrameConstructionPageName pageName(*this, aState, itemsToConstruct,
aFrame);
// If we have first-letter or first-line style then frames can get
// moved around so don't set these flags.

Просмотреть файл

@ -1244,6 +1244,34 @@ class nsCSSFrameConstructor final : public nsFrameManager {
FrameConstructionItem* const mItem;
};
/**
* Updates the nsFrameConstructorState auto page-name value, adding page
* breaks between frames when the end page value of the previous frame is
* different from the start page value of the next frame.
* See https://drafts.csswg.org/css-page-3/#using-named-pages
*
* To track this, this will automatically add PageValuesProperty to
* the frame.
*
* Note that this does not add any page breaks or PageValuesProperty
* to the frame when not in a paginated context, or if
* layout.css.named_pages.enabled is set to false.
*/
class MOZ_RAII AutoFrameConstructionPageName final {
nsCSSFrameConstructor& mFCtor;
nsFrameConstructorState& mState;
FrameConstructionItemList& mItems;
const nsIFrame* const mFrame;
const nsAtom* mNameToRestore;
public:
AutoFrameConstructionPageName(nsCSSFrameConstructor& aFCtor,
nsFrameConstructorState& aState,
FrameConstructionItemList& aItems,
nsIFrame* const aFrame);
~AutoFrameConstructionPageName();
};
/**
* Function to create the anonymous flex or grid items that we need.
* If aParentFrame is not a nsFlexContainerFrame or nsGridContainerFrame then
@ -1392,8 +1420,18 @@ class nsCSSFrameConstructor final : public nsFrameManager {
// for it.
void ReframeTextIfNeeded(nsIContent* aContent);
void AddPageBreakItem(nsIContent* aContent,
FrameConstructionItemList& aItems);
enum InsertPageBreakLocation { eBefore, eAfter };
inline void AppendPageBreakItem(nsIContent* aContent,
FrameConstructionItemList& aItems) {
InsertPageBreakItem(aContent, aItems, InsertPageBreakLocation::eAfter);
}
inline void PrependPageBreakItem(nsIContent* aContent,
FrameConstructionItemList& aItems) {
InsertPageBreakItem(aContent, aItems, InsertPageBreakLocation::eBefore);
}
void InsertPageBreakItem(nsIContent* aContent,
FrameConstructionItemList& aItems,
InsertPageBreakLocation location);
// Function to find FrameConstructionData for aElement. Will return
// null if aElement is not HTML.

Просмотреть файл

@ -8076,6 +8076,25 @@ void nsIFrame::ListGeneric(nsACString& aTo, const char* aPrefix,
aTo += nsPrintfCString(" next-%s=%p", fluid ? "in-flow" : "continuation",
static_cast<void*>(GetNextContinuation()));
}
if (const nsIFrame::PageValues* const pageValues =
GetProperty(PageValuesProperty())) {
nsAutoCString name;
aTo += " PageValues={";
if (pageValues->mStartPageValue) {
pageValues->mStartPageValue->ToUTF8String(name);
aTo += name;
} else {
aTo += "<null>";
}
aTo += ", ";
if (pageValues->mEndPageValue) {
pageValues->mEndPageValue->ToUTF8String(name);
aTo += name;
} else {
aTo += "<null>";
}
aTo += "}";
}
void* IBsibling = GetProperty(IBSplitSibling());
if (IBsibling) {
aTo += nsPrintfCString(" IBSplitSibling=%p", IBsibling);

Просмотреть файл

@ -1300,6 +1300,23 @@ class nsIFrame : public nsQueryFrame {
NS_DECLARE_FRAME_PROPERTY_DELETABLE(UsedPaddingProperty, nsMargin)
NS_DECLARE_FRAME_PROPERTY_DELETABLE(UsedBorderProperty, nsMargin)
// This tracks the start and end page value for a frame.
//
// https://www.w3.org/TR/css-page-3/#using-named-pages
//
// This is only tracked during paginated frame construction when
// layout.css.named-pages.enabled has been set to true.
// This is used to implement fragmentation based on CSS page names. During
// frame construction, we insert page breaks when we begin a new page box and
// the previous page box had a different name.
struct PageValues {
// mFirstChildPageName of null is used to indicate that no child has been
// constructed yet.
RefPtr<const nsAtom> mStartPageValue = nullptr;
RefPtr<const nsAtom> mEndPageValue = nullptr;
};
NS_DECLARE_FRAME_PROPERTY_DELETABLE(PageValuesProperty, PageValues)
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(LineBaselineOffset, nscoord)
NS_DECLARE_FRAME_PROPERTY_DELETABLE(InvalidationRect, nsRect)

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-paged">
<body>
<canvas height="1" style="border: 1px solid black"></canvas>
<p>b</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-paged">
<body style="page:a">
<canvas height="1" style="page:b; border: 1px solid black"></canvas>
<p style="page:b">b</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-paged">
<body>
<p>a</p>
<canvas height="1" style="border: 1px solid black"></canvas>
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-paged">
<body style="page:a">
<p style="page:b">a</p>
<canvas height="1" style="page:b; border: 1px solid black"></canvas>
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-paged">
<body>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAIAAAABc2X6AAAAa0lEQVR42u3QMREAAAwCMfybbl3AEu4NhCQ3abUAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOXwbunRwEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA3d78VrQ4ODmDDUAAAAASUVORK5CYII=">
<p>b</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-paged">
<body style="page:a">
<img style="page:b" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAIAAAABc2X6AAAAa0lEQVR42u3QMREAAAwCMfybbl3AEu4NhCQ3abUAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOXwbunRwEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA3d78VrQ4ODmDDUAAAAASUVORK5CYII=">
<p style="page:b">b</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-paged">
<body>
<p>a</p>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAIAAAABc2X6AAAAa0lEQVR42u3QMREAAAwCMfybbl3AEu4NhCQ3abUAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOXwbunRwEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA3d78VrQ4ODmDDUAAAAASUVORK5CYII=">
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-paged">
<body style="page:a">
<p style="page:b">a</p>
<img style="page:b" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAIAAAABc2X6AAAAa0lEQVR42u3QMREAAAwCMfybbl3AEu4NhCQ3abUAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOXwbunRwEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA3d78VrQ4ODmDDUAAAAASUVORK5CYII=">
</body>
</html>

Просмотреть файл

@ -1,44 +1,49 @@
defaults pref(layout.css.named-pages.enabled,true)
# https://bugzilla.mozilla.org/1764437
fails == page-name-display-none-child.html page-name-display-none-child-ref.html
fails == page-name-siblings-001.html page-name-siblings-ref.html
fails == page-name-siblings-002.html page-name-siblings-ref.html
fails == page-name-siblings-003.html page-name-siblings-ref.html
fails == page-name-siblings-004.html page-name-siblings-ref.html
fails == page-name-siblings-005.html page-name-siblings-ref.html
== page-name-canvas-001.html page-name-canvas-001-ref.html
== page-name-canvas-002.html page-name-canvas-002-ref.html
== page-name-img-001.html page-name-img-001-ref.html
== page-name-img-002.html page-name-img-002-ref.html
== page-name-siblings-001.html page-name-siblings-ref.html
== page-name-siblings-002.html page-name-siblings-ref.html
== page-name-siblings-003.html page-name-siblings-ref.html
== page-name-siblings-004.html page-name-siblings-ref.html
== page-name-siblings-005.html page-name-siblings-ref.html
# Auto-generated test cases
fails == page-name-two-page-001.html page-name-two-page-ref.html
fails == page-name-two-page-002.html page-name-two-page-ref.html
fails == page-name-two-page-003.html page-name-two-page-ref.html
fails == page-name-two-page-004.html page-name-two-page-ref.html
fails == page-name-two-page-005.html page-name-two-page-ref.html
fails == page-name-two-page-006.html page-name-two-page-ref.html
fails == page-name-two-page-007.html page-name-two-page-ref.html
fails == page-name-two-page-008.html page-name-two-page-ref.html
fails == page-name-two-page-009.html page-name-two-page-ref.html
fails == page-name-two-page-010.html page-name-two-page-ref.html
fails == page-name-two-page-011.html page-name-two-page-ref.html
fails == page-name-two-page-012.html page-name-two-page-ref.html
fails == page-name-two-page-013.html page-name-two-page-ref.html
fails == page-name-two-page-014.html page-name-two-page-ref.html
fails == page-name-two-page-015.html page-name-two-page-ref.html
fails == page-name-two-page-016.html page-name-two-page-ref.html
fails == page-name-two-page-017.html page-name-two-page-ref.html
fails == page-name-two-page-018.html page-name-two-page-ref.html
fails == page-name-two-page-019.html page-name-two-page-ref.html
fails == page-name-two-page-020.html page-name-two-page-ref.html
fails == page-name-two-page-021.html page-name-two-page-ref.html
fails == page-name-two-page-022.html page-name-two-page-ref.html
fails == page-name-two-page-023.html page-name-two-page-ref.html
fails == page-name-two-page-024.html page-name-two-page-ref.html
fails == page-name-two-page-025.html page-name-two-page-ref.html
fails == page-name-two-page-026.html page-name-two-page-ref.html
fails == page-name-two-page-027.html page-name-two-page-ref.html
fails == page-name-two-page-028.html page-name-two-page-ref.html
fails == page-name-two-page-029.html page-name-two-page-ref.html
fails == page-name-two-page-030.html page-name-two-page-ref.html
fails == page-name-two-page-031.html page-name-two-page-ref.html
fails == page-name-two-page-032.html page-name-two-page-ref.html
fails == page-name-two-page-033.html page-name-two-page-ref.html
fails == page-name-two-page-034.html page-name-two-page-ref.html
fails == page-name-two-page-035.html page-name-two-page-ref.html
== page-name-two-page-001.html page-name-two-page-ref.html
== page-name-two-page-002.html page-name-two-page-ref.html
== page-name-two-page-003.html page-name-two-page-ref.html
== page-name-two-page-004.html page-name-two-page-ref.html
== page-name-two-page-005.html page-name-two-page-ref.html
== page-name-two-page-006.html page-name-two-page-ref.html
== page-name-two-page-007.html page-name-two-page-ref.html
== page-name-two-page-008.html page-name-two-page-ref.html
== page-name-two-page-009.html page-name-two-page-ref.html
== page-name-two-page-010.html page-name-two-page-ref.html
== page-name-two-page-011.html page-name-two-page-ref.html
== page-name-two-page-012.html page-name-two-page-ref.html
== page-name-two-page-013.html page-name-two-page-ref.html
== page-name-two-page-014.html page-name-two-page-ref.html
== page-name-two-page-015.html page-name-two-page-ref.html
== page-name-two-page-016.html page-name-two-page-ref.html
== page-name-two-page-017.html page-name-two-page-ref.html
== page-name-two-page-018.html page-name-two-page-ref.html
== page-name-two-page-019.html page-name-two-page-ref.html
== page-name-two-page-020.html page-name-two-page-ref.html
== page-name-two-page-021.html page-name-two-page-ref.html
== page-name-two-page-022.html page-name-two-page-ref.html
== page-name-two-page-023.html page-name-two-page-ref.html
== page-name-two-page-024.html page-name-two-page-ref.html
== page-name-two-page-025.html page-name-two-page-ref.html
== page-name-two-page-026.html page-name-two-page-ref.html
== page-name-two-page-027.html page-name-two-page-ref.html
== page-name-two-page-028.html page-name-two-page-ref.html
== page-name-two-page-029.html page-name-two-page-ref.html
== page-name-two-page-030.html page-name-two-page-ref.html
== page-name-two-page-031.html page-name-two-page-ref.html
== page-name-two-page-032.html page-name-two-page-ref.html
== page-name-two-page-033.html page-name-two-page-ref.html
== page-name-two-page-034.html page-name-two-page-ref.html
== page-name-two-page-035.html page-name-two-page-ref.html