1/*
2 * Copyright (C) 2012-2017 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#include "CSSImageValue.h"
30#include "CSSPrimitiveValue.h"
31#include "CachedImage.h"
32#include "CachedResourceLoader.h"
33#include "CachedResourceRequest.h"
34#include "CachedResourceRequestInitiators.h"
35#include "Document.h"
36#include "Page.h"
37#include <wtf/text/StringBuilder.h>
38
39namespace WebCore {
40
41CSSImageSetValue::CSSImageSetValue(LoadedFromOpaqueSource loadedFromOpaqueSource)
42 : CSSValueList(ImageSetClass, CommaSeparator)
43 , m_loadedFromOpaqueSource(loadedFromOpaqueSource)
44{
45}
46
47CSSImageSetValue::~CSSImageSetValue() = default;
48
49void CSSImageSetValue::fillImageSet()
50{
51 size_t length = this->length();
52 size_t i = 0;
53 while (i < length) {
54 CSSValue* imageValue = item(i);
55 URL imageURL = downcast<CSSImageValue>(*imageValue).url();
56
57 ++i;
58 ASSERT_WITH_SECURITY_IMPLICATION(i < length);
59 CSSValue* scaleFactorValue = item(i);
60 float scaleFactor = downcast<CSSPrimitiveValue>(*scaleFactorValue).floatValue();
61
62 ImageWithScale image;
63 image.imageURL = imageURL;
64 image.scaleFactor = scaleFactor;
65 m_imagesInSet.append(image);
66 ++i;
67 }
68
69 // Sort the images so that they are stored in order from lowest resolution to highest.
70 std::sort(m_imagesInSet.begin(), m_imagesInSet.end(), CSSImageSetValue::compareByScaleFactor);
71}
72
73CSSImageSetValue::ImageWithScale CSSImageSetValue::bestImageForScaleFactor()
74{
75 if (!m_imagesInSet.size())
76 fillImageSet();
77
78 ImageWithScale image;
79 size_t numberOfImages = m_imagesInSet.size();
80 for (size_t i = 0; i < numberOfImages; ++i) {
81 image = m_imagesInSet.at(i);
82 if (image.scaleFactor >= m_deviceScaleFactor)
83 return image;
84 }
85 return image;
86}
87
88std::pair<CachedImage*, float> CSSImageSetValue::loadBestFitImage(CachedResourceLoader& loader, const ResourceLoaderOptions& options)
89{
90 Document* document = loader.document();
91 ASSERT(document);
92
93 updateDeviceScaleFactor(*document);
94
95 if (!m_accessedBestFitImage) {
96 m_accessedBestFitImage = true;
97
98 // FIXME: In the future, we want to take much more than deviceScaleFactor into acount here.
99 // All forms of scale should be included: Page::pageScaleFactor(), Frame::pageZoomFactor(),
100 // and any CSS transforms. https://bugs.webkit.org/show_bug.cgi?id=81698
101 ImageWithScale image = bestImageForScaleFactor();
102
103 ResourceLoaderOptions loadOptions = options;
104 loadOptions.loadedFromOpaqueSource = m_loadedFromOpaqueSource;
105 CachedResourceRequest request(ResourceRequest(document->completeURL(image.imageURL)), loadOptions);
106 request.setInitiator(cachedResourceRequestInitiators().css);
107 if (options.mode == FetchOptions::Mode::Cors)
108 request.updateForAccessControl(*document);
109
110 m_cachedImage = loader.requestImage(WTFMove(request)).value_or(nullptr);
111 m_bestFitImageScaleFactor = image.scaleFactor;
112 }
113 return { m_cachedImage.get(), m_bestFitImageScaleFactor };
114}
115
116void CSSImageSetValue::updateDeviceScaleFactor(const Document& document)
117{
118 float deviceScaleFactor = document.page() ? document.page()->deviceScaleFactor() : 1;
119 if (deviceScaleFactor == m_deviceScaleFactor)
120 return;
121 m_deviceScaleFactor = deviceScaleFactor;
122 m_accessedBestFitImage = false;
123 m_cachedImage = nullptr;
124}
125
126String CSSImageSetValue::customCSSText() const
127{
128 StringBuilder result;
129 result.appendLiteral("image-set(");
130
131 size_t length = this->length();
132 size_t i = 0;
133 while (i < length) {
134 if (i > 0)
135 result.appendLiteral(", ");
136
137 const CSSValue* imageValue = item(i);
138 result.append(imageValue->cssText());
139 result.append(' ');
140
141 ++i;
142 ASSERT_WITH_SECURITY_IMPLICATION(i < length);
143 const CSSValue* scaleFactorValue = item(i);
144 result.append(scaleFactorValue->cssText());
145 // FIXME: Eventually the scale factor should contain it's own unit http://wkb.ug/100120.
146 // For now 'x' is hard-coded in the parser, so we hard-code it here too.
147 result.append('x');
148
149 ++i;
150 }
151
152 result.append(')');
153 return result.toString();
154}
155
156bool CSSImageSetValue::traverseSubresources(const WTF::Function<bool (const CachedResource&)>& handler) const
157{
158 if (!m_cachedImage)
159 return false;
160 return handler(*m_cachedImage);
161}
162
163} // namespace WebCore
164