1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2000 Dirk Mueller (mueller@kde.org)
4 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "config.h"
24 #include "RenderReplaced.h"
25
26 #include "GraphicsContext.h"
27 #include "RenderBlock.h"
28 #include "RenderLayer.h"
29 #include "RenderTheme.h"
30 #include "RenderView.h"
31 #include "VisiblePosition.h"
32
33 using namespace std;
34
35 namespace WebCore {
36
37 typedef WTF::HashMap<const RenderReplaced*, IntRect> OverflowRectMap;
38 static OverflowRectMap* gOverflowRectMap = 0;
39
40 const int cDefaultWidth = 300;
41 const int cDefaultHeight = 150;
42
RenderReplaced(Node * node)43 RenderReplaced::RenderReplaced(Node* node)
44 : RenderBox(node)
45 , m_intrinsicSize(cDefaultWidth, cDefaultHeight)
46 {
47 setReplaced(true);
48 }
49
RenderReplaced(Node * node,const IntSize & intrinsicSize)50 RenderReplaced::RenderReplaced(Node* node, const IntSize& intrinsicSize)
51 : RenderBox(node)
52 , m_intrinsicSize(intrinsicSize)
53 {
54 setReplaced(true);
55 }
56
~RenderReplaced()57 RenderReplaced::~RenderReplaced()
58 {
59 if (replacedHasOverflow())
60 gOverflowRectMap->remove(this);
61 }
62
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)63 void RenderReplaced::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
64 {
65 RenderBox::styleDidChange(diff, oldStyle);
66
67 bool hadStyle = (oldStyle != 0);
68 float oldZoom = hadStyle ? oldStyle->effectiveZoom() : RenderStyle::initialZoom();
69 if (hadStyle && style() && style()->effectiveZoom() != oldZoom)
70 intrinsicSizeChanged();
71 }
72
layout()73 void RenderReplaced::layout()
74 {
75 ASSERT(needsLayout());
76
77 LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
78
79 setHeight(minimumReplacedHeight());
80
81 calcWidth();
82 calcHeight();
83 adjustOverflowForBoxShadowAndReflect();
84
85 repainter.repaintAfterLayout();
86
87 setNeedsLayout(false);
88 }
89
intrinsicSizeChanged()90 void RenderReplaced::intrinsicSizeChanged()
91 {
92 int scaledWidth = static_cast<int>(cDefaultWidth * style()->effectiveZoom());
93 int scaledHeight = static_cast<int>(cDefaultHeight * style()->effectiveZoom());
94 m_intrinsicSize = IntSize(scaledWidth, scaledHeight);
95 setNeedsLayoutAndPrefWidthsRecalc();
96 }
97
paint(PaintInfo & paintInfo,int tx,int ty)98 void RenderReplaced::paint(PaintInfo& paintInfo, int tx, int ty)
99 {
100 if (!shouldPaint(paintInfo, tx, ty))
101 return;
102
103 tx += x();
104 ty += y();
105
106 if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection))
107 paintBoxDecorations(paintInfo, tx, ty);
108
109 if (paintInfo.phase == PaintPhaseMask) {
110 paintMask(paintInfo, tx, ty);
111 return;
112 }
113
114 if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth())
115 paintOutline(paintInfo.context, tx, ty, width(), height(), style());
116
117 if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection)
118 return;
119
120 if (!shouldPaintWithinRoot(paintInfo))
121 return;
122
123 bool drawSelectionTint = selectionState() != SelectionNone && !document()->printing();
124 if (paintInfo.phase == PaintPhaseSelection) {
125 if (selectionState() == SelectionNone)
126 return;
127 drawSelectionTint = false;
128 }
129
130 if (style()->hasBorderRadius()) {
131 // Push a clip if we have a border radius, since we want to round the foreground content that gets painted.
132 paintInfo.context->save();
133
134 IntSize topLeft, topRight, bottomLeft, bottomRight;
135 IntRect borderRect = IntRect(tx, ty, width(), height());
136 style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight);
137
138 paintInfo.context->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight);
139 }
140
141 paintReplaced(paintInfo, tx, ty);
142
143 if (style()->hasBorderRadius())
144 paintInfo.context->restore();
145
146 // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of
147 // surrounding content.
148 if (drawSelectionTint) {
149 IntRect selectionPaintingRect = localSelectionRect();
150 selectionPaintingRect.move(tx, ty);
151 paintInfo.context->fillRect(selectionPaintingRect, selectionBackgroundColor());
152 }
153 }
154
shouldPaint(PaintInfo & paintInfo,int & tx,int & ty)155 bool RenderReplaced::shouldPaint(PaintInfo& paintInfo, int& tx, int& ty)
156 {
157 if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline
158 && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseMask)
159 return false;
160
161 if (!shouldPaintWithinRoot(paintInfo))
162 return false;
163
164 // if we're invisible or haven't received a layout yet, then just bail.
165 if (style()->visibility() != VISIBLE)
166 return false;
167
168 int currentTX = tx + x();
169 int currentTY = ty + y();
170
171 // Early exit if the element touches the edges.
172 int top = currentTY + overflowTop();
173 int bottom = currentTY + overflowHeight();
174 if (isSelected() && m_inlineBoxWrapper) {
175 int selTop = ty + m_inlineBoxWrapper->root()->selectionTop();
176 int selBottom = ty + selTop + m_inlineBoxWrapper->root()->selectionHeight();
177 top = min(selTop, top);
178 bottom = max(selBottom, bottom);
179 }
180
181 int os = 2 * maximalOutlineSize(paintInfo.phase);
182 if (currentTX + overflowLeft() >= paintInfo.rect.right() + os || currentTX + overflowWidth() <= paintInfo.rect.x() - os)
183 return false;
184 if (top >= paintInfo.rect.bottom() + os || bottom <= paintInfo.rect.y() - os)
185 return false;
186
187 return true;
188 }
189
calcPrefWidths()190 void RenderReplaced::calcPrefWidths()
191 {
192 ASSERT(prefWidthsDirty());
193
194 int paddingAndBorders = paddingLeft() + paddingRight() + borderLeft() + borderRight();
195 int width = calcReplacedWidth(false) + paddingAndBorders;
196
197 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength)
198 width = min(width, style()->maxWidth().value() + (style()->boxSizing() == CONTENT_BOX ? paddingAndBorders : 0));
199
200 if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) {
201 m_minPrefWidth = 0;
202 m_maxPrefWidth = width;
203 } else
204 m_minPrefWidth = m_maxPrefWidth = width;
205
206 setPrefWidthsDirty(false);
207 }
208
lineHeight(bool,bool) const209 int RenderReplaced::lineHeight(bool, bool) const
210 {
211 return height() + marginTop() + marginBottom();
212 }
213
baselinePosition(bool,bool) const214 int RenderReplaced::baselinePosition(bool, bool) const
215 {
216 return height() + marginTop() + marginBottom();
217 }
218
caretMaxRenderedOffset() const219 unsigned RenderReplaced::caretMaxRenderedOffset() const
220 {
221 return 1;
222 }
223
positionForPoint(const IntPoint & point)224 VisiblePosition RenderReplaced::positionForPoint(const IntPoint& point)
225 {
226 InlineBox* box = inlineBoxWrapper();
227 if (!box)
228 return createVisiblePosition(0, DOWNSTREAM);
229
230 // FIXME: This code is buggy if the replaced element is relative positioned.
231
232 RootInlineBox* root = box->root();
233
234 int top = root->topOverflow();
235 int bottom = root->nextRootBox() ? root->nextRootBox()->topOverflow() : root->bottomOverflow();
236
237 if (point.y() + y() < top)
238 return createVisiblePosition(caretMinOffset(), DOWNSTREAM); // coordinates are above
239
240 if (point.y() + y() >= bottom)
241 return createVisiblePosition(caretMaxOffset(), DOWNSTREAM); // coordinates are below
242
243 if (node()) {
244 if (point.x() <= width() / 2)
245 return createVisiblePosition(0, DOWNSTREAM);
246 return createVisiblePosition(1, DOWNSTREAM);
247 }
248
249 return RenderBox::positionForPoint(point);
250 }
251
selectionRectForRepaint(RenderBoxModelObject * repaintContainer,bool clipToVisibleContent)252 IntRect RenderReplaced::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent)
253 {
254 ASSERT(!needsLayout());
255
256 if (!isSelected())
257 return IntRect();
258
259 IntRect rect = localSelectionRect();
260 if (clipToVisibleContent)
261 computeRectForRepaint(repaintContainer, rect);
262 else
263 rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox();
264
265 return rect;
266 }
267
localSelectionRect(bool checkWhetherSelected) const268 IntRect RenderReplaced::localSelectionRect(bool checkWhetherSelected) const
269 {
270 if (checkWhetherSelected && !isSelected())
271 return IntRect();
272
273 if (!m_inlineBoxWrapper)
274 // We're a block-level replaced element. Just return our own dimensions.
275 return IntRect(0, 0, width(), height());
276
277 RenderBlock* cb = containingBlock();
278 if (!cb)
279 return IntRect();
280
281 RootInlineBox* root = m_inlineBoxWrapper->root();
282 return IntRect(0, root->selectionTop() - y(), width(), root->selectionHeight());
283 }
284
setSelectionState(SelectionState s)285 void RenderReplaced::setSelectionState(SelectionState s)
286 {
287 RenderBox::setSelectionState(s);
288 if (m_inlineBoxWrapper) {
289 RootInlineBox* line = m_inlineBoxWrapper->root();
290 if (line)
291 line->setHasSelectedChildren(isSelected());
292 }
293
294 containingBlock()->setSelectionState(s);
295 }
296
isSelected() const297 bool RenderReplaced::isSelected() const
298 {
299 SelectionState s = selectionState();
300 if (s == SelectionNone)
301 return false;
302 if (s == SelectionInside)
303 return true;
304
305 int selectionStart, selectionEnd;
306 selectionStartEnd(selectionStart, selectionEnd);
307 if (s == SelectionStart)
308 return selectionStart == 0;
309
310 int end = node()->hasChildNodes() ? node()->childNodeCount() : 1;
311 if (s == SelectionEnd)
312 return selectionEnd == end;
313 if (s == SelectionBoth)
314 return selectionStart == 0 && selectionEnd == end;
315
316 ASSERT(0);
317 return false;
318 }
319
intrinsicSize() const320 IntSize RenderReplaced::intrinsicSize() const
321 {
322 return m_intrinsicSize;
323 }
324
setIntrinsicSize(const IntSize & size)325 void RenderReplaced::setIntrinsicSize(const IntSize& size)
326 {
327 m_intrinsicSize = size;
328 }
329
adjustOverflowForBoxShadowAndReflect()330 void RenderReplaced::adjustOverflowForBoxShadowAndReflect()
331 {
332 IntRect overflow;
333 for (ShadowData* boxShadow = style()->boxShadow(); boxShadow; boxShadow = boxShadow->next) {
334 if (boxShadow->style == Inset)
335 continue;
336 IntRect shadow = borderBoxRect();
337 shadow.move(boxShadow->x, boxShadow->y);
338 shadow.inflate(boxShadow->blur + boxShadow->spread);
339 overflow.unite(shadow);
340 }
341
342 // Now that we have an overflow rect including shadow, let's make sure that
343 // the reflection (which can also include the shadow) is also included.
344 if (hasReflection()) {
345 if (overflow.isEmpty())
346 overflow = borderBoxRect();
347 overflow.unite(reflectedRect(overflow));
348 }
349
350 if (!overflow.isEmpty()) {
351 if (!gOverflowRectMap)
352 gOverflowRectMap = new OverflowRectMap();
353 overflow.unite(borderBoxRect());
354 gOverflowRectMap->set(this, overflow);
355 setReplacedHasOverflow(true);
356 } else if (replacedHasOverflow()) {
357 gOverflowRectMap->remove(this);
358 setReplacedHasOverflow(false);
359 }
360 }
361
overflowHeight(bool) const362 int RenderReplaced::overflowHeight(bool) const
363 {
364 if (replacedHasOverflow()) {
365 IntRect *r = &gOverflowRectMap->find(this)->second;
366 return r->height() + r->y();
367 }
368
369 return height();
370 }
371
overflowWidth(bool) const372 int RenderReplaced::overflowWidth(bool) const
373 {
374 if (replacedHasOverflow()) {
375 IntRect *r = &gOverflowRectMap->find(this)->second;
376 return r->width() + r->x();
377 }
378
379 return width();
380 }
381
overflowLeft(bool) const382 int RenderReplaced::overflowLeft(bool) const
383 {
384 if (replacedHasOverflow())
385 return gOverflowRectMap->get(this).x();
386
387 return 0;
388 }
389
overflowTop(bool) const390 int RenderReplaced::overflowTop(bool) const
391 {
392 if (replacedHasOverflow())
393 return gOverflowRectMap->get(this).y();
394
395 return 0;
396 }
397
overflowRect(bool) const398 IntRect RenderReplaced::overflowRect(bool) const
399 {
400 if (replacedHasOverflow())
401 return gOverflowRectMap->find(this)->second;
402
403 return borderBoxRect();
404 }
405
clippedOverflowRectForRepaint(RenderBoxModelObject * repaintContainer)406 IntRect RenderReplaced::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer)
407 {
408 if (style()->visibility() != VISIBLE && !enclosingLayer()->hasVisibleContent())
409 return IntRect();
410
411 // The selectionRect can project outside of the overflowRect, so take their union
412 // for repainting to avoid selection painting glitches.
413 IntRect r = unionRect(localSelectionRect(false), overflowRect(false));
414
415 RenderView* v = view();
416 if (v) {
417 // FIXME: layoutDelta needs to be applied in parts before/after transforms and
418 // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308
419 r.move(v->layoutDelta());
420 }
421
422 if (style()) {
423 if (style()->hasAppearance())
424 // The theme may wish to inflate the rect used when repainting.
425 theme()->adjustRepaintRect(this, r);
426 if (v)
427 r.inflate(style()->outlineSize());
428 }
429 computeRectForRepaint(repaintContainer, r);
430 return r;
431 }
432
433 }
434