• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2003, 2006, 2008 Apple Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 #include "RootInlineBox.h"
22 
23 #include "BidiResolver.h"
24 #include "Chrome.h"
25 #include "ChromeClient.h"
26 #include "Document.h"
27 #include "EllipsisBox.h"
28 #include "Frame.h"
29 #include "GraphicsContext.h"
30 #include "HitTestResult.h"
31 #include "Page.h"
32 #include "RenderArena.h"
33 #include "RenderBlock.h"
34 
35 using namespace std;
36 
37 namespace WebCore {
38 
39 typedef WTF::HashMap<const RootInlineBox*, EllipsisBox*> EllipsisBoxMap;
40 static EllipsisBoxMap* gEllipsisBoxMap = 0;
41 
destroy(RenderArena * arena)42 void RootInlineBox::destroy(RenderArena* arena)
43 {
44     detachEllipsisBox(arena);
45     InlineFlowBox::destroy(arena);
46 }
47 
detachEllipsisBox(RenderArena * arena)48 void RootInlineBox::detachEllipsisBox(RenderArena* arena)
49 {
50     if (m_hasEllipsisBox) {
51         EllipsisBox* box = gEllipsisBoxMap->take(this);
52         box->setParent(0);
53         box->destroy(arena);
54         m_hasEllipsisBox = false;
55     }
56 }
57 
rendererLineBoxes() const58 RenderLineBoxList* RootInlineBox::rendererLineBoxes() const
59 {
60     return block()->lineBoxes();
61 }
62 
clearTruncation()63 void RootInlineBox::clearTruncation()
64 {
65     if (m_hasEllipsisBox) {
66         detachEllipsisBox(renderer()->renderArena());
67         InlineFlowBox::clearTruncation();
68     }
69 }
70 
canAccommodateEllipsis(bool ltr,int blockEdge,int lineBoxEdge,int ellipsisWidth)71 bool RootInlineBox::canAccommodateEllipsis(bool ltr, int blockEdge, int lineBoxEdge, int ellipsisWidth)
72 {
73     // First sanity-check the unoverflowed width of the whole line to see if there is sufficient room.
74     int delta = ltr ? lineBoxEdge - blockEdge : blockEdge - lineBoxEdge;
75     if (width() - delta < ellipsisWidth)
76         return false;
77 
78     // Next iterate over all the line boxes on the line.  If we find a replaced element that intersects
79     // then we refuse to accommodate the ellipsis.  Otherwise we're ok.
80     return InlineFlowBox::canAccommodateEllipsis(ltr, blockEdge, ellipsisWidth);
81 }
82 
placeEllipsis(const AtomicString & ellipsisStr,bool ltr,int blockLeftEdge,int blockRightEdge,int ellipsisWidth,InlineBox * markupBox)83 void RootInlineBox::placeEllipsis(const AtomicString& ellipsisStr,  bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth,
84                                   InlineBox* markupBox)
85 {
86     // Create an ellipsis box.
87     EllipsisBox* ellipsisBox = new (renderer()->renderArena()) EllipsisBox(renderer(), ellipsisStr, this,
88                                                               ellipsisWidth - (markupBox ? markupBox->width() : 0), height(),
89                                                               y(), !prevRootBox(),
90                                                               markupBox);
91 
92     if (!gEllipsisBoxMap)
93         gEllipsisBoxMap = new EllipsisBoxMap();
94     gEllipsisBoxMap->add(this, ellipsisBox);
95     m_hasEllipsisBox = true;
96 
97     // FIXME: Do we need an RTL version of this?
98     if (ltr && (x() + width() + ellipsisWidth) <= blockRightEdge) {
99         ellipsisBox->m_x = x() + width();
100         return;
101     }
102 
103     // Now attempt to find the nearest glyph horizontally and place just to the right (or left in RTL)
104     // of that glyph.  Mark all of the objects that intersect the ellipsis box as not painting (as being
105     // truncated).
106     bool foundBox = false;
107     ellipsisBox->m_x = placeEllipsisBox(ltr, blockLeftEdge, blockRightEdge, ellipsisWidth, foundBox);
108 }
109 
placeEllipsisBox(bool ltr,int blockLeftEdge,int blockRightEdge,int ellipsisWidth,bool & foundBox)110 int RootInlineBox::placeEllipsisBox(bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth, bool& foundBox)
111 {
112     int result = InlineFlowBox::placeEllipsisBox(ltr, blockLeftEdge, blockRightEdge, ellipsisWidth, foundBox);
113     if (result == -1)
114         result = ltr ? blockRightEdge - ellipsisWidth : blockLeftEdge;
115     return result;
116 }
117 
paintEllipsisBox(RenderObject::PaintInfo & paintInfo,int tx,int ty) const118 void RootInlineBox::paintEllipsisBox(RenderObject::PaintInfo& paintInfo, int tx, int ty) const
119 {
120     if (m_hasEllipsisBox && renderer()->shouldPaintWithinRoot(paintInfo) && renderer()->style()->visibility() == VISIBLE &&
121             paintInfo.phase == PaintPhaseForeground)
122         ellipsisBox()->paint(paintInfo, tx, ty);
123 }
124 
125 #if PLATFORM(MAC)
126 
addHighlightOverflow()127 void RootInlineBox::addHighlightOverflow()
128 {
129     Frame* frame = renderer()->document()->frame();
130     if (!frame)
131         return;
132     Page* page = frame->page();
133     if (!page)
134         return;
135 
136     // Highlight acts as a selection inflation.
137     FloatRect rootRect(0, selectionTop(), width(), selectionHeight());
138     IntRect inflatedRect = enclosingIntRect(page->chrome()->client()->customHighlightRect(renderer()->node(), renderer()->style()->highlight(), rootRect));
139     setHorizontalOverflowPositions(leftLayoutOverflow(), rightLayoutOverflow(), min(leftVisualOverflow(), inflatedRect.x()), max(rightVisualOverflow(), inflatedRect.right()));
140     setVerticalOverflowPositions(topLayoutOverflow(), bottomLayoutOverflow(), min(topVisualOverflow(), inflatedRect.y()), max(bottomVisualOverflow(), inflatedRect.bottom()), height());
141 }
142 
paintCustomHighlight(RenderObject::PaintInfo & paintInfo,int tx,int ty,const AtomicString & highlightType)143 void RootInlineBox::paintCustomHighlight(RenderObject::PaintInfo& paintInfo, int tx, int ty, const AtomicString& highlightType)
144 {
145     if (!renderer()->shouldPaintWithinRoot(paintInfo) || renderer()->style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseForeground)
146         return;
147 
148     Frame* frame = renderer()->document()->frame();
149     if (!frame)
150         return;
151     Page* page = frame->page();
152     if (!page)
153         return;
154 
155     // Get the inflated rect so that we can properly hit test.
156     FloatRect rootRect(tx + x(), ty + selectionTop(), width(), selectionHeight());
157     FloatRect inflatedRect = page->chrome()->client()->customHighlightRect(renderer()->node(), highlightType, rootRect);
158     if (inflatedRect.intersects(paintInfo.rect))
159         page->chrome()->client()->paintCustomHighlight(renderer()->node(), highlightType, rootRect, rootRect, false, true);
160 }
161 
162 #endif
163 
paint(RenderObject::PaintInfo & paintInfo,int tx,int ty)164 void RootInlineBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty)
165 {
166     InlineFlowBox::paint(paintInfo, tx, ty);
167     paintEllipsisBox(paintInfo, tx, ty);
168 #if PLATFORM(MAC)
169     RenderStyle* styleToUse = renderer()->style(m_firstLine);
170     if (styleToUse->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
171         paintCustomHighlight(paintInfo, tx, ty, styleToUse->highlight());
172 #endif
173 }
174 
nodeAtPoint(const HitTestRequest & request,HitTestResult & result,int x,int y,int tx,int ty)175 bool RootInlineBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty)
176 {
177     if (m_hasEllipsisBox && visibleToHitTesting()) {
178         if (ellipsisBox()->nodeAtPoint(request, result, x, y, tx, ty)) {
179             renderer()->updateHitTestResult(result, IntPoint(x - tx, y - ty));
180             return true;
181         }
182     }
183     return InlineFlowBox::nodeAtPoint(request, result, x, y, tx, ty);
184 }
185 
adjustPosition(int dx,int dy)186 void RootInlineBox::adjustPosition(int dx, int dy)
187 {
188     InlineFlowBox::adjustPosition(dx, dy);
189     m_lineTop += dy;
190     m_lineBottom += dy;
191     m_blockHeight += dy;
192 }
193 
childRemoved(InlineBox * box)194 void RootInlineBox::childRemoved(InlineBox* box)
195 {
196     if (box->renderer() == m_lineBreakObj)
197         setLineBreakInfo(0, 0, BidiStatus());
198 
199     for (RootInlineBox* prev = prevRootBox(); prev && prev->lineBreakObj() == box->renderer(); prev = prev->prevRootBox()) {
200         prev->setLineBreakInfo(0, 0, BidiStatus());
201         prev->markDirty();
202     }
203 }
204 
verticallyAlignBoxes(int heightOfBlock)205 int RootInlineBox::verticallyAlignBoxes(int heightOfBlock)
206 {
207     int maxPositionTop = 0;
208     int maxPositionBottom = 0;
209     int maxAscent = 0;
210     int maxDescent = 0;
211 
212     // Figure out if we're in strict mode.  Note that we can't simply use !style()->htmlHacks(),
213     // because that would match almost strict mode as well.
214     RenderObject* curr = renderer();
215     while (curr && !curr->node())
216         curr = curr->container();
217     bool strictMode = (curr && curr->document()->inStrictMode());
218 
219     computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode);
220 
221     if (maxAscent + maxDescent < max(maxPositionTop, maxPositionBottom))
222         adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom);
223 
224     int maxHeight = maxAscent + maxDescent;
225     int lineTop = heightOfBlock;
226     int lineBottom = heightOfBlock;
227     placeBoxesVertically(heightOfBlock, maxHeight, maxAscent, strictMode, lineTop, lineBottom);
228     computeVerticalOverflow(lineTop, lineBottom, strictMode);
229     setLineTopBottomPositions(lineTop, lineBottom);
230 
231     heightOfBlock += maxHeight;
232 
233     return heightOfBlock;
234 }
235 
fillLineSelectionGap(int selTop,int selHeight,RenderBlock * rootBlock,int blockX,int blockY,int tx,int ty,const RenderObject::PaintInfo * paintInfo)236 GapRects RootInlineBox::fillLineSelectionGap(int selTop, int selHeight, RenderBlock* rootBlock, int blockX, int blockY, int tx, int ty,
237                                              const RenderObject::PaintInfo* paintInfo)
238 {
239     RenderObject::SelectionState lineState = selectionState();
240 
241     bool leftGap, rightGap;
242     block()->getHorizontalSelectionGapInfo(lineState, leftGap, rightGap);
243 
244     GapRects result;
245 
246     InlineBox* firstBox = firstSelectedBox();
247     InlineBox* lastBox = lastSelectedBox();
248     if (leftGap)
249         result.uniteLeft(block()->fillLeftSelectionGap(firstBox->parent()->renderer(),
250                                                        firstBox->x(), selTop, selHeight,
251                                                        rootBlock, blockX, blockY, tx, ty, paintInfo));
252     if (rightGap)
253         result.uniteRight(block()->fillRightSelectionGap(lastBox->parent()->renderer(),
254                                                          lastBox->x() + lastBox->width(), selTop, selHeight,
255                                                          rootBlock, blockX, blockY, tx, ty, paintInfo));
256 
257     // When dealing with bidi text, a non-contiguous selection region is possible.
258     // e.g. The logical text aaaAAAbbb (capitals denote RTL text and non-capitals LTR) is layed out
259     // visually as 3 text runs |aaa|bbb|AAA| if we select 4 characters from the start of the text the
260     // selection will look like (underline denotes selection):
261     // |aaa|bbb|AAA|
262     //  ___       _
263     // We can see that the |bbb| run is not part of the selection while the runs around it are.
264     if (firstBox && firstBox != lastBox) {
265         // Now fill in any gaps on the line that occurred between two selected elements.
266         int lastX = firstBox->x() + firstBox->width();
267         bool isPreviousBoxSelected = firstBox->selectionState() != RenderObject::SelectionNone;
268         for (InlineBox* box = firstBox->nextLeafChild(); box; box = box->nextLeafChild()) {
269             if (box->selectionState() != RenderObject::SelectionNone) {
270                 if (isPreviousBoxSelected)  // VisibleSelection may be non-contiguous, see comment above.
271                     result.uniteCenter(block()->fillHorizontalSelectionGap(box->parent()->renderer(),
272                                                                            lastX + tx, selTop + ty,
273                                                                            box->x() - lastX, selHeight, paintInfo));
274                 lastX = box->x() + box->width();
275             }
276             if (box == lastBox)
277                 break;
278             isPreviousBoxSelected = box->selectionState() != RenderObject::SelectionNone;
279         }
280     }
281 
282     return result;
283 }
284 
setHasSelectedChildren(bool b)285 void RootInlineBox::setHasSelectedChildren(bool b)
286 {
287     if (m_hasSelectedChildren == b)
288         return;
289     m_hasSelectedChildren = b;
290 }
291 
selectionState()292 RenderObject::SelectionState RootInlineBox::selectionState()
293 {
294     // Walk over all of the selected boxes.
295     RenderObject::SelectionState state = RenderObject::SelectionNone;
296     for (InlineBox* box = firstLeafChild(); box; box = box->nextLeafChild()) {
297         RenderObject::SelectionState boxState = box->selectionState();
298         if ((boxState == RenderObject::SelectionStart && state == RenderObject::SelectionEnd) ||
299             (boxState == RenderObject::SelectionEnd && state == RenderObject::SelectionStart))
300             state = RenderObject::SelectionBoth;
301         else if (state == RenderObject::SelectionNone ||
302                  ((boxState == RenderObject::SelectionStart || boxState == RenderObject::SelectionEnd) &&
303                   (state == RenderObject::SelectionNone || state == RenderObject::SelectionInside)))
304             state = boxState;
305         if (state == RenderObject::SelectionBoth)
306             break;
307     }
308 
309     return state;
310 }
311 
firstSelectedBox()312 InlineBox* RootInlineBox::firstSelectedBox()
313 {
314     for (InlineBox* box = firstLeafChild(); box; box = box->nextLeafChild()) {
315         if (box->selectionState() != RenderObject::SelectionNone)
316             return box;
317     }
318 
319     return 0;
320 }
321 
lastSelectedBox()322 InlineBox* RootInlineBox::lastSelectedBox()
323 {
324     for (InlineBox* box = lastLeafChild(); box; box = box->prevLeafChild()) {
325         if (box->selectionState() != RenderObject::SelectionNone)
326             return box;
327     }
328 
329     return 0;
330 }
331 
selectionTop() const332 int RootInlineBox::selectionTop() const
333 {
334     int selectionTop = m_lineTop;
335     if (!prevRootBox())
336         return selectionTop;
337 
338     int prevBottom = prevRootBox()->selectionBottom();
339     if (prevBottom < selectionTop && block()->containsFloats()) {
340         // This line has actually been moved further down, probably from a large line-height, but possibly because the
341         // line was forced to clear floats.  If so, let's check the offsets, and only be willing to use the previous
342         // line's bottom overflow if the offsets are greater on both sides.
343         int prevLeft = block()->leftOffset(prevBottom, !prevRootBox());
344         int prevRight = block()->rightOffset(prevBottom, !prevRootBox());
345         int newLeft = block()->leftOffset(selectionTop, !prevRootBox());
346         int newRight = block()->rightOffset(selectionTop, !prevRootBox());
347         if (prevLeft > newLeft || prevRight < newRight)
348             return selectionTop;
349     }
350 
351     return prevBottom;
352 }
353 
block() const354 RenderBlock* RootInlineBox::block() const
355 {
356     return toRenderBlock(renderer());
357 }
358 
isEditableLeaf(InlineBox * leaf)359 static bool isEditableLeaf(InlineBox* leaf)
360 {
361     return leaf && leaf->renderer() && leaf->renderer()->node() && leaf->renderer()->node()->isContentEditable();
362 }
363 
closestLeafChildForXPos(int x,bool onlyEditableLeaves)364 InlineBox* RootInlineBox::closestLeafChildForXPos(int x, bool onlyEditableLeaves)
365 {
366     InlineBox* firstLeaf = firstLeafChild();
367     InlineBox* lastLeaf = lastLeafChild();
368     if (firstLeaf == lastLeaf && (!onlyEditableLeaves || isEditableLeaf(firstLeaf)))
369         return firstLeaf;
370 
371     // Avoid returning a list marker when possible.
372     if (x <= firstLeaf->m_x && !firstLeaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(firstLeaf)))
373         // The x coordinate is less or equal to left edge of the firstLeaf.
374         // Return it.
375         return firstLeaf;
376 
377     if (x >= lastLeaf->m_x + lastLeaf->m_width && !lastLeaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(lastLeaf)))
378         // The x coordinate is greater or equal to right edge of the lastLeaf.
379         // Return it.
380         return lastLeaf;
381 
382     InlineBox* closestLeaf = 0;
383     for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) {
384         if (!leaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(leaf))) {
385             closestLeaf = leaf;
386             if (x < leaf->m_x + leaf->m_width)
387                 // The x coordinate is less than the right edge of the box.
388                 // Return it.
389                 return leaf;
390         }
391     }
392 
393     return closestLeaf ? closestLeaf : lastLeaf;
394 }
395 
lineBreakBidiStatus() const396 BidiStatus RootInlineBox::lineBreakBidiStatus() const
397 {
398     return BidiStatus(m_lineBreakBidiStatusEor, m_lineBreakBidiStatusLastStrong, m_lineBreakBidiStatusLast, m_lineBreakContext);
399 }
400 
setLineBreakInfo(RenderObject * obj,unsigned breakPos,const BidiStatus & status)401 void RootInlineBox::setLineBreakInfo(RenderObject* obj, unsigned breakPos, const BidiStatus& status)
402 {
403     m_lineBreakObj = obj;
404     m_lineBreakPos = breakPos;
405     m_lineBreakBidiStatusEor = status.eor;
406     m_lineBreakBidiStatusLastStrong = status.lastStrong;
407     m_lineBreakBidiStatusLast = status.last;
408     m_lineBreakContext = status.context;
409 }
410 
ellipsisBox() const411 EllipsisBox* RootInlineBox::ellipsisBox() const
412 {
413     if (!m_hasEllipsisBox)
414         return false;
415     return gEllipsisBoxMap->get(this);
416 }
417 
removeLineBoxFromRenderObject()418 void RootInlineBox::removeLineBoxFromRenderObject()
419 {
420     block()->lineBoxes()->removeLineBox(this);
421 }
422 
extractLineBoxFromRenderObject()423 void RootInlineBox::extractLineBoxFromRenderObject()
424 {
425     block()->lineBoxes()->extractLineBox(this);
426 }
427 
attachLineBoxToRenderObject()428 void RootInlineBox::attachLineBoxToRenderObject()
429 {
430     block()->lineBoxes()->attachLineBox(this);
431 }
432 
433 } // namespace WebCore
434