Fix the ACCELERATED_COMPOSITING code to not expose RenderLayer outside rendering
[WebKit.git] / Source / WebCore / rendering / RenderImage.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2000 Dirk Mueller (mueller@kde.org)
5  *           (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com)
6  *           (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
7  * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
8  * Copyright (C) 2010 Google Inc. All rights reserved.
9  * Copyright (C) Research In Motion Limited 2011-2012. All rights reserved.
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public License
22  * along with this library; see the file COPYING.LIB.  If not, write to
23  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301, USA.
25  *
26  */
27
28 #include "config.h"
29 #include "RenderImage.h"
30
31 #include "BitmapImage.h"
32 #include "FontCache.h"
33 #include "Frame.h"
34 #include "FrameSelection.h"
35 #include "GraphicsContext.h"
36 #include "HTMLAreaElement.h"
37 #include "HTMLImageElement.h"
38 #include "HTMLInputElement.h"
39 #include "HTMLMapElement.h"
40 #include "HTMLNames.h"
41 #include "HitTestResult.h"
42 #include "Page.h"
43 #include "PaintInfo.h"
44 #include "RenderView.h"
45 #include "SVGImage.h"
46 #include <wtf/UnusedParam.h>
47
48 using namespace std;
49
50 namespace WebCore {
51
52 using namespace HTMLNames;
53
54 RenderImage::RenderImage(Node* node)
55     : RenderReplaced(node, IntSize())
56     , m_needsToSetSizeForAltText(false)
57     , m_didIncrementVisuallyNonEmptyPixelCount(false)
58     , m_isGeneratedContent(false)
59 {
60     updateAltText();
61 }
62
63 RenderImage::~RenderImage()
64 {
65     ASSERT(m_imageResource);
66     m_imageResource->shutdown();
67 }
68
69 void RenderImage::setImageResource(PassOwnPtr<RenderImageResource> imageResource)
70 {
71     ASSERT(!m_imageResource);
72     m_imageResource = imageResource;
73     m_imageResource->initialize(this);
74 }
75
76 // If we'll be displaying either alt text or an image, add some padding.
77 static const unsigned short paddingWidth = 4;
78 static const unsigned short paddingHeight = 4;
79
80 // Alt text is restricted to this maximum size, in pixels.  These are
81 // signed integers because they are compared with other signed values.
82 static const float maxAltTextWidth = 1024;
83 static const int maxAltTextHeight = 256;
84
85 IntSize RenderImage::imageSizeForError(CachedImage* newImage) const
86 {
87     ASSERT_ARG(newImage, newImage);
88     ASSERT_ARG(newImage, newImage->imageForRenderer(this));
89
90     IntSize imageSize;
91     if (newImage->willPaintBrokenImage()) {
92         float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
93         pair<Image*, float> brokenImageAndImageScaleFactor = newImage->brokenImage(deviceScaleFactor);
94         imageSize = brokenImageAndImageScaleFactor.first->size();
95         imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
96     } else
97         imageSize = newImage->imageForRenderer(this)->size();
98
99     // imageSize() returns 0 for the error image. We need the true size of the
100     // error image, so we have to get it by grabbing image() directly.
101     return IntSize(paddingWidth + imageSize.width() * style()->effectiveZoom(), paddingHeight + imageSize.height() * style()->effectiveZoom());
102 }
103
104 // Sets the image height and width to fit the alt text.  Returns true if the
105 // image size changed.
106 bool RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */)
107 {
108     IntSize imageSize;
109     if (newImage && newImage->imageForRenderer(this))
110         imageSize = imageSizeForError(newImage);
111     else if (!m_altText.isEmpty() || newImage) {
112         // If we'll be displaying either text or an image, add a little padding.
113         imageSize = IntSize(paddingWidth, paddingHeight);
114     }
115
116     // we have an alt and the user meant it (its not a text we invented)
117     if (!m_altText.isEmpty()) {
118         FontCachePurgePreventer fontCachePurgePreventer;
119
120         const Font& font = style()->font();
121         IntSize textSize(min(font.width(RenderBlock::constructTextRun(this, font, m_altText, style())), maxAltTextWidth), min(font.fontMetrics().height(), maxAltTextHeight));
122         imageSize = imageSize.expandedTo(textSize);
123     }
124
125     if (imageSize == intrinsicSize())
126         return false;
127
128     setIntrinsicSize(imageSize);
129     return true;
130 }
131
132 void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
133 {
134     RenderReplaced::styleDidChange(diff, oldStyle);
135     if (m_needsToSetSizeForAltText) {
136         if (!m_altText.isEmpty() && setImageSizeForAltText(m_imageResource->cachedImage()))
137             imageDimensionsChanged(true /* imageSizeChanged */);
138         m_needsToSetSizeForAltText = false;
139     }
140 }
141
142 void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
143 {
144     if (documentBeingDestroyed())
145         return;
146
147     if (hasBoxDecorations() || hasMask())
148         RenderReplaced::imageChanged(newImage, rect);
149     
150     if (!m_imageResource)
151         return;
152
153     if (newImage != m_imageResource->imagePtr() || !newImage)
154         return;
155     
156     if (!m_didIncrementVisuallyNonEmptyPixelCount) {
157         view()->frameView()->incrementVisuallyNonEmptyPixelCount(m_imageResource->imageSize(1.0f));
158         m_didIncrementVisuallyNonEmptyPixelCount = true;
159     }
160
161     bool imageSizeChanged = false;
162
163     // Set image dimensions, taking into account the size of the alt text.
164     if (m_imageResource->errorOccurred()) {
165         if (!m_altText.isEmpty() && document()->isPendingStyleRecalc()) {
166             ASSERT(node());
167             if (node()) {
168                 m_needsToSetSizeForAltText = true;
169                 node()->setNeedsStyleRecalc(SyntheticStyleChange);
170             }
171             return;
172         }
173         imageSizeChanged = setImageSizeForAltText(m_imageResource->cachedImage());
174     }
175
176     imageDimensionsChanged(imageSizeChanged, rect);
177 }
178
179 bool RenderImage::updateIntrinsicSizeIfNeeded(const IntSize& newSize, bool imageSizeChanged)
180 {
181     if (newSize == intrinsicSize() && !imageSizeChanged)
182         return false;
183     if (m_imageResource->errorOccurred())
184         return imageSizeChanged;
185     setIntrinsicSize(newSize);
186     return true;
187 }
188
189 void RenderImage::imageDimensionsChanged(bool imageSizeChanged, const IntRect* rect)
190 {
191     bool shouldRepaint = true;
192     if (updateIntrinsicSizeIfNeeded(m_imageResource->imageSize(style()->effectiveZoom()), imageSizeChanged)) {
193         // In the case of generated image content using :before/:after, we might not be in the
194         // render tree yet.  In that case, we don't need to worry about check for layout, since we'll get a
195         // layout when we get added in to the render tree hierarchy later.
196         if (containingBlock()) {
197             // lets see if we need to relayout at all..
198             int oldwidth = width();
199             int oldheight = height();
200             if (!preferredLogicalWidthsDirty())
201                 setPreferredLogicalWidthsDirty(true);
202             computeLogicalWidth();
203             computeLogicalHeight();
204
205             if (imageSizeChanged || width() != oldwidth || height() != oldheight) {
206                 shouldRepaint = false;
207                 if (!selfNeedsLayout())
208                     setNeedsLayout(true);
209             }
210
211             setWidth(oldwidth);
212             setHeight(oldheight);
213         }
214     }
215
216     if (shouldRepaint) {
217         LayoutRect repaintRect;
218         if (rect) {
219             // The image changed rect is in source image coordinates (pre-zooming),
220             // so map from the bounds of the image to the contentsBox.
221             repaintRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), m_imageResource->imageSize(1.0f)), contentBoxRect()));
222             // Guard against too-large changed rects.
223             repaintRect.intersect(contentBoxRect());
224         } else
225             repaintRect = contentBoxRect();
226         
227         repaintRectangle(repaintRect);
228
229 #if USE(ACCELERATED_COMPOSITING)
230         // Tell any potential compositing layers that the image needs updating.
231         contentChanged(ImageChanged);
232 #endif
233     }
234 }
235
236 void RenderImage::notifyFinished(CachedResource* newImage)
237 {
238     if (!m_imageResource)
239         return;
240     
241     if (documentBeingDestroyed())
242         return;
243
244 #if USE(ACCELERATED_COMPOSITING)
245     if (newImage == m_imageResource->cachedImage()) {
246         // tell any potential compositing layers
247         // that the image is done and they can reference it directly.
248         contentChanged(ImageChanged);
249     }
250 #else
251     UNUSED_PARAM(newImage);
252 #endif
253 }
254
255 void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
256 {
257     LayoutUnit cWidth = contentWidth();
258     LayoutUnit cHeight = contentHeight();
259     LayoutUnit leftBorder = borderLeft();
260     LayoutUnit topBorder = borderTop();
261     LayoutUnit leftPad = paddingLeft();
262     LayoutUnit topPad = paddingTop();
263
264     GraphicsContext* context = paintInfo.context;
265
266     Page* page = 0;
267     if (Frame* frame = this->frame())
268         page = frame->page();
269
270     if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) {
271         if (paintInfo.phase == PaintPhaseSelection)
272             return;
273
274         if (page && paintInfo.phase == PaintPhaseForeground)
275             page->addRelevantUnpaintedObject(this, visualOverflowRect());
276
277         if (cWidth > 2 && cHeight > 2) {
278             // Draw an outline rect where the image should be.
279             context->setStrokeStyle(SolidStroke);
280             context->setStrokeColor(Color::lightGray, style()->colorSpace());
281             context->setFillColor(Color::transparent, style()->colorSpace());
282             context->drawRect(pixelSnappedIntRect(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight)));
283
284             bool errorPictureDrawn = false;
285             LayoutSize imageOffset;
286             // When calculating the usable dimensions, exclude the pixels of
287             // the ouline rect so the error image/alt text doesn't draw on it.
288             LayoutUnit usableWidth = cWidth - 2;
289             LayoutUnit usableHeight = cHeight - 2;
290
291             RefPtr<Image> image = m_imageResource->image();
292
293             if (m_imageResource->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) {
294                 float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
295                 // Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution.
296                 pair<Image*, float> brokenImageAndImageScaleFactor = m_imageResource->cachedImage()->brokenImage(deviceScaleFactor);
297                 image = brokenImageAndImageScaleFactor.first;
298                 IntSize imageSize = image->size();
299                 imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
300                 // Center the error image, accounting for border and padding.
301                 LayoutUnit centerX = (usableWidth - imageSize.width()) / 2;
302                 if (centerX < 0)
303                     centerX = 0;
304                 LayoutUnit centerY = (usableHeight - imageSize.height()) / 2;
305                 if (centerY < 0)
306                     centerY = 0;
307                 imageOffset = LayoutSize(leftBorder + leftPad + centerX + 1, topBorder + topPad + centerY + 1);
308                 context->drawImage(image.get(), style()->colorSpace(), IntRect(roundedIntPoint(paintOffset + imageOffset), imageSize), CompositeSourceOver, shouldRespectImageOrientation());
309                 errorPictureDrawn = true;
310             }
311
312             if (!m_altText.isEmpty()) {
313                 String text = document()->displayStringModifiedByEncoding(m_altText);
314                 context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
315                 const Font& font = style()->font();
316                 const FontMetrics& fontMetrics = font.fontMetrics();
317                 LayoutUnit ascent = fontMetrics.ascent();
318                 LayoutPoint altTextOffset = paintOffset;
319                 altTextOffset.move(leftBorder + leftPad, topBorder + topPad + ascent);
320
321                 // Only draw the alt text if it'll fit within the content box,
322                 // and only if it fits above the error image.
323                 TextRun textRun = RenderBlock::constructTextRun(this, font, text, style());
324                 LayoutUnit textWidth = font.width(textRun);
325                 if (errorPictureDrawn) {
326                     if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height())
327                         context->drawText(font, textRun, altTextOffset);
328                 } else if (usableWidth >= textWidth && cHeight >= fontMetrics.height())
329                     context->drawText(font, textRun, altTextOffset);
330             }
331         }
332     } else if (m_imageResource->hasImage() && cWidth > 0 && cHeight > 0) {
333         RefPtr<Image> img = m_imageResource->image(cWidth, cHeight);
334         if (!img || img->isNull()) {
335             if (page && paintInfo.phase == PaintPhaseForeground)
336                 page->addRelevantUnpaintedObject(this, visualOverflowRect());
337             return;
338         }
339
340 #if PLATFORM(MAC)
341         if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
342             paintCustomHighlight(toPoint(paintOffset - location()), style()->highlight(), true);
343 #endif
344
345         LayoutSize contentSize(cWidth, cHeight);
346         LayoutPoint contentLocation = paintOffset;
347         contentLocation.move(leftBorder + leftPad, topBorder + topPad);
348         paintIntoRect(context, LayoutRect(contentLocation, contentSize));
349         
350         if (cachedImage() && page && paintInfo.phase == PaintPhaseForeground) {
351             // For now, count images as unpainted if they are still progressively loading. We may want 
352             // to refine this in the future to account for the portion of the image that has painted.
353             if (cachedImage()->isLoading())
354                 page->addRelevantUnpaintedObject(this, LayoutRect(contentLocation, contentSize));
355             else
356                 page->addRelevantRepaintedObject(this, LayoutRect(contentLocation, contentSize));
357         }
358     }
359 }
360
361 void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
362 {
363     RenderReplaced::paint(paintInfo, paintOffset);
364     
365     if (paintInfo.phase == PaintPhaseOutline)
366         paintAreaElementFocusRing(paintInfo);
367 }
368     
369 void RenderImage::paintAreaElementFocusRing(PaintInfo& paintInfo)
370 {
371     Document* document = this->document();
372     
373     if (document->printing() || !document->frame()->selection()->isFocusedAndActive())
374         return;
375     
376     if (paintInfo.context->paintingDisabled() && !paintInfo.context->updatingControlTints())
377         return;
378
379     Node* focusedNode = document->focusedNode();
380     if (!focusedNode || !focusedNode->hasTagName(areaTag))
381         return;
382
383     HTMLAreaElement* areaElement = static_cast<HTMLAreaElement*>(focusedNode);
384     if (areaElement->imageElement() != node())
385         return;
386
387     // Even if the theme handles focus ring drawing for entire elements, it won't do it for
388     // an area within an image, so we don't call RenderTheme::supportsFocusRing here.
389
390     Path path = areaElement->computePath(this);
391     if (path.isEmpty())
392         return;
393
394     // FIXME: Do we need additional code to clip the path to the image's bounding box?
395
396     RenderStyle* areaElementStyle = areaElement->computedStyle();
397     unsigned short outlineWidth = areaElementStyle->outlineWidth();
398     if (!outlineWidth)
399         return;
400
401     paintInfo.context->drawFocusRing(path, outlineWidth,
402         areaElementStyle->outlineOffset(),
403         areaElementStyle->visitedDependentColor(CSSPropertyOutlineColor));
404 }
405
406 void RenderImage::areaElementFocusChanged(HTMLAreaElement* element)
407 {
408     ASSERT_UNUSED(element, element->imageElement() == node());
409
410     // It would be more efficient to only repaint the focus ring rectangle
411     // for the passed-in area element. That would require adding functions
412     // to the area element class.
413     repaint();
414 }
415
416 void RenderImage::paintIntoRect(GraphicsContext* context, const LayoutRect& rect)
417 {
418     IntRect alignedRect = pixelSnappedIntRect(rect);
419     if (!m_imageResource->hasImage() || m_imageResource->errorOccurred() || alignedRect.width() <= 0 || alignedRect.height() <= 0)
420         return;
421
422     RefPtr<Image> img = m_imageResource->image(alignedRect.width(), alignedRect.height());
423     if (!img || img->isNull())
424         return;
425
426     HTMLImageElement* imageElt = (node() && node()->hasTagName(imgTag)) ? static_cast<HTMLImageElement*>(node()) : 0;
427     CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
428     Image* image = m_imageResource->image().get();
429     bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, image, alignedRect.size());
430     context->drawImage(m_imageResource->image(alignedRect.width(), alignedRect.height()).get(), style()->colorSpace(), alignedRect, compositeOperator, shouldRespectImageOrientation(), useLowQualityScaling);
431 }
432
433 bool RenderImage::backgroundIsObscured() const
434 {
435     if (!m_imageResource->hasImage() || m_imageResource->errorOccurred())
436         return false;
437
438     if (m_imageResource->cachedImage() && !m_imageResource->cachedImage()->isLoaded())
439         return false;
440
441     EFillBox backgroundClip = style()->backgroundClip();
442
443     // Background paints under borders.
444     if (backgroundClip == BorderFillBox && style()->hasBorder() && !borderObscuresBackground())
445         return false;
446
447     // Background shows in padding area.
448     if ((backgroundClip == BorderFillBox || backgroundClip == PaddingFillBox) && style()->hasPadding())
449         return false;
450
451     // Check for bitmap image with alpha.
452     Image* image = m_imageResource->image().get();
453     if (!image || !image->isBitmapImage() || image->currentFrameHasAlpha())
454         return false;
455         
456     return true;
457 }
458
459 int RenderImage::minimumReplacedHeight() const
460 {
461     return m_imageResource->errorOccurred() ? intrinsicSize().height() : 0;
462 }
463
464 HTMLMapElement* RenderImage::imageMap() const
465 {
466     HTMLImageElement* i = node() && node()->hasTagName(imgTag) ? static_cast<HTMLImageElement*>(node()) : 0;
467     return i ? i->treeScope()->getImageMap(i->fastGetAttribute(usemapAttr)) : 0;
468 }
469
470 bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
471 {
472     HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding(), result.shadowContentFilterPolicy());
473     bool inside = RenderReplaced::nodeAtPoint(request, tempResult, pointInContainer, accumulatedOffset, hitTestAction);
474
475     if (tempResult.innerNode() && node()) {
476         if (HTMLMapElement* map = imageMap()) {
477             LayoutRect contentBox = contentBoxRect();
478             float scaleFactor = 1 / style()->effectiveZoom();
479             LayoutPoint mapLocation(pointInContainer.x() - accumulatedOffset.x() - this->x() - contentBox.x(), pointInContainer.y() - accumulatedOffset.y() - this->y() - contentBox.y());
480             mapLocation.scale(scaleFactor, scaleFactor);
481             
482             if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult))
483                 tempResult.setInnerNonSharedNode(node());
484         }
485     }
486
487     if (!inside && result.isRectBasedTest())
488         result.append(tempResult);
489     if (inside)
490         result = tempResult;
491     return inside;
492 }
493
494 void RenderImage::updateAltText()
495 {
496     if (!node())
497         return;
498
499     if (node()->hasTagName(inputTag))
500         m_altText = static_cast<HTMLInputElement*>(node())->altText();
501     else if (node()->hasTagName(imgTag))
502         m_altText = static_cast<HTMLImageElement*>(node())->altText();
503 }
504
505 void RenderImage::layout()
506 {
507     RenderReplaced::layout();
508
509     // Propagate container size to image resource.
510     IntSize containerSize(contentWidth(), contentHeight());
511     if (!containerSize.isEmpty())
512         m_imageResource->setContainerSizeForRenderer(containerSize);
513 }
514
515 void RenderImage::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio, bool& isPercentageIntrinsicSize) const
516 {
517     RenderReplaced::computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio, isPercentageIntrinsicSize);
518
519     // Our intrinsicSize is empty if we're rendering generated images with relative width/height. Figure out the right intrinsic size to use.
520     if (intrinsicSize.isEmpty() && (m_imageResource->imageHasRelativeWidth() || m_imageResource->imageHasRelativeHeight())) {
521         RenderObject* containingBlock = isPositioned() ? container() : this->containingBlock();
522         if (containingBlock->isBox()) {
523             RenderBox* box = toRenderBox(containingBlock);
524             intrinsicSize.setWidth(box->availableLogicalWidth());
525             intrinsicSize.setHeight(box->availableLogicalHeight());
526         }
527     }
528     // Don't compute an intrinsic ratio to preserve historical WebKit behavior if we're painting alt text and/or a broken image.
529     if (m_imageResource && m_imageResource->errorOccurred()) {
530         intrinsicRatio = 1;
531         return;
532     }
533 }
534
535 bool RenderImage::needsPreferredWidthsRecalculation() const
536 {
537     if (RenderReplaced::needsPreferredWidthsRecalculation())
538         return true;
539     return embeddedContentBox();
540 }
541
542 RenderBox* RenderImage::embeddedContentBox() const
543 {
544     if (!m_imageResource)
545         return 0;
546
547 #if ENABLE(SVG)
548     CachedImage* cachedImage = m_imageResource->cachedImage();
549     if (cachedImage && cachedImage->image() && cachedImage->image()->isSVGImage())
550         return static_cast<SVGImage*>(cachedImage->image())->embeddedContentBox();
551 #endif
552
553     return 0;
554 }
555
556 } // namespace WebCore