2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include "core/rendering/TextAutosizer.h"
34 #include "core/dom/Document.h"
35 #include "core/frame/FrameHost.h"
36 #include "core/frame/FrameView.h"
37 #include "core/frame/LocalFrame.h"
38 #include "core/frame/PinchViewport.h"
39 #include "core/frame/Settings.h"
40 #include "core/html/HTMLTextAreaElement.h"
41 #include "core/page/Page.h"
42 #include "core/rendering/InlineIterator.h"
43 #include "core/rendering/RenderBlock.h"
44 #include "core/rendering/RenderListItem.h"
45 #include "core/rendering/RenderListMarker.h"
46 #include "core/rendering/RenderTableCell.h"
47 #include "core/rendering/RenderView.h"
49 #ifdef AUTOSIZING_DOM_DEBUG_INFO
50 #include "core/dom/ExecutionContextTask.h"
55 #ifdef AUTOSIZING_DOM_DEBUG_INFO
56 class WriteDebugInfoTask : public ExecutionContextTask {
58 WriteDebugInfoTask(PassRefPtrWillBeRawPtr<Element> element, AtomicString value)
64 virtual void performTask(ExecutionContext*)
66 m_element->setAttribute("data-autosizing", m_value, ASSERT_NO_EXCEPTION);
70 RefPtrWillBePersistent<Element> m_element;
74 static void writeDebugInfo(RenderObject* renderer, const AtomicString& output)
76 Node* node = renderer->node();
79 if (node->isDocumentNode())
80 node = toDocument(node)->documentElement();
81 if (!node->isElementNode())
83 node->document().postTask(adoptPtr(new WriteDebugInfoTask(toElement(node), output)));
86 void TextAutosizer::writeClusterDebugInfo(Cluster* cluster)
88 String explanation = "";
89 if (cluster->m_flags & SUPPRESSING) {
90 explanation = "[suppressed]";
91 } else if (!(cluster->m_flags & (INDEPENDENT | WIDER_OR_NARROWER))) {
92 explanation = "[inherited]";
93 } else if (cluster->m_supercluster) {
94 explanation = "[supercluster]";
95 } else if (!clusterHasEnoughTextToAutosize(cluster)) {
96 explanation = "[insufficient-text]";
98 const RenderBlock* widthProvider = clusterWidthProvider(cluster->m_root);
99 if (cluster->m_hasTableAncestor && cluster->m_multiplier < multiplierFromBlock(widthProvider)) {
100 explanation = "[table-ancestor-limited]";
102 explanation = String::format("[from width %d of %s]",
103 static_cast<int>(widthFromBlock(widthProvider)), widthProvider->debugName().utf8().data());
106 String pageInfo = "";
107 if (cluster->m_root->isRenderView()) {
108 pageInfo = String::format("; pageinfo: bm %f * (lw %d / fw %d)",
109 m_pageInfo.m_baseMultiplier, m_pageInfo.m_layoutWidth, m_pageInfo.m_frameWidth);
111 float multiplier = cluster->m_flags & SUPPRESSING ? 1.0 : cluster->m_multiplier;
112 writeDebugInfo(const_cast<RenderBlock*>(cluster->m_root),
113 AtomicString(String::format("cluster: %f %s%s", multiplier,
114 explanation.utf8().data(), pageInfo.utf8().data())));
118 static const RenderObject* parentElementRenderer(const RenderObject* renderer)
120 // At style recalc, the renderer's parent may not be attached,
121 // so we need to obtain this from the DOM tree.
122 const Node* node = renderer->node();
126 // FIXME: This should be using NodeRenderingTraversal::parent().
127 if (Element* parent = node->parentElement())
128 return parent->renderer();
132 static bool isNonTextAreaFormControl(const RenderObject* renderer)
134 const Node* node = renderer ? renderer->node() : 0;
135 if (!node || !node->isElementNode())
137 const Element* element = toElement(node);
139 return (element->isFormControlElement() && !isHTMLTextAreaElement(element));
142 static bool isPotentialClusterRoot(const RenderObject* renderer)
144 // "Potential cluster roots" are the smallest unit for which we can
145 // enable/disable text autosizing.
146 // - Must not be inline, as different multipliers on one line looks terrible.
147 // Exceptions are inline-block and alike elements (inline-table, -webkit-inline-*),
148 // as they often contain entire multi-line columns of text.
149 // - Must not be normal list items, as items in the same list should look
150 // consistent, unless they are floating or position:absolute/fixed.
151 Node* node = renderer->generatingNode();
152 if (node && !node->hasChildren())
154 if (!renderer->isRenderBlock())
156 if (renderer->isInline() && !renderer->style()->isDisplayReplacedType())
158 if (renderer->isListItem())
159 return (renderer->isFloating() || renderer->isOutOfFlowPositioned());
164 static bool isIndependentDescendant(const RenderBlock* renderer)
166 ASSERT(isPotentialClusterRoot(renderer));
168 RenderBlock* containingBlock = renderer->containingBlock();
169 return renderer->isRenderView()
170 || renderer->isFloating()
171 || renderer->isOutOfFlowPositioned()
172 || renderer->isTableCell()
173 || renderer->isTableCaption()
174 || renderer->isFlexibleBoxIncludingDeprecated()
175 || renderer->hasColumns()
176 || (containingBlock && containingBlock->isHorizontalWritingMode() != renderer->isHorizontalWritingMode())
177 || renderer->style()->isDisplayReplacedType()
178 || renderer->isTextArea()
179 || renderer->style()->userModify() != READ_ONLY;
182 static bool blockIsRowOfLinks(const RenderBlock* block)
184 // A "row of links" is a block for which:
185 // 1. It does not contain non-link text elements longer than 3 characters
186 // 2. It contains a minimum of 3 inline links and all links should
187 // have the same specified font size.
188 // 3. It should not contain <br> elements.
189 // 4. It should contain only inline elements unless they are containers,
190 // children of link elements or children of sub-containers.
192 RenderObject* renderer = block->firstChild();
193 float matchingFontSize = -1;
196 if (!isPotentialClusterRoot(renderer)) {
197 if (renderer->isText() && toRenderText(renderer)->text().stripWhiteSpace().length() > 3)
199 if (!renderer->isInline() || renderer->isBR())
202 if (renderer->style()->isLink()) {
204 if (matchingFontSize < 0)
205 matchingFontSize = renderer->style()->specifiedFontSize();
206 else if (matchingFontSize != renderer->style()->specifiedFontSize())
209 // Skip traversing descendants of the link.
210 renderer = renderer->nextInPreOrderAfterChildren(block);
213 renderer = renderer->nextInPreOrder(block);
216 return (linkCount >= 3);
219 static bool blockHeightConstrained(const RenderBlock* block)
221 // FIXME: Propagate constrainedness down the tree, to avoid inefficiently walking back up from each box.
222 // FIXME: This code needs to take into account vertical writing modes.
223 // FIXME: Consider additional heuristics, such as ignoring fixed heights if the content is already overflowing before autosizing kicks in.
224 for (; block; block = block->containingBlock()) {
225 RenderStyle* style = block->style();
226 if (style->overflowY() >= OSCROLL)
228 if (style->height().isSpecified() || style->maxHeight().isSpecified() || block->isOutOfFlowPositioned()) {
229 // Some sites (e.g. wikipedia) set their html and/or body elements to height:100%,
230 // without intending to constrain the height of the content within them.
231 return !block->isDocumentElement() && !block->isBody();
233 if (block->isFloating())
239 static bool blockOrImmediateChildrenAreFormControls(const RenderBlock* block)
241 if (isNonTextAreaFormControl(block))
243 const RenderObject* renderer = block->firstChild();
245 if (isNonTextAreaFormControl(renderer))
247 renderer = renderer->nextSibling();
253 // Some blocks are not autosized even if their parent cluster wants them to.
254 static bool blockSuppressesAutosizing(const RenderBlock* block)
256 if (blockOrImmediateChildrenAreFormControls(block))
259 if (blockIsRowOfLinks(block))
262 // Don't autosize block-level text that can't wrap (as it's likely to
263 // expand sideways and break the page's layout).
264 if (!block->style()->autoWrap())
267 if (blockHeightConstrained(block))
273 static bool hasExplicitWidth(const RenderBlock* block)
275 // FIXME: This heuristic may need to be expanded to other ways a block can be wider or narrower
276 // than its parent containing block.
277 return block->style() && block->style()->width().isSpecified();
280 TextAutosizer::TextAutosizer(const Document* document)
281 : m_document(document)
282 , m_firstBlockToBeginLayout(0)
284 , m_blocksThatHaveBegunLayout()
288 , m_fingerprintMapper()
290 , m_updatePageInfoDeferred(false)
294 void TextAutosizer::record(const RenderBlock* block)
296 if (!m_pageInfo.m_settingEnabled)
299 ASSERT(!m_blocksThatHaveBegunLayout.contains(block));
301 if (!classifyBlock(block, INDEPENDENT | EXPLICIT_WIDTH))
304 if (Fingerprint fingerprint = computeFingerprint(block))
305 m_fingerprintMapper.addTentativeClusterRoot(block, fingerprint);
308 void TextAutosizer::destroy(const RenderBlock* block)
310 if (!m_pageInfo.m_settingEnabled && !m_fingerprintMapper.hasFingerprints())
313 ASSERT(!m_blocksThatHaveBegunLayout.contains(block));
315 if (m_fingerprintMapper.remove(block) && m_firstBlockToBeginLayout) {
316 // RenderBlock with a fingerprint was destroyed during layout.
317 // Clear the cluster stack and the supercluster map to avoid stale pointers.
318 // Speculative fix for http://crbug.com/369485.
319 m_firstBlockToBeginLayout = 0;
320 m_clusterStack.clear();
321 m_superclusters.clear();
325 TextAutosizer::BeginLayoutBehavior TextAutosizer::prepareForLayout(const RenderBlock* block)
328 m_blocksThatHaveBegunLayout.add(block);
331 if (!m_firstBlockToBeginLayout) {
332 m_firstBlockToBeginLayout = block;
333 prepareClusterStack(block->parent());
334 } else if (block == currentCluster()->m_root) {
335 // Ignore beginLayout on the same block twice.
336 // This can happen with paginated overflow.
340 return ContinueLayout;
343 void TextAutosizer::prepareClusterStack(const RenderObject* renderer)
347 prepareClusterStack(renderer->parent());
349 if (renderer->isRenderBlock()) {
350 const RenderBlock* block = toRenderBlock(renderer);
352 m_blocksThatHaveBegunLayout.add(block);
354 if (Cluster* cluster = maybeCreateCluster(block))
355 m_clusterStack.append(adoptPtr(cluster));
359 void TextAutosizer::beginLayout(RenderBlock* block)
361 ASSERT(shouldHandleLayout());
363 if (prepareForLayout(block) == StopLayout)
366 if (Cluster* cluster = maybeCreateCluster(block))
367 m_clusterStack.append(adoptPtr(cluster));
369 // Cells in auto-layout tables are handled separately by inflateAutoTable.
370 bool isAutoTableCell = block->isTableCell() && !toRenderTableCell(block)->table()->style()->isFixedTableLayout();
371 if (!isAutoTableCell && !m_clusterStack.isEmpty())
375 void TextAutosizer::inflateListItem(RenderListItem* listItem, RenderListMarker* listItemMarker)
377 if (!shouldHandleLayout())
379 ASSERT(listItem && listItemMarker);
381 if (prepareForLayout(listItem) == StopLayout)
384 // Force the LI to be inside the DBCAT when computing the multiplier.
385 // This guarantees that the DBCAT has entered layout, so we can ask for its width.
386 // It also makes sense because the list marker is autosized like a text node.
387 float multiplier = clusterMultiplier(currentCluster());
389 applyMultiplier(listItem, multiplier);
390 applyMultiplier(listItemMarker, multiplier);
393 void TextAutosizer::inflateAutoTable(RenderTable* table)
396 ASSERT(!table->style()->isFixedTableLayout());
397 ASSERT(table->containingBlock());
399 Cluster* cluster = currentCluster();
400 if (cluster->m_root != table)
403 // Pre-inflate cells that have enough text so that their inflated preferred widths will be used
404 // for column sizing.
405 for (RenderObject* section = table->firstChild(); section; section = section->nextSibling()) {
406 if (!section->isTableSection())
408 for (RenderTableRow* row = toRenderTableSection(section)->firstRow(); row; row = row->nextRow()) {
409 for (RenderTableCell* cell = row->firstCell(); cell; cell = cell->nextCell()) {
410 if (!cell->needsLayout())
414 inflate(cell, DescendToInnerBlocks);
421 void TextAutosizer::endLayout(RenderBlock* block)
423 ASSERT(shouldHandleLayout());
425 if (block == m_firstBlockToBeginLayout) {
426 m_firstBlockToBeginLayout = 0;
427 m_clusterStack.clear();
428 m_superclusters.clear();
429 m_stylesRetainedDuringLayout.clear();
431 m_blocksThatHaveBegunLayout.clear();
433 // Tables can create two layout scopes for the same block so the isEmpty
434 // check below is needed to guard against endLayout being called twice.
435 } else if (!m_clusterStack.isEmpty() && currentCluster()->m_root == block) {
436 m_clusterStack.removeLast();
440 float TextAutosizer::inflate(RenderObject* parent, InflateBehavior behavior, float multiplier)
442 Cluster* cluster = currentCluster();
443 bool hasTextChild = false;
445 RenderObject* child = 0;
446 if (parent->isRenderBlock() && (parent->childrenInline() || behavior == DescendToInnerBlocks))
447 child = toRenderBlock(parent)->firstChild();
448 else if (parent->isRenderInline())
449 child = toRenderInline(parent)->firstChild();
452 if (child->isText()) {
454 // We only calculate this multiplier on-demand to ensure the parent block of this text
455 // has entered layout.
457 multiplier = cluster->m_flags & SUPPRESSING ? 1.0f : clusterMultiplier(cluster);
458 applyMultiplier(child, multiplier);
459 // FIXME: Investigate why MarkOnlyThis is sufficient.
460 if (parent->isRenderInline())
461 child->setPreferredLogicalWidthsDirty(MarkOnlyThis);
462 } else if (child->isRenderInline()) {
463 multiplier = inflate(child, behavior, multiplier);
464 } else if (child->isRenderBlock() && behavior == DescendToInnerBlocks
465 && !classifyBlock(child, INDEPENDENT | EXPLICIT_WIDTH | SUPPRESSING)) {
466 multiplier = inflate(child, behavior, multiplier);
468 child = child->nextSibling();
472 applyMultiplier(parent, multiplier); // Parent handles line spacing.
473 } else if (!parent->isListItem()) {
474 // For consistency, a block with no immediate text child should always have a
475 // multiplier of 1 (except for list items which are handled in inflateListItem).
476 applyMultiplier(parent, 1);
481 bool TextAutosizer::shouldHandleLayout() const
483 return m_pageInfo.m_settingEnabled && m_pageInfo.m_pageNeedsAutosizing && !m_updatePageInfoDeferred;
486 void TextAutosizer::updatePageInfoInAllFrames()
488 ASSERT(!m_document->frame() || m_document->frame()->isMainFrame());
490 for (Frame* frame = m_document->frame(); frame; frame = frame->tree().traverseNext()) {
491 if (!frame->isLocalFrame())
493 if (TextAutosizer* textAutosizer = toLocalFrame(frame)->document()->textAutosizer())
494 textAutosizer->updatePageInfo();
498 void TextAutosizer::updatePageInfo()
500 if (m_updatePageInfoDeferred || !m_document->page() || !m_document->settings())
503 PageInfo previousPageInfo(m_pageInfo);
504 m_pageInfo.m_settingEnabled = m_document->settings()->textAutosizingEnabled();
506 if (!m_pageInfo.m_settingEnabled || m_document->printing()) {
507 m_pageInfo.m_pageNeedsAutosizing = false;
509 RenderView* renderView = m_document->renderView();
510 bool horizontalWritingMode = isHorizontalWritingMode(renderView->style()->writingMode());
512 // FIXME: With out-of-process iframes, the top frame can be remote and
513 // doesn't have sizing information. Just return if this is the case.
514 Frame* frame = m_document->frame()->tree().top();
515 if (frame->isRemoteFrame())
518 LocalFrame* mainFrame = m_document->page()->deprecatedLocalMainFrame();
519 IntSize frameSize = m_document->settings()->textAutosizingWindowSizeOverride();
520 if (frameSize.isEmpty())
521 frameSize = windowSize();
523 m_pageInfo.m_frameWidth = horizontalWritingMode ? frameSize.width() : frameSize.height();
525 IntSize layoutSize = mainFrame->view()->layoutSize();
526 m_pageInfo.m_layoutWidth = horizontalWritingMode ? layoutSize.width() : layoutSize.height();
528 // Compute the base font scale multiplier based on device and accessibility settings.
529 m_pageInfo.m_baseMultiplier = m_document->settings()->accessibilityFontScaleFactor();
530 // If the page has a meta viewport or @viewport, don't apply the device scale adjustment.
531 const ViewportDescription& viewportDescription = mainFrame->document()->viewportDescription();
532 if (!viewportDescription.isSpecifiedByAuthor()) {
533 float deviceScaleAdjustment = m_document->settings()->deviceScaleAdjustment();
534 m_pageInfo.m_baseMultiplier *= deviceScaleAdjustment;
537 m_pageInfo.m_pageNeedsAutosizing = !!m_pageInfo.m_frameWidth
538 && (m_pageInfo.m_baseMultiplier * (static_cast<float>(m_pageInfo.m_layoutWidth) / m_pageInfo.m_frameWidth) > 1.0f);
541 if (m_pageInfo.m_pageNeedsAutosizing) {
542 // If page info has changed, multipliers may have changed. Force a layout to recompute them.
543 if (m_pageInfo.m_frameWidth != previousPageInfo.m_frameWidth
544 || m_pageInfo.m_layoutWidth != previousPageInfo.m_layoutWidth
545 || m_pageInfo.m_baseMultiplier != previousPageInfo.m_baseMultiplier
546 || m_pageInfo.m_settingEnabled != previousPageInfo.m_settingEnabled)
547 setAllTextNeedsLayout();
548 } else if (previousPageInfo.m_hasAutosized) {
549 // If we are no longer autosizing the page, we won't do anything during the next layout.
550 // Set all the multipliers back to 1 now.
552 m_pageInfo.m_hasAutosized = false;
556 IntSize TextAutosizer::windowSize() const
558 Page * page = m_document->page();
560 return page->settings().pinchVirtualViewportEnabled() ?
561 page->frameHost().pinchViewport().size() :
562 page->deprecatedLocalMainFrame()->view()->unscaledVisibleContentSize(IncludeScrollbars);
565 void TextAutosizer::resetMultipliers()
567 RenderObject* renderer = m_document->renderView();
569 if (RenderStyle* style = renderer->style()) {
570 if (style->textAutosizingMultiplier() != 1)
571 applyMultiplier(renderer, 1, LayoutNeeded);
573 renderer = renderer->nextInPreOrder();
577 void TextAutosizer::setAllTextNeedsLayout()
579 RenderObject* renderer = m_document->renderView();
581 if (renderer->isText())
582 renderer->setNeedsLayoutAndFullPaintInvalidation();
583 renderer = renderer->nextInPreOrder();
587 TextAutosizer::BlockFlags TextAutosizer::classifyBlock(const RenderObject* renderer, BlockFlags mask) const
589 if (!renderer->isRenderBlock())
592 const RenderBlock* block = toRenderBlock(renderer);
593 BlockFlags flags = 0;
595 if (isPotentialClusterRoot(block)) {
596 if (mask & POTENTIAL_ROOT)
597 flags |= POTENTIAL_ROOT;
599 if ((mask & INDEPENDENT) && (isIndependentDescendant(block) || block->isTable()))
600 flags |= INDEPENDENT;
602 if ((mask & EXPLICIT_WIDTH) && hasExplicitWidth(block))
603 flags |= EXPLICIT_WIDTH;
605 if ((mask & SUPPRESSING) && blockSuppressesAutosizing(block))
606 flags |= SUPPRESSING;
611 bool TextAutosizer::clusterWouldHaveEnoughTextToAutosize(const RenderBlock* root, const RenderBlock* widthProvider)
613 Cluster hypotheticalCluster(root, classifyBlock(root), 0);
614 return clusterHasEnoughTextToAutosize(&hypotheticalCluster, widthProvider);
617 bool TextAutosizer::clusterHasEnoughTextToAutosize(Cluster* cluster, const RenderBlock* widthProvider)
619 if (cluster->m_hasEnoughTextToAutosize != UnknownAmountOfText)
620 return cluster->m_hasEnoughTextToAutosize == HasEnoughText;
622 const RenderBlock* root = cluster->m_root;
624 widthProvider = clusterWidthProvider(root);
626 // TextAreas and user-modifiable areas get a free pass to autosize regardless of text content.
627 if (root->isTextArea() || (root->style() && root->style()->userModify() != READ_ONLY)) {
628 cluster->m_hasEnoughTextToAutosize = HasEnoughText;
632 if (cluster->m_flags & SUPPRESSING) {
633 cluster->m_hasEnoughTextToAutosize = NotEnoughText;
637 // 4 lines of text is considered enough to autosize.
638 float minimumTextLengthToAutosize = widthFromBlock(widthProvider) * 4;
641 RenderObject* descendant = root->firstChild();
643 if (descendant->isRenderBlock()) {
644 if (classifyBlock(descendant, INDEPENDENT | SUPPRESSING)) {
645 descendant = descendant->nextInPreOrderAfterChildren(root);
648 } else if (descendant->isText()) {
649 // Note: Using text().stripWhiteSpace().length() instead of renderedTextLength() because
650 // the lineboxes will not be built until layout. These values can be different.
651 // Note: This is an approximation assuming each character is 1em wide.
652 length += toRenderText(descendant)->text().stripWhiteSpace().length() * descendant->style()->specifiedFontSize();
654 if (length >= minimumTextLengthToAutosize) {
655 cluster->m_hasEnoughTextToAutosize = HasEnoughText;
659 descendant = descendant->nextInPreOrder(root);
662 cluster->m_hasEnoughTextToAutosize = NotEnoughText;
666 TextAutosizer::Fingerprint TextAutosizer::getFingerprint(const RenderObject* renderer)
668 Fingerprint result = m_fingerprintMapper.get(renderer);
670 result = computeFingerprint(renderer);
671 m_fingerprintMapper.add(renderer, result);
676 TextAutosizer::Fingerprint TextAutosizer::computeFingerprint(const RenderObject* renderer)
678 Node* node = renderer->generatingNode();
679 if (!node || !node->isElementNode())
682 FingerprintSourceData data;
683 if (const RenderObject* parent = parentElementRenderer(renderer))
684 data.m_parentHash = getFingerprint(parent);
686 data.m_qualifiedNameHash = QualifiedNameHash::hash(toElement(node)->tagQName());
688 if (RenderStyle* style = renderer->style()) {
689 data.m_packedStyleProperties = style->direction();
690 data.m_packedStyleProperties |= (style->position() << 1);
691 data.m_packedStyleProperties |= (style->floating() << 4);
692 data.m_packedStyleProperties |= (style->display() << 6);
693 data.m_packedStyleProperties |= (style->width().type() << 11);
694 // packedStyleProperties effectively using 15 bits now.
696 // consider for adding: writing mode, padding.
698 data.m_width = style->width().getFloatValue();
701 // Use nodeIndex as a rough approximation of column number
702 // (it's too early to call RenderTableCell::col).
703 // FIXME: account for colspan
704 if (renderer->isTableCell())
705 data.m_column = renderer->node()->nodeIndex();
707 return StringHasher::computeHash<UChar>(
708 static_cast<const UChar*>(static_cast<const void*>(&data)),
709 sizeof data / sizeof(UChar));
712 TextAutosizer::Cluster* TextAutosizer::maybeCreateCluster(const RenderBlock* block)
714 BlockFlags flags = classifyBlock(block);
715 if (!(flags & POTENTIAL_ROOT))
718 Cluster* parentCluster = m_clusterStack.isEmpty() ? 0 : currentCluster();
719 ASSERT(parentCluster || block->isRenderView());
721 // If a non-independent block would not alter the SUPPRESSING flag, it doesn't need to be a cluster.
722 bool parentSuppresses = parentCluster && (parentCluster->m_flags & SUPPRESSING);
723 if (!(flags & INDEPENDENT) && !(flags & EXPLICIT_WIDTH) && !!(flags & SUPPRESSING) == parentSuppresses)
726 Cluster* cluster = new Cluster(block, flags, parentCluster, getSupercluster(block));
727 #ifdef AUTOSIZING_DOM_DEBUG_INFO
728 // Non-SUPPRESSING clusters are annotated in clusterMultiplier.
729 if (flags & SUPPRESSING)
730 writeClusterDebugInfo(cluster);
735 TextAutosizer::Supercluster* TextAutosizer::getSupercluster(const RenderBlock* block)
737 Fingerprint fingerprint = m_fingerprintMapper.get(block);
741 BlockSet* roots = m_fingerprintMapper.getTentativeClusterRoots(fingerprint);
742 if (!roots || roots->size() < 2 || !roots->contains(block))
745 SuperclusterMap::AddResult addResult = m_superclusters.add(fingerprint, PassOwnPtr<Supercluster>());
746 if (!addResult.isNewEntry)
747 return addResult.storedValue->value.get();
749 Supercluster* supercluster = new Supercluster(roots);
750 addResult.storedValue->value = adoptPtr(supercluster);
754 float TextAutosizer::clusterMultiplier(Cluster* cluster)
756 if (cluster->m_multiplier)
757 return cluster->m_multiplier;
759 // FIXME: why does isWiderOrNarrowerDescendant crash on independent clusters?
760 if (!(cluster->m_flags & INDEPENDENT) && isWiderOrNarrowerDescendant(cluster))
761 cluster->m_flags |= WIDER_OR_NARROWER;
763 if (cluster->m_flags & (INDEPENDENT | WIDER_OR_NARROWER)) {
764 if (cluster->m_supercluster)
765 cluster->m_multiplier = superclusterMultiplier(cluster);
766 else if (clusterHasEnoughTextToAutosize(cluster))
767 cluster->m_multiplier = multiplierFromBlock(clusterWidthProvider(cluster->m_root));
769 cluster->m_multiplier = 1.0f;
771 cluster->m_multiplier = cluster->m_parent ? clusterMultiplier(cluster->m_parent) : 1.0f;
774 #ifdef AUTOSIZING_DOM_DEBUG_INFO
775 writeClusterDebugInfo(cluster);
778 ASSERT(cluster->m_multiplier);
779 return cluster->m_multiplier;
782 bool TextAutosizer::superclusterHasEnoughTextToAutosize(Supercluster* supercluster, const RenderBlock* widthProvider)
784 if (supercluster->m_hasEnoughTextToAutosize != UnknownAmountOfText)
785 return supercluster->m_hasEnoughTextToAutosize == HasEnoughText;
787 BlockSet::iterator end = supercluster->m_roots->end();
788 for (BlockSet::iterator it = supercluster->m_roots->begin(); it != end; ++it) {
789 if (clusterWouldHaveEnoughTextToAutosize(*it, widthProvider)) {
790 supercluster->m_hasEnoughTextToAutosize = HasEnoughText;
794 supercluster->m_hasEnoughTextToAutosize = NotEnoughText;
798 float TextAutosizer::superclusterMultiplier(Cluster* cluster)
800 Supercluster* supercluster = cluster->m_supercluster;
801 if (!supercluster->m_multiplier) {
802 const RenderBlock* widthProvider = maxClusterWidthProvider(cluster->m_supercluster, cluster->m_root);
803 supercluster->m_multiplier = superclusterHasEnoughTextToAutosize(supercluster, widthProvider)
804 ? multiplierFromBlock(widthProvider) : 1.0f;
806 ASSERT(supercluster->m_multiplier);
807 return supercluster->m_multiplier;
810 const RenderBlock* TextAutosizer::clusterWidthProvider(const RenderBlock* root) const
812 if (root->isTable() || root->isTableCell())
815 return deepestBlockContainingAllText(root);
818 const RenderBlock* TextAutosizer::maxClusterWidthProvider(const Supercluster* supercluster, const RenderBlock* currentRoot) const
820 const RenderBlock* result = clusterWidthProvider(currentRoot);
821 float maxWidth = widthFromBlock(result);
823 const BlockSet* roots = supercluster->m_roots;
824 for (BlockSet::iterator it = roots->begin(); it != roots->end(); ++it) {
825 const RenderBlock* widthProvider = clusterWidthProvider(*it);
826 if (widthProvider->needsLayout())
828 float width = widthFromBlock(widthProvider);
829 if (width > maxWidth) {
831 result = widthProvider;
834 RELEASE_ASSERT(result);
838 float TextAutosizer::widthFromBlock(const RenderBlock* block) const
840 RELEASE_ASSERT(block);
841 RELEASE_ASSERT(block->style());
843 if (!(block->isTable() || block->isTableCell() || block->isListItem()))
844 return block->contentLogicalWidth().toFloat();
846 if (!block->containingBlock())
849 // Tables may be inflated before computing their preferred widths. Try several methods to
850 // obtain a width, and fall back on a containing block's width.
851 for (; block; block = block->containingBlock()) {
853 Length specifiedWidth = block->isTableCell()
854 ? toRenderTableCell(block)->styleOrColLogicalWidth() : block->style()->logicalWidth();
855 if (specifiedWidth.isFixed()) {
856 if ((width = specifiedWidth.value()) > 0)
859 if (specifiedWidth.isPercent()) {
860 if (float containerWidth = block->containingBlock()->contentLogicalWidth().toFloat()) {
861 if ((width = floatValueForLength(specifiedWidth, containerWidth)) > 0)
865 if ((width = block->contentLogicalWidth().toFloat()) > 0)
871 float TextAutosizer::multiplierFromBlock(const RenderBlock* block)
873 // If block->needsLayout() is false, it does not need to be in m_blocksThatHaveBegunLayout.
874 // This can happen during layout of a positioned object if the cluster's DBCAT is deeper
875 // than the positioned object's containing block, and wasn't marked as needing layout.
876 ASSERT(m_blocksThatHaveBegunLayout.contains(block) || !block->needsLayout());
878 // Block width, in CSS pixels.
879 float blockWidth = widthFromBlock(block);
880 float multiplier = m_pageInfo.m_frameWidth ? std::min(blockWidth, static_cast<float>(m_pageInfo.m_layoutWidth)) / m_pageInfo.m_frameWidth : 1.0f;
882 return std::max(m_pageInfo.m_baseMultiplier * multiplier, 1.0f);
885 const RenderBlock* TextAutosizer::deepestBlockContainingAllText(Cluster* cluster)
887 if (!cluster->m_deepestBlockContainingAllText)
888 cluster->m_deepestBlockContainingAllText = deepestBlockContainingAllText(cluster->m_root);
890 return cluster->m_deepestBlockContainingAllText;
893 // FIXME: Refactor this to look more like TextAutosizer::deepestCommonAncestor.
894 const RenderBlock* TextAutosizer::deepestBlockContainingAllText(const RenderBlock* root) const
896 size_t firstDepth = 0;
897 const RenderObject* firstTextLeaf = findTextLeaf(root, firstDepth, First);
901 size_t lastDepth = 0;
902 const RenderObject* lastTextLeaf = findTextLeaf(root, lastDepth, Last);
903 ASSERT(lastTextLeaf);
905 // Equalize the depths if necessary. Only one of the while loops below will get executed.
906 const RenderObject* firstNode = firstTextLeaf;
907 const RenderObject* lastNode = lastTextLeaf;
908 while (firstDepth > lastDepth) {
909 firstNode = firstNode->parent();
912 while (lastDepth > firstDepth) {
913 lastNode = lastNode->parent();
917 // Go up from both nodes until the parent is the same. Both pointers will point to the LCA then.
918 while (firstNode != lastNode) {
919 firstNode = firstNode->parent();
920 lastNode = lastNode->parent();
923 if (firstNode->isRenderBlock())
924 return toRenderBlock(firstNode);
926 // containingBlock() should never leave the cluster, since it only skips ancestors when finding
927 // the container of position:absolute/fixed blocks, and those cannot exist between a cluster and
928 // its text node's lowest common ancestor as isAutosizingCluster would have made them into their
929 // own independent cluster.
930 const RenderBlock* containingBlock = firstNode->containingBlock();
931 if (!containingBlock)
934 ASSERT(containingBlock->isDescendantOf(root));
935 return containingBlock;
938 const RenderObject* TextAutosizer::findTextLeaf(const RenderObject* parent, size_t& depth, TextLeafSearch firstOrLast) const
940 // List items are treated as text due to the marker.
941 // The actual renderer for the marker (RenderListMarker) may not be in the tree yet since it is added during layout.
942 if (parent->isListItem())
945 if (parent->isText())
949 const RenderObject* child = (firstOrLast == First) ? parent->slowFirstChild() : parent->slowLastChild();
951 // Note: At this point clusters may not have been created for these blocks so we cannot rely
952 // on m_clusters. Instead, we use a best-guess about whether the block will become a cluster.
953 if (!classifyBlock(child, INDEPENDENT)) {
954 if (const RenderObject* leaf = findTextLeaf(child, depth, firstOrLast))
957 child = (firstOrLast == First) ? child->nextSibling() : child->previousSibling();
964 void TextAutosizer::applyMultiplier(RenderObject* renderer, float multiplier, RelayoutBehavior relayoutBehavior)
966 ASSERT(renderer && renderer->style());
967 RenderStyle* currentStyle = renderer->style();
968 if (currentStyle->textAutosizingMultiplier() == multiplier)
971 // We need to clone the render style to avoid breaking style sharing.
972 RefPtr<RenderStyle> style = RenderStyle::clone(currentStyle);
973 style->setTextAutosizingMultiplier(multiplier);
976 switch (relayoutBehavior) {
977 case AlreadyInLayout:
978 // Don't free currentStyle until the end of the layout pass. This allows other parts of the system
979 // to safely hold raw RenderStyle* pointers during layout, e.g. BreakingContext::m_currentStyle.
980 m_stylesRetainedDuringLayout.append(currentStyle);
982 renderer->setStyleInternal(style.release());
983 renderer->setNeedsLayoutAndFullPaintInvalidation();
987 renderer->setStyle(style.release());
992 m_pageInfo.m_hasAutosized = true;
995 bool TextAutosizer::isWiderOrNarrowerDescendant(Cluster* cluster)
997 // FIXME: Why do we return true when hasExplicitWidth returns false??
998 if (!cluster->m_parent || !hasExplicitWidth(cluster->m_root))
1001 const RenderBlock* parentDeepestBlockContainingAllText = deepestBlockContainingAllText(cluster->m_parent);
1002 ASSERT(m_blocksThatHaveBegunLayout.contains(cluster->m_root));
1003 ASSERT(m_blocksThatHaveBegunLayout.contains(parentDeepestBlockContainingAllText));
1005 float contentWidth = cluster->m_root->contentLogicalWidth().toFloat();
1006 float clusterTextWidth = parentDeepestBlockContainingAllText->contentLogicalWidth().toFloat();
1008 // Clusters with a root that is wider than the deepestBlockContainingAllText of their parent
1009 // autosize independently of their parent.
1010 if (contentWidth > clusterTextWidth)
1013 // Clusters with a root that is significantly narrower than the deepestBlockContainingAllText of
1014 // their parent autosize independently of their parent.
1015 static float narrowWidthDifference = 200;
1016 if (clusterTextWidth - contentWidth > narrowWidthDifference)
1022 TextAutosizer::Cluster* TextAutosizer::currentCluster() const
1024 ASSERT_WITH_SECURITY_IMPLICATION(!m_clusterStack.isEmpty());
1025 return m_clusterStack.last().get();
1029 void TextAutosizer::FingerprintMapper::assertMapsAreConsistent()
1031 // For each fingerprint -> block mapping in m_blocksForFingerprint we should have an associated
1032 // map from block -> fingerprint in m_fingerprints.
1033 ReverseFingerprintMap::iterator end = m_blocksForFingerprint.end();
1034 for (ReverseFingerprintMap::iterator fingerprintIt = m_blocksForFingerprint.begin(); fingerprintIt != end; ++fingerprintIt) {
1035 Fingerprint fingerprint = fingerprintIt->key;
1036 BlockSet* blocks = fingerprintIt->value.get();
1037 for (BlockSet::iterator blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt) {
1038 const RenderBlock* block = (*blockIt);
1039 ASSERT(m_fingerprints.get(block) == fingerprint);
1045 void TextAutosizer::FingerprintMapper::add(const RenderObject* renderer, Fingerprint fingerprint)
1049 m_fingerprints.set(renderer, fingerprint);
1051 assertMapsAreConsistent();
1055 void TextAutosizer::FingerprintMapper::addTentativeClusterRoot(const RenderBlock* block, Fingerprint fingerprint)
1057 add(block, fingerprint);
1059 ReverseFingerprintMap::AddResult addResult = m_blocksForFingerprint.add(fingerprint, PassOwnPtr<BlockSet>());
1060 if (addResult.isNewEntry)
1061 addResult.storedValue->value = adoptPtr(new BlockSet);
1062 addResult.storedValue->value->add(block);
1064 assertMapsAreConsistent();
1068 bool TextAutosizer::FingerprintMapper::remove(const RenderObject* renderer)
1070 Fingerprint fingerprint = m_fingerprints.take(renderer);
1071 if (!fingerprint || !renderer->isRenderBlock())
1074 ReverseFingerprintMap::iterator blocksIter = m_blocksForFingerprint.find(fingerprint);
1075 if (blocksIter == m_blocksForFingerprint.end())
1078 BlockSet& blocks = *blocksIter->value;
1079 blocks.remove(toRenderBlock(renderer));
1080 if (blocks.isEmpty())
1081 m_blocksForFingerprint.remove(blocksIter);
1083 assertMapsAreConsistent();
1088 TextAutosizer::Fingerprint TextAutosizer::FingerprintMapper::get(const RenderObject* renderer)
1090 return m_fingerprints.get(renderer);
1093 TextAutosizer::BlockSet* TextAutosizer::FingerprintMapper::getTentativeClusterRoots(Fingerprint fingerprint)
1095 return m_blocksForFingerprint.get(fingerprint);
1098 TextAutosizer::LayoutScope::LayoutScope(RenderBlock* block)
1099 : m_textAutosizer(block->document().textAutosizer())
1102 if (!m_textAutosizer)
1105 if (m_textAutosizer->shouldHandleLayout())
1106 m_textAutosizer->beginLayout(m_block);
1108 m_textAutosizer = 0;
1111 TextAutosizer::LayoutScope::~LayoutScope()
1113 if (m_textAutosizer)
1114 m_textAutosizer->endLayout(m_block);
1118 TextAutosizer::TableLayoutScope::TableLayoutScope(RenderTable* table)
1119 : LayoutScope(table)
1121 if (m_textAutosizer) {
1122 ASSERT(m_textAutosizer->shouldHandleLayout());
1123 m_textAutosizer->inflateAutoTable(table);
1127 TextAutosizer::DeferUpdatePageInfo::DeferUpdatePageInfo(Page* page)
1128 : m_mainFrame(page->deprecatedLocalMainFrame())
1130 if (TextAutosizer* textAutosizer = m_mainFrame->document()->textAutosizer()) {
1131 ASSERT(!textAutosizer->m_updatePageInfoDeferred);
1132 textAutosizer->m_updatePageInfoDeferred = true;
1136 TextAutosizer::DeferUpdatePageInfo::~DeferUpdatePageInfo()
1138 if (TextAutosizer* textAutosizer = m_mainFrame->document()->textAutosizer()) {
1139 ASSERT(textAutosizer->m_updatePageInfoDeferred);
1140 textAutosizer->m_updatePageInfoDeferred = false;
1141 textAutosizer->updatePageInfoInAllFrames();
1145 float TextAutosizer::computeAutosizedFontSize(float specifiedSize, float multiplier)
1147 // Somewhat arbitrary "pleasant" font size.
1148 const float pleasantSize = 16;
1150 // Multiply fonts that the page author has specified to be larger than
1151 // pleasantSize by less and less, until huge fonts are not increased at all.
1152 // For specifiedSize between 0 and pleasantSize we directly apply the
1153 // multiplier; hence for specifiedSize == pleasantSize, computedSize will be
1154 // multiplier * pleasantSize. For greater specifiedSizes we want to
1155 // gradually fade out the multiplier, so for every 1px increase in
1156 // specifiedSize beyond pleasantSize we will only increase computedSize
1157 // by gradientAfterPleasantSize px until we meet the
1158 // computedSize = specifiedSize line, after which we stay on that line (so
1159 // then every 1px increase in specifiedSize increases computedSize by 1px).
1160 const float gradientAfterPleasantSize = 0.5;
1163 if (specifiedSize <= pleasantSize) {
1164 computedSize = multiplier * specifiedSize;
1166 computedSize = multiplier * pleasantSize + gradientAfterPleasantSize * (specifiedSize - pleasantSize);
1167 if (computedSize < specifiedSize)
1168 computedSize = specifiedSize;
1170 return computedSize;
1173 void TextAutosizer::trace(Visitor* visitor)
1175 visitor->trace(m_document);
1178 } // namespace blink