blob: 09024973add8f1e3c9ecd8445b5036137c878dd0 [file] [log] [blame]
/*
* Copyright 2014 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cobalt/layout/box_generator.h"
#include <string>
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "cobalt/cssom/computed_style.h"
#include "cobalt/cssom/css_computed_style_declaration.h"
#include "cobalt/cssom/css_transition_set.h"
#include "cobalt/cssom/keyword_value.h"
#include "cobalt/cssom/property_definitions.h"
#include "cobalt/cssom/property_value_visitor.h"
#include "cobalt/dom/html_br_element.h"
#include "cobalt/dom/html_element.h"
#include "cobalt/dom/html_video_element.h"
#include "cobalt/dom/text.h"
#include "cobalt/layout/base_direction.h"
#include "cobalt/layout/block_formatting_block_container_box.h"
#include "cobalt/layout/block_level_replaced_box.h"
#include "cobalt/layout/inline_container_box.h"
#include "cobalt/layout/inline_level_replaced_box.h"
#include "cobalt/layout/layout_boxes.h"
#include "cobalt/layout/layout_stat_tracker.h"
#include "cobalt/layout/text_box.h"
#include "cobalt/layout/used_style.h"
#include "cobalt/layout/white_space_processing.h"
#include "cobalt/render_tree/image.h"
#include "cobalt/web_animations/keyframe_effect_read_only.h"
#include "media/base/shell_video_frame_provider.h"
namespace cobalt {
namespace layout {
using ::media::ShellVideoFrameProvider;
using ::media::VideoFrame;
namespace {
scoped_refptr<render_tree::Image> GetVideoFrame(
const scoped_refptr<ShellVideoFrameProvider>& frame_provider) {
DCHECK(frame_provider);
scoped_refptr<VideoFrame> video_frame = frame_provider->GetCurrentFrame();
if (video_frame && video_frame->texture_id()) {
scoped_refptr<render_tree::Image> image =
reinterpret_cast<render_tree::Image*>(video_frame->texture_id());
return image;
}
return NULL;
}
} // namespace
BoxGenerator::BoxGenerator(
const scoped_refptr<cssom::CSSComputedStyleDeclaration>&
parent_css_computed_style_declaration,
const scoped_refptr<const web_animations::AnimationSet>& parent_animations,
scoped_refptr<Paragraph>* paragraph, const Context* context)
: parent_css_computed_style_declaration_(
parent_css_computed_style_declaration),
parent_animations_(parent_animations),
paragraph_(paragraph),
context_(context) {}
BoxGenerator::~BoxGenerator() {
if (generating_html_element_) {
scoped_ptr<LayoutBoxes> layout_boxes;
if (!boxes_.empty()) {
layout_boxes = make_scoped_ptr(new LayoutBoxes());
layout_boxes->SwapBoxes(boxes_);
}
generating_html_element_->set_layout_boxes(
layout_boxes.PassAs<dom::LayoutBoxes>());
}
}
void BoxGenerator::Visit(dom::Element* element) {
scoped_refptr<dom::HTMLElement> html_element = element->AsHTMLElement();
DCHECK(html_element);
generating_html_element_ = html_element;
bool partial_layout_is_enabled = true;
#if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
partial_layout_is_enabled =
html_element->node_document()->partial_layout_is_enabled();
#endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
// If the html element already has layout boxes, we can reuse them.
if (partial_layout_is_enabled && html_element->layout_boxes() &&
html_element->layout_boxes()->type() ==
dom::LayoutBoxes::kLayoutLayoutBoxes) {
LayoutBoxes* layout_boxes =
base::polymorphic_downcast<LayoutBoxes*>(html_element->layout_boxes());
DCHECK(boxes_.empty());
DCHECK(!layout_boxes->boxes().empty());
if (layout_boxes->boxes().front()->GetLevel() == Box::kBlockLevel) {
boxes_ = layout_boxes->boxes();
for (Boxes::const_iterator box_iterator = boxes_.begin();
box_iterator != boxes_.end(); ++box_iterator) {
Box* box = *box_iterator;
do {
box->InvalidateParent();
box = box->GetSplitSibling();
} while (box != NULL);
}
return;
}
}
scoped_refptr<dom::HTMLVideoElement> video_element =
html_element->AsHTMLVideoElement();
if (video_element) {
VisitVideoElement(video_element);
return;
}
scoped_refptr<dom::HTMLBRElement> br_element =
html_element->AsHTMLBRElement();
if (br_element) {
VisitBrElement(br_element);
return;
}
VisitNonReplacedElement(html_element);
}
namespace {
class ReplacedBoxGenerator : public cssom::NotReachedPropertyValueVisitor {
public:
ReplacedBoxGenerator(const scoped_refptr<cssom::CSSComputedStyleDeclaration>&
css_computed_style_declaration,
const ReplacedBox::ReplaceImageCB& replace_image_cb,
const scoped_refptr<Paragraph>& paragraph,
int32 text_position,
const base::optional<LayoutUnit>& maybe_intrinsic_width,
const base::optional<LayoutUnit>& maybe_intrinsic_height,
const base::optional<float>& maybe_intrinsic_ratio,
const BoxGenerator::Context* context)
: css_computed_style_declaration_(css_computed_style_declaration),
replace_image_cb_(replace_image_cb),
paragraph_(paragraph),
text_position_(text_position),
maybe_intrinsic_width_(maybe_intrinsic_width),
maybe_intrinsic_height_(maybe_intrinsic_height),
maybe_intrinsic_ratio_(maybe_intrinsic_ratio),
context_(context) {}
void VisitKeyword(cssom::KeywordValue* keyword) OVERRIDE;
const scoped_refptr<ReplacedBox>& replaced_box() { return replaced_box_; }
private:
const scoped_refptr<cssom::CSSComputedStyleDeclaration>
css_computed_style_declaration_;
const ReplacedBox::ReplaceImageCB replace_image_cb_;
const scoped_refptr<Paragraph> paragraph_;
const int32 text_position_;
const base::optional<LayoutUnit> maybe_intrinsic_width_;
const base::optional<LayoutUnit> maybe_intrinsic_height_;
const base::optional<float> maybe_intrinsic_ratio_;
const BoxGenerator::Context* context_;
scoped_refptr<ReplacedBox> replaced_box_;
};
void ReplacedBoxGenerator::VisitKeyword(cssom::KeywordValue* keyword) {
// See https://www.w3.org/TR/CSS21/visuren.html#display-prop.
switch (keyword->value()) {
// Generate a block-level replaced box.
case cssom::KeywordValue::kBlock:
replaced_box_ = make_scoped_refptr(new BlockLevelReplacedBox(
css_computed_style_declaration_, replace_image_cb_, paragraph_,
text_position_, maybe_intrinsic_width_, maybe_intrinsic_height_,
maybe_intrinsic_ratio_, context_->used_style_provider,
context_->layout_stat_tracker));
break;
// Generate an inline-level replaced box. There is no need to distinguish
// between inline replaced elements and inline-block replaced elements
// because their widths, heights, and margins are calculated in the same
// way.
case cssom::KeywordValue::kInline:
case cssom::KeywordValue::kInlineBlock:
replaced_box_ = make_scoped_refptr(new InlineLevelReplacedBox(
css_computed_style_declaration_, replace_image_cb_, paragraph_,
text_position_, maybe_intrinsic_width_, maybe_intrinsic_height_,
maybe_intrinsic_ratio_, context_->used_style_provider,
context_->layout_stat_tracker));
break;
// The element generates no boxes and has no effect on layout.
case cssom::KeywordValue::kNone:
// Leave |replaced_box_| NULL.
break;
case cssom::KeywordValue::kAbsolute:
case cssom::KeywordValue::kAlternate:
case cssom::KeywordValue::kAlternateReverse:
case cssom::KeywordValue::kAuto:
case cssom::KeywordValue::kBackwards:
case cssom::KeywordValue::kBaseline:
case cssom::KeywordValue::kBoth:
case cssom::KeywordValue::kBottom:
case cssom::KeywordValue::kBreakWord:
case cssom::KeywordValue::kCenter:
case cssom::KeywordValue::kClip:
case cssom::KeywordValue::kContain:
case cssom::KeywordValue::kCover:
case cssom::KeywordValue::kCurrentColor:
case cssom::KeywordValue::kCursive:
case cssom::KeywordValue::kEllipsis:
case cssom::KeywordValue::kEnd:
case cssom::KeywordValue::kFantasy:
case cssom::KeywordValue::kForwards:
case cssom::KeywordValue::kFixed:
case cssom::KeywordValue::kHidden:
case cssom::KeywordValue::kInfinite:
case cssom::KeywordValue::kInherit:
case cssom::KeywordValue::kInitial:
case cssom::KeywordValue::kLeft:
case cssom::KeywordValue::kLineThrough:
case cssom::KeywordValue::kMiddle:
case cssom::KeywordValue::kMonospace:
case cssom::KeywordValue::kNoRepeat:
case cssom::KeywordValue::kNormal:
case cssom::KeywordValue::kNoWrap:
case cssom::KeywordValue::kPre:
case cssom::KeywordValue::kPreLine:
case cssom::KeywordValue::kPreWrap:
case cssom::KeywordValue::kRelative:
case cssom::KeywordValue::kRepeat:
case cssom::KeywordValue::kReverse:
case cssom::KeywordValue::kRight:
case cssom::KeywordValue::kSansSerif:
case cssom::KeywordValue::kSerif:
case cssom::KeywordValue::kSolid:
case cssom::KeywordValue::kStart:
case cssom::KeywordValue::kStatic:
case cssom::KeywordValue::kTop:
case cssom::KeywordValue::kUppercase:
case cssom::KeywordValue::kVisible:
default:
NOTREACHED();
break;
}
}
} // namespace
void BoxGenerator::VisitVideoElement(dom::HTMLVideoElement* video_element) {
// For video elements, create a replaced box.
// A replaced box is formatted as an atomic inline element. It is treated
// directionally as a neutral character and its line breaking behavior is
// equivalent to that of the Object Replacement Character.
// https://www.w3.org/TR/CSS21/visuren.html#inline-boxes
// https://www.w3.org/TR/CSS21/visuren.html#propdef-unicode-bidi
// https://www.w3.org/TR/css3-text/#line-break-details
int32 text_position =
(*paragraph_)->AppendCodePoint(
Paragraph::kObjectReplacementCharacterCodePoint);
// Unlike in Chromium, we do not set the intrinsic width, height, or ratio
// based on the video frame. This allows to avoid relayout while playing
// adaptive videos.
ReplacedBoxGenerator replaced_box_generator(
video_element->css_computed_style_declaration(),
video_element->GetVideoFrameProvider()
? base::Bind(GetVideoFrame, video_element->GetVideoFrameProvider())
: ReplacedBox::ReplaceImageCB(),
*paragraph_, text_position, base::nullopt, base::nullopt, base::nullopt,
context_);
video_element->computed_style()->display()->Accept(&replaced_box_generator);
scoped_refptr<ReplacedBox> replaced_box =
replaced_box_generator.replaced_box();
if (replaced_box == NULL) {
// The element with "display: none" generates no boxes and has no effect
// on layout. Descendant elements do not generate any boxes either.
// This behavior cannot be overridden by setting the "display" property on
// the descendants.
// https://www.w3.org/TR/CSS21/visuren.html#display-prop
return;
}
#ifdef COBALT_BOX_DUMP_ENABLED
replaced_box->SetGeneratingNode(video_element);
#endif // COBALT_BOX_DUMP_ENABLED
boxes_.push_back(replaced_box);
// The content of replaced elements is not considered in the CSS rendering
// model.
// https://www.w3.org/TR/CSS21/conform.html#replaced-element
}
void BoxGenerator::VisitBrElement(dom::HTMLBRElement* br_element) {
// If the br element has "display: none", then it has no effect on the layout.
if (br_element->computed_style()->display() ==
cssom::KeywordValue::GetNone()) {
return;
}
DCHECK(*paragraph_);
int32 text_position = (*paragraph_)->GetTextEndPosition();
scoped_refptr<TextBox> br_text_box =
new TextBox(br_element->css_computed_style_declaration(), *paragraph_,
text_position, text_position, true,
context_->used_style_provider, context_->layout_stat_tracker);
// Add a line feed code point to the paragraph to signify the new line for
// the line breaking and bidirectional algorithms.
(*paragraph_)->AppendCodePoint(Paragraph::kLineFeedCodePoint);
#ifdef COBALT_BOX_DUMP_ENABLED
br_text_box->SetGeneratingNode(br_element);
#endif // COBALT_BOX_DUMP_ENABLED
boxes_.push_back(br_text_box);
}
namespace {
class ContainerBoxGenerator : public cssom::NotReachedPropertyValueVisitor {
public:
enum CloseParagraph {
kDoNotCloseParagraph,
kCloseParagraph,
};
ContainerBoxGenerator(dom::Directionality directionality,
const scoped_refptr<cssom::CSSComputedStyleDeclaration>&
css_computed_style_declaration,
scoped_refptr<Paragraph>* paragraph,
const BoxGenerator::Context* context)
: directionality_(directionality),
css_computed_style_declaration_(css_computed_style_declaration),
context_(context),
has_scoped_directional_embedding_(false),
paragraph_(paragraph),
paragraph_scoped_(false) {}
~ContainerBoxGenerator();
void VisitKeyword(cssom::KeywordValue* keyword) OVERRIDE;
const scoped_refptr<ContainerBox>& container_box() { return container_box_; }
private:
void CreateScopedParagraph(CloseParagraph close_prior_paragraph);
const dom::Directionality directionality_;
const scoped_refptr<cssom::CSSComputedStyleDeclaration>
css_computed_style_declaration_;
const BoxGenerator::Context* context_;
// If a directional embedding was added to the paragraph by this container box
// and needs to be popped in the destructor:
// http://unicode.org/reports/tr9/#Explicit_Directional_Embeddings
bool has_scoped_directional_embedding_;
scoped_refptr<Paragraph>* paragraph_;
scoped_refptr<Paragraph> prior_paragraph_;
bool paragraph_scoped_;
scoped_refptr<ContainerBox> container_box_;
};
ContainerBoxGenerator::~ContainerBoxGenerator() {
// If there's a scoped directional embedding, then it needs to popped from
// the paragraph so that this box does not impact the directionality of later
// boxes in the paragraph.
// http://unicode.org/reports/tr9/#Terminating_Explicit_Directional_Embeddings_and_Overrides
if (has_scoped_directional_embedding_) {
(*paragraph_)
->AppendCodePoint(
Paragraph::kPopDirectionalFormattingCharacterCodePoint);
}
if (paragraph_scoped_) {
(*paragraph_)->Close();
// If the prior paragraph was closed, then replace it with a new paragraph
// that has the same direction as the previous one. Otherwise, restore the
// prior one.
if (prior_paragraph_->IsClosed()) {
*paragraph_ = new Paragraph(
prior_paragraph_->GetLocale(), prior_paragraph_->GetBaseDirection(),
prior_paragraph_->GetDirectionalEmbeddingStack(),
context_->line_break_iterator, context_->character_break_iterator);
} else {
*paragraph_ = prior_paragraph_;
}
}
}
void ContainerBoxGenerator::VisitKeyword(cssom::KeywordValue* keyword) {
// See https://www.w3.org/TR/CSS21/visuren.html#display-prop.
switch (keyword->value()) {
// Generate a block-level block container box.
case cssom::KeywordValue::kBlock:
// The block ends the current paragraph and begins a new one that ends
// with the block, so close the current paragraph, and create a new
// paragraph that will close when the container box generator is
// destroyed.
CreateScopedParagraph(kCloseParagraph);
container_box_ = make_scoped_refptr(new BlockLevelBlockContainerBox(
css_computed_style_declaration_, (*paragraph_)->GetBaseDirection(),
context_->used_style_provider, context_->layout_stat_tracker));
break;
// Generate one or more inline boxes. Note that more inline boxes may be
// generated when the original inline box is split due to participation
// in the formatting context.
case cssom::KeywordValue::kInline:
// If the creating HTMLElement had an explicit directionality, then append
// a directional embedding to the paragraph. This will be popped from the
// paragraph, when the ContainerBoxGenerator goes out of scope.
// https://dev.w3.org/html5/spec-preview/global-attributes.html#the-directionality
// http://unicode.org/reports/tr9/#Explicit_Directional_Embeddings
if (directionality_ == dom::kLeftToRightDirectionality) {
has_scoped_directional_embedding_ = true;
(*paragraph_)->AppendCodePoint(Paragraph::kLeftToRightEmbedCodePoint);
} else if (directionality_ == dom::kRightToLeftDirectionality) {
has_scoped_directional_embedding_ = true;
(*paragraph_)->AppendCodePoint(Paragraph::kRightToLeftEmbedCodePoint);
}
// If the paragraph has not started yet, then add a no-break space to it,
// thereby starting the paragraph without providing a wrappable location,
// as the line should never wrap at the start of text.
// http://unicode.org/reports/tr14/#BreakingRules
//
// Starting the paragraph ensures that subsequent text nodes create text
// boxes, even when they consist of only collapsible white-space. This is
// necessary because empty inline container boxes can justify a line's
// existence if they have a non-zero margin, border or padding, which
// means that the collapsible white-space is potentially wrappable
// regardless of whether any intervening text is added to the paragraph.
// Not creating the collapsible text box in this case would incorrectly
// eliminate a wrappable location from the line.
if ((*paragraph_)->GetTextEndPosition() == 0) {
(*paragraph_)->AppendCodePoint(Paragraph::kNoBreakSpaceCodePoint);
}
container_box_ = make_scoped_refptr(new InlineContainerBox(
css_computed_style_declaration_, context_->used_style_provider,
context_->layout_stat_tracker));
break;
// Generate an inline-level block container box. The inside of
// an inline-block is formatted as a block box, and the element itself
// is formatted as an atomic inline-level box.
// https://www.w3.org/TR/CSS21/visuren.html#inline-boxes
case cssom::KeywordValue::kInlineBlock: {
// An inline block is is treated directionally as a neutral character and
// its line breaking behavior is equivalent to that of the Object
// Replacement Character.
// https://www.w3.org/TR/CSS21/visuren.html#propdef-unicode-bidi
// https://www.w3.org/TR/css3-text/#line-break-details
int32 text_position =
(*paragraph_)->AppendCodePoint(
Paragraph::kObjectReplacementCharacterCodePoint);
scoped_refptr<Paragraph> prior_paragraph = *paragraph_;
// The inline block creates a new paragraph, which the old paragraph
// flows around. Create a new paragraph, which will close with the end
// of the inline block. However, do not close the old paragraph, because
// it will continue once the scope of the inline block ends.
CreateScopedParagraph(kDoNotCloseParagraph);
container_box_ = make_scoped_refptr(new InlineLevelBlockContainerBox(
css_computed_style_declaration_, (*paragraph_)->GetBaseDirection(),
prior_paragraph, text_position, context_->used_style_provider,
context_->layout_stat_tracker));
} break;
// The element generates no boxes and has no effect on layout.
case cssom::KeywordValue::kNone:
// Leave |container_box_| NULL.
break;
case cssom::KeywordValue::kAbsolute:
case cssom::KeywordValue::kAlternate:
case cssom::KeywordValue::kAlternateReverse:
case cssom::KeywordValue::kAuto:
case cssom::KeywordValue::kBackwards:
case cssom::KeywordValue::kBaseline:
case cssom::KeywordValue::kBoth:
case cssom::KeywordValue::kBottom:
case cssom::KeywordValue::kBreakWord:
case cssom::KeywordValue::kCenter:
case cssom::KeywordValue::kContain:
case cssom::KeywordValue::kCover:
case cssom::KeywordValue::kCurrentColor:
case cssom::KeywordValue::kCursive:
case cssom::KeywordValue::kClip:
case cssom::KeywordValue::kEllipsis:
case cssom::KeywordValue::kEnd:
case cssom::KeywordValue::kFantasy:
case cssom::KeywordValue::kFixed:
case cssom::KeywordValue::kForwards:
case cssom::KeywordValue::kHidden:
case cssom::KeywordValue::kInfinite:
case cssom::KeywordValue::kInherit:
case cssom::KeywordValue::kInitial:
case cssom::KeywordValue::kLeft:
case cssom::KeywordValue::kLineThrough:
case cssom::KeywordValue::kMiddle:
case cssom::KeywordValue::kMonospace:
case cssom::KeywordValue::kNoRepeat:
case cssom::KeywordValue::kNormal:
case cssom::KeywordValue::kNoWrap:
case cssom::KeywordValue::kPre:
case cssom::KeywordValue::kPreLine:
case cssom::KeywordValue::kPreWrap:
case cssom::KeywordValue::kRelative:
case cssom::KeywordValue::kRepeat:
case cssom::KeywordValue::kReverse:
case cssom::KeywordValue::kRight:
case cssom::KeywordValue::kSansSerif:
case cssom::KeywordValue::kSerif:
case cssom::KeywordValue::kSolid:
case cssom::KeywordValue::kStart:
case cssom::KeywordValue::kStatic:
case cssom::KeywordValue::kTop:
case cssom::KeywordValue::kUppercase:
case cssom::KeywordValue::kVisible:
default:
NOTREACHED();
break;
}
}
void ContainerBoxGenerator::CreateScopedParagraph(
CloseParagraph close_prior_paragraph) {
DCHECK(!paragraph_scoped_);
paragraph_scoped_ = true;
prior_paragraph_ = *paragraph_;
// Determine the base direction of the new paragraph based upon the
// directionality of the creating HTMLElement. If there was no explicit
// directionality, then it is based upon the prior paragraph, meaning that
// it is inherited from the parent element.
// https://dev.w3.org/html5/spec-preview/global-attributes.html#the-directionality
BaseDirection base_direction;
if (directionality_ == dom::kLeftToRightDirectionality) {
base_direction = kLeftToRightBaseDirection;
} else if (directionality_ == dom::kRightToLeftDirectionality) {
base_direction = kRightToLeftBaseDirection;
} else {
base_direction = prior_paragraph_->GetDirectionalEmbeddingStackDirection();
}
if (close_prior_paragraph == kCloseParagraph) {
prior_paragraph_->Close();
}
*paragraph_ = new Paragraph(prior_paragraph_->GetLocale(), base_direction,
Paragraph::DirectionalEmbeddingStack(),
context_->line_break_iterator,
context_->character_break_iterator);
}
} // namespace
void BoxGenerator::AppendChildBoxToLine(const scoped_refptr<Box>& child_box) {
// When an inline box contains an in-flow block-level box, the inline box
// (and its inline ancestors within the same block container box*) are
// broken around the block-level box, splitting the inline box into two
// boxes (even if either side is empty), one on each side of
// the block-level box. The line boxes before the break and after
// the break are enclosed in anonymous block boxes, and the block-level
// box becomes a sibling of those anonymous boxes.
// https://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level
//
// * CSS 2.1 says "the same line box" but line boxes are not real boxes
// in Cobalt, see |LineBox| for details.
ContainerBox* last_container_box =
base::polymorphic_downcast<ContainerBox*>(boxes_.back().get());
if (!last_container_box->TryAddChild(child_box)) {
scoped_refptr<ContainerBox> next_container_box =
last_container_box->TrySplitAtEnd();
DCHECK(next_container_box);
// Attempt to add the box to the next container before adding it to the top
// level. In the case where a line break was blocking the add in the last
// container, the child should successfully go into the next container.
if (!next_container_box->TryAddChild(child_box)) {
boxes_.push_back(child_box);
}
boxes_.push_back(next_container_box);
}
}
namespace {
class ContentProvider : public cssom::NotReachedPropertyValueVisitor {
public:
ContentProvider() : is_element_generated_(false) {}
const std::string& content_string() const { return content_string_; }
bool is_element_generated() const { return is_element_generated_; }
void VisitString(cssom::StringValue* string_value) OVERRIDE {
content_string_ = string_value->value();
is_element_generated_ = true;
}
void VisitKeyword(cssom::KeywordValue* keyword) OVERRIDE {
switch (keyword->value()) {
case cssom::KeywordValue::kNone:
case cssom::KeywordValue::kNormal:
// The pseudo-element is not generated.
// https://www.w3.org/TR/CSS21/generate.html#propdef-content
is_element_generated_ = false;
break;
case cssom::KeywordValue::kAbsolute:
case cssom::KeywordValue::kAlternate:
case cssom::KeywordValue::kAlternateReverse:
case cssom::KeywordValue::kAuto:
case cssom::KeywordValue::kBackwards:
case cssom::KeywordValue::kBaseline:
case cssom::KeywordValue::kBlock:
case cssom::KeywordValue::kBoth:
case cssom::KeywordValue::kBottom:
case cssom::KeywordValue::kBreakWord:
case cssom::KeywordValue::kCenter:
case cssom::KeywordValue::kClip:
case cssom::KeywordValue::kContain:
case cssom::KeywordValue::kCover:
case cssom::KeywordValue::kCurrentColor:
case cssom::KeywordValue::kCursive:
case cssom::KeywordValue::kEllipsis:
case cssom::KeywordValue::kEnd:
case cssom::KeywordValue::kFantasy:
case cssom::KeywordValue::kFixed:
case cssom::KeywordValue::kForwards:
case cssom::KeywordValue::kHidden:
case cssom::KeywordValue::kInfinite:
case cssom::KeywordValue::kInherit:
case cssom::KeywordValue::kInitial:
case cssom::KeywordValue::kInline:
case cssom::KeywordValue::kInlineBlock:
case cssom::KeywordValue::kLeft:
case cssom::KeywordValue::kLineThrough:
case cssom::KeywordValue::kMiddle:
case cssom::KeywordValue::kMonospace:
case cssom::KeywordValue::kNoRepeat:
case cssom::KeywordValue::kNoWrap:
case cssom::KeywordValue::kPre:
case cssom::KeywordValue::kPreLine:
case cssom::KeywordValue::kPreWrap:
case cssom::KeywordValue::kRelative:
case cssom::KeywordValue::kRepeat:
case cssom::KeywordValue::kReverse:
case cssom::KeywordValue::kRight:
case cssom::KeywordValue::kSansSerif:
case cssom::KeywordValue::kSerif:
case cssom::KeywordValue::kSolid:
case cssom::KeywordValue::kStart:
case cssom::KeywordValue::kStatic:
case cssom::KeywordValue::kTop:
case cssom::KeywordValue::kUppercase:
case cssom::KeywordValue::kVisible:
default:
NOTREACHED();
}
}
private:
std::string content_string_;
bool is_element_generated_;
};
bool HasOnlyColorPropertyAnimations(
const scoped_refptr<const web_animations::AnimationSet>& animations) {
const web_animations::AnimationSet::InternalSet& animation_set =
animations->animations();
if (animation_set.empty()) {
return false;
}
for (web_animations::AnimationSet::InternalSet::const_iterator iter =
animation_set.begin();
iter != animation_set.end(); ++iter) {
const web_animations::KeyframeEffectReadOnly* keyframe_effect =
base::polymorphic_downcast<
const web_animations::KeyframeEffectReadOnly*>(
(*iter)->effect().get());
if (!keyframe_effect->data().IsOnlyPropertyAnimated(
cssom::kColorProperty)) {
return false;
}
}
return true;
}
} // namespace
void BoxGenerator::AppendPseudoElementToLine(
dom::HTMLElement* html_element,
dom::PseudoElementType pseudo_element_type) {
// Add boxes with generated content from :before or :after pseudo elements to
// the line.
// https://www.w3.org/TR/CSS21/generate.html#before-after-content
dom::PseudoElement* pseudo_element =
html_element->pseudo_element(pseudo_element_type);
if (pseudo_element) {
ContainerBoxGenerator pseudo_element_box_generator(
dom::kNoExplicitDirectionality,
pseudo_element->css_computed_style_declaration(), paragraph_, context_);
pseudo_element->computed_style()->display()->Accept(
&pseudo_element_box_generator);
scoped_refptr<ContainerBox> pseudo_element_box =
pseudo_element_box_generator.container_box();
// A pseudo element with "display: none" generates no boxes and has no
// effect on layout.
if (pseudo_element_box != NULL) {
// Generate the box(es) to be added to the associated html element, using
// the computed style of the pseudo element.
// The generated content is a text node with the string value of the
// 'content' property.
ContentProvider content_provider;
pseudo_element->computed_style()->content()->Accept(&content_provider);
if (content_provider.is_element_generated()) {
scoped_refptr<dom::Text> child_node(new dom::Text(
html_element->node_document(), content_provider.content_string()));
// In the case where the pseudo element has no color property of its
// own, but is directly inheriting a color property from its parent html
// element, we use the parent's animations if the pseudo element has
// none and the parent has only color property animations. This allows
// the child text boxes to animate properly and fixes bugs, while
// keeping the impact of the fix as small as possible to minimize the
// risk of introducing new bugs.
// TODO: Remove this logic when support for inheriting
// animations on inherited properties is added.
bool use_html_element_animations =
!pseudo_element->computed_style()->IsDeclared(
cssom::kColorProperty) &&
html_element->computed_style()->IsDeclared(cssom::kColorProperty) &&
pseudo_element->css_computed_style_declaration()
->animations()
->IsEmpty() &&
HasOnlyColorPropertyAnimations(
html_element->css_computed_style_declaration()->animations());
BoxGenerator child_box_generator(
pseudo_element->css_computed_style_declaration(),
use_html_element_animations ? html_element->animations()
: pseudo_element->animations(),
paragraph_, context_);
child_node->Accept(&child_box_generator);
const Boxes& child_boxes = child_box_generator.boxes();
for (Boxes::const_iterator child_box_iterator = child_boxes.begin();
child_box_iterator != child_boxes.end(); ++child_box_iterator) {
if (!pseudo_element_box->TryAddChild(*child_box_iterator)) {
return;
}
}
// Add the box(es) from the pseudo element to the associated element.
AppendChildBoxToLine(pseudo_element_box);
}
}
}
}
namespace {
scoped_refptr<cssom::CSSComputedStyleDeclaration> StripBackground(
const scoped_refptr<cssom::CSSComputedStyleDeclaration>& style) {
scoped_refptr<cssom::CSSComputedStyleDeclaration> new_style(
new cssom::CSSComputedStyleDeclaration());
new_style->set_animations(style->animations());
scoped_refptr<cssom::CSSComputedStyleData> new_data(
new cssom::CSSComputedStyleData());
new_data->AssignFrom(*style->data());
new_data->SetPropertyValue(cssom::kBackgroundColorProperty, NULL);
new_data->SetPropertyValue(cssom::kBackgroundImageProperty, NULL);
new_style->set_data(new_data);
return new_style;
}
} // namespace
void BoxGenerator::VisitNonReplacedElement(dom::HTMLElement* html_element) {
const scoped_refptr<cssom::CSSComputedStyleDeclaration>& element_style(
html_element->css_computed_style_declaration());
ContainerBoxGenerator container_box_generator(
html_element->directionality(),
html_element == context_->ignore_background_element
? StripBackground(element_style)
: element_style,
paragraph_, context_);
html_element->computed_style()->display()->Accept(&container_box_generator);
scoped_refptr<ContainerBox> container_box_before_split =
container_box_generator.container_box();
if (container_box_before_split == NULL) {
// The element with "display: none" generates no boxes and has no effect
// on layout. Descendant elements do not generate any boxes either.
// This behavior cannot be overridden by setting the "display" property on
// the descendants.
// https://www.w3.org/TR/CSS21/visuren.html#display-prop
return;
}
#ifdef COBALT_BOX_DUMP_ENABLED
container_box_before_split->SetGeneratingNode(html_element);
#endif // COBALT_BOX_DUMP_ENABLED
boxes_.push_back(container_box_before_split);
AppendPseudoElementToLine(html_element, dom::kBeforePseudoElementType);
// Generate child boxes.
for (scoped_refptr<dom::Node> child_node = html_element->first_child();
child_node; child_node = child_node->next_sibling()) {
BoxGenerator child_box_generator(
html_element->css_computed_style_declaration(),
html_element->css_computed_style_declaration()->animations(),
paragraph_, context_);
child_node->Accept(&child_box_generator);
const Boxes& child_boxes = child_box_generator.boxes();
for (Boxes::const_iterator child_box_iterator = child_boxes.begin();
child_box_iterator != child_boxes.end(); ++child_box_iterator) {
AppendChildBoxToLine(*child_box_iterator);
}
}
AppendPseudoElementToLine(html_element, dom::kAfterPseudoElementType);
}
void BoxGenerator::Visit(dom::CDATASection* /* cdata_section */) {}
void BoxGenerator::Visit(dom::Comment* /*comment*/) {}
void BoxGenerator::Visit(dom::Document* /*document*/) { NOTREACHED(); }
void BoxGenerator::Visit(dom::DocumentType* /*document_type*/) { NOTREACHED(); }
// Append the text from the text node to the text paragraph and create the
// node's initial text box. The text box has indices that map to the paragraph,
// which allows it to retrieve its underlying text. Initially, a single text box
// is created that encompasses the entire node.
// Prior to layout, the paragraph applies the Unicode bidirectional algorithm
// to its text (http://www.unicode.org/reports/tr9/) and causes the text boxes
// referencing it to split at level runs.
//
// During layout, the text boxes are potentially split further, as the paragraph
// determines line breaking locations for its text at soft wrap opportunities
// (https://www.w3.org/TR/css-text-3/#soft-wrap-opportunity) according to the
// Unicode line breaking algorithm (http://www.unicode.org/reports/tr14/).
void BoxGenerator::Visit(dom::Text* text) {
scoped_refptr<cssom::CSSComputedStyleDeclaration>
css_computed_style_declaration = new cssom::CSSComputedStyleDeclaration();
css_computed_style_declaration->set_data(GetComputedStyleOfAnonymousBox(
parent_css_computed_style_declaration_->data()));
// Copy the animations from the parent.
css_computed_style_declaration->set_animations(parent_animations_);
DCHECK(text);
DCHECK(css_computed_style_declaration->data());
const std::string& original_text = text->text();
if (original_text.empty()) {
return;
}
const scoped_refptr<cssom::PropertyValue>& white_space_property =
css_computed_style_declaration->data()->white_space();
bool should_preserve_segment_breaks =
!DoesCollapseSegmentBreaks(white_space_property);
bool should_collapse_white_space =
DoesCollapseWhiteSpace(white_space_property);
bool should_prevent_text_wrapping =
!DoesAllowTextWrapping(white_space_property);
// Loop until the entire text is consumed. If the white-space property does
// not have a value of "pre" or "pre-wrap" then the entire text will be
// processed the first time through the loop; otherwise, the text will be
// split at newline sequences.
size_t start_index = 0;
while (start_index < original_text.size()) {
size_t end_index;
size_t newline_sequence_length;
// Phase I: Segment Break Transformation Rules
// https://www.w3.org/TR/css3-text/#line-break-transform
bool generates_newline = false;
if (should_preserve_segment_breaks) {
generates_newline = FindNextNewlineSequence(
original_text, start_index, &end_index, &newline_sequence_length);
} else {
end_index = original_text.size();
newline_sequence_length = 0;
}
std::string modifiable_text =
original_text.substr(start_index, end_index - start_index);
// Phase I: Collapsing and Transformation
// https://www.w3.org/TR/css3-text/#white-space-phase-1
if (should_collapse_white_space) {
CollapseWhiteSpace(&modifiable_text);
// If the paragraph hasn't been started yet and the text only consists of
// a collapsible space, then return without creating the box. The leading
// spaces in a line box are collapsed, so this box would be collapsed
// away during the layout.
if ((*paragraph_)->GetTextEndPosition() == 0 && modifiable_text == " ") {
return;
}
}
Paragraph::TextTransform transform;
if (css_computed_style_declaration->data()->text_transform() ==
cssom::KeywordValue::GetUppercase()) {
transform = Paragraph::kUppercaseTextTransform;
} else {
transform = Paragraph::kNoTextTransform;
}
DCHECK(*paragraph_);
int32 text_start_position =
(*paragraph_)->AppendUtf8String(modifiable_text, transform);
int32 text_end_position = (*paragraph_)->GetTextEndPosition();
boxes_.push_back(new TextBox(
css_computed_style_declaration, *paragraph_, text_start_position,
text_end_position, generates_newline, context_->used_style_provider,
context_->layout_stat_tracker));
// Newline sequences should be transformed into a preserved line feed.
// https://www.w3.org/TR/css3-text/#line-break-transform
if (generates_newline) {
(*paragraph_)->AppendCodePoint(Paragraph::kLineFeedCodePoint);
}
start_index = end_index + newline_sequence_length;
}
// If the white-space style prevents text wrapping and the text ends in a
// space, then add a no-break space to the paragraph, so that the last space
// will be treated as a no-break space when determining if wrapping can occur
// before the subsequent box.
//
// While CSS3 gives little direction to the user agent as to what should occur
// in this case, this is guidance given by CSS2, which states that "any
// sequence of spaces (U+0020) unbroken by an element boundary is treated as a
// sequence of non-breaking spaces." Furthermore, this matches the behavior of
// WebKit, Firefox, and IE.
// https://www.w3.org/TR/css-text-3/#white-space-phase-1
// https://www.w3.org/TR/CSS2/text.html#white-space-model
if (should_prevent_text_wrapping &&
original_text[original_text.size() - 1] == ' ') {
(*paragraph_)->AppendCodePoint(Paragraph::kNoBreakSpaceCodePoint);
}
}
} // namespace layout
} // namespace cobalt