1/* 2 * Copyright (C) 2012, 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "CSSImageSetValue.h" 28 29#if ENABLE(CSS_IMAGE_SET) 30 31#include "CSSImageValue.h" 32#include "CSSPrimitiveValue.h" 33#include "CachedImage.h" 34#include "CachedResourceLoader.h" 35#include "CachedResourceRequest.h" 36#include "CachedResourceRequestInitiators.h" 37#include "Document.h" 38#include "Page.h" 39#include "StyleCachedImageSet.h" 40#include "StylePendingImage.h" 41#include <wtf/text/StringBuilder.h> 42 43namespace WebCore { 44 45CSSImageSetValue::CSSImageSetValue() 46 : CSSValueList(ImageSetClass, CommaSeparator) 47 , m_accessedBestFitImage(false) 48 , m_scaleFactor(1) 49{ 50} 51 52inline void CSSImageSetValue::detachPendingImage() 53{ 54 if (m_imageSet && m_imageSet->isPendingImage()) 55 static_cast<StylePendingImage&>(*m_imageSet).detachFromCSSValue(); 56} 57 58CSSImageSetValue::~CSSImageSetValue() 59{ 60 detachPendingImage(); 61 62 if (m_imageSet && m_imageSet->isCachedImageSet()) 63 static_cast<StyleCachedImageSet*>(m_imageSet.get())->clearImageSetValue(); 64} 65 66void CSSImageSetValue::fillImageSet() 67{ 68 size_t length = this->length(); 69 size_t i = 0; 70 while (i < length) { 71 CSSValue* imageValue = item(i); 72 ASSERT_WITH_SECURITY_IMPLICATION(imageValue->isImageValue()); 73 String imageURL = static_cast<CSSImageValue*>(imageValue)->url(); 74 75 ++i; 76 ASSERT_WITH_SECURITY_IMPLICATION(i < length); 77 CSSValue* scaleFactorValue = item(i); 78 ASSERT_WITH_SECURITY_IMPLICATION(scaleFactorValue->isPrimitiveValue()); 79 float scaleFactor = static_cast<CSSPrimitiveValue*>(scaleFactorValue)->getFloatValue(); 80 81 ImageWithScale image; 82 image.imageURL = imageURL; 83 image.scaleFactor = scaleFactor; 84 m_imagesInSet.append(image); 85 ++i; 86 } 87 88 // Sort the images so that they are stored in order from lowest resolution to highest. 89 std::sort(m_imagesInSet.begin(), m_imagesInSet.end(), CSSImageSetValue::compareByScaleFactor); 90} 91 92CSSImageSetValue::ImageWithScale CSSImageSetValue::bestImageForScaleFactor() 93{ 94 ImageWithScale image; 95 size_t numberOfImages = m_imagesInSet.size(); 96 for (size_t i = 0; i < numberOfImages; ++i) { 97 image = m_imagesInSet.at(i); 98 if (image.scaleFactor >= m_scaleFactor) 99 return image; 100 } 101 return image; 102} 103 104StyleCachedImageSet* CSSImageSetValue::cachedImageSet(CachedResourceLoader* loader) 105{ 106 ASSERT(loader); 107 108 Document* document = loader->document(); 109 if (Page* page = document->page()) 110 m_scaleFactor = page->deviceScaleFactor(); 111 else 112 m_scaleFactor = 1; 113 114 if (!m_imagesInSet.size()) 115 fillImageSet(); 116 117 if (!m_accessedBestFitImage) { 118 // FIXME: In the future, we want to take much more than deviceScaleFactor into acount here. 119 // All forms of scale should be included: Page::pageScaleFactor(), Frame::pageZoomFactor(), 120 // and any CSS transforms. https://bugs.webkit.org/show_bug.cgi?id=81698 121 ImageWithScale image = bestImageForScaleFactor(); 122 CachedResourceRequest request(ResourceRequest(document->completeURL(image.imageURL))); 123 request.setInitiator(cachedResourceRequestInitiators().css); 124 if (CachedResourceHandle<CachedImage> cachedImage = loader->requestImage(request)) { 125 detachPendingImage(); 126 m_imageSet = StyleCachedImageSet::create(cachedImage.get(), image.scaleFactor, this); 127 m_accessedBestFitImage = true; 128 } 129 } 130 131 return (m_imageSet && m_imageSet->isCachedImageSet()) ? static_cast<StyleCachedImageSet*>(m_imageSet.get()) : 0; 132} 133 134StyleImage* CSSImageSetValue::cachedOrPendingImageSet(Document* document) 135{ 136 if (!m_imageSet) 137 m_imageSet = StylePendingImage::create(this); 138 else if (document && !m_imageSet->isPendingImage()) { 139 float deviceScaleFactor = 1; 140 if (Page* page = document->page()) 141 deviceScaleFactor = page->deviceScaleFactor(); 142 143 // If the deviceScaleFactor has changed, we may not have the best image loaded, so we have to re-assess. 144 if (deviceScaleFactor != m_scaleFactor) { 145 m_accessedBestFitImage = false; 146 m_imageSet = StylePendingImage::create(this); 147 } 148 } 149 150 return m_imageSet.get(); 151} 152 153String CSSImageSetValue::customCssText() const 154{ 155 StringBuilder result; 156 result.appendLiteral("-webkit-image-set("); 157 158 size_t length = this->length(); 159 size_t i = 0; 160 while (i < length) { 161 if (i > 0) 162 result.appendLiteral(", "); 163 164 const CSSValue* imageValue = item(i); 165 result.append(imageValue->cssText()); 166 result.append(' '); 167 168 ++i; 169 ASSERT_WITH_SECURITY_IMPLICATION(i < length); 170 const CSSValue* scaleFactorValue = item(i); 171 result.append(scaleFactorValue->cssText()); 172 // FIXME: Eventually the scale factor should contain it's own unit http://wkb.ug/100120. 173 // For now 'x' is hard-coded in the parser, so we hard-code it here too. 174 result.append('x'); 175 176 ++i; 177 } 178 179 result.append(')'); 180 return result.toString(); 181} 182 183bool CSSImageSetValue::hasFailedOrCanceledSubresources() const 184{ 185 if (!m_imageSet || !m_imageSet->isCachedImageSet()) 186 return false; 187 CachedResource* cachedResource = static_cast<StyleCachedImageSet*>(m_imageSet.get())->cachedImage(); 188 if (!cachedResource) 189 return true; 190 return cachedResource->loadFailedOrCanceled(); 191} 192 193CSSImageSetValue::CSSImageSetValue(const CSSImageSetValue& cloneFrom) 194 : CSSValueList(cloneFrom) 195 , m_accessedBestFitImage(false) 196 , m_scaleFactor(1) 197{ 198 // Non-CSSValueList data is not accessible through CSS OM, no need to clone. 199} 200 201PassRefPtr<CSSImageSetValue> CSSImageSetValue::cloneForCSSOM() const 202{ 203 return adoptRef(new CSSImageSetValue(*this)); 204} 205 206} // namespace WebCore 207 208#endif // ENABLE(CSS_IMAGE_SET) 209