RenderBoxModelObject.cpp 83.1 KB
Newer Older
1
/*
2 3 4 5 6
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
 *           (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com)
 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
7
 * Copyright (C) 2010 Google Inc. All rights reserved.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "RenderBoxModelObject.h"

29
#include "GraphicsContext.h"
30
#include "HTMLFrameOwnerElement.h"
31 32
#include "HTMLNames.h"
#include "ImageBuffer.h"
33
#include "Path.h"
34
#include "RenderBlock.h"
35
#include "RenderInline.h"
36 37
#include "RenderLayer.h"
#include "RenderView.h"
38
#include <wtf/CurrentTime.h>
39

40 41
using namespace std;

42 43
namespace WebCore {

44 45
using namespace HTMLNames;

46
bool RenderBoxModelObject::s_wasFloating = false;
hyatt@apple.com's avatar
hyatt@apple.com committed
47 48
bool RenderBoxModelObject::s_hadLayer = false;
bool RenderBoxModelObject::s_layerWasSelfPainting = false;
49

50 51 52
static const double cInterpolationCutoff = 800. * 800.;
static const double cLowQualityTimeThreshold = 0.500; // 500 ms

53 54
typedef pair<RenderBoxModelObject*, const void*> LastPaintSizeMapKey;
typedef HashMap<LastPaintSizeMapKey, IntSize> LastPaintSizeMap;
55

56 57 58
class ImageQualityController : public Noncopyable {
public:
    ImageQualityController();
59 60
    bool shouldPaintAtLowQuality(GraphicsContext*, RenderBoxModelObject*, Image*, const void* layer, const IntSize&);
    void keyDestroyed(LastPaintSizeMapKey key);
61
    void objectDestroyed(RenderBoxModelObject*);
62 63

private:
64 65 66
    void highQualityRepaintTimerFired(Timer<ImageQualityController>*);
    void restartTimer();

67
    LastPaintSizeMap m_lastPaintSizeMap;
68
    Timer<ImageQualityController> m_timer;
69
    bool m_animatedResizeIsActive;
70 71
};

72 73
ImageQualityController::ImageQualityController()
    : m_timer(this, &ImageQualityController::highQualityRepaintTimerFired)
74
    , m_animatedResizeIsActive(false)
75 76
{
}
77

78
void ImageQualityController::keyDestroyed(LastPaintSizeMapKey key)
79
{
80
    m_lastPaintSizeMap.remove(key);
81 82
    if (m_lastPaintSizeMap.isEmpty()) {
        m_animatedResizeIsActive = false;
83
        m_timer.stop();
84
    }
85 86
}
    
87 88 89 90 91 92 93 94 95 96
void ImageQualityController::objectDestroyed(RenderBoxModelObject* object)
{
    Vector<LastPaintSizeMapKey> keysToDie;
    for (LastPaintSizeMap::iterator it = m_lastPaintSizeMap.begin(); it != m_lastPaintSizeMap.end(); ++it)
        if (it->first.first == object)
            keysToDie.append(it->first);
    for (Vector<LastPaintSizeMapKey>::iterator it = keysToDie.begin(); it != keysToDie.end(); ++it)
        keyDestroyed(*it);
}
    
97 98
void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityController>*)
{
99 100 101
    if (m_animatedResizeIsActive) {
        m_animatedResizeIsActive = false;
        for (LastPaintSizeMap::iterator it = m_lastPaintSizeMap.begin(); it != m_lastPaintSizeMap.end(); ++it)
102
            it->first.first->repaint();
103
    }
104
}
105

106 107
void ImageQualityController::restartTimer()
{
108
    m_timer.startOneShot(cLowQualityTimeThreshold);
109
}
110

111
bool ImageQualityController::shouldPaintAtLowQuality(GraphicsContext* context, RenderBoxModelObject* object, Image* image, const void *layer, const IntSize& size)
112 113 114
{
    // If the image is not a bitmap image, then none of this is relevant and we just paint at high
    // quality.
115
    if (!image || !image->isBitmapImage() || context->paintingDisabled())
116 117 118 119 120 121 122
        return false;

    // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image
    // is actually being scaled.
    IntSize imageSize(image->width(), image->height());

    // Look ourselves up in the hashtable.
123 124
    LastPaintSizeMapKey key(object, layer);
    LastPaintSizeMap::iterator i = m_lastPaintSizeMap.find(key);
125

126
    const AffineTransform& currentTransform = context->getCTM();
127
    bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped();
128
    if (!contextIsScaled && imageSize == size) {
129
        // There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list.
130
        if (i != m_lastPaintSizeMap.end())
131
            m_lastPaintSizeMap.remove(key);
132

133 134 135
        return false;
    }

136
    // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case.
137 138 139 140 141
    if (object->document()->page()->inLowQualityImageInterpolationMode()) {
        double totalPixels = static_cast<double>(image->width()) * static_cast<double>(image->height());
        if (totalPixels > cInterpolationCutoff)
            return true;
    }
142 143
    // If an animated resize is active, paint in low quality and kick the timer ahead.
    if (m_animatedResizeIsActive) {
144
        m_lastPaintSizeMap.set(key, size);
145 146 147
        restartTimer();
        return true;
    }
148 149 150 151 152
    // If this is the first time resizing this image, or its size is the
    // same as the last resize, draw at high res, but record the paint
    // size and set the timer.
    if (i == m_lastPaintSizeMap.end() || size == i->second) {
        restartTimer();
153
        m_lastPaintSizeMap.set(key, size);
154 155 156 157 158
        return false;
    }
    // If the timer is no longer active, draw at high quality and don't
    // set the timer.
    if (!m_timer.isActive()) {
159
        keyDestroyed(key);
160 161
        return false;
    }
162 163 164
    // This object has been resized to two different sizes while the timer
    // is active, so draw at low quality, set the flag for animated resizes and
    // the object to the list for high quality redraw.
165
    m_lastPaintSizeMap.set(key, size);
166
    m_animatedResizeIsActive = true;
167 168
    restartTimer();
    return true;
169 170
}

171
static ImageQualityController* imageQualityController()
172
{
173 174
    static ImageQualityController* controller = new ImageQualityController;
    return controller;
175 176
}

177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
void RenderBoxModelObject::setSelectionState(SelectionState s)
{
    if (selectionState() == s)
        return;
    
    if (s == SelectionInside && selectionState() != SelectionNone)
        return;

    if ((s == SelectionStart && selectionState() == SelectionEnd)
        || (s == SelectionEnd && selectionState() == SelectionStart))
        RenderObject::setSelectionState(SelectionBoth);
    else
        RenderObject::setSelectionState(s);
    
    // FIXME:
    // We should consider whether it is OK propagating to ancestor RenderInlines.
    // This is a workaround for http://webkit.org/b/32123
    RenderBlock* cb = containingBlock();
    if (cb && !cb->isRenderView())
        cb->setSelectionState(s);
}

199
bool RenderBoxModelObject::shouldPaintAtLowQuality(GraphicsContext* context, Image* image, const void* layer, const IntSize& size)
200
{
201
    return imageQualityController()->shouldPaintAtLowQuality(context, this, image, layer, size);
202
}
203

204 205
RenderBoxModelObject::RenderBoxModelObject(Node* node)
    : RenderObject(node)
206
    , m_layer(0)
207 208 209 210 211
{
}

RenderBoxModelObject::~RenderBoxModelObject()
{
eric@webkit.org's avatar
eric@webkit.org committed
212 213 214
    // Our layer should have been destroyed and cleared by now
    ASSERT(!hasLayer());
    ASSERT(!m_layer);
215
    imageQualityController()->objectDestroyed(this);
eric@webkit.org's avatar
eric@webkit.org committed
216 217 218 219
}

void RenderBoxModelObject::destroyLayer()
{
bdakin@apple.com's avatar
bdakin@apple.com committed
220
    ASSERT(!hasLayer()); // Callers should have already called setHasLayer(false)
eric@webkit.org's avatar
eric@webkit.org committed
221 222 223
    ASSERT(m_layer);
    m_layer->destroy(renderArena());
    m_layer = 0;
224 225
}

226 227 228 229 230
void RenderBoxModelObject::destroy()
{
    // This must be done before we destroy the RenderObject.
    if (m_layer)
        m_layer->clearClipRects();
eric@webkit.org's avatar
eric@webkit.org committed
231 232

    // RenderObject::destroy calls back to destroyLayer() for layer destruction
233 234 235
    RenderObject::destroy();
}

hyatt@apple.com's avatar
hyatt@apple.com committed
236 237 238 239 240
bool RenderBoxModelObject::hasSelfPaintingLayer() const
{
    return m_layer && m_layer->isSelfPaintingLayer();
}

241 242 243
void RenderBoxModelObject::styleWillChange(StyleDifference diff, const RenderStyle* newStyle)
{
    s_wasFloating = isFloating();
hyatt@apple.com's avatar
hyatt@apple.com committed
244 245 246
    s_hadLayer = hasLayer();
    if (s_hadLayer)
        s_layerWasSelfPainting = layer()->isSelfPaintingLayer();
mitz@apple.com's avatar
mitz@apple.com committed
247 248 249 250

    // If our z-index changes value or our visibility changes,
    // we need to dirty our stacking context's z-order list.
    if (style() && newStyle) {
hyatt@apple.com's avatar
hyatt@apple.com committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
        if (parent()) {
            // Do a repaint with the old style first, e.g., for example if we go from
            // having an outline to not having an outline.
            if (diff == StyleDifferenceRepaintLayer) {
                layer()->repaintIncludingDescendants();
                if (!(style()->clip() == newStyle->clip()))
                    layer()->clearClipRectsIncludingDescendants();
            } else if (diff == StyleDifferenceRepaint || newStyle->outlineSize() < style()->outlineSize())
                repaint();
        }
        
        if (diff == StyleDifferenceLayout) {
            // When a layout hint happens, we go ahead and do a repaint of the layer, since the layer could
            // end up being destroyed.
            if (hasLayer()) {
                if (style()->position() != newStyle->position() ||
                    style()->zIndex() != newStyle->zIndex() ||
                    style()->hasAutoZIndex() != newStyle->hasAutoZIndex() ||
                    !(style()->clip() == newStyle->clip()) ||
                    style()->hasClip() != newStyle->hasClip() ||
                    style()->opacity() != newStyle->opacity() ||
                    style()->transform() != newStyle->transform())
                layer()->repaintIncludingDescendants();
            } else if (newStyle->hasTransform() || newStyle->opacity() < 1) {
                // If we don't have a layer yet, but we are going to get one because of transform or opacity,
                //  then we need to repaint the old position of the object.
                repaint();
            }
        }

mitz@apple.com's avatar
mitz@apple.com committed
281 282 283 284 285 286 287 288 289
        if (hasLayer() && (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() ||
                           style()->zIndex() != newStyle->zIndex() ||
                           style()->visibility() != newStyle->visibility())) {
            layer()->dirtyStackingContextZOrderLists();
            if (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || style()->visibility() != newStyle->visibility())
                layer()->dirtyZOrderLists();
        }
    }

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
    RenderObject::styleWillChange(diff, newStyle);
}

void RenderBoxModelObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
    RenderObject::styleDidChange(diff, oldStyle);
    updateBoxModelInfoFromStyle();
    
    if (requiresLayer()) {
        if (!layer()) {
            if (s_wasFloating && isFloating())
                setChildNeedsLayout(true);
            m_layer = new (renderArena()) RenderLayer(this);
            setHasLayer(true);
            m_layer->insertOnlyThisLayer();
            if (parent() && !needsLayout() && containingBlock())
                m_layer->updateLayerPositions();
        }
    } else if (layer() && layer()->parent()) {
        setHasTransform(false); // Either a transform wasn't specified or the object doesn't support transforms, so just null out the bit.
        setHasReflection(false);
eric@webkit.org's avatar
eric@webkit.org committed
311
        m_layer->removeOnlyThisLayer(); // calls destroyLayer() which clears m_layer
312 313 314 315
        if (s_wasFloating && isFloating())
            setChildNeedsLayout(true);
    }

hyatt@apple.com's avatar
hyatt@apple.com committed
316 317 318 319 320
    if (layer()) {
        layer()->styleChanged(diff, oldStyle);
        if (s_hadLayer && layer()->isSelfPaintingLayer() != s_layerWasSelfPainting)
            setChildNeedsLayout(true);
    }
321 322 323 324 325 326
}

void RenderBoxModelObject::updateBoxModelInfoFromStyle()
{
    // Set the appropriate bits for a box model object.  Since all bits are cleared in styleWillChange,
    // we only check for bits that could possibly be set to true.
327
    setHasBoxDecorations(hasBackground() || style()->hasBorder() || style()->hasAppearance() || style()->boxShadow());
328 329 330 331
    setInline(style()->isDisplayInlineType());
    setRelPositioned(style()->position() == RelativePosition);
}

332 333
int RenderBoxModelObject::relativePositionOffsetX() const
{
hyatt@apple.com's avatar
hyatt@apple.com committed
334 335
    // Objects that shrink to avoid floats normally use available line width when computing containing block width.  However
    // in the case of relative positioning using percentages, we can't do this.  The offset should always be resolved using the
336
    // available width of the containing block.  Therefore we don't use containingBlockLogicalWidthForContent() here, but instead explicitly
hyatt@apple.com's avatar
hyatt@apple.com committed
337
    // call availableWidth on our containing block.
338
    if (!style()->left().isAuto()) {
hyatt@apple.com's avatar
hyatt@apple.com committed
339
        RenderBlock* cb = containingBlock();
340
        if (!style()->right().isAuto() && !containingBlock()->style()->isLeftToRightDirection())
hyatt@apple.com's avatar
hyatt@apple.com committed
341 342 343 344 345 346
            return -style()->right().calcValue(cb->availableWidth());
        return style()->left().calcValue(cb->availableWidth());
    }
    if (!style()->right().isAuto()) {
        RenderBlock* cb = containingBlock();
        return -style()->right().calcValue(cb->availableWidth());
347 348 349 350 351 352
    }
    return 0;
}

int RenderBoxModelObject::relativePositionOffsetY() const
{
353 354 355 356 357 358 359 360 361 362 363
    RenderBlock* containingBlock = this->containingBlock();

    // If the containing block of a relatively positioned element does not
    // specify a height, a percentage top or bottom offset should be resolved as
    // auto. An exception to this is if the containing block has the WinIE quirk
    // where <html> and <body> assume the size of the viewport. In this case,
    // calculate the percent offset based on this height.
    // See <https://bugs.webkit.org/show_bug.cgi?id=26396>.
    if (!style()->top().isAuto()
        && (!containingBlock->style()->height().isAuto()
            || !style()->top().isPercent()
364
            || containingBlock->stretchesToViewport()))
365 366 367 368 369
        return style()->top().calcValue(containingBlock->availableHeight());

    if (!style()->bottom().isAuto()
        && (!containingBlock->style()->height().isAuto()
            || !style()->bottom().isPercent()
370
            || containingBlock->stretchesToViewport()))
371
        return -style()->bottom().calcValue(containingBlock->availableHeight());
bdakin@apple.com's avatar
bdakin@apple.com committed
372

373 374 375
    return 0;
}

376 377
int RenderBoxModelObject::offsetLeft() const
{
levin@chromium.org's avatar
levin@chromium.org committed
378 379 380
    // If the element is the HTML body element or does not have an associated box
    // return 0 and stop this algorithm.
    if (isBody())
381
        return 0;
levin@chromium.org's avatar
levin@chromium.org committed
382 383
    
    RenderBoxModelObject* offsetPar = offsetParent();
384
    int xPos = (isBox() ? toRenderBox(this)->x() : 0);
levin@chromium.org's avatar
levin@chromium.org committed
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
    
    // If the offsetParent of the element is null, or is the HTML body element,
    // return the distance between the canvas origin and the left border edge 
    // of the element and stop this algorithm.
    if (offsetPar) {
        if (offsetPar->isBox() && !offsetPar->isBody())
            xPos -= toRenderBox(offsetPar)->borderLeft();
        if (!isPositioned()) {
            if (isRelPositioned())
                xPos += relativePositionOffsetX();
            RenderObject* curr = parent();
            while (curr && curr != offsetPar) {
                // FIXME: What are we supposed to do inside SVG content?
                if (curr->isBox() && !curr->isTableRow())
                    xPos += toRenderBox(curr)->x();
                curr = curr->parent();
            }
            if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned())
                xPos += toRenderBox(offsetPar)->x();
404 405
        }
    }
levin@chromium.org's avatar
levin@chromium.org committed
406

407 408 409 410 411
    return xPos;
}

int RenderBoxModelObject::offsetTop() const
{
levin@chromium.org's avatar
levin@chromium.org committed
412 413 414
    // If the element is the HTML body element or does not have an associated box
    // return 0 and stop this algorithm.
    if (isBody())
415
        return 0;
levin@chromium.org's avatar
levin@chromium.org committed
416 417
    
    RenderBoxModelObject* offsetPar = offsetParent();
418
    int yPos = (isBox() ? toRenderBox(this)->y() : 0);
levin@chromium.org's avatar
levin@chromium.org committed
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
    
    // If the offsetParent of the element is null, or is the HTML body element,
    // return the distance between the canvas origin and the top border edge 
    // of the element and stop this algorithm.
    if (offsetPar) {
        if (offsetPar->isBox() && !offsetPar->isBody())
            yPos -= toRenderBox(offsetPar)->borderTop();
        if (!isPositioned()) {
            if (isRelPositioned())
                yPos += relativePositionOffsetY();
            RenderObject* curr = parent();
            while (curr && curr != offsetPar) {
                // FIXME: What are we supposed to do inside SVG content?
                if (curr->isBox() && !curr->isTableRow())
                    yPos += toRenderBox(curr)->y();
                curr = curr->parent();
            }
            if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned())
                yPos += toRenderBox(offsetPar)->y();
438 439 440 441 442 443 444 445 446 447
        }
    }
    return yPos;
}

int RenderBoxModelObject::paddingTop(bool) const
{
    int w = 0;
    Length padding = style()->paddingTop();
    if (padding.isPercent())
448
        w = containingBlock()->availableLogicalWidth();
449 450 451 452 453 454 455 456
    return padding.calcMinValue(w);
}

int RenderBoxModelObject::paddingBottom(bool) const
{
    int w = 0;
    Length padding = style()->paddingBottom();
    if (padding.isPercent())
457
        w = containingBlock()->availableLogicalWidth();
458 459 460 461 462 463 464 465
    return padding.calcMinValue(w);
}

int RenderBoxModelObject::paddingLeft(bool) const
{
    int w = 0;
    Length padding = style()->paddingLeft();
    if (padding.isPercent())
466
        w = containingBlock()->availableLogicalWidth();
467 468 469 470 471 472 473 474
    return padding.calcMinValue(w);
}

int RenderBoxModelObject::paddingRight(bool) const
{
    int w = 0;
    Length padding = style()->paddingRight();
    if (padding.isPercent())
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
        w = containingBlock()->availableLogicalWidth();
    return padding.calcMinValue(w);
}

int RenderBoxModelObject::paddingBefore(bool) const
{
    int w = 0;
    Length padding = style()->paddingBefore();
    if (padding.isPercent())
        w = containingBlock()->availableLogicalWidth();
    return padding.calcMinValue(w);
}

int RenderBoxModelObject::paddingAfter(bool) const
{
    int w = 0;
    Length padding = style()->paddingAfter();
    if (padding.isPercent())
        w = containingBlock()->availableLogicalWidth();
    return padding.calcMinValue(w);
}

int RenderBoxModelObject::paddingStart(bool) const
{
    int w = 0;
    Length padding = style()->paddingStart();
    if (padding.isPercent())
        w = containingBlock()->availableLogicalWidth();
    return padding.calcMinValue(w);
}

int RenderBoxModelObject::paddingEnd(bool) const
{
    int w = 0;
    Length padding = style()->paddingEnd();
    if (padding.isPercent())
        w = containingBlock()->availableLogicalWidth();
512 513 514
    return padding.calcMinValue(w);
}

515
void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& c, const FillLayer* bgLayer, int tx, int ty, int w, int h, InlineFlowBox* box, CompositeOperator op, RenderObject* backgroundObject)
516 517
{
    GraphicsContext* context = paintInfo.context;
518 519 520
    if (context->paintingDisabled())
        return;

521 522
    bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true;
    bool includeRightEdge = box ? box->includeLogicalRightEdge() : true;
523 524 525 526 527 528 529
    int bLeft = includeLeftEdge ? borderLeft() : 0;
    int bRight = includeRightEdge ? borderRight() : 0;
    int pLeft = includeLeftEdge ? paddingLeft() : 0;
    int pRight = includeRightEdge ? paddingRight() : 0;

    bool clippedToBorderRadius = false;
    if (style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge)) {
mitz@apple.com's avatar
mitz@apple.com committed
530 531 532 533 534
        IntRect borderRect(tx, ty, w, h);

        if (borderRect.isEmpty())
            return;

535
        context->save();
bfulgham@webkit.org's avatar
bfulgham@webkit.org committed
536 537 538 539

        IntSize topLeft, topRight, bottomLeft, bottomRight;
        style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight);

540 541
        if (!includeLeftEdge) {
            topLeft = IntSize();
542
            if (box->isHorizontal())
543
                bottomLeft = IntSize();
544 545
            else
                topRight = IntSize();
546 547 548
        }
        
        if (!includeRightEdge) {
549
            if (box->isHorizontal())
550
                topRight = IntSize();
551 552
            else
                bottomLeft = IntSize();
553 554 555 556
            bottomRight = IntSize();
        }
        
        context->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight);
557 558 559
        clippedToBorderRadius = true;
    }

hyatt@apple.com's avatar
hyatt@apple.com committed
560 561 562 563 564 565 566
    bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer->attachment() == LocalBackgroundAttachment;
    if (clippedWithLocalScrolling) {
        // Clip to the overflow area.
        context->save();
        context->clip(toRenderBox(this)->overflowClipRect(tx, ty));
        
        // Now adjust our tx, ty, w, h to reflect a scrolled content box with borders at the ends.
567 568 569
        IntSize offset = layer()->scrolledContentOffset();
        tx -= offset.width();
        ty -= offset.height();
hyatt@apple.com's avatar
hyatt@apple.com committed
570 571 572 573
        w = bLeft + layer()->scrollWidth() + bRight;
        h = borderTop() + layer()->scrollHeight() + borderBottom();
    }
    
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
    if (bgLayer->clip() == PaddingFillBox || bgLayer->clip() == ContentFillBox) {
        // Clip to the padding or content boxes as necessary.
        bool includePadding = bgLayer->clip() == ContentFillBox;
        int x = tx + bLeft + (includePadding ? pLeft : 0);
        int y = ty + borderTop() + (includePadding ? paddingTop() : 0);
        int width = w - bLeft - bRight - (includePadding ? pLeft + pRight : 0);
        int height = h - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0);
        context->save();
        context->clip(IntRect(x, y, width, height));
    } else if (bgLayer->clip() == TextFillBox) {
        // We have to draw our text into a mask that can then be used to clip background drawing.
        // First figure out how big the mask has to be.  It should be no bigger than what we need
        // to actually render, so we should intersect the dirty rect with the border box of the background.
        IntRect maskRect(tx, ty, w, h);
        maskRect.intersect(paintInfo.rect);
        
        // Now create the mask.
591
        OwnPtr<ImageBuffer> maskImage = ImageBuffer::create(maskRect.size());
592
        if (!maskImage)
593 594 595 596 597 598 599 600 601
            return;
        
        GraphicsContext* maskImageContext = maskImage->context();
        maskImageContext->translate(-maskRect.x(), -maskRect.y());
        
        // Now add the text to the clip.  We do this by painting using a special paint phase that signals to
        // InlineTextBoxes that they should just add their contents to the clip.
        PaintInfo info(maskImageContext, maskRect, PaintPhaseTextClip, true, 0, 0);
        if (box)
602
            box->paint(info, tx - box->x(), ty - box->y());
603 604 605 606 607 608
        else {
            int x = isBox() ? toRenderBox(this)->x() : 0;
            int y = isBox() ? toRenderBox(this)->y() : 0;
            paint(info, tx - x, ty - y);
        }
        
609 610
        // The mask has been created.  Now we just need to clip to it.
        context->save();
611
        context->clipToImageBuffer(maskImage.get(), maskRect);
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
    }
    
    StyleImage* bg = bgLayer->image();
    bool shouldPaintBackgroundImage = bg && bg->canRender(style()->effectiveZoom());
    Color bgColor = c;

    // When this style flag is set, change existing background colors and images to a solid white background.
    // If there's no bg color or image, leave it untouched to avoid affecting transparency.
    // We don't try to avoid loading the background images, because this style flag is only set
    // when printing, and at that point we've already loaded the background images anyway. (To avoid
    // loading the background images we'd have to do this check when applying styles rather than
    // while rendering.)
    if (style()->forceBackgroundsToWhite()) {
        // Note that we can't reuse this variable below because the bgColor might be changed
        bool shouldPaintBackgroundColor = !bgLayer->next() && bgColor.isValid() && bgColor.alpha() > 0;
        if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) {
            bgColor = Color::white;
            shouldPaintBackgroundImage = false;
        }
    }

mitz@apple.com's avatar
mitz@apple.com committed
633 634
    bool isRoot = this->isRoot();

635 636
    // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with
    // no background in the child document should show the parent's background.
mitz@apple.com's avatar
mitz@apple.com committed
637
    bool isOpaqueRoot = false;
mitz@apple.com's avatar
mitz@apple.com committed
638
    if (isRoot) {
mitz@apple.com's avatar
mitz@apple.com committed
639 640 641 642 643 644 645 646 647 648 649 650 651 652
        isOpaqueRoot = true;
        if (!bgLayer->next() && !(bgColor.isValid() && bgColor.alpha() == 255) && view()->frameView()) {
            Element* ownerElement = document()->ownerElement();
            if (ownerElement) {
                if (!ownerElement->hasTagName(frameTag)) {
                    // Locate the <body> element using the DOM.  This is easier than trying
                    // to crawl around a render tree with potential :before/:after content and
                    // anonymous blocks created by inline <body> tags etc.  We can locate the <body>
                    // render object very easily via the DOM.
                    HTMLElement* body = document()->body();
                    if (body) {
                        // Can't scroll a frameset document anyway.
                        isOpaqueRoot = body->hasLocalName(framesetTag);
                    }
653 654 655 656 657 658
#if ENABLE(SVG)
                    else {
                        // SVG documents and XML documents with SVG root nodes are transparent.
                        isOpaqueRoot = !document()->hasSVGRootNode();
                    }
#endif
659
                }
mitz@apple.com's avatar
mitz@apple.com committed
660 661 662 663
            } else
                isOpaqueRoot = !view()->frameView()->isTransparent();
        }
        view()->frameView()->setContentIsOpaque(isOpaqueRoot);
664 665 666 667
    }

    // Paint the color first underneath all images.
    if (!bgLayer->next()) {
668 669
        IntRect rect(tx, ty, w, h);
        rect.intersect(paintInfo.rect);
670
        // If we have an alpha and we are painting the root element, go ahead and blend with the base background color.
mitz@apple.com's avatar
mitz@apple.com committed
671
        if (isOpaqueRoot) {
672 673
            Color baseColor = view()->frameView()->baseBackgroundColor();
            if (baseColor.alpha() > 0) {
674
                CompositeOperator previousOperator = context->compositeOperation();
675
                context->setCompositeOperation(CompositeCopy);
676
                context->fillRect(rect, baseColor, style()->colorSpace());
677
                context->setCompositeOperation(previousOperator);
678 679 680 681 682
            } else
                context->clearRect(rect);
        }

        if (bgColor.isValid() && bgColor.alpha() > 0)
683
            context->fillRect(rect, bgColor, style()->colorSpace());
684 685 686 687 688 689 690 691
    }

    // no progressive loading of the background image
    if (shouldPaintBackgroundImage) {
        IntRect destRect;
        IntPoint phase;
        IntSize tileSize;

692
        calculateBackgroundImageGeometry(bgLayer, tx, ty, w, h, destRect, phase, tileSize);
mitz@apple.com's avatar
mitz@apple.com committed
693 694
        IntPoint destOrigin = destRect.location();
        destRect.intersect(paintInfo.rect);
695
        if (!destRect.isEmpty()) {
mitz@apple.com's avatar
mitz@apple.com committed
696
            phase += destRect.location() - destOrigin;
697
            CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer->composite() : op;
698
            RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this;
699
            Image* image = bg->image(clientForBackgroundImage, tileSize);
700
            bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, bgLayer, tileSize);
701
            context->drawTiledImage(image, style()->colorSpace(), destRect, phase, tileSize, compositeOp, useLowQualityScaling);
702 703 704 705 706 707 708 709 710 711
        }
    }

    if (bgLayer->clip() != BorderFillBox)
        // Undo the background clip
        context->restore();

    if (clippedToBorderRadius)
        // Undo the border radius clip
        context->restore();
hyatt@apple.com's avatar
hyatt@apple.com committed
712 713 714
        
    if (clippedWithLocalScrolling) // Undo the clip for local background attachments.
        context->restore();
715 716
}

717
IntSize RenderBoxModelObject::calculateFillTileSize(const FillLayer* fillLayer, IntSize positioningAreaSize) const
718
{
719 720
    StyleImage* image = fillLayer->image();
    image->setImageContainerSize(positioningAreaSize); // Use the box established by background-origin.
721

722
    EFillSizeType type = fillLayer->size().type;
mitz@apple.com's avatar
mitz@apple.com committed
723 724 725

    switch (type) {
        case SizeLength: {
726 727
            int w = positioningAreaSize.width();
            int h = positioningAreaSize.height();
728

729 730 731 732 733 734 735
            Length layerWidth = fillLayer->size().size.width();
            Length layerHeight = fillLayer->size().size.height();

            if (layerWidth.isFixed())
                w = layerWidth.value();
            else if (layerWidth.isPercent())
                w = layerWidth.calcValue(positioningAreaSize.width());
mitz@apple.com's avatar
mitz@apple.com committed
736
            
737 738 739 740
            if (layerHeight.isFixed())
                h = layerHeight.value();
            else if (layerHeight.isPercent())
                h = layerHeight.calcValue(positioningAreaSize.height());
mitz@apple.com's avatar
mitz@apple.com committed
741 742 743
            
            // If one of the values is auto we have to use the appropriate
            // scale to maintain our aspect ratio.
744 745 746 747 748 749 750 751 752 753 754 755 756
            if (layerWidth.isAuto() && !layerHeight.isAuto()) {
                IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
                if (imageIntrinsicSize.height())
                    w = imageIntrinsicSize.width() * h / imageIntrinsicSize.height();        
            } else if (!layerWidth.isAuto() && layerHeight.isAuto()) {
                IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
                if (imageIntrinsicSize.width())
                    h = imageIntrinsicSize.height() * w / imageIntrinsicSize.width();
            } else if (layerWidth.isAuto() && layerHeight.isAuto()) {
                // If both width and height are auto, use the image's intrinsic size.
                IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
                w = imageIntrinsicSize.width();
                h = imageIntrinsicSize.height();
mitz@apple.com's avatar
mitz@apple.com committed
757 758 759
            }
            
            return IntSize(max(1, w), max(1, h));
760
        }
mitz@apple.com's avatar
mitz@apple.com committed
761 762
        case Contain:
        case Cover: {
763
            IntSize imageIntrinsicSize = image->imageSize(this, 1);
764 765 766 767
            float horizontalScaleFactor = imageIntrinsicSize.width()
                ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width() : 1;
            float verticalScaleFactor = imageIntrinsicSize.height()
                ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height() : 1;
mitz@apple.com's avatar
mitz@apple.com committed
768 769 770 771 772 773
            float scaleFactor = type == Contain ? min(horizontalScaleFactor, verticalScaleFactor) : max(horizontalScaleFactor, verticalScaleFactor);
            return IntSize(max<int>(1, imageIntrinsicSize.width() * scaleFactor), max<int>(1, imageIntrinsicSize.height() * scaleFactor));
        }
        case SizeNone:
            break;
    }
774

775
    return image->imageSize(this, style()->effectiveZoom());
776 777
}

778
void RenderBoxModelObject::calculateBackgroundImageGeometry(const FillLayer* fillLayer, int tx, int ty, int w, int h, 
779
                                                            IntRect& destRect, IntPoint& phase, IntSize& tileSize)
780 781 782
{
    int left = 0;
    int top = 0;
783 784 785 786 787
    IntSize positioningAreaSize;

    // Determine the background positioning area and set destRect to the background painting area.
    // destRect will be adjusted later if the background is non-repeating.
    bool fixedAttachment = fillLayer->attachment() == FixedBackgroundAttachment;
788 789 790 791 792 793 794 795 796 797 798

#if ENABLE(FAST_MOBILE_SCROLLING)
    if (view()->frameView() && view()->frameView()->canBlitOnScroll()) {
        // As a side effect of an optimization to blit on scroll, we do not honor the CSS
        // property "background-attachment: fixed" because it may result in rendering
        // artifacts. Note, these artifacts only appear if we are blitting on scroll of
        // a page that has fixed background images.
        fixedAttachment = false;
    }
#endif

hyatt@apple.com's avatar
hyatt@apple.com committed
799
    if (!fixedAttachment) {
800
        destRect = IntRect(tx, ty, w, h);
801 802 803 804 805

        int right = 0;
        int bottom = 0;
        // Scroll and Local.
        if (fillLayer->origin() != BorderFillBox) {
806 807 808 809
            left = borderLeft();
            right = borderRight();
            top = borderTop();
            bottom = borderBottom();
810
            if (fillLayer->origin() == ContentFillBox) {
811 812 813 814 815 816
                left += paddingLeft();
                right += paddingRight();
                top += paddingTop();
                bottom += paddingBottom();
            }
        }
817

818
        // The background of the box generated by the root element covers the entire canvas including
819 820
        // its margins. Since those were added in already, we have to factor them out when computing
        // the background positioning area.
821
        if (isRoot()) {
822
            positioningAreaSize = IntSize(toRenderBox(this)->width() - left - right, toRenderBox(this)->height() - top - bottom);
823 824
            left += marginLeft();
            top += marginTop();
825 826
        } else
            positioningAreaSize = IntSize(w - left - right, h - top - bottom);
827
    } else {
828 829
        destRect = viewRect();
        positioningAreaSize = destRect.size();
830 831
    }

832
    tileSize = calculateFillTileSize(fillLayer, positioningAreaSize);
833

834 835
    EFillRepeat backgroundRepeatX = fillLayer->repeatX();
    EFillRepeat backgroundRepeatY = fillLayer->repeatY();
836

837
    int xPosition = fillLayer->xPosition().calcMinValue(positioningAreaSize.width() - tileSize.width(), true);
838
    if (backgroundRepeatX == RepeatFill)
839 840 841 842 843
        phase.setX(tileSize.width() ? tileSize.width() - (xPosition + left) % tileSize.width() : 0);
    else {
        destRect.move(max(xPosition + left, 0), 0);
        phase.setX(-min(xPosition + left, 0));
        destRect.setWidth(tileSize.width() + min(xPosition + left, 0));
844 845
    }

846
    int yPosition = fillLayer->yPosition().calcMinValue(positioningAreaSize.height() - tileSize.height(), true);
847
    if (backgroundRepeatY == RepeatFill)
848 849 850 851 852
        phase.setY(tileSize.height() ? tileSize.height() - (yPosition + top) % tileSize.height() : 0);
    else {
        destRect.move(0, max(yPosition + top, 0));
        phase.setY(-min(yPosition + top, 0));
        destRect.setHeight(tileSize.height() + min(yPosition + top, 0));
853 854
    }

855 856 857
    if (fixedAttachment)
        phase.move(max(tx - destRect.x(), 0), max(ty - destRect.y(), 0));

858 859 860
    destRect.intersect(IntRect(tx, ty, w, h));
}

861 862 863 864
bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, const RenderStyle* style,
                                               const NinePieceImage& ninePieceImage, CompositeOperator op)
{
    StyleImage* styleImage = ninePieceImage.image();
hyatt@apple.com's avatar
hyatt@apple.com committed
865
    if (!styleImage)
866 867 868 869 870
        return false;

    if (!styleImage->isLoaded())
        return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either.

hyatt@apple.com's avatar
hyatt@apple.com committed
871 872
    if (!styleImage->canRender(style->effectiveZoom()))
        return false;
873 874 875 876 877 878 879 880

    // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function
    // doesn't have any understanding of the zoom that is in effect on the tile.
    styleImage->setImageContainerSize(IntSize(w, h));
    IntSize imageSize = styleImage->imageSize(this, 1.0f);
    int imageWidth = imageSize.width();
    int imageHeight = imageSize.height();

881 882 883 884
    int topSlice = min(imageHeight, ninePieceImage.slices().top().calcValue(imageHeight));
    int bottomSlice = min(imageHeight, ninePieceImage.slices().bottom().calcValue(imageHeight));
    int leftSlice = min(imageWidth, ninePieceImage.slices().left().calcValue(imageWidth));
    int rightSlice = min(imageWidth, ninePieceImage.slices().right().calcValue(imageWidth));
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903

    ENinePieceImageRule hRule = ninePieceImage.horizontalRule();
    ENinePieceImageRule vRule = ninePieceImage.verticalRule();

    bool fitToBorder = style->borderImage() == ninePieceImage;
    
    int leftWidth = fitToBorder ? style->borderLeftWidth() : leftSlice;
    int topWidth = fitToBorder ? style->borderTopWidth() : topSlice;
    int rightWidth = fitToBorder ? style->borderRightWidth() : rightSlice;
    int bottomWidth = fitToBorder ? style->borderBottomWidth() : bottomSlice;

    bool drawLeft = leftSlice > 0 && leftWidth > 0;
    bool drawTop = topSlice > 0 && topWidth > 0;
    bool drawRight = rightSlice > 0 && rightWidth > 0;
    bool drawBottom = bottomSlice > 0 && bottomWidth > 0;
    bool drawMiddle = (imageWidth - leftSlice - rightSlice) > 0 && (w - leftWidth - rightWidth) > 0 &&
                      (imageHeight - topSlice - bottomSlice) > 0 && (h - topWidth - bottomWidth) > 0;

    Image* image = styleImage->image(this, imageSize);
904
    ColorSpace colorSpace = style->colorSpace();
905 906 907 908 909 910 911

    if (drawLeft) {
        // Paint the top and bottom left corners.

        // The top left corner rect is (tx, ty, leftWidth, topWidth)
        // The rect to use from within the image is obtained from our slice, and is (0, 0, leftSlice, topSlice)
        if (drawTop)
912 913
            graphicsContext->drawImage(image, colorSpace, IntRect(tx, ty, leftWidth, topWidth),
                                       IntRect(0, 0, leftSlice, topSlice), op);
914 915 916 917

        // The bottom left corner rect is (tx, ty + h - bottomWidth, leftWidth, bottomWidth)
        // The rect to use from within the image is (0, imageHeight - bottomSlice, leftSlice, botomSlice)
        if (drawBottom)
918 919
            graphicsContext->drawImage(image, colorSpace, IntRect(tx, ty + h - bottomWidth, leftWidth, bottomWidth),
                                       IntRect(0, imageHeight - bottomSlice, leftSlice, bottomSlice), op);
920 921 922

        // Paint the left edge.
        // Have to scale and tile into the border rect.
923 924 925 926
        graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx, ty + topWidth, leftWidth,
                                        h - topWidth - bottomWidth),
                                        IntRect(0, topSlice, leftSlice, imageHeight - topSlice - bottomSlice),
                                        Image::StretchTile, (Image::TileRule)vRule, op);
927 928 929 930 931 932 933
    }

    if (drawRight) {
        // Paint the top and bottom right corners
        // The top right corner rect is (tx + w - rightWidth, ty, rightWidth, topWidth)
        // The rect to use from within the image is obtained from our slice, and is (imageWidth - rightSlice, 0, rightSlice, topSlice)
        if (drawTop)
934 935
            graphicsContext->drawImage(image, colorSpace, IntRect(tx + w - rightWidth, ty, rightWidth, topWidth),
                                       IntRect(imageWidth - rightSlice, 0, rightSlice, topSlice), op);
936 937 938 939

        // The bottom right corner rect is (tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth)
        // The rect to use from within the image is (imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice)
        if (drawBottom)
940 941
            graphicsContext->drawImage(image, colorSpace, IntRect(tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth),
                                       IntRect(imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice), op);
942 943

        // Paint the right edge.
944 945 946 947
        graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + w - rightWidth, ty + topWidth, rightWidth,
                                        h - topWidth - bottomWidth),
                                        IntRect(imageWidth - rightSlice, topSlice, rightSlice, imageHeight - topSlice - bottomSlice),
                                        Image::StretchTile, (Image::TileRule)vRule, op);
948 949 950 951
    }

    // Paint the top edge.
    if (drawTop)
952 953 954
        graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty, w - leftWidth - rightWidth, topWidth),
                                        IntRect(leftSlice, 0, imageWidth - rightSlice - leftSlice, topSlice),
                                        (Image::TileRule)hRule, Image::StretchTile, op);
955 956 957

    // Paint the bottom edge.
    if (drawBottom)
958 959 960 961
        graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty + h - bottomWidth,
                                        w - leftWidth - rightWidth, bottomWidth),
                                        IntRect(leftSlice, imageHeight - bottomSlice, imageWidth - rightSlice - leftSlice, bottomSlice),
                                        (Image::TileRule)hRule, Image::StretchTile, op);
962 963 964

    // Paint the middle.
    if (drawMiddle)
965 966 967 968
        graphicsContext->drawTiledImage(image, colorSpace, IntRect(tx + leftWidth, ty + topWidth, w - leftWidth - rightWidth,
                                        h - topWidth - bottomWidth),
                                        IntRect(leftSlice, topSlice, imageWidth - rightSlice - leftSlice, imageHeight - topSlice - bottomSlice),
                                        (Image::TileRule)hRule, (Image::TileRule)vRule, op);
969 970 971 972

    return true;
}

bdakin@apple.com's avatar