1/* 2 * Copyright (C) 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include "config.h" 30#include "RenderLineBoxList.h" 31 32#include "InlineTextBox.h" 33#include "RenderArena.h" 34#include "RenderInline.h" 35#include "RenderView.h" 36#include "RootInlineBox.h" 37 38using namespace std; 39 40namespace WebCore { 41 42#ifndef NDEBUG 43RenderLineBoxList::~RenderLineBoxList() 44{ 45 ASSERT(!m_firstLineBox); 46 ASSERT(!m_lastLineBox); 47} 48#endif 49 50void RenderLineBoxList::appendLineBox(InlineFlowBox* box) 51{ 52 checkConsistency(); 53 54 if (!m_firstLineBox) 55 m_firstLineBox = m_lastLineBox = box; 56 else { 57 m_lastLineBox->setNextLineBox(box); 58 box->setPreviousLineBox(m_lastLineBox); 59 m_lastLineBox = box; 60 } 61 62 checkConsistency(); 63} 64 65void RenderLineBoxList::deleteLineBoxTree(RenderArena* arena) 66{ 67 InlineFlowBox* line = m_firstLineBox; 68 InlineFlowBox* nextLine; 69 while (line) { 70 nextLine = line->nextFlowBox(); 71 line->deleteLine(arena); 72 line = nextLine; 73 } 74 m_firstLineBox = m_lastLineBox = 0; 75} 76 77void RenderLineBoxList::extractLineBox(InlineFlowBox* box) 78{ 79 checkConsistency(); 80 81 m_lastLineBox = box->prevFlowBox(); 82 if (box == m_firstLineBox) 83 m_firstLineBox = 0; 84 if (box->prevLineBox()) 85 box->prevLineBox()->setNextLineBox(0); 86 box->setPreviousLineBox(0); 87 for (InlineRunBox* curr = box; curr; curr = curr->nextLineBox()) 88 curr->setExtracted(); 89 90 checkConsistency(); 91} 92 93void RenderLineBoxList::attachLineBox(InlineFlowBox* box) 94{ 95 checkConsistency(); 96 97 if (m_lastLineBox) { 98 m_lastLineBox->setNextLineBox(box); 99 box->setPreviousLineBox(m_lastLineBox); 100 } else 101 m_firstLineBox = box; 102 InlineFlowBox* last = box; 103 for (InlineFlowBox* curr = box; curr; curr = curr->nextFlowBox()) { 104 curr->setExtracted(false); 105 last = curr; 106 } 107 m_lastLineBox = last; 108 109 checkConsistency(); 110} 111 112void RenderLineBoxList::removeLineBox(InlineFlowBox* box) 113{ 114 checkConsistency(); 115 116 if (box == m_firstLineBox) 117 m_firstLineBox = box->nextFlowBox(); 118 if (box == m_lastLineBox) 119 m_lastLineBox = box->prevFlowBox(); 120 if (box->nextLineBox()) 121 box->nextLineBox()->setPreviousLineBox(box->prevLineBox()); 122 if (box->prevLineBox()) 123 box->prevLineBox()->setNextLineBox(box->nextLineBox()); 124 125 checkConsistency(); 126} 127 128void RenderLineBoxList::deleteLineBoxes(RenderArena* arena) 129{ 130 if (m_firstLineBox) { 131 InlineRunBox* next; 132 for (InlineRunBox* curr = m_firstLineBox; curr; curr = next) { 133 next = curr->nextLineBox(); 134 curr->destroy(arena); 135 } 136 m_firstLineBox = 0; 137 m_lastLineBox = 0; 138 } 139} 140 141void RenderLineBoxList::dirtyLineBoxes() 142{ 143 for (InlineRunBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) 144 curr->dirtyLineBoxes(); 145} 146 147void RenderLineBoxList::paint(RenderBoxModelObject* renderer, RenderObject::PaintInfo& paintInfo, int tx, int ty) const 148{ 149 // Only paint during the foreground/selection phases. 150 if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseOutline 151 && paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines && paintInfo.phase != PaintPhaseTextClip 152 && paintInfo.phase != PaintPhaseMask) 153 return; 154 155 ASSERT(renderer->isRenderBlock() || (renderer->isRenderInline() && renderer->hasLayer())); // The only way an inline could paint like this is if it has a layer. 156 157 // If we have no lines then we have no work to do. 158 if (!firstLineBox()) 159 return; 160 161 // We can check the first box and last box and avoid painting if we don't 162 // intersect. This is a quick short-circuit that we can take to avoid walking any lines. 163 // FIXME: This check is flawed in the following extremely obscure way: 164 // if some line in the middle has a huge overflow, it might actually extend below the last line. 165 int yPos = firstLineBox()->topVisibleOverflow() - renderer->maximalOutlineSize(paintInfo.phase); 166 int h = renderer->maximalOutlineSize(paintInfo.phase) + lastLineBox()->bottomVisibleOverflow() - yPos; 167 yPos += ty; 168 if (yPos >= paintInfo.rect.bottom() || yPos + h <= paintInfo.rect.y()) 169 return; 170 171 RenderObject::PaintInfo info(paintInfo); 172 ListHashSet<RenderInline*> outlineObjects; 173 info.outlineObjects = &outlineObjects; 174 175 // See if our root lines intersect with the dirty rect. If so, then we paint 176 // them. Note that boxes can easily overlap, so we can't make any assumptions 177 // based off positions of our first line box or our last line box. 178 RenderView* v = renderer->view(); 179 bool usePrintRect = !v->printRect().isEmpty(); 180 for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextFlowBox()) { 181 if (usePrintRect) { 182 // FIXME: This is a feeble effort to avoid splitting a line across two pages. 183 // It is utterly inadequate, and this should not be done at paint time at all. 184 // The whole way objects break across pages needs to be redone. 185 // Try to avoid splitting a line vertically, but only if it's less than the height 186 // of the entire page. 187 if (curr->bottomVisibleOverflow() - curr->topVisibleOverflow() <= v->printRect().height()) { 188 if (ty + curr->bottomVisibleOverflow() > v->printRect().bottom()) { 189 if (ty + curr->topVisibleOverflow() < v->truncatedAt()) 190 v->setBestTruncatedAt(ty + curr->root()->topVisibleOverflow(), renderer); 191 // If we were able to truncate, don't paint. 192 if (ty + curr->topVisibleOverflow() >= v->truncatedAt()) 193 break; 194 } 195 } 196 } 197 198 int top = min(curr->topVisibleOverflow(), curr->root()->selectionTop()) - renderer->maximalOutlineSize(info.phase); 199 int bottom = curr->bottomVisibleOverflow() + renderer->maximalOutlineSize(info.phase); 200 h = bottom - top; 201 yPos = ty + top; 202 v->setMinimumColumnHeight(h); 203 if (yPos < info.rect.bottom() && yPos + h > info.rect.y()) 204 curr->paint(info, tx, ty); 205 } 206 207 if (info.phase == PaintPhaseOutline || info.phase == PaintPhaseSelfOutline || info.phase == PaintPhaseChildOutlines) { 208 ListHashSet<RenderInline*>::iterator end = info.outlineObjects->end(); 209 for (ListHashSet<RenderInline*>::iterator it = info.outlineObjects->begin(); it != end; ++it) { 210 RenderInline* flow = *it; 211 flow->paintOutline(info.context, tx, ty); 212 } 213 info.outlineObjects->clear(); 214 } 215} 216 217 218bool RenderLineBoxList::hitTest(RenderBoxModelObject* renderer, const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) const 219{ 220 if (hitTestAction != HitTestForeground) 221 return false; 222 223 ASSERT(renderer->isRenderBlock() || (renderer->isRenderInline() && renderer->hasLayer())); // The only way an inline could hit test like this is if it has a layer. 224 225 // If we have no lines then we have no work to do. 226 if (!firstLineBox()) 227 return false; 228 229 // We can check the first box and last box and avoid hit testing if we don't 230 // contain the point. This is a quick short-circuit that we can take to avoid walking any lines. 231 // FIXME: This check is flawed in the following extremely obscure way: 232 // if some line in the middle has a huge overflow, it might actually extend below the last line. 233 if ((y >= ty + lastLineBox()->root()->bottomVisibleOverflow()) || (y < ty + firstLineBox()->root()->topVisibleOverflow())) 234 return false; 235 236 // See if our root lines contain the point. If so, then we hit test 237 // them further. Note that boxes can easily overlap, so we can't make any assumptions 238 // based off positions of our first line box or our last line box. 239 for (InlineFlowBox* curr = lastLineBox(); curr; curr = curr->prevFlowBox()) { 240 if (y >= ty + curr->root()->topVisibleOverflow() && y < ty + curr->root()->bottomVisibleOverflow()) { 241 bool inside = curr->nodeAtPoint(request, result, x, y, tx, ty); 242 if (inside) { 243 renderer->updateHitTestResult(result, IntPoint(x - tx, y - ty)); 244 return true; 245 } 246 } 247 } 248 249 return false; 250} 251 252void RenderLineBoxList::dirtyLinesFromChangedChild(RenderObject* container, RenderObject* child) 253{ 254 if (!container->parent() || (container->isRenderBlock() && (container->selfNeedsLayout() || !container->isBlockFlow()))) 255 return; 256 257 // If we have no first line box, then just bail early. 258 if (!firstLineBox()) { 259 // For an empty inline, go ahead and propagate the check up to our parent, unless the parent 260 // is already dirty. 261 if (container->isInline() && !container->parent()->selfNeedsLayout()) 262 container->parent()->dirtyLinesFromChangedChild(container); 263 return; 264 } 265 266 // Try to figure out which line box we belong in. First try to find a previous 267 // line box by examining our siblings. If we didn't find a line box, then use our 268 // parent's first line box. 269 RootInlineBox* box = 0; 270 RenderObject* curr = 0; 271 for (curr = child->previousSibling(); curr; curr = curr->previousSibling()) { 272 if (curr->isFloatingOrPositioned()) 273 continue; 274 275 if (curr->isReplaced()) { 276 InlineBox* wrapper = toRenderBox(curr)->inlineBoxWrapper(); 277 if (wrapper) 278 box = wrapper->root(); 279 } else if (curr->isText()) { 280 InlineTextBox* textBox = toRenderText(curr)->lastTextBox(); 281 if (textBox) 282 box = textBox->root(); 283 } else if (curr->isRenderInline()) { 284 InlineRunBox* runBox = toRenderInline(curr)->lastLineBox(); 285 if (runBox) 286 box = runBox->root(); 287 } 288 289 if (box) 290 break; 291 } 292 if (!box) 293 box = firstLineBox()->root(); 294 295 // If we found a line box, then dirty it. 296 if (box) { 297 RootInlineBox* adjacentBox; 298 box->markDirty(); 299 300 // dirty the adjacent lines that might be affected 301 // NOTE: we dirty the previous line because RootInlineBox objects cache 302 // the address of the first object on the next line after a BR, which we may be 303 // invalidating here. For more info, see how RenderBlock::layoutInlineChildren 304 // calls setLineBreakInfo with the result of findNextLineBreak. findNextLineBreak, 305 // despite the name, actually returns the first RenderObject after the BR. 306 // <rdar://problem/3849947> "Typing after pasting line does not appear until after window resize." 307 adjacentBox = box->prevRootBox(); 308 if (adjacentBox) 309 adjacentBox->markDirty(); 310 if (child->isBR() || (curr && curr->isBR())) { 311 adjacentBox = box->nextRootBox(); 312 if (adjacentBox) 313 adjacentBox->markDirty(); 314 } 315 } 316} 317 318#ifndef NDEBUG 319 320void RenderLineBoxList::checkConsistency() const 321{ 322#ifdef CHECK_CONSISTENCY 323 const InlineFlowBox* prev = 0; 324 for (const InlineFlowBox* child = m_firstLineBox; child != 0; child = child->nextFlowBox()) { 325 ASSERT(child->prevFlowBox() == prev); 326 prev = child; 327 } 328 ASSERT(prev == m_lastLineBox); 329#endif 330} 331 332#endif 333 334} 335