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 "AXObjectCache.h" |
32 | #include "BitmapImage.h" |
33 | #include "CachedImage.h" |
34 | #include "FocusController.h" |
35 | #include "FontCache.h" |
36 | #include "FontCascade.h" |
37 | #include "Frame.h" |
38 | #include "FrameSelection.h" |
39 | #include "GeometryUtilities.h" |
40 | #include "GraphicsContext.h" |
41 | #include "HTMLAreaElement.h" |
42 | #include "HTMLImageElement.h" |
43 | #include "HTMLInputElement.h" |
44 | #include "HTMLMapElement.h" |
45 | #include "HTMLNames.h" |
46 | #include "HitTestResult.h" |
47 | #include "InlineElementBox.h" |
48 | #include "Page.h" |
49 | #include "PaintInfo.h" |
50 | #include "RenderFragmentedFlow.h" |
51 | #include "RenderImageResourceStyleImage.h" |
52 | #include "RenderLayoutState.h" |
53 | #include "RenderTheme.h" |
54 | #include "RenderView.h" |
55 | #include "RuntimeEnabledFeatures.h" |
56 | #include "SVGImage.h" |
57 | #include "Settings.h" |
58 | #include <wtf/IsoMallocInlines.h> |
59 | #include <wtf/StackStats.h> |
60 | |
61 | #if PLATFORM(IOS_FAMILY) |
62 | #include "LogicalSelectionOffsetCaches.h" |
63 | #include "SelectionRect.h" |
64 | #endif |
65 | |
66 | #if USE(CG) |
67 | #include "PDFDocumentImage.h" |
68 | #include "Settings.h" |
69 | #endif |
70 | |
71 | namespace WebCore { |
72 | |
73 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderImage); |
74 | |
75 | #if PLATFORM(IOS_FAMILY) |
76 | // FIXME: This doesn't behave correctly for floating or positioned images, but WebCore doesn't handle those well |
77 | // during selection creation yet anyway. |
78 | // FIXME: We can't tell whether or not we contain the start or end of the selected Range using only the offsets |
79 | // of the start and end, we need to know the whole Position. |
80 | void RenderImage::collectSelectionRects(Vector<SelectionRect>& rects, unsigned, unsigned) |
81 | { |
82 | RenderBlock* containingBlock = this->containingBlock(); |
83 | |
84 | IntRect imageRect; |
85 | // FIXME: It doesn't make sense to package line bounds into SelectionRects. We should find |
86 | // the right and left extent of the selection once for the entire selected Range, perhaps |
87 | // using the Range's common ancestor. |
88 | IntRect lineExtentRect; |
89 | bool isFirstOnLine = false; |
90 | bool isLastOnLine = false; |
91 | |
92 | InlineBox* inlineBox = inlineBoxWrapper(); |
93 | if (!inlineBox) { |
94 | // This is a block image. |
95 | imageRect = IntRect(0, 0, width(), height()); |
96 | isFirstOnLine = true; |
97 | isLastOnLine = true; |
98 | lineExtentRect = imageRect; |
99 | if (containingBlock->isHorizontalWritingMode()) { |
100 | lineExtentRect.setX(containingBlock->x()); |
101 | lineExtentRect.setWidth(containingBlock->width()); |
102 | } else { |
103 | lineExtentRect.setY(containingBlock->y()); |
104 | lineExtentRect.setHeight(containingBlock->height()); |
105 | } |
106 | } else { |
107 | LayoutUnit selectionTop = !containingBlock->style().isFlippedBlocksWritingMode() ? inlineBox->root().selectionTop() - logicalTop() : logicalBottom() - inlineBox->root().selectionBottom(); |
108 | imageRect = IntRect(0, selectionTop, logicalWidth(), inlineBox->root().selectionHeight()); |
109 | isFirstOnLine = !inlineBox->previousOnLineExists(); |
110 | isLastOnLine = !inlineBox->nextOnLineExists(); |
111 | LogicalSelectionOffsetCaches cache(*containingBlock); |
112 | LayoutUnit leftOffset = containingBlock->logicalLeftSelectionOffset(*containingBlock, inlineBox->logicalTop(), cache); |
113 | LayoutUnit rightOffset = containingBlock->logicalRightSelectionOffset(*containingBlock, inlineBox->logicalTop(), cache); |
114 | lineExtentRect = IntRect(leftOffset - logicalLeft(), imageRect.y(), rightOffset - leftOffset, imageRect.height()); |
115 | if (!inlineBox->isHorizontal()) { |
116 | imageRect = imageRect.transposedRect(); |
117 | lineExtentRect = lineExtentRect.transposedRect(); |
118 | } |
119 | } |
120 | |
121 | bool isFixed = false; |
122 | IntRect absoluteBounds = localToAbsoluteQuad(FloatRect(imageRect), UseTransforms, &isFixed).enclosingBoundingBox(); |
123 | IntRect lineExtentBounds = localToAbsoluteQuad(FloatRect(lineExtentRect)).enclosingBoundingBox(); |
124 | if (!containingBlock->isHorizontalWritingMode()) |
125 | lineExtentBounds = lineExtentBounds.transposedRect(); |
126 | |
127 | // FIXME: We should consider either making SelectionRect a struct or better organize its optional fields into |
128 | // an auxiliary struct to simplify its initialization. |
129 | rects.append(SelectionRect(absoluteBounds, containingBlock->style().direction(), lineExtentBounds.x(), lineExtentBounds.maxX(), lineExtentBounds.maxY(), 0, false /* line break */, isFirstOnLine, isLastOnLine, false /* contains start */, false /* contains end */, containingBlock->style().isHorizontalWritingMode(), isFixed, false /* ruby text */, view().pageNumberForBlockProgressionOffset(absoluteBounds.x()))); |
130 | } |
131 | #endif |
132 | |
133 | using namespace HTMLNames; |
134 | |
135 | RenderImage::RenderImage(Element& element, RenderStyle&& style, StyleImage* styleImage, const float imageDevicePixelRatio) |
136 | : RenderReplaced(element, WTFMove(style), IntSize()) |
137 | , m_imageResource(styleImage ? std::make_unique<RenderImageResourceStyleImage>(*styleImage) : std::make_unique<RenderImageResource>()) |
138 | , m_imageDevicePixelRatio(imageDevicePixelRatio) |
139 | { |
140 | updateAltText(); |
141 | if (is<HTMLImageElement>(element)) |
142 | m_hasShadowControls = downcast<HTMLImageElement>(element).hasShadowControls(); |
143 | } |
144 | |
145 | RenderImage::RenderImage(Document& document, RenderStyle&& style, StyleImage* styleImage) |
146 | : RenderReplaced(document, WTFMove(style), IntSize()) |
147 | , m_imageResource(styleImage ? std::make_unique<RenderImageResourceStyleImage>(*styleImage) : std::make_unique<RenderImageResource>()) |
148 | { |
149 | } |
150 | |
151 | RenderImage::~RenderImage() |
152 | { |
153 | // Do not add any code here. Add it to willBeDestroyed() instead. |
154 | } |
155 | |
156 | void RenderImage::willBeDestroyed() |
157 | { |
158 | imageResource().shutdown(); |
159 | RenderReplaced::willBeDestroyed(); |
160 | } |
161 | |
162 | // If we'll be displaying either alt text or an image, add some padding. |
163 | static const unsigned short paddingWidth = 4; |
164 | static const unsigned short paddingHeight = 4; |
165 | |
166 | // Alt text is restricted to this maximum size, in pixels. These are |
167 | // signed integers because they are compared with other signed values. |
168 | static const float maxAltTextWidth = 1024; |
169 | static const int maxAltTextHeight = 256; |
170 | |
171 | IntSize RenderImage::imageSizeForError(CachedImage* newImage) const |
172 | { |
173 | ASSERT_ARG(newImage, newImage); |
174 | ASSERT_ARG(newImage, newImage->imageForRenderer(this)); |
175 | |
176 | FloatSize imageSize; |
177 | if (newImage->willPaintBrokenImage()) { |
178 | std::pair<Image*, float> brokenImageAndImageScaleFactor = newImage->brokenImage(document().deviceScaleFactor()); |
179 | imageSize = brokenImageAndImageScaleFactor.first->size(); |
180 | imageSize.scale(1 / brokenImageAndImageScaleFactor.second); |
181 | } else |
182 | imageSize = newImage->imageForRenderer(this)->size(); |
183 | |
184 | // imageSize() returns 0 for the error image. We need the true size of the |
185 | // error image, so we have to get it by grabbing image() directly. |
186 | return IntSize(paddingWidth + imageSize.width() * style().effectiveZoom(), paddingHeight + imageSize.height() * style().effectiveZoom()); |
187 | } |
188 | |
189 | // Sets the image height and width to fit the alt text. Returns true if the |
190 | // image size changed. |
191 | ImageSizeChangeType RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */) |
192 | { |
193 | IntSize imageSize; |
194 | if (newImage && newImage->imageForRenderer(this)) |
195 | imageSize = imageSizeForError(newImage); |
196 | else if (!m_altText.isEmpty() || newImage) { |
197 | // If we'll be displaying either text or an image, add a little padding. |
198 | imageSize = IntSize(paddingWidth, paddingHeight); |
199 | } |
200 | |
201 | // we have an alt and the user meant it (its not a text we invented) |
202 | if (!m_altText.isEmpty()) { |
203 | const FontCascade& font = style().fontCascade(); |
204 | IntSize paddedTextSize(paddingWidth + std::min(ceilf(font.width(RenderBlock::constructTextRun(m_altText, style()))), maxAltTextWidth), paddingHeight + std::min(font.fontMetrics().height(), maxAltTextHeight)); |
205 | imageSize = imageSize.expandedTo(paddedTextSize); |
206 | } |
207 | |
208 | if (imageSize == intrinsicSize()) |
209 | return ImageSizeChangeNone; |
210 | |
211 | setIntrinsicSize(imageSize); |
212 | return ImageSizeChangeForAltText; |
213 | } |
214 | |
215 | bool RenderImage::isEditableImage() const |
216 | { |
217 | if (!element() || !is<HTMLImageElement>(element())) |
218 | return false; |
219 | return downcast<HTMLImageElement>(element())->hasEditableImageAttribute(); |
220 | } |
221 | |
222 | bool RenderImage::requiresLayer() const |
223 | { |
224 | if (RenderReplaced::requiresLayer()) |
225 | return true; |
226 | |
227 | if (isEditableImage()) |
228 | return true; |
229 | |
230 | return false; |
231 | } |
232 | |
233 | void RenderImage::styleWillChange(StyleDifference diff, const RenderStyle& newStyle) |
234 | { |
235 | if (!hasInitializedStyle()) |
236 | imageResource().initialize(*this); |
237 | RenderReplaced::styleWillChange(diff, newStyle); |
238 | } |
239 | |
240 | void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) |
241 | { |
242 | RenderReplaced::styleDidChange(diff, oldStyle); |
243 | if (m_needsToSetSizeForAltText) { |
244 | if (!m_altText.isEmpty() && setImageSizeForAltText(cachedImage())) |
245 | repaintOrMarkForLayout(ImageSizeChangeForAltText); |
246 | m_needsToSetSizeForAltText = false; |
247 | } |
248 | #if ENABLE(CSS_IMAGE_ORIENTATION) |
249 | if (diff == StyleDifference::Layout && oldStyle->imageOrientation() != style().imageOrientation()) |
250 | return repaintOrMarkForLayout(ImageSizeChangeNone); |
251 | #endif |
252 | |
253 | #if ENABLE(CSS_IMAGE_RESOLUTION) |
254 | if (diff == StyleDifference::Layout |
255 | && (oldStyle->imageResolution() != style().imageResolution() |
256 | || oldStyle->imageResolutionSnap() != style().imageResolutionSnap() |
257 | || oldStyle->imageResolutionSource() != style().imageResolutionSource())) |
258 | repaintOrMarkForLayout(ImageSizeChangeNone); |
259 | #endif |
260 | } |
261 | |
262 | void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect) |
263 | { |
264 | if (renderTreeBeingDestroyed()) |
265 | return; |
266 | |
267 | if (hasVisibleBoxDecorations() || hasMask() || hasShapeOutside()) |
268 | RenderReplaced::imageChanged(newImage, rect); |
269 | |
270 | if (newImage != imageResource().imagePtr() || !newImage) |
271 | return; |
272 | |
273 | if (!m_didIncrementVisuallyNonEmptyPixelCount) { |
274 | // At a zoom level of 1 the image is guaranteed to have an integer size. |
275 | view().frameView().incrementVisuallyNonEmptyPixelCount(flooredIntSize(imageResource().imageSize(1.0f))); |
276 | m_didIncrementVisuallyNonEmptyPixelCount = true; |
277 | } |
278 | |
279 | ImageSizeChangeType imageSizeChange = ImageSizeChangeNone; |
280 | |
281 | // Set image dimensions, taking into account the size of the alt text. |
282 | if (imageResource().errorOccurred()) { |
283 | if (!m_altText.isEmpty() && document().hasPendingStyleRecalc()) { |
284 | ASSERT(element()); |
285 | if (element()) { |
286 | m_needsToSetSizeForAltText = true; |
287 | element()->invalidateStyle(); |
288 | } |
289 | return; |
290 | } |
291 | imageSizeChange = setImageSizeForAltText(cachedImage()); |
292 | } |
293 | repaintOrMarkForLayout(imageSizeChange, rect); |
294 | if (AXObjectCache* cache = document().existingAXObjectCache()) |
295 | cache->deferRecomputeIsIgnoredIfNeeded(element()); |
296 | } |
297 | |
298 | void RenderImage::updateIntrinsicSizeIfNeeded(const LayoutSize& newSize) |
299 | { |
300 | if (imageResource().errorOccurred() || !m_imageResource->cachedImage()) |
301 | return; |
302 | setIntrinsicSize(newSize); |
303 | } |
304 | |
305 | void RenderImage::updateInnerContentRect() |
306 | { |
307 | // Propagate container size to image resource. |
308 | IntSize containerSize(replacedContentRect().size()); |
309 | if (!containerSize.isEmpty()) { |
310 | URL imageSourceURL; |
311 | if (HTMLImageElement* imageElement = is<HTMLImageElement>(element()) ? downcast<HTMLImageElement>(element()) : nullptr) |
312 | imageSourceURL = document().completeURL(imageElement->imageSourceURL()); |
313 | imageResource().setContainerContext(containerSize, imageSourceURL); |
314 | } |
315 | } |
316 | |
317 | void RenderImage::repaintOrMarkForLayout(ImageSizeChangeType imageSizeChange, const IntRect* rect) |
318 | { |
319 | #if ENABLE(CSS_IMAGE_RESOLUTION) |
320 | double scale = style().imageResolution(); |
321 | if (style().imageResolutionSnap() == ImageResolutionSnap::Pixels) |
322 | scale = roundForImpreciseConversion<int>(scale); |
323 | if (scale <= 0) |
324 | scale = 1; |
325 | LayoutSize newIntrinsicSize = imageResource().intrinsicSize(style().effectiveZoom() / scale); |
326 | #else |
327 | LayoutSize newIntrinsicSize = imageResource().intrinsicSize(style().effectiveZoom()); |
328 | #endif |
329 | LayoutSize oldIntrinsicSize = intrinsicSize(); |
330 | |
331 | updateIntrinsicSizeIfNeeded(newIntrinsicSize); |
332 | |
333 | // In the case of generated image content using :before/:after/content, we might not be |
334 | // in the render tree yet. In that case, we just need to update our intrinsic size. |
335 | // layout() will be called after we are inserted in the tree which will take care of |
336 | // what we are doing here. |
337 | if (!containingBlock()) |
338 | return; |
339 | |
340 | bool imageSourceHasChangedSize = oldIntrinsicSize != newIntrinsicSize || imageSizeChange != ImageSizeChangeNone; |
341 | |
342 | if (imageSourceHasChangedSize && setNeedsLayoutIfNeededAfterIntrinsicSizeChange()) |
343 | return; |
344 | |
345 | if (everHadLayout() && !selfNeedsLayout()) { |
346 | // The inner content rectangle is calculated during layout, but may need an update now |
347 | // (unless the box has already been scheduled for layout). In order to calculate it, we |
348 | // may need values from the containing block, though, so make sure that we're not too |
349 | // early. It may be that layout hasn't even taken place once yet. |
350 | |
351 | // FIXME: we should not have to trigger another call to setContainerContextForRenderer() |
352 | // from here, since it's already being done during layout. |
353 | updateInnerContentRect(); |
354 | } |
355 | |
356 | LayoutRect repaintRect = contentBoxRect(); |
357 | if (rect) { |
358 | // The image changed rect is in source image coordinates (pre-zooming), |
359 | // so map from the bounds of the image to the contentsBox. |
360 | repaintRect.intersect(enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), imageResource().imageSize(1.0f)), repaintRect))); |
361 | } |
362 | |
363 | repaintRectangle(repaintRect); |
364 | |
365 | // Tell any potential compositing layers that the image needs updating. |
366 | contentChanged(ImageChanged); |
367 | } |
368 | |
369 | void RenderImage::notifyFinished(CachedResource& newImage) |
370 | { |
371 | if (renderTreeBeingDestroyed()) |
372 | return; |
373 | |
374 | invalidateBackgroundObscurationStatus(); |
375 | |
376 | if (&newImage == cachedImage()) { |
377 | // tell any potential compositing layers |
378 | // that the image is done and they can reference it directly. |
379 | contentChanged(ImageChanged); |
380 | } |
381 | } |
382 | |
383 | bool RenderImage::isShowingMissingOrImageError() const |
384 | { |
385 | return !imageResource().cachedImage() || imageResource().errorOccurred(); |
386 | } |
387 | |
388 | bool RenderImage::isShowingAltText() const |
389 | { |
390 | return isShowingMissingOrImageError() && !m_altText.isEmpty(); |
391 | } |
392 | |
393 | bool RenderImage::hasNonBitmapImage() const |
394 | { |
395 | if (!imageResource().cachedImage()) |
396 | return false; |
397 | |
398 | Image* image = cachedImage()->imageForRenderer(this); |
399 | return image && !is<BitmapImage>(image); |
400 | } |
401 | |
402 | void RenderImage::paintIncompleteImageOutline(PaintInfo& paintInfo, LayoutPoint paintOffset, LayoutUnit borderWidth) const |
403 | { |
404 | auto contentSize = this->contentSize(); |
405 | if (contentSize.width() <= 2 || contentSize.height() <= 2) |
406 | return; |
407 | |
408 | auto leftBorder = borderLeft(); |
409 | auto topBorder = borderTop(); |
410 | auto leftPadding = paddingLeft(); |
411 | auto topPadding = paddingTop(); |
412 | |
413 | // Draw an outline rect where the image should be. |
414 | GraphicsContext& context = paintInfo.context(); |
415 | context.setStrokeStyle(SolidStroke); |
416 | context.setStrokeColor(Color::lightGray); |
417 | context.setFillColor(Color::transparent); |
418 | context.drawRect(snapRectToDevicePixels(LayoutRect({ paintOffset.x() + leftBorder + leftPadding, paintOffset.y() + topBorder + topPadding }, contentSize), document().deviceScaleFactor()), borderWidth); |
419 | } |
420 | |
421 | void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) |
422 | { |
423 | GraphicsContext& context = paintInfo.context(); |
424 | if (context.invalidatingImagesWithAsyncDecodes()) { |
425 | if (cachedImage() && cachedImage()->isClientWaitingForAsyncDecoding(*this)) |
426 | cachedImage()->removeAllClientsWaitingForAsyncDecoding(); |
427 | return; |
428 | } |
429 | |
430 | auto contentSize = this->contentSize(); |
431 | float deviceScaleFactor = document().deviceScaleFactor(); |
432 | LayoutUnit missingImageBorderWidth(1 / deviceScaleFactor); |
433 | |
434 | if (!imageResource().cachedImage() || imageResource().errorOccurred()) { |
435 | if (paintInfo.phase == PaintPhase::Selection) |
436 | return; |
437 | |
438 | if (paintInfo.phase == PaintPhase::Foreground) |
439 | page().addRelevantUnpaintedObject(this, visualOverflowRect()); |
440 | |
441 | paintIncompleteImageOutline(paintInfo, paintOffset, missingImageBorderWidth); |
442 | |
443 | if (contentSize.width() > 2 && contentSize.height() > 2) { |
444 | LayoutUnit leftBorder = borderLeft(); |
445 | LayoutUnit topBorder = borderTop(); |
446 | LayoutUnit leftPad = paddingLeft(); |
447 | LayoutUnit topPad = paddingTop(); |
448 | |
449 | bool errorPictureDrawn = false; |
450 | LayoutSize imageOffset; |
451 | // When calculating the usable dimensions, exclude the pixels of |
452 | // the ouline rect so the error image/alt text doesn't draw on it. |
453 | LayoutSize usableSize = contentSize - LayoutSize(2 * missingImageBorderWidth, 2 * missingImageBorderWidth); |
454 | |
455 | RefPtr<Image> image = imageResource().image(); |
456 | |
457 | if (imageResource().errorOccurred() && !image->isNull() && usableSize.width() >= image->width() && usableSize.height() >= image->height()) { |
458 | // Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution. |
459 | std::pair<Image*, float> brokenImageAndImageScaleFactor = cachedImage()->brokenImage(deviceScaleFactor); |
460 | image = brokenImageAndImageScaleFactor.first; |
461 | FloatSize imageSize = image->size(); |
462 | imageSize.scale(1 / brokenImageAndImageScaleFactor.second); |
463 | // Center the error image, accounting for border and padding. |
464 | LayoutUnit centerX = (usableSize.width() - imageSize.width()) / 2; |
465 | if (centerX < 0) |
466 | centerX = 0; |
467 | LayoutUnit centerY = (usableSize.height() - imageSize.height()) / 2; |
468 | if (centerY < 0) |
469 | centerY = 0; |
470 | imageOffset = LayoutSize(leftBorder + leftPad + centerX + missingImageBorderWidth, topBorder + topPad + centerY + missingImageBorderWidth); |
471 | |
472 | ImageOrientationDescription orientationDescription(shouldRespectImageOrientation()); |
473 | #if ENABLE(CSS_IMAGE_ORIENTATION) |
474 | orientationDescription.setImageOrientationEnum(style().imageOrientation()); |
475 | #endif |
476 | context.drawImage(*image, snapRectToDevicePixels(LayoutRect(paintOffset + imageOffset, imageSize), deviceScaleFactor), orientationDescription); |
477 | errorPictureDrawn = true; |
478 | } |
479 | |
480 | if (!m_altText.isEmpty()) { |
481 | String text = document().displayStringModifiedByEncoding(m_altText); |
482 | context.setFillColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor)); |
483 | const FontCascade& font = style().fontCascade(); |
484 | const FontMetrics& fontMetrics = font.fontMetrics(); |
485 | LayoutUnit ascent = fontMetrics.ascent(); |
486 | LayoutPoint altTextOffset = paintOffset; |
487 | altTextOffset.move(leftBorder + leftPad + (paddingWidth / 2) - missingImageBorderWidth, topBorder + topPad + ascent + (paddingHeight / 2) - missingImageBorderWidth); |
488 | |
489 | // Only draw the alt text if it'll fit within the content box, |
490 | // and only if it fits above the error image. |
491 | TextRun textRun = RenderBlock::constructTextRun(text, style()); |
492 | LayoutUnit textWidth = font.width(textRun); |
493 | if (errorPictureDrawn) { |
494 | if (usableSize.width() >= textWidth && fontMetrics.height() <= imageOffset.height()) |
495 | context.drawText(font, textRun, altTextOffset); |
496 | } else if (usableSize.width() >= textWidth && usableSize.height() >= fontMetrics.height()) |
497 | context.drawText(font, textRun, altTextOffset); |
498 | } |
499 | } |
500 | return; |
501 | } |
502 | |
503 | if (contentSize.isEmpty()) |
504 | return; |
505 | |
506 | bool showBorderForIncompleteImage = settings().incompleteImageBorderEnabled(); |
507 | |
508 | RefPtr<Image> img = imageResource().image(flooredIntSize(contentSize)); |
509 | if (!img || img->isNull()) { |
510 | if (showBorderForIncompleteImage) |
511 | paintIncompleteImageOutline(paintInfo, paintOffset, missingImageBorderWidth); |
512 | |
513 | if (paintInfo.phase == PaintPhase::Foreground) |
514 | page().addRelevantUnpaintedObject(this, visualOverflowRect()); |
515 | return; |
516 | } |
517 | |
518 | LayoutRect contentBoxRect = this->contentBoxRect(); |
519 | contentBoxRect.moveBy(paintOffset); |
520 | LayoutRect replacedContentRect = this->replacedContentRect(); |
521 | replacedContentRect.moveBy(paintOffset); |
522 | bool clip = !contentBoxRect.contains(replacedContentRect); |
523 | GraphicsContextStateSaver stateSaver(context, clip); |
524 | if (clip) |
525 | context.clip(contentBoxRect); |
526 | |
527 | ImageDrawResult result = paintIntoRect(paintInfo, snapRectToDevicePixels(replacedContentRect, deviceScaleFactor)); |
528 | |
529 | if (showBorderForIncompleteImage && (result != ImageDrawResult::DidDraw || (cachedImage() && cachedImage()->isLoading()))) |
530 | paintIncompleteImageOutline(paintInfo, paintOffset, missingImageBorderWidth); |
531 | |
532 | if (cachedImage() && paintInfo.phase == PaintPhase::Foreground) { |
533 | // For now, count images as unpainted if they are still progressively loading. We may want |
534 | // to refine this in the future to account for the portion of the image that has painted. |
535 | LayoutRect visibleRect = intersection(replacedContentRect, contentBoxRect); |
536 | if (cachedImage()->isLoading() || result == ImageDrawResult::DidRequestDecoding) |
537 | page().addRelevantUnpaintedObject(this, visibleRect); |
538 | else |
539 | page().addRelevantRepaintedObject(this, visibleRect); |
540 | } |
541 | } |
542 | |
543 | void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) |
544 | { |
545 | RenderReplaced::paint(paintInfo, paintOffset); |
546 | |
547 | if (paintInfo.phase == PaintPhase::Outline) |
548 | paintAreaElementFocusRing(paintInfo, paintOffset); |
549 | } |
550 | |
551 | void RenderImage::paintAreaElementFocusRing(PaintInfo& paintInfo, const LayoutPoint& paintOffset) |
552 | { |
553 | #if !PLATFORM(IOS_FAMILY) || ENABLE(FULL_KEYBOARD_ACCESS) |
554 | if (document().printing() || !frame().selection().isFocusedAndActive()) |
555 | return; |
556 | |
557 | if (paintInfo.context().paintingDisabled() && !paintInfo.context().performingPaintInvalidation()) |
558 | return; |
559 | |
560 | Element* focusedElement = document().focusedElement(); |
561 | if (!is<HTMLAreaElement>(focusedElement)) |
562 | return; |
563 | |
564 | HTMLAreaElement& areaElement = downcast<HTMLAreaElement>(*focusedElement); |
565 | if (areaElement.imageElement() != element()) |
566 | return; |
567 | |
568 | auto* areaElementStyle = areaElement.computedStyle(); |
569 | if (!areaElementStyle) |
570 | return; |
571 | |
572 | float outlineWidth = areaElementStyle->outlineWidth(); |
573 | if (!outlineWidth) |
574 | return; |
575 | |
576 | // Even if the theme handles focus ring drawing for entire elements, it won't do it for |
577 | // an area within an image, so we don't call RenderTheme::supportsFocusRing here. |
578 | auto path = areaElement.computePathForFocusRing(size()); |
579 | if (path.isEmpty()) |
580 | return; |
581 | |
582 | AffineTransform zoomTransform; |
583 | zoomTransform.scale(style().effectiveZoom()); |
584 | path.transform(zoomTransform); |
585 | |
586 | auto adjustedOffset = paintOffset; |
587 | adjustedOffset.moveBy(location()); |
588 | path.translate(toFloatSize(adjustedOffset)); |
589 | |
590 | #if PLATFORM(MAC) |
591 | bool needsRepaint; |
592 | paintInfo.context().drawFocusRing(path, page().focusController().timeSinceFocusWasSet().seconds(), needsRepaint, RenderTheme::singleton().focusRingColor(styleColorOptions())); |
593 | if (needsRepaint) |
594 | page().focusController().setFocusedElementNeedsRepaint(); |
595 | #else |
596 | paintInfo.context().drawFocusRing(path, outlineWidth, areaElementStyle->outlineOffset(), areaElementStyle->visitedDependentColorWithColorFilter(CSSPropertyOutlineColor)); |
597 | #endif // PLATFORM(MAC) |
598 | #else |
599 | UNUSED_PARAM(paintInfo); |
600 | UNUSED_PARAM(paintOffset); |
601 | #endif // ENABLE(FULL_KEYBOARD_ACCESS) |
602 | } |
603 | |
604 | void RenderImage::areaElementFocusChanged(HTMLAreaElement* element) |
605 | { |
606 | ASSERT_UNUSED(element, element->imageElement() == this->element()); |
607 | |
608 | // It would be more efficient to only repaint the focus ring rectangle |
609 | // for the passed-in area element. That would require adding functions |
610 | // to the area element class. |
611 | repaint(); |
612 | } |
613 | |
614 | ImageDrawResult RenderImage::paintIntoRect(PaintInfo& paintInfo, const FloatRect& rect) |
615 | { |
616 | if (!imageResource().cachedImage() || imageResource().errorOccurred() || rect.width() <= 0 || rect.height() <= 0) |
617 | return ImageDrawResult::DidNothing; |
618 | |
619 | RefPtr<Image> img = imageResource().image(flooredIntSize(rect.size())); |
620 | if (!img || img->isNull()) |
621 | return ImageDrawResult::DidNothing; |
622 | |
623 | HTMLImageElement* imageElement = is<HTMLImageElement>(element()) ? downcast<HTMLImageElement>(element()) : nullptr; |
624 | CompositeOperator compositeOperator = imageElement ? imageElement->compositeOperator() : CompositeSourceOver; |
625 | |
626 | // FIXME: Document when image != img.get(). |
627 | Image* image = imageResource().image().get(); |
628 | InterpolationQuality interpolation = image ? chooseInterpolationQuality(paintInfo.context(), *image, image, LayoutSize(rect.size())) : InterpolationDefault; |
629 | |
630 | #if USE(CG) |
631 | if (is<PDFDocumentImage>(image)) |
632 | downcast<PDFDocumentImage>(*image).setPdfImageCachingPolicy(settings().pdfImageCachingPolicy()); |
633 | #endif |
634 | |
635 | if (is<BitmapImage>(image)) |
636 | downcast<BitmapImage>(*image).updateFromSettings(settings()); |
637 | |
638 | ImageOrientationDescription orientationDescription(shouldRespectImageOrientation(), style().imageOrientation()); |
639 | auto decodingMode = decodingModeForImageDraw(*image, paintInfo); |
640 | auto drawResult = paintInfo.context().drawImage(*img, rect, ImagePaintingOptions(compositeOperator, BlendMode::Normal, decodingMode, orientationDescription, interpolation)); |
641 | if (drawResult == ImageDrawResult::DidRequestDecoding) |
642 | imageResource().cachedImage()->addClientWaitingForAsyncDecoding(*this); |
643 | |
644 | #if USE(SYSTEM_PREVIEW) |
645 | if (imageElement && imageElement->isSystemPreviewImage() && drawResult == ImageDrawResult::DidDraw && RuntimeEnabledFeatures::sharedFeatures().systemPreviewEnabled()) |
646 | theme().paintSystemPreviewBadge(*img, paintInfo, rect); |
647 | #endif |
648 | |
649 | return drawResult; |
650 | } |
651 | |
652 | bool RenderImage::boxShadowShouldBeAppliedToBackground(const LayoutPoint& paintOffset, BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox*) const |
653 | { |
654 | if (!RenderBoxModelObject::boxShadowShouldBeAppliedToBackground(paintOffset, bleedAvoidance)) |
655 | return false; |
656 | |
657 | return !const_cast<RenderImage*>(this)->backgroundIsKnownToBeObscured(paintOffset); |
658 | } |
659 | |
660 | bool RenderImage::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const |
661 | { |
662 | UNUSED_PARAM(maxDepthToTest); |
663 | if (!imageResource().cachedImage() || imageResource().errorOccurred()) |
664 | return false; |
665 | if (cachedImage() && !cachedImage()->isLoaded()) |
666 | return false; |
667 | if (!contentBoxRect().contains(localRect)) |
668 | return false; |
669 | FillBox backgroundClip = style().backgroundClip(); |
670 | // Background paints under borders. |
671 | if (backgroundClip == FillBox::Border && style().hasBorder() && !borderObscuresBackground()) |
672 | return false; |
673 | // Background shows in padding area. |
674 | if ((backgroundClip == FillBox::Border || backgroundClip == FillBox::Padding) && style().hasPadding()) |
675 | return false; |
676 | // Object-fit may leave parts of the content box empty. |
677 | ObjectFit objectFit = style().objectFit(); |
678 | if (objectFit != ObjectFit::Fill && objectFit != ObjectFit::Cover) |
679 | return false; |
680 | |
681 | LengthPoint objectPosition = style().objectPosition(); |
682 | if (objectPosition != RenderStyle::initialObjectPosition()) |
683 | return false; |
684 | |
685 | // Check for image with alpha. |
686 | return cachedImage() && cachedImage()->currentFrameKnownToBeOpaque(this); |
687 | } |
688 | |
689 | bool RenderImage::computeBackgroundIsKnownToBeObscured(const LayoutPoint& paintOffset) |
690 | { |
691 | if (!hasBackground()) |
692 | return false; |
693 | |
694 | LayoutRect paintedExtent; |
695 | if (!getBackgroundPaintedExtent(paintOffset, paintedExtent)) |
696 | return false; |
697 | return foregroundIsKnownToBeOpaqueInRect(paintedExtent, 0); |
698 | } |
699 | |
700 | LayoutUnit RenderImage::minimumReplacedHeight() const |
701 | { |
702 | return imageResource().errorOccurred() ? intrinsicSize().height() : 0_lu; |
703 | } |
704 | |
705 | HTMLMapElement* RenderImage::imageMap() const |
706 | { |
707 | auto* imageElement = element(); |
708 | if (!imageElement || !is<HTMLImageElement>(imageElement)) |
709 | return nullptr; |
710 | return downcast<HTMLImageElement>(imageElement)->associatedMapElement(); |
711 | } |
712 | |
713 | bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) |
714 | { |
715 | HitTestResult tempResult(result.hitTestLocation()); |
716 | bool inside = RenderReplaced::nodeAtPoint(request, tempResult, locationInContainer, accumulatedOffset, hitTestAction); |
717 | |
718 | if (tempResult.innerNode() && element()) { |
719 | if (HTMLMapElement* map = imageMap()) { |
720 | LayoutRect contentBox = contentBoxRect(); |
721 | float scaleFactor = 1 / style().effectiveZoom(); |
722 | LayoutPoint mapLocation = locationInContainer.point() - toLayoutSize(accumulatedOffset) - locationOffset() - toLayoutSize(contentBox.location()); |
723 | mapLocation.scale(scaleFactor); |
724 | |
725 | if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult)) |
726 | tempResult.setInnerNonSharedNode(element()); |
727 | } |
728 | } |
729 | |
730 | if (!inside && request.resultIsElementList()) |
731 | result.append(tempResult, request); |
732 | if (inside) |
733 | result = tempResult; |
734 | return inside; |
735 | } |
736 | |
737 | void RenderImage::updateAltText() |
738 | { |
739 | if (!element()) |
740 | return; |
741 | |
742 | if (is<HTMLInputElement>(*element())) |
743 | m_altText = downcast<HTMLInputElement>(*element()).altText(); |
744 | else if (is<HTMLImageElement>(*element())) |
745 | m_altText = downcast<HTMLImageElement>(*element()).altText(); |
746 | } |
747 | |
748 | bool RenderImage::canHaveChildren() const |
749 | { |
750 | #if !ENABLE(SERVICE_CONTROLS) |
751 | return false; |
752 | #else |
753 | return m_hasShadowControls; |
754 | #endif |
755 | } |
756 | |
757 | void RenderImage::layout() |
758 | { |
759 | // Recomputing overflow is required only when child content is present. |
760 | if (needsSimplifiedNormalFlowLayoutOnly() && !m_hasShadowControls) { |
761 | clearNeedsLayout(); |
762 | return; |
763 | } |
764 | |
765 | StackStats::LayoutCheckPoint layoutCheckPoint; |
766 | |
767 | LayoutSize oldSize = contentBoxRect().size(); |
768 | RenderReplaced::layout(); |
769 | |
770 | updateInnerContentRect(); |
771 | |
772 | if (m_hasShadowControls) |
773 | layoutShadowControls(oldSize); |
774 | } |
775 | |
776 | void RenderImage::layoutShadowControls(const LayoutSize& oldSize) |
777 | { |
778 | // We expect a single containing box under the UA shadow root. |
779 | ASSERT(firstChild() == lastChild()); |
780 | |
781 | auto* controlsRenderer = downcast<RenderBox>(firstChild()); |
782 | if (!controlsRenderer) |
783 | return; |
784 | |
785 | bool controlsNeedLayout = controlsRenderer->needsLayout(); |
786 | // If the region chain has changed we also need to relayout the controls to update the region box info. |
787 | // FIXME: We can do better once we compute region box info for RenderReplaced, not only for RenderBlock. |
788 | const RenderFragmentedFlow* fragmentedFlow = enclosingFragmentedFlow(); |
789 | if (fragmentedFlow && !controlsNeedLayout) { |
790 | if (fragmentedFlow->pageLogicalSizeChanged()) |
791 | controlsNeedLayout = true; |
792 | } |
793 | |
794 | LayoutSize newSize = contentBoxRect().size(); |
795 | if (newSize == oldSize && !controlsNeedLayout) |
796 | return; |
797 | |
798 | // When calling layout() on a child node, a parent must either push a LayoutStateMaintainter, or |
799 | // instantiate LayoutStateDisabler. Since using a LayoutStateMaintainer is slightly more efficient, |
800 | // and this method might be called many times per second during video playback, use a LayoutStateMaintainer: |
801 | LayoutStateMaintainer statePusher(*this, locationOffset(), hasTransform() || hasReflection() || style().isFlippedBlocksWritingMode()); |
802 | |
803 | if (shadowControlsNeedCustomLayoutMetrics()) { |
804 | controlsRenderer->setLocation(LayoutPoint(borderLeft(), borderTop()) + LayoutSize(paddingLeft(), paddingTop())); |
805 | controlsRenderer->mutableStyle().setHeight(Length(newSize.height(), Fixed)); |
806 | controlsRenderer->mutableStyle().setWidth(Length(newSize.width(), Fixed)); |
807 | } |
808 | |
809 | controlsRenderer->setNeedsLayout(MarkOnlyThis); |
810 | controlsRenderer->layout(); |
811 | clearChildNeedsLayout(); |
812 | } |
813 | |
814 | void RenderImage::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio) const |
815 | { |
816 | RenderReplaced::computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio); |
817 | |
818 | // Our intrinsicSize is empty if we're rendering generated images with relative width/height. Figure out the right intrinsic size to use. |
819 | if (intrinsicSize.isEmpty() && (imageResource().imageHasRelativeWidth() || imageResource().imageHasRelativeHeight())) { |
820 | RenderObject* containingBlock = isOutOfFlowPositioned() ? container() : this->containingBlock(); |
821 | if (is<RenderBox>(*containingBlock)) { |
822 | auto& box = downcast<RenderBox>(*containingBlock); |
823 | intrinsicSize.setWidth(box.availableLogicalWidth()); |
824 | intrinsicSize.setHeight(box.availableLogicalHeight(IncludeMarginBorderPadding)); |
825 | } |
826 | } |
827 | // Don't compute an intrinsic ratio to preserve historical WebKit behavior if we're painting alt text and/or a broken image. |
828 | if (imageResource().errorOccurred()) { |
829 | intrinsicRatio = 1; |
830 | return; |
831 | } |
832 | } |
833 | |
834 | bool RenderImage::needsPreferredWidthsRecalculation() const |
835 | { |
836 | if (RenderReplaced::needsPreferredWidthsRecalculation()) |
837 | return true; |
838 | return embeddedContentBox(); |
839 | } |
840 | |
841 | RenderBox* RenderImage::embeddedContentBox() const |
842 | { |
843 | CachedImage* cachedImage = this->cachedImage(); |
844 | if (cachedImage && is<SVGImage>(cachedImage->image())) |
845 | return downcast<SVGImage>(*cachedImage->image()).embeddedContentBox(); |
846 | |
847 | return nullptr; |
848 | } |
849 | |
850 | } // namespace WebCore |
851 | |