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