2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
6 Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
25 #include "core/fetch/ImageResource.h"
27 #include "core/fetch/ImageResourceClient.h"
28 #include "core/fetch/MemoryCache.h"
29 #include "core/fetch/ResourceClient.h"
30 #include "core/fetch/ResourceClientWalker.h"
31 #include "core/fetch/ResourceFetcher.h"
32 #include "core/fetch/ResourceLoader.h"
33 #include "core/html/HTMLImageElement.h"
34 #include "core/layout/LayoutObject.h"
35 #include "core/svg/graphics/SVGImage.h"
36 #include "core/svg/graphics/SVGImageForContainer.h"
37 #include "platform/Logging.h"
38 #include "platform/RuntimeEnabledFeatures.h"
39 #include "platform/SharedBuffer.h"
40 #include "platform/TraceEvent.h"
41 #include "platform/graphics/BitmapImage.h"
42 #include "public/platform/Platform.h"
43 #include "wtf/CurrentTime.h"
44 #include "wtf/StdLibExtras.h"
48 ResourcePtr
<ImageResource
> ImageResource::fetch(FetchRequest
& request
, ResourceFetcher
* fetcher
)
50 if (request
.resourceRequest().requestContext() == WebURLRequest::RequestContextUnspecified
)
51 request
.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextImage
);
52 if (fetcher
->context().pageDismissalEventBeingDispatched()) {
53 KURL requestURL
= request
.resourceRequest().url();
54 if (requestURL
.isValid() && fetcher
->context().canRequest(Resource::Image
, request
.resourceRequest(), requestURL
, request
.options(), request
.forPreload(), request
.originRestriction()))
55 fetcher
->context().sendImagePing(requestURL
);
59 if (fetcher
->clientDefersImage(request
.resourceRequest().url()))
60 request
.setDefer(FetchRequest::DeferredByClient
);
62 return toImageResource(fetcher
->requestResource(request
, ImageResourceFactory()));
65 ImageResource::ImageResource(const ResourceRequest
& resourceRequest
)
66 : Resource(resourceRequest
, Image
)
67 , m_devicePixelRatioHeaderValue(1.0)
69 , m_hasDevicePixelRatioHeaderValue(false)
71 WTF_LOG(Timers
, "new ImageResource(ResourceRequest) %p", this);
73 setCustomAcceptHeader();
76 ImageResource::ImageResource(blink::Image
* image
)
77 : Resource(ResourceRequest(""), Image
)
78 , m_devicePixelRatioHeaderValue(1.0)
80 , m_hasDevicePixelRatioHeaderValue(false)
82 WTF_LOG(Timers
, "new ImageResource(Image) %p", this);
85 setCustomAcceptHeader();
88 ImageResource::ImageResource(const ResourceRequest
& resourceRequest
, blink::Image
* image
)
89 : Resource(resourceRequest
, Image
)
92 WTF_LOG(Timers
, "new ImageResource(ResourceRequest, Image) %p", this);
95 setCustomAcceptHeader();
98 ImageResource::~ImageResource()
100 WTF_LOG(Timers
, "~ImageResource %p", this);
104 void ImageResource::load(ResourceFetcher
* fetcher
, const ResourceLoaderOptions
& options
)
106 if (!fetcher
|| fetcher
->autoLoadImages())
107 Resource::load(fetcher
, options
);
112 void ImageResource::didAddClient(ResourceClient
* c
)
114 if (m_data
&& !m_image
&& !errorOccurred()) {
116 m_image
->setData(m_data
, true);
119 ASSERT(c
->resourceClientType() == ImageResourceClient::expectedType());
120 if (m_image
&& !m_image
->isNull())
121 static_cast<ImageResourceClient
*>(c
)->imageChanged(this);
123 Resource::didAddClient(c
);
126 void ImageResource::didRemoveClient(ResourceClient
* c
)
129 ASSERT(c
->resourceClientType() == ImageResourceClient::expectedType());
130 if (m_imageForContainerMap
)
131 m_imageForContainerMap
->remove(static_cast<ImageResourceClient
*>(c
));
133 Resource::didRemoveClient(c
);
136 bool ImageResource::isSafeToUnlock() const
138 // Note that |m_image| holds a reference to |m_data| in addition to the one held by the Resource parent class.
139 return !m_image
|| (m_image
->hasOneRef() && m_data
->refCount() == 2);
142 void ImageResource::destroyDecodedDataIfPossible()
144 if (!hasClients() && !isLoading() && (!m_image
|| (m_image
->hasOneRef() && m_image
->isBitmapImage()))) {
147 } else if (m_image
&& !errorOccurred()) {
148 m_image
->destroyDecodedData(true);
152 void ImageResource::allClientsRemoved()
154 if (m_image
&& !errorOccurred())
155 m_image
->resetAnimation();
156 Resource::allClientsRemoved();
159 pair
<blink::Image
*, float> ImageResource::brokenImage(float deviceScaleFactor
)
161 if (deviceScaleFactor
>= 2) {
162 DEFINE_STATIC_REF(blink::Image
, brokenImageHiRes
, (blink::Image::loadPlatformResource("missingImage@2x")));
163 return std::make_pair(brokenImageHiRes
, 2);
166 DEFINE_STATIC_REF(blink::Image
, brokenImageLoRes
, (blink::Image::loadPlatformResource("missingImage")));
167 return std::make_pair(brokenImageLoRes
, 1);
170 bool ImageResource::willPaintBrokenImage() const
172 return errorOccurred();
175 blink::Image
* ImageResource::image()
177 ASSERT(!isPurgeable());
179 if (errorOccurred()) {
180 // Returning the 1x broken image is non-ideal, but we cannot reliably access the appropriate
181 // deviceScaleFactor from here. It is critical that callers use ImageResource::brokenImage()
182 // when they need the real, deviceScaleFactor-appropriate broken image icon.
183 return brokenImage(1).first
;
187 return m_image
.get();
189 return blink::Image::nullImage();
192 blink::Image
* ImageResource::imageForLayoutObject(const LayoutObject
* layoutObject
)
194 ASSERT(!isPurgeable());
196 if (errorOccurred()) {
197 // Returning the 1x broken image is non-ideal, but we cannot reliably access the appropriate
198 // deviceScaleFactor from here. It is critical that callers use ImageResource::brokenImage()
199 // when they need the real, deviceScaleFactor-appropriate broken image icon.
200 return brokenImage(1).first
;
204 return blink::Image::nullImage();
206 if (m_image
->isSVGImage()) {
207 blink::Image
* image
= svgImageForLayoutObject(layoutObject
);
208 if (image
!= blink::Image::nullImage())
212 return m_image
.get();
215 void ImageResource::setContainerSizeForLayoutObject(const ImageResourceClient
* layoutObject
, const IntSize
& containerSize
, float containerZoom
)
217 if (containerSize
.isEmpty())
219 ASSERT(layoutObject
);
220 ASSERT(containerZoom
);
223 if (!m_image
->isSVGImage()) {
224 m_image
->setContainerSize(containerSize
);
228 FloatSize
containerSizeWithoutZoom(containerSize
);
229 containerSizeWithoutZoom
.scale(1 / containerZoom
);
230 m_imageForContainerMap
->set(layoutObject
, SVGImageForContainer::create(toSVGImage(m_image
.get()), containerSizeWithoutZoom
, containerZoom
));
233 bool ImageResource::usesImageContainerSize() const
236 return m_image
->usesContainerSize();
241 bool ImageResource::imageHasRelativeWidth() const
244 return m_image
->hasRelativeWidth();
249 bool ImageResource::imageHasRelativeHeight() const
252 return m_image
->hasRelativeHeight();
257 LayoutSize
ImageResource::imageSizeForLayoutObject(const LayoutObject
* layoutObject
, float multiplier
, SizeType sizeType
)
259 ASSERT(!isPurgeable());
264 LayoutSize imageSize
;
266 if (m_image
->isBitmapImage() && (layoutObject
&& layoutObject
->shouldRespectImageOrientation() == RespectImageOrientation
))
267 imageSize
= LayoutSize(toBitmapImage(m_image
.get())->sizeRespectingOrientation());
268 else if (m_image
->isSVGImage() && sizeType
== NormalSize
)
269 imageSize
= LayoutSize(svgImageSizeForLayoutObject(layoutObject
));
271 imageSize
= LayoutSize(m_image
->size());
273 if (sizeType
== IntrinsicCorrectedToDPR
&& m_hasDevicePixelRatioHeaderValue
&& m_devicePixelRatioHeaderValue
> 0)
274 multiplier
= 1.0 / m_devicePixelRatioHeaderValue
;
276 if (multiplier
== 1.0f
)
279 // Don't let images that have a width/height >= 1 shrink below 1 when zoomed.
280 float widthScale
= m_image
->hasRelativeWidth() ? 1.0f
: multiplier
;
281 float heightScale
= m_image
->hasRelativeHeight() ? 1.0f
: multiplier
;
282 LayoutSize
minimumSize(imageSize
.width() > 0 ? 1 : 0, imageSize
.height() > 0 ? 1 : 0);
283 imageSize
.scale(widthScale
, heightScale
);
284 imageSize
.clampToMinimumSize(minimumSize
);
285 ASSERT(multiplier
!= 1.0f
|| (imageSize
.width().fraction() == 0.0f
&& imageSize
.height().fraction() == 0.0f
));
289 void ImageResource::computeIntrinsicDimensions(Length
& intrinsicWidth
, Length
& intrinsicHeight
, FloatSize
& intrinsicRatio
)
292 m_image
->computeIntrinsicDimensions(intrinsicWidth
, intrinsicHeight
, intrinsicRatio
);
295 void ImageResource::notifyObservers(const IntRect
* changeRect
)
297 ResourceClientWalker
<ImageResourceClient
> w(m_clients
);
298 while (ImageResourceClient
* c
= w
.next())
299 c
->imageChanged(this, changeRect
);
302 void ImageResource::clear()
309 void ImageResource::setCustomAcceptHeader()
311 DEFINE_STATIC_LOCAL(const AtomicString
, acceptImages
, ("image/webp,image/*,*/*;q=0.8", AtomicString::ConstructFromLiteral
));
312 setAccept(acceptImages
);
315 inline void ImageResource::createImage()
317 // Create the image if it doesn't yet exist.
321 if (m_response
.mimeType() == "image/svg+xml") {
322 m_image
= SVGImage::create(this);
323 m_imageForContainerMap
= adoptPtr(new ImageForContainerMap
);
325 m_image
= BitmapImage::create(this);
329 inline void ImageResource::clearImage()
331 // If our Image has an observer, it's always us so we need to clear the back pointer
332 // before dropping our reference.
334 m_image
->setImageObserver(nullptr);
338 void ImageResource::appendData(const char* data
, unsigned length
)
340 Resource::appendData(data
, length
);
341 if (!loadingMultipartContent())
345 void ImageResource::updateImage(bool allDataReceived
)
347 TRACE_EVENT0("blink", "ImageResource::updateImage");
352 bool sizeAvailable
= false;
354 // Have the image update its data from its internal buffer.
355 // It will not do anything now, but will delay decoding until
356 // queried for info (like size or specific image frames).
358 sizeAvailable
= m_image
->setData(m_data
, allDataReceived
);
360 // Go ahead and tell our observers to try to draw if we have either
361 // received all the data or the size is known. Each chunk from the
362 // network causes observers to repaint, which will force that chunk
364 if (sizeAvailable
|| allDataReceived
) {
365 if (!m_image
|| m_image
->isNull()) {
366 error(errorOccurred() ? status() : DecodeError
);
367 if (memoryCache()->contains(this))
368 memoryCache()->remove(this);
372 // It would be nice to only redraw the decoded band of the image, but with the current design
373 // (decoding delayed until painting) that seems hard.
378 void ImageResource::finishOnePart()
380 if (loadingMultipartContent())
383 if (loadingMultipartContent())
385 Resource::finishOnePart();
388 void ImageResource::error(Resource::Status status
)
391 Resource::error(status
);
395 void ImageResource::responseReceived(const ResourceResponse
& response
, PassOwnPtr
<WebDataConsumerHandle
> handle
)
397 if (loadingMultipartContent() && m_data
)
399 if (RuntimeEnabledFeatures::clientHintsEnabled()) {
400 m_devicePixelRatioHeaderValue
= response
.httpHeaderField("content-dpr").toFloat(&m_hasDevicePixelRatioHeaderValue
);
401 if (!m_hasDevicePixelRatioHeaderValue
|| m_devicePixelRatioHeaderValue
<= 0.0) {
402 m_devicePixelRatioHeaderValue
= 1.0;
403 m_hasDevicePixelRatioHeaderValue
= false;
407 Resource::responseReceived(response
, handle
);
410 void ImageResource::decodedSizeChanged(const blink::Image
* image
, int delta
)
412 if (!image
|| image
!= m_image
)
415 setDecodedSize(decodedSize() + delta
);
418 void ImageResource::didDraw(const blink::Image
* image
)
420 if (!image
|| image
!= m_image
)
422 // decodedSize() == 0 indicates that the image is decoded into DiscardableMemory,
423 // not in MemoryCache. So we don't need to call Resource::didAccessDecodedData()
424 // to update MemoryCache.
425 if (decodedSize() != 0)
426 Resource::didAccessDecodedData();
429 bool ImageResource::shouldPauseAnimation(const blink::Image
* image
)
431 if (!image
|| image
!= m_image
)
434 ResourceClientWalker
<ImageResourceClient
> w(m_clients
);
435 while (ImageResourceClient
* c
= w
.next()) {
436 if (c
->willRenderImage(this))
443 void ImageResource::animationAdvanced(const blink::Image
* image
)
445 if (!image
|| image
!= m_image
)
450 void ImageResource::updateImageAnimationPolicy()
455 ImageAnimationPolicy newPolicy
= ImageAnimationPolicyAllowed
;
456 ResourceClientWalker
<ImageResourceClient
> w(m_clients
);
457 while (ImageResourceClient
* c
= w
.next()) {
458 if (c
->getImageAnimationPolicy(this, newPolicy
))
462 if (m_image
->animationPolicy() != newPolicy
) {
463 m_image
->resetAnimation();
464 m_image
->setAnimationPolicy(newPolicy
);
468 void ImageResource::changedInRect(const blink::Image
* image
, const IntRect
& rect
)
470 if (!image
|| image
!= m_image
)
472 notifyObservers(&rect
);
475 bool ImageResource::currentFrameKnownToBeOpaque(const LayoutObject
* layoutObject
)
477 blink::Image
* image
= imageForLayoutObject(layoutObject
);
478 if (image
->isBitmapImage()) {
479 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage", "data", InspectorPaintImageEvent::data(layoutObject
, *this));
480 // BitmapImage::currentFrameKnownToBeOpaque() conservatively returns true for uncached
481 // frames. To get an accurate answer, we pre-cache the current frame metadata.
482 image
->imageForCurrentFrame();
484 return image
->currentFrameKnownToBeOpaque();
487 bool ImageResource::isAccessAllowed(SecurityOrigin
* securityOrigin
)
489 if (response().wasFetchedViaServiceWorker())
490 return response().serviceWorkerResponseType() != WebServiceWorkerResponseTypeOpaque
;
491 if (!image()->currentFrameHasSingleSecurityOrigin())
493 if (passesAccessControlCheck(securityOrigin
))
495 return !securityOrigin
->taintsCanvas(response().url());
498 IntSize
ImageResource::svgImageSizeForLayoutObject(const LayoutObject
* layoutObject
) const
500 IntSize imageSize
= m_image
->size();
504 ImageForContainerMap::const_iterator it
= m_imageForContainerMap
->find(layoutObject
);
505 if (it
== m_imageForContainerMap
->end())
508 RefPtr
<SVGImageForContainer
> imageForContainer
= it
->value
;
509 ASSERT(!imageForContainer
->size().isEmpty());
510 return imageForContainer
->size();
513 // FIXME: This doesn't take into account the animation timeline so animations will not
514 // restart on page load, nor will two animations in different pages have different timelines.
515 Image
* ImageResource::svgImageForLayoutObject(const LayoutObject
* layoutObject
)
518 return Image::nullImage();
520 ImageForContainerMap::iterator it
= m_imageForContainerMap
->find(layoutObject
);
521 if (it
== m_imageForContainerMap
->end())
522 return Image::nullImage();
524 RefPtr
<SVGImageForContainer
> imageForContainer
= it
->value
;
525 ASSERT(!imageForContainer
->size().isEmpty());
527 Node
* node
= layoutObject
->node();
528 if (node
&& isHTMLImageElement(node
)) {
529 const AtomicString
& urlString
= toHTMLImageElement(node
)->imageSourceURL();
530 KURL url
= node
->document().completeURL(urlString
);
531 imageForContainer
->setURL(url
);
534 return imageForContainer
.get();
537 bool ImageResource::loadingMultipartContent() const
539 return m_loader
&& m_loader
->loadingMultipartContent();