| /* |
| * Copyright (C) 2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "RenderTreeBuilderRuby.h" |
| |
| #include "RenderAncestorIterator.h" |
| #include "RenderRuby.h" |
| #include "RenderRubyBase.h" |
| #include "RenderRubyRun.h" |
| #include "RenderTreeBuilder.h" |
| #include "RenderTreeBuilderBlock.h" |
| #include "RenderTreeBuilderBlockFlow.h" |
| #include "RenderTreeBuilderInline.h" |
| |
| namespace WebCore { |
| |
| static inline RenderRubyRun& findRubyRunParent(RenderObject& child) |
| { |
| return *lineageOfType<RenderRubyRun>(child).first(); |
| } |
| |
| static inline bool isAnonymousRubyInlineBlock(const RenderObject* object) |
| { |
| ASSERT(!object |
| || !isRuby(object->parent()) |
| || is<RenderRubyRun>(*object) |
| || (object->isInline() && (object->isBeforeContent() || object->isAfterContent())) |
| || (object->isAnonymous() && is<RenderBlock>(*object) && object->style().display() == DisplayType::InlineBlock) |
| || object->isRenderMultiColumnFlow() |
| || object->isRenderMultiColumnSet()); |
| |
| return object |
| && isRuby(object->parent()) |
| && is<RenderBlock>(*object) |
| && !is<RenderRubyRun>(*object); |
| } |
| |
| static inline bool isRubyBeforeBlock(const RenderObject* object) |
| { |
| return isAnonymousRubyInlineBlock(object) |
| && !object->previousSibling() |
| && downcast<RenderBlock>(*object).firstChild() |
| && downcast<RenderBlock>(*object).firstChild()->style().styleType() == PseudoId::Before; |
| } |
| |
| static inline bool isRubyAfterBlock(const RenderObject* object) |
| { |
| return isAnonymousRubyInlineBlock(object) |
| && !object->nextSibling() |
| && downcast<RenderBlock>(*object).firstChild() |
| && downcast<RenderBlock>(*object).firstChild()->style().styleType() == PseudoId::After; |
| } |
| |
| #if ASSERT_ENABLED |
| static inline bool isRubyChildForNormalRemoval(const RenderObject& object) |
| { |
| return object.isRubyRun() |
| || object.isBeforeContent() |
| || object.isAfterContent() |
| || object.isRenderMultiColumnFlow() |
| || object.isRenderMultiColumnSet() |
| || isAnonymousRubyInlineBlock(&object); |
| } |
| #endif // ASSERT_ENABLED |
| |
| static inline RenderBlock* rubyBeforeBlock(const RenderElement* ruby) |
| { |
| RenderObject* child = ruby->firstChild(); |
| return isRubyBeforeBlock(child) ? downcast<RenderBlock>(child) : nullptr; |
| } |
| |
| static inline RenderBlock* rubyAfterBlock(const RenderElement* ruby) |
| { |
| RenderObject* child = ruby->lastChild(); |
| return isRubyAfterBlock(child) ? downcast<RenderBlock>(child) : nullptr; |
| } |
| |
| static auto createAnonymousRubyInlineBlock(RenderObject& ruby) |
| { |
| auto newBlock = createRenderer<RenderBlockFlow>(ruby.document(), RenderStyle::createAnonymousStyleWithDisplay(ruby.style(), DisplayType::InlineBlock)); |
| newBlock->initializeStyle(); |
| return newBlock; |
| } |
| |
| static RenderRubyRun* lastRubyRun(const RenderElement* ruby) |
| { |
| RenderObject* child = ruby->lastChild(); |
| if (child && !is<RenderRubyRun>(*child)) |
| child = child->previousSibling(); |
| if (!is<RenderRubyRun>(child)) { |
| ASSERT(!child || child->isBeforeContent() || child == rubyBeforeBlock(ruby) || child->isRenderMultiColumnFlow() || child->isRenderMultiColumnSet()); |
| return nullptr; |
| } |
| return downcast<RenderRubyRun>(child); |
| } |
| |
| RenderTreeBuilder::Ruby::Ruby(RenderTreeBuilder& builder) |
| : m_builder(builder) |
| { |
| } |
| |
| void RenderTreeBuilder::Ruby::moveInlineChildren(RenderRubyBase& from, RenderRubyBase& to, RenderObject* beforeChild) |
| { |
| ASSERT(from.childrenInline()); |
| |
| if (!from.firstChild()) |
| return; |
| |
| RenderBlock* toBlock = nullptr; |
| if (to.childrenInline()) { |
| // The standard and easy case: move the children into the target base |
| toBlock = &to; |
| } else { |
| // We need to wrap the inline objects into an anonymous block. |
| // If toBase has a suitable block, we re-use it, otherwise create a new one. |
| auto* lastChild = to.lastChild(); |
| if (lastChild && lastChild->isAnonymousBlock() && lastChild->childrenInline()) |
| toBlock = downcast<RenderBlock>(lastChild); |
| else { |
| auto newToBlock = to.createAnonymousBlock(); |
| toBlock = newToBlock.get(); |
| m_builder.attachToRenderElementInternal(to, WTFMove(newToBlock)); |
| } |
| } |
| ASSERT(toBlock); |
| // Move our inline children into the target block we determined above. |
| m_builder.moveChildren(from, *toBlock, from.firstChild(), beforeChild, RenderTreeBuilder::NormalizeAfterInsertion::No); |
| } |
| |
| void RenderTreeBuilder::Ruby::moveBlockChildren(RenderRubyBase& from, RenderRubyBase& to, RenderObject* beforeChild) |
| { |
| ASSERT(!from.childrenInline()); |
| |
| if (!from.firstChild()) |
| return; |
| |
| if (to.childrenInline()) |
| m_builder.makeChildrenNonInline(to); |
| |
| // If an anonymous block would be put next to another such block, then merge those. |
| auto* firstChildHere = from.firstChild(); |
| auto* lastChildThere = to.lastChild(); |
| if (firstChildHere->isAnonymousBlock() && firstChildHere->childrenInline() |
| && lastChildThere && lastChildThere->isAnonymousBlock() && lastChildThere->childrenInline()) { |
| auto* anonBlockHere = downcast<RenderBlock>(firstChildHere); |
| auto* anonBlockThere = downcast<RenderBlock>(lastChildThere); |
| m_builder.moveAllChildren(*anonBlockHere, *anonBlockThere, RenderTreeBuilder::NormalizeAfterInsertion::Yes); |
| anonBlockHere->deleteLines(); |
| m_builder.destroy(*anonBlockHere); |
| } |
| // Move all remaining children normally. |
| m_builder.moveChildren(from, to, from.firstChild(), beforeChild, RenderTreeBuilder::NormalizeAfterInsertion::No); |
| } |
| |
| void RenderTreeBuilder::Ruby::moveChildren(RenderRubyBase& from, RenderRubyBase& to) |
| { |
| moveChildrenInternal(from, to); |
| from.addFloatsToNewParent(to); |
| } |
| |
| void RenderTreeBuilder::Ruby::moveChildrenInternal(RenderRubyBase& from, RenderRubyBase& to, RenderObject* beforeChild) |
| { |
| // This function removes all children that are before (!) beforeChild |
| // and appends them to toBase. |
| if (beforeChild && beforeChild->parent() != &from) |
| beforeChild = m_builder.splitAnonymousBoxesAroundChild(from, *beforeChild); |
| |
| if (from.childrenInline()) |
| moveInlineChildren(from, to, beforeChild); |
| else |
| moveBlockChildren(from, to, beforeChild); |
| |
| from.setNeedsLayoutAndPrefWidthsRecalc(); |
| to.setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| |
| void RenderTreeBuilder::Ruby::attach(RenderRubyRun& parent, RenderPtr<RenderObject> child, RenderObject* beforeChild) |
| { |
| if (child->isRubyText()) { |
| if (!beforeChild) { |
| // RenderRuby has already ascertained that we can add the child here. |
| ASSERT(!parent.hasRubyText()); |
| // prepend ruby texts as first child |
| m_builder.blockFlowBuilder().attach(parent, WTFMove(child), parent.firstChild()); |
| return; |
| } |
| if (beforeChild->isRubyText()) { |
| // New text is inserted just before another. |
| // In this case the new text takes the place of the old one, and |
| // the old text goes into a new run that is inserted as next sibling. |
| ASSERT(beforeChild->parent() == &parent); |
| RenderElement* ruby = parent.parent(); |
| ASSERT(isRuby(ruby)); |
| auto newRun = RenderRubyRun::staticCreateRubyRun(ruby); |
| auto& run = *newRun; |
| m_builder.attach(*ruby, WTFMove(newRun), parent.nextSibling()); |
| // Add the new ruby text and move the old one to the new run |
| // Note: Doing it in this order and not using RenderRubyRun's methods, |
| // in order to avoid automatic removal of the ruby run in case there is no |
| // other child besides the old ruby text. |
| m_builder.blockFlowBuilder().attach(parent, WTFMove(child), beforeChild); |
| auto takenBeforeChild = m_builder.blockBuilder().detach(parent, *beforeChild); |
| |
| m_builder.attach(run, WTFMove(takenBeforeChild)); |
| return; |
| } |
| if (parent.hasRubyBase()) { |
| // Insertion before a ruby base object. |
| // In this case we need insert a new run before the current one and split the base. |
| RenderElement* ruby = parent.parent(); |
| auto newRun = RenderRubyRun::staticCreateRubyRun(ruby); |
| auto& run = *newRun; |
| m_builder.attach(*ruby, WTFMove(newRun), &parent); |
| m_builder.attach(run, WTFMove(child)); |
| moveChildrenInternal(rubyBaseSafe(parent), rubyBaseSafe(run), beforeChild); |
| } |
| return; |
| } |
| // child is not a text -> insert it into the base |
| // (append it instead if beforeChild is the ruby text) |
| if (beforeChild && beforeChild->isRubyText()) |
| beforeChild = nullptr; |
| m_builder.attach(rubyBaseSafe(parent), WTFMove(child), beforeChild); |
| } |
| |
| RenderElement& RenderTreeBuilder::Ruby::findOrCreateParentForChild(RenderRubyAsBlock& parent, const RenderObject& child, RenderObject*& beforeChild) |
| { |
| // Insert :before and :after content before/after the RenderRubyRun(s) |
| if (child.isBeforeContent()) { |
| // Add generated inline content normally |
| if (child.isInline()) |
| return parent; |
| // Wrap non-inline content in an anonymous inline-block. |
| auto* beforeBlock = rubyBeforeBlock(&parent); |
| if (!beforeBlock) { |
| auto newBlock = createAnonymousRubyInlineBlock(parent); |
| beforeBlock = newBlock.get(); |
| m_builder.blockFlowBuilder().attach(parent, WTFMove(newBlock), parent.firstChild()); |
| } |
| beforeChild = nullptr; |
| return *beforeBlock; |
| } |
| |
| if (child.isAfterContent()) { |
| // Add generated inline content normally |
| if (child.isInline()) |
| return parent; |
| // Wrap non-inline content with an anonymous inline-block. |
| auto* afterBlock = rubyAfterBlock(&parent); |
| if (!afterBlock) { |
| auto newBlock = createAnonymousRubyInlineBlock(parent); |
| afterBlock = newBlock.get(); |
| m_builder.blockFlowBuilder().attach(parent, WTFMove(newBlock), nullptr); |
| } |
| beforeChild = nullptr; |
| return *afterBlock; |
| } |
| |
| // If the child is a ruby run, just add it normally. |
| if (child.isRubyRun()) |
| return parent; |
| |
| if (beforeChild && !parent.isAfterContent(beforeChild)) { |
| // insert child into run |
| ASSERT(!beforeChild->isRubyRun()); |
| auto* run = beforeChild->parent(); |
| while (run && !run->isRubyRun()) |
| run = run->parent(); |
| if (run) |
| return *run; |
| ASSERT_NOT_REACHED(); // beforeChild should always have a run as parent! |
| // Emergency fallback: fall through and just append. |
| } |
| |
| // If the new child would be appended, try to add the child to the previous run |
| // if possible, or create a new run otherwise. |
| // (The RenderRubyRun object will handle the details) |
| auto* lastRun = lastRubyRun(&parent); |
| if (!lastRun || lastRun->hasRubyText()) { |
| auto newRun = RenderRubyRun::staticCreateRubyRun(&parent); |
| lastRun = newRun.get(); |
| m_builder.blockFlowBuilder().attach(parent, WTFMove(newRun), beforeChild); |
| } |
| beforeChild = nullptr; |
| return *lastRun; |
| } |
| |
| RenderElement& RenderTreeBuilder::Ruby::findOrCreateParentForChild(RenderRubyAsInline& parent, const RenderObject& child, RenderObject*& beforeChild) |
| { |
| // Insert :before and :after content before/after the RenderRubyRun(s) |
| if (child.isBeforeContent()) { |
| // Add generated inline content normally |
| if (child.isInline()) |
| return parent; |
| // Wrap non-inline content with an anonymous inline-block. |
| auto* beforeBlock = rubyBeforeBlock(&parent); |
| if (!beforeBlock) { |
| auto newBlock = createAnonymousRubyInlineBlock(parent); |
| beforeBlock = newBlock.get(); |
| m_builder.inlineBuilder().attach(parent, WTFMove(newBlock), parent.firstChild()); |
| } |
| beforeChild = nullptr; |
| return *beforeBlock; |
| } |
| |
| if (child.isAfterContent()) { |
| // Add generated inline content normally |
| if (child.isInline()) |
| return parent; |
| // Wrap non-inline content with an anonymous inline-block. |
| auto* afterBlock = rubyAfterBlock(&parent); |
| if (!afterBlock) { |
| auto newBlock = createAnonymousRubyInlineBlock(parent); |
| afterBlock = newBlock.get(); |
| m_builder.inlineBuilder().attach(parent, WTFMove(newBlock), nullptr); |
| } |
| beforeChild = nullptr; |
| return *afterBlock; |
| } |
| |
| // If the child is a ruby run, just add it normally. |
| if (child.isRubyRun()) |
| return parent; |
| |
| if (beforeChild && !parent.isAfterContent(beforeChild)) { |
| // insert child into run |
| ASSERT(!beforeChild->isRubyRun()); |
| auto* run = beforeChild->parent(); |
| while (run && !run->isRubyRun()) |
| run = run->parent(); |
| if (run) |
| return *run; |
| ASSERT_NOT_REACHED(); // beforeChild should always have a run as parent! |
| // Emergency fallback: fall through and just append. |
| } |
| |
| // If the new child would be appended, try to add the child to the previous run |
| // if possible, or create a new run otherwise. |
| // (The RenderRubyRun object will handle the details) |
| auto* lastRun = lastRubyRun(&parent); |
| if (!lastRun || lastRun->hasRubyText()) { |
| auto newRun = RenderRubyRun::staticCreateRubyRun(&parent); |
| lastRun = newRun.get(); |
| m_builder.inlineBuilder().attach(parent, WTFMove(newRun), beforeChild); |
| } |
| beforeChild = nullptr; |
| return *lastRun; |
| } |
| |
| RenderRubyBase& RenderTreeBuilder::Ruby::rubyBaseSafe(RenderRubyRun& rubyRun) |
| { |
| auto* base = rubyRun.rubyBase(); |
| if (!base) { |
| auto newBase = rubyRun.createRubyBase(); |
| base = newBase.get(); |
| m_builder.blockFlowBuilder().attach(rubyRun, WTFMove(newBase), nullptr); |
| } |
| return *base; |
| } |
| |
| RenderPtr<RenderObject> RenderTreeBuilder::Ruby::detach(RenderRubyAsInline& parent, RenderObject& child) |
| { |
| // If the child's parent is *this (must be a ruby run or generated content or anonymous block), |
| // just use the normal remove method. |
| if (child.parent() == &parent) { |
| ASSERT(isRubyChildForNormalRemoval(child)); |
| return m_builder.detachFromRenderElement(parent, child); |
| } |
| // If the child's parent is an anoymous block (must be generated :before/:after content) |
| // just use the block's remove method. |
| if (isAnonymousRubyInlineBlock(child.parent())) { |
| ASSERT(child.isBeforeContent() || child.isAfterContent()); |
| auto& parent = *child.parent(); |
| auto takenChild = m_builder.detach(parent, child); |
| m_builder.destroy(parent); |
| return takenChild; |
| } |
| |
| // Otherwise find the containing run and remove it from there. |
| return m_builder.detach(findRubyRunParent(child), child); |
| } |
| |
| RenderPtr<RenderObject> RenderTreeBuilder::Ruby::detach(RenderRubyAsBlock& parent, RenderObject& child) |
| { |
| // If the child's parent is *this (must be a ruby run or generated content or anonymous block), |
| // just use the normal remove method. |
| if (child.parent() == &parent) { |
| ASSERT(isRubyChildForNormalRemoval(child)); |
| return m_builder.blockBuilder().detach(parent, child); |
| } |
| // If the child's parent is an anoymous block (must be generated :before/:after content) |
| // just use the block's remove method. |
| if (isAnonymousRubyInlineBlock(child.parent())) { |
| ASSERT(child.isBeforeContent() || child.isAfterContent()); |
| auto& parent = *child.parent(); |
| auto takenChild = m_builder.detach(parent, child); |
| m_builder.destroy(parent); |
| return takenChild; |
| } |
| |
| // Otherwise find the containing run and remove it from there. |
| return m_builder.detach(findRubyRunParent(child), child); |
| } |
| |
| RenderPtr<RenderObject> RenderTreeBuilder::Ruby::detach(RenderRubyRun& parent, RenderObject& child) |
| { |
| // If the child is a ruby text, then merge the ruby base with the base of |
| // the right sibling run, if possible. |
| if (!parent.beingDestroyed() && !parent.renderTreeBeingDestroyed() && child.isRubyText()) { |
| RenderRubyBase* base = parent.rubyBase(); |
| RenderObject* rightNeighbour = parent.nextSibling(); |
| if (base && is<RenderRubyRun>(rightNeighbour)) { |
| // Ruby run without a base can happen only at the first run. |
| RenderRubyRun& rightRun = downcast<RenderRubyRun>(*rightNeighbour); |
| if (rightRun.hasRubyBase()) { |
| RenderRubyBase* rightBase = rightRun.rubyBase(); |
| // Collect all children in a single base, then swap the bases. |
| moveChildren(*rightBase, *base); |
| m_builder.move(parent, rightRun, *base, RenderTreeBuilder::NormalizeAfterInsertion::No); |
| m_builder.move(rightRun, parent, *rightBase, RenderTreeBuilder::NormalizeAfterInsertion::No); |
| // The now empty ruby base will be removed below. |
| ASSERT(!parent.rubyBase()->firstChild()); |
| } |
| } |
| } |
| |
| auto takenChild = m_builder.blockBuilder().detach(parent, child); |
| |
| if (!parent.beingDestroyed() && !parent.renderTreeBeingDestroyed()) { |
| // Check if our base (if any) is now empty. If so, destroy it. |
| RenderBlock* base = parent.rubyBase(); |
| if (base && !base->firstChild()) { |
| auto takenBase = m_builder.blockBuilder().detach(parent, *base); |
| base->deleteLines(); |
| } |
| } |
| return takenChild; |
| } |
| |
| } |