/* * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "SimpleLineLayoutCoverage.h" #include "Logging.h" #include "RenderBlockFlow.h" #include "RenderChildIterator.h" #include "RenderStyle.h" #include "RenderView.h" #include "Settings.h" #include "SimpleLineLayout.h" #include "TextStream.h" namespace WebCore { namespace SimpleLineLayout { #ifndef NDEBUG static void printReason(AvoidanceReason reason, TextStream& stream) { switch (reason) { case FlowIsInsideRegion: stream << "flow is inside region"; break; case FlowHasHorizonalWritingMode: stream << "horizontal writing mode"; break; case FlowHasOutline: stream << "outline"; break; case FlowIsRuby: stream << "ruby"; break; case FlowHasHangingPunctuation: stream << "hanging punctuation"; break; case FlowIsPaginated: stream << "paginated"; break; case FlowHasTextOverflow: stream << "text-overflow"; break; case FlowIsDepricatedFlexBox: stream << "depricatedFlexBox"; break; case FlowParentIsPlaceholderElement: stream << "placeholder element"; break; case FlowParentIsTextAreaWithWrapping: stream << "wrapping textarea"; break; case FlowHasNonSupportedChild: stream << "nested renderers"; break; case FlowHasUnsupportedFloat: stream << "complicated float"; break; case FlowHasUnsupportedUnderlineDecoration: stream << "text-underline-position: under"; break; case FlowHasJustifiedNonLatinText: stream << "text-align: justify with non-latin text"; break; case FlowHasOverflowNotVisible: stream << "overflow: hidden | scroll | auto"; break; case FlowHasWebKitNBSPMode: stream << "-webkit-nbsp-mode: space"; break; case FlowIsNotLTR: stream << "dir is not LTR"; break; case FlowHasLineBoxContainProperty: stream << "line-box-contain value indicates variable line height"; break; case FlowIsNotTopToBottom: stream << "non top-to-bottom flow"; break; case FlowHasLineBreak: stream << "line-break property"; break; case FlowHasNonNormalUnicodeBiDi: stream << "non-normal Unicode bidi"; break; case FlowHasRTLOrdering: stream << "-webkit-rtl-ordering"; break; case FlowHasLineAlignEdges: stream << "-webkit-line-align edges"; break; case FlowHasLineSnap: stream << "-webkit-line-snap property"; break; case FlowHasTextEmphasisFillOrMark: stream << "text-emphasis (fill/mark)"; break; case FlowHasPseudoFirstLine: stream << "first-line"; break; case FlowHasPseudoFirstLetter: stream << "first-letter"; break; case FlowHasTextCombine: stream << "text combine"; break; case FlowHasTextFillBox: stream << "background-color (text-fill)"; break; case FlowHasBorderFitLines: stream << "-webkit-border-fit"; break; case FlowHasNonAutoLineBreak: stream << "line-break is not auto"; break; case FlowHasNonAutoTrailingWord: stream << "-apple-trailing-word is not auto"; break; case FlowHasSVGFont: stream << "SVG font"; break; case FlowTextHasSoftHyphen: stream << "soft hyphen character"; break; case FlowTextHasDirectionCharacter: stream << "direction character"; break; case FlowIsMissingPrimaryFont: stream << "missing primary font"; break; case FlowPrimaryFontIsInsufficient: stream << "missing glyph or glyph needs another font"; break; case FlowTextIsCombineText: stream << "text is combine"; break; case FlowTextIsRenderCounter: stream << "unsupported RenderCounter"; break; case FlowTextIsRenderQuote: stream << "unsupported RenderQuote"; break; case FlowTextIsTextFragment: stream << "unsupported TextFragment"; break; case FlowTextIsSVGInlineText: stream << "unsupported SVGInlineText"; break; case FlowHasComplexFontCodePath: stream << "text with complex font codepath"; break; case FlowHasTextShadow: stream << "text-shadow"; break; case FlowChildIsSelected: stream << "selected content"; break; case FlowFontHasOverflowGlyph: stream << "-webkit-line-box-contain: glyphs with overflowing text."; break; case FlowTextHasSurrogatePair: stream << "surrogate pair"; break; case FlowTextIsEmpty: case FlowHasNoChild: case FlowHasNoParent: case FeatureIsDisabled: default: break; } } static void printReasons(AvoidanceReasonFlags reasons, TextStream& stream) { bool first = true; for (auto reasonItem = EndOfReasons >> 1; reasonItem != NoReason; reasonItem >>= 1) { if (!(reasons & reasonItem)) continue; stream << (first ? " " : ", "); first = false; printReason(reasonItem, stream); } } static void printTextForSubtree(const RenderObject& renderer, unsigned& charactersLeft, TextStream& stream) { if (!charactersLeft) return; if (is(renderer)) { String text = downcast(renderer).text(); text = text.stripWhiteSpace(); unsigned len = std::min(charactersLeft, text.length()); stream << text.left(len); charactersLeft -= len; return; } if (!is(renderer)) return; for (const auto* child = downcast(renderer).firstChild(); child; child = child->nextSibling()) printTextForSubtree(*child, charactersLeft, stream); } static unsigned textLengthForSubtree(const RenderObject& renderer) { if (is(renderer)) return downcast(renderer).textLength(); if (!is(renderer)) return 0; unsigned textLength = 0; for (const auto* child = downcast(renderer).firstChild(); child; child = child->nextSibling()) textLength += textLengthForSubtree(*child); return textLength; } static void collectNonEmptyLeafRenderBlockFlows(const RenderObject& renderer, HashSet& leafRenderers) { if (is(renderer)) { if (!downcast(renderer).textLength()) return; // Find RenderBlockFlow ancestor. for (const auto* current = renderer.parent(); current; current = current->parent()) { if (!is(current)) continue; leafRenderers.add(downcast(current)); break; } return; } if (!is(renderer)) return; for (const auto* child = downcast(renderer).firstChild(); child; child = child->nextSibling()) collectNonEmptyLeafRenderBlockFlows(*child, leafRenderers); } static void collectNonEmptyLeafRenderBlockFlowsForCurrentPage(HashSet& leafRenderers) { for (const auto* document : Document::allDocuments()) { if (!document->renderView() || document->pageCacheState() != Document::NotInPageCache) continue; if (!document->isHTMLDocument() && !document->isXHTMLDocument()) continue; collectNonEmptyLeafRenderBlockFlows(*document->renderView(), leafRenderers); } } void toggleSimpleLineLayout() { for (auto* document : Document::allDocuments()) { auto& settings = document->mutableSettings(); settings.setSimpleLineLayoutEnabled(!settings.simpleLineLayoutEnabled()); } } void printSimpleLineLayoutBlockList() { HashSet leafRenderers; collectNonEmptyLeafRenderBlockFlowsForCurrentPage(leafRenderers); if (!leafRenderers.size()) { WTFLogAlways("No text found in this document\n"); return; } TextStream stream; stream << "---------------------------------------------------\n"; for (const auto* flow : leafRenderers) { auto reason = canUseForWithReason(*flow, IncludeReasons::All); if (reason == NoReason) continue; unsigned printedLength = 30; stream << "\""; printTextForSubtree(*flow, printedLength, stream); for (;printedLength > 0; --printedLength) stream << " "; stream << "\"(" << textLengthForSubtree(*flow) << "):"; printReasons(reason, stream); stream << "\n"; } stream << "---------------------------------------------------\n"; WTFLogAlways("%s", stream.release().utf8().data()); } void printSimpleLineLayoutCoverage() { HashSet leafRenderers; collectNonEmptyLeafRenderBlockFlowsForCurrentPage(leafRenderers); if (!leafRenderers.size()) { WTFLogAlways("No text found in this document\n"); return; } TextStream stream; HashMap flowStatistics; unsigned textLength = 0; unsigned unsupportedTextLength = 0; unsigned numberOfUnsupportedLeafBlocks = 0; unsigned supportedButForcedToLineLayoutTextLength = 0; unsigned numberOfSupportedButForcedToLineLayoutLeafBlocks = 0; for (const auto* flow : leafRenderers) { auto flowLength = textLengthForSubtree(*flow); textLength += flowLength; auto reasons = canUseForWithReason(*flow, IncludeReasons::All); if (reasons == NoReason) { if (flow->lineLayoutPath() == RenderBlockFlow::ForceLineBoxesPath) { supportedButForcedToLineLayoutTextLength += flowLength; ++numberOfSupportedButForcedToLineLayoutLeafBlocks; } continue; } ++numberOfUnsupportedLeafBlocks; unsupportedTextLength += flowLength; for (auto reasonItem = EndOfReasons >> 1; reasonItem != NoReason; reasonItem >>= 1) { if (!(reasons & reasonItem)) continue; auto result = flowStatistics.add(reasonItem, flowLength); if (!result.isNewEntry) result.iterator->value += flowLength; } } stream << "---------------------------------------------------\n"; stream << "Number of blocks: total(" << leafRenderers.size() << ") non-simple(" << numberOfUnsupportedLeafBlocks << ")\nContent length: total(" << textLength << ") non-simple(" << unsupportedTextLength << ")\n"; for (const auto reasonEntry : flowStatistics) { printReason(reasonEntry.key, stream); stream << ": " << (float)reasonEntry.value / (float)textLength * 100 << "%\n"; } if (supportedButForcedToLineLayoutTextLength) { stream << "Simple line layout potential coverage: " << (float)(textLength - unsupportedTextLength) / (float)textLength * 100 << "%\n\n"; stream << "Simple line layout actual coverage: " << (float)(textLength - unsupportedTextLength - supportedButForcedToLineLayoutTextLength) / (float)textLength * 100 << "%\nForced line layout blocks: " << numberOfSupportedButForcedToLineLayoutLeafBlocks << " content length: " << supportedButForcedToLineLayoutTextLength << "(" << (float)supportedButForcedToLineLayoutTextLength / (float)textLength * 100 << "%)"; } else stream << "Simple line layout coverage: " << (float)(textLength - unsupportedTextLength) / (float)textLength * 100 << "%"; stream << "\n---------------------------------------------------\n"; WTFLogAlways("%s", stream.release().utf8().data()); } #endif } }