1 /*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 * Copyright (C) 2012 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include "config.h"
22 #include "core/rendering/TextAutosizer.h"
23
24 #include <algorithm>
25
26 #include "core/dom/Document.h"
27 #include "core/html/HTMLElement.h"
28 #include "core/inspector/InspectorInstrumentation.h"
29 #include "core/frame/Settings.h"
30 #include "core/rendering/RenderListItem.h"
31 #include "core/rendering/RenderObject.h"
32 #include "core/rendering/RenderText.h"
33 #include "core/rendering/RenderView.h"
34 #include "core/rendering/style/RenderStyle.h"
35 #include "core/rendering/style/StyleInheritedData.h"
36 #include "platform/TraceEvent.h"
37 #include "platform/geometry/IntSize.h"
38 #include "wtf/StdLibExtras.h"
39 #include "wtf/Vector.h"
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
45 struct TextAutosizingWindowInfo {
46 IntSize windowSize;
47 IntSize minLayoutSize;
48 };
49
50 // Represents cluster related data. Instances should not persist between calls to processSubtree.
51 struct TextAutosizingClusterInfo {
TextAutosizingClusterInfoWebCore::TextAutosizingClusterInfo52 explicit TextAutosizingClusterInfo(RenderBlock* root)
53 : root(root)
54 , blockContainingAllText(0)
55 , maxAllowedDifferenceFromTextWidth(150)
56 {
57 }
58
59 RenderBlock* root;
60 const RenderBlock* blockContainingAllText;
61
62 // Upper limit on the difference between the width of the cluster's block containing all
63 // text and that of a narrow child before the child becomes a separate cluster.
64 float maxAllowedDifferenceFromTextWidth;
65
66 // Descendants of the cluster that are narrower than the block containing all text and must be
67 // processed together.
68 Vector<TextAutosizingClusterInfo> narrowDescendants;
69 };
70
71 #ifdef AUTOSIZING_DOM_DEBUG_INFO
writeDebugInfo(RenderObject * renderObject,const AtomicString & output)72 static void writeDebugInfo(RenderObject* renderObject, const AtomicString& output)
73 {
74 Node* node = renderObject->node();
75 if (node && node->isElementNode())
76 toElement(node)->setAttribute("data-autosizing", output, ASSERT_NO_EXCEPTION);
77 }
78 #endif
79
formInputTags()80 static const Vector<QualifiedName>& formInputTags()
81 {
82 // Returns the tags for the form input elements.
83 DEFINE_STATIC_LOCAL(Vector<QualifiedName>, formInputTags, ());
84 if (formInputTags.isEmpty()) {
85 formInputTags.append(inputTag);
86 formInputTags.append(buttonTag);
87 formInputTags.append(selectTag);
88 }
89 return formInputTags;
90 }
91
getAncestorListItem(const RenderObject * renderer)92 static RenderListItem* getAncestorListItem(const RenderObject* renderer)
93 {
94 RenderObject* ancestor = renderer->parent();
95 while (ancestor && (ancestor->isRenderInline() || ancestor->isAnonymousBlock()))
96 ancestor = ancestor->parent();
97
98 return (ancestor && ancestor->isListItem()) ? toRenderListItem(ancestor) : 0;
99 }
100
getAncestorList(const RenderObject * renderer)101 static RenderObject* getAncestorList(const RenderObject* renderer)
102 {
103 // FIXME: Add support for <menu> elements as a possible ancestor of an <li> element,
104 // see http://www.whatwg.org/specs/web-apps/current-work/multipage/grouping-content.html#the-li-element
105 for (RenderObject* ancestor = renderer->parent(); ancestor; ancestor = ancestor->parent()) {
106 Node* parentNode = ancestor->generatingNode();
107 if (parentNode && (parentNode->hasTagName(olTag) || parentNode->hasTagName(ulTag)))
108 return ancestor;
109 }
110 return 0;
111 }
112
TextAutosizer(Document * document)113 TextAutosizer::TextAutosizer(Document* document)
114 : m_document(document)
115 {
116 }
117
recalculateMultipliers()118 void TextAutosizer::recalculateMultipliers()
119 {
120 RenderObject* renderer = m_document->renderer();
121 while (renderer) {
122 if (renderer->style() && renderer->style()->textAutosizingMultiplier() != 1)
123 setMultiplier(renderer, 1);
124 renderer = renderer->nextInPreOrder();
125 }
126 }
127
processSubtree(RenderObject * layoutRoot)128 bool TextAutosizer::processSubtree(RenderObject* layoutRoot)
129 {
130 TRACE_EVENT0("webkit", "TextAutosizer::processSubtree");
131
132 if (!m_document->settings() || !m_document->settings()->textAutosizingEnabled() || layoutRoot->view()->document().printing() || !m_document->page())
133 return false;
134
135 Frame* mainFrame = m_document->page()->mainFrame();
136
137 TextAutosizingWindowInfo windowInfo;
138
139 // Window area, in logical (density-independent) pixels.
140 windowInfo.windowSize = m_document->settings()->textAutosizingWindowSizeOverride();
141 if (windowInfo.windowSize.isEmpty())
142 windowInfo.windowSize = mainFrame->view()->unscaledVisibleContentSize(ScrollableArea::IncludeScrollbars);
143
144 // Largest area of block that can be visible at once (assuming the main
145 // frame doesn't get scaled to less than overview scale), in CSS pixels.
146 windowInfo.minLayoutSize = mainFrame->view()->layoutSize();
147 for (Frame* frame = m_document->frame(); frame; frame = frame->tree().parent())
148 windowInfo.minLayoutSize = windowInfo.minLayoutSize.shrunkTo(frame->view()->layoutSize());
149
150 // The layoutRoot could be neither a container nor a cluster, so walk up the tree till we find each of these.
151 RenderBlock* container = layoutRoot->isRenderBlock() ? toRenderBlock(layoutRoot) : layoutRoot->containingBlock();
152 while (container && !isAutosizingContainer(container))
153 container = container->containingBlock();
154
155 RenderBlock* cluster = container;
156 while (cluster && (!isAutosizingContainer(cluster) || !isIndependentDescendant(cluster)))
157 cluster = cluster->containingBlock();
158
159 // Skip autosizing for orphaned trees, or if it will have no effect.
160 // Note: this might suppress autosizing of an inner cluster with a different writing mode.
161 // It's not clear what the correct behavior is for mixed writing modes anyway.
162 if (!cluster || clusterMultiplier(cluster->style()->writingMode(), windowInfo,
163 std::numeric_limits<float>::infinity()) == 1.0f)
164 return false;
165
166 TextAutosizingClusterInfo clusterInfo(cluster);
167 processCluster(clusterInfo, container, layoutRoot, windowInfo);
168 InspectorInstrumentation::didAutosizeText(layoutRoot);
169 return true;
170 }
171
clusterMultiplier(WritingMode writingMode,const TextAutosizingWindowInfo & windowInfo,float textWidth) const172 float TextAutosizer::clusterMultiplier(WritingMode writingMode, const TextAutosizingWindowInfo& windowInfo, float textWidth) const
173 {
174 int logicalWindowWidth = isHorizontalWritingMode(writingMode) ? windowInfo.windowSize.width() : windowInfo.windowSize.height();
175 int logicalLayoutWidth = isHorizontalWritingMode(writingMode) ? windowInfo.minLayoutSize.width() : windowInfo.minLayoutSize.height();
176 // Ignore box width in excess of the layout width, to avoid extreme multipliers.
177 float logicalClusterWidth = std::min<float>(textWidth, logicalLayoutWidth);
178
179 float multiplier = logicalClusterWidth / logicalWindowWidth;
180 multiplier *= m_document->settings()->accessibilityFontScaleFactor();
181
182 // If the page has a meta viewport or @viewport, don't apply the device scale adjustment.
183 const ViewportDescription& viewportDescription = m_document->page()->mainFrame()->document()->viewportDescription();
184 if (!viewportDescription.isSpecifiedByAuthor()) {
185 multiplier *= m_document->settings()->deviceScaleAdjustment();
186 }
187 return std::max(1.0f, multiplier);
188 }
189
processClusterInternal(TextAutosizingClusterInfo & clusterInfo,RenderBlock * container,RenderObject * subtreeRoot,const TextAutosizingWindowInfo & windowInfo,float multiplier)190 void TextAutosizer::processClusterInternal(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo, float multiplier)
191 {
192 processContainer(multiplier, container, clusterInfo, subtreeRoot, windowInfo);
193 #ifdef AUTOSIZING_DOM_DEBUG_INFO
194 writeDebugInfo(clusterInfo.root, String::format("cluster:%f", multiplier));
195 #endif
196
197 Vector<Vector<TextAutosizingClusterInfo> > narrowDescendantsGroups;
198 getNarrowDescendantsGroupedByWidth(clusterInfo, narrowDescendantsGroups);
199 for (size_t i = 0; i < narrowDescendantsGroups.size(); ++i)
200 processCompositeCluster(narrowDescendantsGroups[i], windowInfo);
201 }
202
processCluster(TextAutosizingClusterInfo & clusterInfo,RenderBlock * container,RenderObject * subtreeRoot,const TextAutosizingWindowInfo & windowInfo)203 void TextAutosizer::processCluster(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo)
204 {
205 // Many pages set a max-width on their content. So especially for the RenderView, instead of
206 // just taking the width of |cluster| we find the lowest common ancestor of the first and last
207 // descendant text node of the cluster (i.e. the deepest wrapper block that contains all the
208 // text), and use its width instead.
209 clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root);
210 float textWidth = clusterInfo.blockContainingAllText->contentLogicalWidth();
211 float multiplier = 1.0;
212 if (clusterShouldBeAutosized(clusterInfo, textWidth))
213 multiplier = clusterMultiplier(clusterInfo.root->style()->writingMode(), windowInfo, textWidth);
214 processClusterInternal(clusterInfo, container, subtreeRoot, windowInfo, multiplier);
215 }
216
processCompositeCluster(Vector<TextAutosizingClusterInfo> & clusterInfos,const TextAutosizingWindowInfo & windowInfo)217 void TextAutosizer::processCompositeCluster(Vector<TextAutosizingClusterInfo>& clusterInfos, const TextAutosizingWindowInfo& windowInfo)
218 {
219 if (clusterInfos.isEmpty())
220 return;
221
222 float maxTextWidth = 0;
223 for (size_t i = 0; i < clusterInfos.size(); ++i) {
224 TextAutosizingClusterInfo& clusterInfo = clusterInfos[i];
225 clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root);
226 maxTextWidth = max<float>(maxTextWidth, clusterInfo.blockContainingAllText->contentLogicalWidth());
227 }
228
229 float multiplier = 1.0;
230 if (compositeClusterShouldBeAutosized(clusterInfos, maxTextWidth))
231 multiplier = clusterMultiplier(clusterInfos[0].root->style()->writingMode(), windowInfo, maxTextWidth);
232 for (size_t i = 0; i < clusterInfos.size(); ++i) {
233 ASSERT(clusterInfos[i].root->style()->writingMode() == clusterInfos[0].root->style()->writingMode());
234 processClusterInternal(clusterInfos[i], clusterInfos[i].root, clusterInfos[i].root, windowInfo, multiplier);
235 }
236 }
237
processContainer(float multiplier,RenderBlock * container,TextAutosizingClusterInfo & clusterInfo,RenderObject * subtreeRoot,const TextAutosizingWindowInfo & windowInfo)238 void TextAutosizer::processContainer(float multiplier, RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo)
239 {
240 ASSERT(isAutosizingContainer(container));
241 #ifdef AUTOSIZING_DOM_DEBUG_INFO
242 writeDebugInfo(container, "container");
243 #endif
244
245 float localMultiplier = containerShouldBeAutosized(container) ? multiplier: 1;
246
247 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(subtreeRoot, subtreeRoot);
248 while (descendant) {
249 if (descendant->isText()) {
250 if (localMultiplier != 1 && descendant->style()->textAutosizingMultiplier() == 1) {
251 setMultiplier(descendant, localMultiplier);
252 setMultiplier(descendant->parent(), localMultiplier); // Parent does line spacing.
253
254 if (RenderListItem* listItemAncestor = getAncestorListItem(descendant)) {
255 if (RenderObject* list = getAncestorList(listItemAncestor)) {
256 if (list->style()->textAutosizingMultiplier() == 1)
257 setMultiplierForList(list, localMultiplier);
258 }
259 }
260 }
261 } else if (isAutosizingContainer(descendant)) {
262 RenderBlock* descendantBlock = toRenderBlock(descendant);
263 TextAutosizingClusterInfo descendantClusterInfo(descendantBlock);
264 if (isWiderDescendant(descendantBlock, clusterInfo) || isIndependentDescendant(descendantBlock))
265 processCluster(descendantClusterInfo, descendantBlock, descendantBlock, windowInfo);
266 else if (isNarrowDescendant(descendantBlock, clusterInfo)) {
267 // Narrow descendants are processed together later to be able to apply the same multiplier
268 // to each of them if necessary.
269 clusterInfo.narrowDescendants.append(descendantClusterInfo);
270 } else
271 processContainer(multiplier, descendantBlock, clusterInfo, descendantBlock, windowInfo);
272 }
273 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, subtreeRoot);
274 }
275 }
276
setMultiplier(RenderObject * renderer,float multiplier)277 void TextAutosizer::setMultiplier(RenderObject* renderer, float multiplier)
278 {
279 RefPtr<RenderStyle> newStyle = RenderStyle::clone(renderer->style());
280 newStyle->setTextAutosizingMultiplier(multiplier);
281 newStyle->setUnique();
282 renderer->setStyle(newStyle.release());
283 }
284
setMultiplierForList(RenderObject * renderer,float multiplier)285 void TextAutosizer::setMultiplierForList(RenderObject* renderer, float multiplier)
286 {
287 #ifndef NDEBUG
288 Node* parentNode = renderer->generatingNode();
289 ASSERT(parentNode);
290 ASSERT(parentNode->hasTagName(olTag) || parentNode->hasTagName(ulTag));
291 #endif
292 setMultiplier(renderer, multiplier);
293
294 // Make sure all list items are autosized consistently.
295 for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) {
296 if (child->isListItem() && child->style()->textAutosizingMultiplier() == 1)
297 setMultiplier(child, multiplier);
298 }
299 }
300
computeAutosizedFontSize(float specifiedSize,float multiplier)301 float TextAutosizer::computeAutosizedFontSize(float specifiedSize, float multiplier)
302 {
303 // Somewhat arbitrary "pleasant" font size.
304 const float pleasantSize = 16;
305
306 // Multiply fonts that the page author has specified to be larger than
307 // pleasantSize by less and less, until huge fonts are not increased at all.
308 // For specifiedSize between 0 and pleasantSize we directly apply the
309 // multiplier; hence for specifiedSize == pleasantSize, computedSize will be
310 // multiplier * pleasantSize. For greater specifiedSizes we want to
311 // gradually fade out the multiplier, so for every 1px increase in
312 // specifiedSize beyond pleasantSize we will only increase computedSize
313 // by gradientAfterPleasantSize px until we meet the
314 // computedSize = specifiedSize line, after which we stay on that line (so
315 // then every 1px increase in specifiedSize increases computedSize by 1px).
316 const float gradientAfterPleasantSize = 0.5;
317
318 float computedSize;
319 if (specifiedSize <= pleasantSize)
320 computedSize = multiplier * specifiedSize;
321 else {
322 computedSize = multiplier * pleasantSize + gradientAfterPleasantSize * (specifiedSize - pleasantSize);
323 if (computedSize < specifiedSize)
324 computedSize = specifiedSize;
325 }
326 return computedSize;
327 }
328
isAutosizingContainer(const RenderObject * renderer)329 bool TextAutosizer::isAutosizingContainer(const RenderObject* renderer)
330 {
331 // "Autosizing containers" are the smallest unit for which we can
332 // enable/disable Text Autosizing.
333 // - Must not be inline, as different multipliers on one line looks terrible.
334 // Exceptions are inline-block and alike elements (inline-table, -webkit-inline-*),
335 // as they often contain entire multi-line columns of text.
336 // - Must not be list items, as items in the same list should look consistent (*).
337 // - Must not be normal list items, as items in the same list should look
338 // consistent, unless they are floating or position:absolute/fixed.
339 Node* node = renderer->generatingNode();
340 if ((node && !node->hasChildNodes())
341 || !renderer->isRenderBlock()
342 || (renderer->isInline() && !renderer->style()->isDisplayReplacedType()))
343 return false;
344 if (renderer->isListItem())
345 return renderer->isFloating() || renderer->isOutOfFlowPositioned();
346 // Avoid creating containers for text within text controls, buttons, or <select> buttons.
347 Node* parentNode = renderer->parent() ? renderer->parent()->generatingNode() : 0;
348 if (parentNode && parentNode->isElementNode() && formInputTags().contains(toElement(parentNode)->tagQName()))
349 return false;
350
351 return true;
352 }
353
isNarrowDescendant(const RenderBlock * renderer,TextAutosizingClusterInfo & parentClusterInfo)354 bool TextAutosizer::isNarrowDescendant(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo)
355 {
356 ASSERT(isAutosizingContainer(renderer));
357
358 // Autosizing containers that are significantly narrower than the |blockContainingAllText| of
359 // their enclosing cluster may be acting as separate columns, hence must be autosized
360 // separately. For example the 2nd div in:
361 // <body>
362 // <div style="float: right; width: 50%"></div>
363 // <div style="width: 50%"></div>
364 // <body>
365 // is the left column, and should be autosized differently from the body.
366 // If however the container is only narrower by 150px or less, it's considered part of
367 // the enclosing cluster. This 150px limit is adjusted whenever a descendant container is
368 // less than 50px narrower than the current limit.
369 const float differenceFromMaxWidthDifference = 50;
370 float contentWidth = renderer->contentLogicalWidth();
371 float clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth();
372 float widthDifference = clusterTextWidth - contentWidth;
373
374 if (widthDifference - parentClusterInfo.maxAllowedDifferenceFromTextWidth > differenceFromMaxWidthDifference)
375 return true;
376
377 parentClusterInfo.maxAllowedDifferenceFromTextWidth = std::max(widthDifference, parentClusterInfo.maxAllowedDifferenceFromTextWidth);
378 return false;
379 }
380
isWiderDescendant(const RenderBlock * renderer,const TextAutosizingClusterInfo & parentClusterInfo)381 bool TextAutosizer::isWiderDescendant(const RenderBlock* renderer, const TextAutosizingClusterInfo& parentClusterInfo)
382 {
383 ASSERT(isAutosizingContainer(renderer));
384
385 // Autosizing containers that are wider than the |blockContainingAllText| of their enclosing
386 // cluster are treated the same way as autosizing clusters to be autosized separately.
387 float contentWidth = renderer->contentLogicalWidth();
388 float clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth();
389 return contentWidth > clusterTextWidth;
390 }
391
isIndependentDescendant(const RenderBlock * renderer)392 bool TextAutosizer::isIndependentDescendant(const RenderBlock* renderer)
393 {
394 ASSERT(isAutosizingContainer(renderer));
395
396 // "Autosizing clusters" are special autosizing containers within which we
397 // want to enforce a uniform text size multiplier, in the hopes of making
398 // the major sections of the page look internally consistent.
399 // All their descendants (including other autosizing containers) must share
400 // the same multiplier, except for subtrees which are themselves clusters,
401 // and some of their descendant containers might not be autosized at all
402 // (for example if their height is constrained).
403 // Additionally, clusterShouldBeAutosized requires each cluster to contain a
404 // minimum amount of text, without which it won't be autosized.
405 //
406 // Clusters are chosen using very similar criteria to CSS flow roots, aka
407 // block formatting contexts (http://w3.org/TR/css3-box/#flow-root), since
408 // flow roots correspond to box containers that behave somewhat
409 // independently from their parent (for example they don't overlap floats).
410 // The definition of a flow root also conveniently includes most of the
411 // ways that a box and its children can have significantly different width
412 // from the box's parent (we want to avoid having significantly different
413 // width blocks within a cluster, since the narrower blocks would end up
414 // larger than would otherwise be necessary).
415 return renderer->isRenderView()
416 || renderer->isFloating()
417 || renderer->isOutOfFlowPositioned()
418 || renderer->isTableCell()
419 || renderer->isTableCaption()
420 || renderer->isFlexibleBoxIncludingDeprecated()
421 || renderer->hasColumns()
422 || renderer->containingBlock()->isHorizontalWritingMode() != renderer->isHorizontalWritingMode()
423 || renderer->style()->isDisplayReplacedType()
424 || renderer->isTextArea()
425 || renderer->style()->userModify() != READ_ONLY;
426 // FIXME: Tables need special handling to multiply all their columns by
427 // the same amount even if they're different widths; so do hasColumns()
428 // containers, and probably flexboxes...
429 }
430
isAutosizingCluster(const RenderBlock * renderer,TextAutosizingClusterInfo & parentClusterInfo)431 bool TextAutosizer::isAutosizingCluster(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo)
432 {
433 ASSERT(isAutosizingContainer(renderer));
434
435 return isNarrowDescendant(renderer, parentClusterInfo)
436 || isWiderDescendant(renderer, parentClusterInfo)
437 || isIndependentDescendant(renderer);
438 }
439
containerShouldBeAutosized(const RenderBlock * container)440 bool TextAutosizer::containerShouldBeAutosized(const RenderBlock* container)
441 {
442 if (containerContainsOneOfTags(container, formInputTags()))
443 return false;
444
445 if (containerIsRowOfLinks(container))
446 return false;
447
448 // Don't autosize block-level text that can't wrap (as it's likely to
449 // expand sideways and break the page's layout).
450 if (!container->style()->autoWrap())
451 return false;
452
453 return !contentHeightIsConstrained(container);
454 }
455
containerContainsOneOfTags(const RenderBlock * container,const Vector<QualifiedName> & tags)456 bool TextAutosizer::containerContainsOneOfTags(const RenderBlock* container, const Vector<QualifiedName>& tags)
457 {
458 const RenderObject* renderer = container;
459 while (renderer) {
460 const Node* rendererNode = renderer->node();
461 if (rendererNode && rendererNode->isElementNode()) {
462 if (tags.contains(toElement(rendererNode)->tagQName()))
463 return true;
464 }
465 renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container);
466 }
467
468 return false;
469 }
470
containerIsRowOfLinks(const RenderObject * container)471 bool TextAutosizer::containerIsRowOfLinks(const RenderObject* container)
472 {
473 // A "row of links" is a container for which holds:
474 // 1. it should not contain non-link text elements longer than 3 characters
475 // 2. it should contain min. 3 inline links and all links should
476 // have the same specified font size
477 // 3. it should not contain <br> elements
478 // 4. it should contain only inline elements unless they are containers,
479 // children of link elements or children of sub-containers.
480 int linkCount = 0;
481 RenderObject* renderer = container->nextInPreOrder(container);
482 float matchingFontSize = -1;
483
484 while (renderer) {
485 if (!isAutosizingContainer(renderer)) {
486 if (renderer->isText() && toRenderText(renderer)->text().impl()->stripWhiteSpace()->length() > 3)
487 return false;
488 if (!renderer->isInline())
489 return false;
490 if (renderer->isBR())
491 return false;
492 }
493 if (renderer->style()->isLink()) {
494 if (matchingFontSize < 0)
495 matchingFontSize = renderer->style()->specifiedFontSize();
496 else {
497 if (matchingFontSize != renderer->style()->specifiedFontSize())
498 return false;
499 }
500
501 linkCount++;
502 // Skip traversing descendants of the link.
503 renderer = renderer->nextInPreOrderAfterChildren(container);
504 } else
505 renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container);
506 }
507
508 return (linkCount >= 3);
509 }
510
contentHeightIsConstrained(const RenderBlock * container)511 bool TextAutosizer::contentHeightIsConstrained(const RenderBlock* container)
512 {
513 // FIXME: Propagate constrainedness down the tree, to avoid inefficiently walking back up from each box.
514 // FIXME: This code needs to take into account vertical writing modes.
515 // FIXME: Consider additional heuristics, such as ignoring fixed heights if the content is already overflowing before autosizing kicks in.
516 for (; container; container = container->containingBlock()) {
517 RenderStyle* style = container->style();
518 if (style->overflowY() >= OSCROLL)
519 return false;
520 if (style->height().isSpecified() || style->maxHeight().isSpecified() || container->isOutOfFlowPositioned()) {
521 // Some sites (e.g. wikipedia) set their html and/or body elements to height:100%,
522 // without intending to constrain the height of the content within them.
523 return !container->isRoot() && !container->isBody();
524 }
525 if (container->isFloating())
526 return false;
527 }
528 return false;
529 }
530
clusterShouldBeAutosized(TextAutosizingClusterInfo & clusterInfo,float blockWidth)531 bool TextAutosizer::clusterShouldBeAutosized(TextAutosizingClusterInfo& clusterInfo, float blockWidth)
532 {
533 Vector<TextAutosizingClusterInfo> clusterInfos(1, clusterInfo);
534 return compositeClusterShouldBeAutosized(clusterInfos, blockWidth);
535 }
536
compositeClusterShouldBeAutosized(Vector<TextAutosizingClusterInfo> & clusterInfos,float blockWidth)537 bool TextAutosizer::compositeClusterShouldBeAutosized(Vector<TextAutosizingClusterInfo>& clusterInfos, float blockWidth)
538 {
539 // Don't autosize clusters that contain less than 4 lines of text (in
540 // practice less lines are required, since measureDescendantTextWidth
541 // assumes that characters are 1em wide, but most characters are narrower
542 // than that, so we're overestimating their contribution to the linecount).
543 //
544 // This is to reduce the likelihood of autosizing things like headers and
545 // footers, which can be quite visually distracting. The rationale is that
546 // if a cluster contains very few lines of text then it's ok to have to zoom
547 // in and pan from side to side to read each line, since if there are very
548 // few lines of text you'll only need to pan across once or twice.
549 //
550 // An exception to the 4 lines of text are the textarea and contenteditable
551 // clusters, which are always autosized by default (i.e. threated as if they
552 // contain more than 4 lines of text). This is to ensure that the text does
553 // not suddenly get autosized when the user enters more than 4 lines of text.
554 float totalTextWidth = 0;
555 const float minLinesOfText = 4;
556 float minTextWidth = blockWidth * minLinesOfText;
557 for (size_t i = 0; i < clusterInfos.size(); ++i) {
558 if (clusterInfos[i].root->isTextArea() || (clusterInfos[i].root->style() && clusterInfos[i].root->style()->userModify() != READ_ONLY))
559 return true;
560 measureDescendantTextWidth(clusterInfos[i].blockContainingAllText, clusterInfos[i], minTextWidth, totalTextWidth);
561 if (totalTextWidth >= minTextWidth)
562 return true;
563 }
564 return false;
565 }
566
measureDescendantTextWidth(const RenderBlock * container,TextAutosizingClusterInfo & clusterInfo,float minTextWidth,float & textWidth)567 void TextAutosizer::measureDescendantTextWidth(const RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, float minTextWidth, float& textWidth)
568 {
569 bool skipLocalText = !containerShouldBeAutosized(container);
570
571 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(container, container);
572 while (descendant) {
573 if (!skipLocalText && descendant->isText()) {
574 textWidth += toRenderText(descendant)->renderedTextLength() * descendant->style()->specifiedFontSize();
575 } else if (isAutosizingContainer(descendant)) {
576 RenderBlock* descendantBlock = toRenderBlock(descendant);
577 if (!isAutosizingCluster(descendantBlock, clusterInfo))
578 measureDescendantTextWidth(descendantBlock, clusterInfo, minTextWidth, textWidth);
579 }
580 if (textWidth >= minTextWidth)
581 return;
582 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, container);
583 }
584 }
585
nextInPreOrderSkippingDescendantsOfContainers(const RenderObject * current,const RenderObject * stayWithin)586 RenderObject* TextAutosizer::nextInPreOrderSkippingDescendantsOfContainers(const RenderObject* current, const RenderObject* stayWithin)
587 {
588 if (current == stayWithin || !isAutosizingContainer(current))
589 return current->nextInPreOrder(stayWithin);
590 return current->nextInPreOrderAfterChildren(stayWithin);
591 }
592
findDeepestBlockContainingAllText(const RenderBlock * cluster)593 const RenderBlock* TextAutosizer::findDeepestBlockContainingAllText(const RenderBlock* cluster)
594 {
595 size_t firstDepth = 0;
596 const RenderObject* firstTextLeaf = findFirstTextLeafNotInCluster(cluster, firstDepth, FirstToLast);
597 if (!firstTextLeaf)
598 return cluster;
599
600 size_t lastDepth = 0;
601 const RenderObject* lastTextLeaf = findFirstTextLeafNotInCluster(cluster, lastDepth, LastToFirst);
602 ASSERT(lastTextLeaf);
603
604 // Equalize the depths if necessary. Only one of the while loops below will get executed.
605 const RenderObject* firstNode = firstTextLeaf;
606 const RenderObject* lastNode = lastTextLeaf;
607 while (firstDepth > lastDepth) {
608 firstNode = firstNode->parent();
609 --firstDepth;
610 }
611 while (lastDepth > firstDepth) {
612 lastNode = lastNode->parent();
613 --lastDepth;
614 }
615
616 // Go up from both nodes until the parent is the same. Both pointers will point to the LCA then.
617 while (firstNode != lastNode) {
618 firstNode = firstNode->parent();
619 lastNode = lastNode->parent();
620 }
621
622 if (firstNode->isRenderBlock())
623 return toRenderBlock(firstNode);
624
625 // containingBlock() should never leave the cluster, since it only skips ancestors when finding the
626 // container of position:absolute/fixed blocks, and those cannot exist between a cluster and its text
627 // nodes lowest common ancestor as isAutosizingCluster would have made them into their own independent
628 // cluster.
629 RenderBlock* containingBlock = firstNode->containingBlock();
630 ASSERT(containingBlock->isDescendantOf(cluster));
631
632 return containingBlock;
633 }
634
findFirstTextLeafNotInCluster(const RenderObject * parent,size_t & depth,TraversalDirection direction)635 const RenderObject* TextAutosizer::findFirstTextLeafNotInCluster(const RenderObject* parent, size_t& depth, TraversalDirection direction)
636 {
637 if (parent->isEmpty())
638 return parent->isText() ? parent : 0;
639
640 ++depth;
641 const RenderObject* child = (direction == FirstToLast) ? parent->firstChild() : parent->lastChild();
642 while (child) {
643 if (!isAutosizingContainer(child) || !isIndependentDescendant(toRenderBlock(child))) {
644 const RenderObject* leaf = findFirstTextLeafNotInCluster(child, depth, direction);
645 if (leaf)
646 return leaf;
647 }
648 child = (direction == FirstToLast) ? child->nextSibling() : child->previousSibling();
649 }
650 --depth;
651
652 return 0;
653 }
654
655 namespace {
656
657 // Compares the width of the specified cluster's roots in descending order.
clusterWiderThanComparisonFn(const TextAutosizingClusterInfo & first,const TextAutosizingClusterInfo & second)658 bool clusterWiderThanComparisonFn(const TextAutosizingClusterInfo& first, const TextAutosizingClusterInfo& second)
659 {
660 return first.root->contentLogicalWidth() > second.root->contentLogicalWidth();
661 }
662
663 } // namespace
664
getNarrowDescendantsGroupedByWidth(const TextAutosizingClusterInfo & parentClusterInfo,Vector<Vector<TextAutosizingClusterInfo>> & groups)665 void TextAutosizer::getNarrowDescendantsGroupedByWidth(const TextAutosizingClusterInfo& parentClusterInfo, Vector<Vector<TextAutosizingClusterInfo> >& groups)
666 {
667 ASSERT(parentClusterInfo.blockContainingAllText);
668 ASSERT(groups.isEmpty());
669
670 Vector<TextAutosizingClusterInfo> clusterInfos(parentClusterInfo.narrowDescendants);
671 if (clusterInfos.isEmpty())
672 return;
673
674 std::sort(clusterInfos.begin(), clusterInfos.end(), &clusterWiderThanComparisonFn);
675 groups.grow(1);
676
677 // If the width difference between two consecutive elements of |clusterInfos| is greater than
678 // this empirically determined value, the next element should start a new group.
679 const float maxWidthDifferenceWithinGroup = 100;
680 for (size_t i = 0; i < clusterInfos.size(); ++i) {
681 groups.last().append(clusterInfos[i]);
682
683 if (i + 1 < clusterInfos.size()) {
684 float currentWidth = clusterInfos[i].root->contentLogicalWidth();
685 float nextWidth = clusterInfos[i + 1].root->contentLogicalWidth();
686 if (currentWidth - nextWidth > maxWidthDifferenceWithinGroup)
687 groups.grow(groups.size() + 1);
688 }
689 }
690 }
691
692 } // namespace WebCore
693