diff options
Diffstat (limited to 'chromium/third_party/WebKit/Source/core/loader/cache/ImageResource.cpp')
-rw-r--r-- | chromium/third_party/WebKit/Source/core/loader/cache/ImageResource.cpp | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/core/loader/cache/ImageResource.cpp b/chromium/third_party/WebKit/Source/core/loader/cache/ImageResource.cpp new file mode 100644 index 00000000000..c07d864d850 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/loader/cache/ImageResource.cpp @@ -0,0 +1,451 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + 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 "core/loader/cache/ImageResource.h" + +#include "core/loader/cache/ImageResourceClient.h" +#include "core/loader/cache/MemoryCache.h" +#include "core/loader/cache/ResourceClient.h" +#include "core/loader/cache/ResourceClientWalker.h" +#include "core/loader/cache/ResourceFetcher.h" +#include "core/page/FrameView.h" +#include "core/platform/SharedBuffer.h" +#include "core/platform/graphics/BitmapImage.h" +#include "core/rendering/RenderObject.h" +#include "core/svg/graphics/SVGImage.h" +#include "wtf/CurrentTime.h" +#include "wtf/StdLibExtras.h" +#include "wtf/Vector.h" + +using namespace std; + +namespace WebCore { + +ImageResource::ImageResource(const ResourceRequest& resourceRequest) + : Resource(resourceRequest, Image) + , m_image(0) + , m_loadingMultipartContent(false) +{ + setStatus(Unknown); + setCustomAcceptHeader(); +} + +ImageResource::ImageResource(WebCore::Image* image) + : Resource(ResourceRequest(), Image) + , m_image(image) +{ + setStatus(Cached); + setLoading(false); + setCustomAcceptHeader(); +} + +ImageResource::~ImageResource() +{ + clearImage(); +} + +void ImageResource::load(ResourceFetcher* fetcher, const ResourceLoaderOptions& options) +{ + if (!fetcher || fetcher->autoLoadImages()) + Resource::load(fetcher, options); + else + setLoading(false); +} + +void ImageResource::didAddClient(ResourceClient* c) +{ + if (m_data && !m_image && !errorOccurred()) { + createImage(); + m_image->setData(m_data, true); + } + + ASSERT(c->resourceClientType() == ImageResourceClient::expectedType()); + if (m_image && !m_image->isNull()) + static_cast<ImageResourceClient*>(c)->imageChanged(this); + + Resource::didAddClient(c); +} + +void ImageResource::didRemoveClient(ResourceClient* c) +{ + ASSERT(c); + ASSERT(c->resourceClientType() == ImageResourceClient::expectedType()); + + m_pendingContainerSizeRequests.remove(static_cast<ImageResourceClient*>(c)); + if (m_svgImageCache) + m_svgImageCache->removeClientFromCache(static_cast<ImageResourceClient*>(c)); + + Resource::didRemoveClient(c); +} + +void ImageResource::switchClientsToRevalidatedResource() +{ + ASSERT(resourceToRevalidate()); + ASSERT(resourceToRevalidate()->isImage()); + // Pending container size requests need to be transferred to the revalidated resource. + if (!m_pendingContainerSizeRequests.isEmpty()) { + // A copy of pending size requests is needed as they are deleted during Resource::switchClientsToRevalidateResouce(). + ContainerSizeRequests switchContainerSizeRequests; + for (ContainerSizeRequests::iterator it = m_pendingContainerSizeRequests.begin(); it != m_pendingContainerSizeRequests.end(); ++it) + switchContainerSizeRequests.set(it->key, it->value); + Resource::switchClientsToRevalidatedResource(); + ImageResource* revalidatedImageResource = static_cast<ImageResource*>(resourceToRevalidate()); + for (ContainerSizeRequests::iterator it = switchContainerSizeRequests.begin(); it != switchContainerSizeRequests.end(); ++it) + revalidatedImageResource->setContainerSizeForRenderer(it->key, it->value.first, it->value.second); + return; + } + + Resource::switchClientsToRevalidatedResource(); +} + +void ImageResource::allClientsRemoved() +{ + m_pendingContainerSizeRequests.clear(); + if (m_image && !errorOccurred()) + m_image->resetAnimation(); + Resource::allClientsRemoved(); +} + +pair<WebCore::Image*, float> ImageResource::brokenImage(float deviceScaleFactor) const +{ + if (deviceScaleFactor >= 2) { + DEFINE_STATIC_LOCAL(WebCore::Image*, brokenImageHiRes, (WebCore::Image::loadPlatformResource("missingImage@2x").leakRef())); + return std::make_pair(brokenImageHiRes, 2); + } + + DEFINE_STATIC_LOCAL(WebCore::Image*, brokenImageLoRes, (WebCore::Image::loadPlatformResource("missingImage").leakRef())); + return std::make_pair(brokenImageLoRes, 1); +} + +bool ImageResource::willPaintBrokenImage() const +{ + return errorOccurred(); +} + +WebCore::Image* ImageResource::image() +{ + ASSERT(!isPurgeable()); + + if (errorOccurred()) { + // Returning the 1x broken image is non-ideal, but we cannot reliably access the appropriate + // deviceScaleFactor from here. It is critical that callers use ImageResource::brokenImage() + // when they need the real, deviceScaleFactor-appropriate broken image icon. + return brokenImage(1).first; + } + + if (m_image) + return m_image.get(); + + return WebCore::Image::nullImage(); +} + +WebCore::Image* ImageResource::imageForRenderer(const RenderObject* renderer) +{ + ASSERT(!isPurgeable()); + + if (errorOccurred()) { + // Returning the 1x broken image is non-ideal, but we cannot reliably access the appropriate + // deviceScaleFactor from here. It is critical that callers use ImageResource::brokenImage() + // when they need the real, deviceScaleFactor-appropriate broken image icon. + return brokenImage(1).first; + } + + if (!m_image) + return WebCore::Image::nullImage(); + + if (m_image->isSVGImage()) { + WebCore::Image* image = m_svgImageCache->imageForRenderer(renderer); + if (image != WebCore::Image::nullImage()) + return image; + } + + return m_image.get(); +} + +void ImageResource::setContainerSizeForRenderer(const ImageResourceClient* renderer, const IntSize& containerSize, float containerZoom) +{ + if (containerSize.isEmpty()) + return; + ASSERT(renderer); + ASSERT(containerZoom); + if (!m_image) { + m_pendingContainerSizeRequests.set(renderer, SizeAndZoom(containerSize, containerZoom)); + return; + } + if (!m_image->isSVGImage()) { + m_image->setContainerSize(containerSize); + return; + } + + m_svgImageCache->setContainerSizeForRenderer(renderer, containerSize, containerZoom); +} + +bool ImageResource::usesImageContainerSize() const +{ + if (m_image) + return m_image->usesContainerSize(); + + return false; +} + +bool ImageResource::imageHasRelativeWidth() const +{ + if (m_image) + return m_image->hasRelativeWidth(); + + return false; +} + +bool ImageResource::imageHasRelativeHeight() const +{ + if (m_image) + return m_image->hasRelativeHeight(); + + return false; +} + +LayoutSize ImageResource::imageSizeForRenderer(const RenderObject* renderer, float multiplier) +{ + ASSERT(!isPurgeable()); + + if (!m_image) + return IntSize(); + + LayoutSize imageSize; + + if (m_image->isBitmapImage() && (renderer && renderer->shouldRespectImageOrientation() == RespectImageOrientation)) + imageSize = static_cast<BitmapImage*>(m_image.get())->sizeRespectingOrientation(); + else if (m_image->isSVGImage()) + imageSize = m_svgImageCache->imageSizeForRenderer(renderer); + else + imageSize = m_image->size(); + + if (multiplier == 1.0f) + return imageSize; + + // Don't let images that have a width/height >= 1 shrink below 1 when zoomed. + float widthScale = m_image->hasRelativeWidth() ? 1.0f : multiplier; + float heightScale = m_image->hasRelativeHeight() ? 1.0f : multiplier; + LayoutSize minimumSize(imageSize.width() > 0 ? 1 : 0, imageSize.height() > 0 ? 1 : 0); + imageSize.scale(widthScale, heightScale); + imageSize.clampToMinimumSize(minimumSize); + ASSERT(multiplier != 1.0f || (imageSize.width().fraction() == 0.0f && imageSize.height().fraction() == 0.0f)); + return imageSize; +} + +void ImageResource::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio) +{ + if (m_image) + m_image->computeIntrinsicDimensions(intrinsicWidth, intrinsicHeight, intrinsicRatio); +} + +void ImageResource::notifyObservers(const IntRect* changeRect) +{ + ResourceClientWalker<ImageResourceClient> w(m_clients); + while (ImageResourceClient* c = w.next()) + c->imageChanged(this, changeRect); +} + +void ImageResource::clear() +{ + destroyDecodedData(); + clearImage(); + m_pendingContainerSizeRequests.clear(); + setEncodedSize(0); +} + +void ImageResource::setCustomAcceptHeader() +{ + DEFINE_STATIC_LOCAL(const AtomicString, acceptWebP, ("image/webp,*/*;q=0.8", AtomicString::ConstructFromLiteral)); + setAccept(acceptWebP); +} + +inline void ImageResource::createImage() +{ + // Create the image if it doesn't yet exist. + if (m_image) + return; + + if (m_response.mimeType() == "image/svg+xml") { + RefPtr<SVGImage> svgImage = SVGImage::create(this); + m_svgImageCache = SVGImageCache::create(svgImage.get()); + m_image = svgImage.release(); + } else { + m_image = BitmapImage::create(this); + } + + if (m_image) { + // Send queued container size requests. + if (m_image->usesContainerSize()) { + for (ContainerSizeRequests::iterator it = m_pendingContainerSizeRequests.begin(); it != m_pendingContainerSizeRequests.end(); ++it) + setContainerSizeForRenderer(it->key, it->value.first, it->value.second); + } + m_pendingContainerSizeRequests.clear(); + } +} + +inline void ImageResource::clearImage() +{ + // If our Image has an observer, it's always us so we need to clear the back pointer + // before dropping our reference. + if (m_image) + m_image->setImageObserver(0); + m_image.clear(); +} + +void ImageResource::appendData(const char* data, int length) +{ + Resource::appendData(data, length); + if (!m_loadingMultipartContent) + updateImage(false); +} + +void ImageResource::updateImage(bool allDataReceived) +{ + if (m_data) + createImage(); + + bool sizeAvailable = false; + + // Have the image update its data from its internal buffer. + // It will not do anything now, but will delay decoding until + // queried for info (like size or specific image frames). + if (m_image) + sizeAvailable = m_image->setData(m_data, allDataReceived); + + // Go ahead and tell our observers to try to draw if we have either + // received all the data or the size is known. Each chunk from the + // network causes observers to repaint, which will force that chunk + // to decode. + if (sizeAvailable || allDataReceived) { + if (!m_image || m_image->isNull()) { + error(errorOccurred() ? status() : DecodeError); + if (inCache()) + memoryCache()->remove(this); + return; + } + + // It would be nice to only redraw the decoded band of the image, but with the current design + // (decoding delayed until painting) that seems hard. + notifyObservers(); + } +} + +void ImageResource::finishOnePart() +{ + if (m_loadingMultipartContent) + clear(); + updateImage(true); + if (m_loadingMultipartContent) + m_data.clear(); + Resource::finishOnePart(); +} + +void ImageResource::error(Resource::Status status) +{ + clear(); + Resource::error(status); + notifyObservers(); +} + +void ImageResource::responseReceived(const ResourceResponse& response) +{ + if (m_loadingMultipartContent && m_data) + finishOnePart(); + else if (response.isMultipart()) + m_loadingMultipartContent = true; + Resource::responseReceived(response); +} + +void ImageResource::destroyDecodedData() +{ + bool canDeleteImage = !m_image || (m_image->hasOneRef() && m_image->isBitmapImage()); + if (isSafeToMakePurgeable() && canDeleteImage && !isLoading()) { + // Image refs the data buffer so we should not make it purgeable while the image is alive. + // Invoking addClient() will reconstruct the image object. + m_image = 0; + setDecodedSize(0); + makePurgeable(true); + } else if (m_image && !errorOccurred()) { + m_image->destroyDecodedData(); + } +} + +void ImageResource::decodedSizeChanged(const WebCore::Image* image, int delta) +{ + if (!image || image != m_image) + return; + + setDecodedSize(decodedSize() + delta); +} + +void ImageResource::didDraw(const WebCore::Image* image) +{ + if (!image || image != m_image) + return; + + double timeStamp = FrameView::currentPaintTimeStamp(); + if (!timeStamp) // If didDraw is called outside of a Frame paint. + timeStamp = currentTime(); + + Resource::didAccessDecodedData(timeStamp); +} + +bool ImageResource::shouldPauseAnimation(const WebCore::Image* image) +{ + if (!image || image != m_image) + return false; + + ResourceClientWalker<ImageResourceClient> w(m_clients); + while (ImageResourceClient* c = w.next()) { + if (c->willRenderImage(this)) + return false; + } + + return true; +} + +void ImageResource::animationAdvanced(const WebCore::Image* image) +{ + if (!image || image != m_image) + return; + notifyObservers(); +} + +void ImageResource::changedInRect(const WebCore::Image* image, const IntRect& rect) +{ + if (!image || image != m_image) + return; + notifyObservers(&rect); +} + +bool ImageResource::currentFrameKnownToBeOpaque(const RenderObject* renderer) +{ + WebCore::Image* image = imageForRenderer(renderer); + if (image->isBitmapImage()) + image->nativeImageForCurrentFrame(); // force decode + return image->currentFrameKnownToBeOpaque(); +} + +} // namespace WebCore |