1 | /* |
2 | * Copyright (C) 2018 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 "CustomPaintImage.h" |
28 | |
29 | #if ENABLE(CSS_PAINTING_API) |
30 | |
31 | #include "CSSComputedStyleDeclaration.h" |
32 | #include "CSSImageValue.h" |
33 | #include "CSSPrimitiveValue.h" |
34 | #include "CSSPropertyParser.h" |
35 | #include "CustomPaintCanvas.h" |
36 | #include "GraphicsContext.h" |
37 | #include "ImageBitmap.h" |
38 | #include "ImageBuffer.h" |
39 | #include "JSCSSPaintCallback.h" |
40 | #include "PaintRenderingContext2D.h" |
41 | #include "RenderElement.h" |
42 | #include "StylePropertyMap.h" |
43 | #include "TypedOMCSSImageValue.h" |
44 | #include "TypedOMCSSUnitValue.h" |
45 | #include "TypedOMCSSUnparsedValue.h" |
46 | #include <JavaScriptCore/ConstructData.h> |
47 | |
48 | namespace WebCore { |
49 | |
50 | CustomPaintImage::CustomPaintImage(PaintWorkletGlobalScope::PaintDefinition& definition, const FloatSize& size, RenderElement& element, const Vector<String>& arguments) |
51 | : m_paintDefinition(makeWeakPtr(definition)) |
52 | , m_inputProperties(definition.inputProperties) |
53 | , m_element(makeWeakPtr(element)) |
54 | , m_arguments(arguments) |
55 | { |
56 | setContainerSize(size); |
57 | } |
58 | |
59 | CustomPaintImage::~CustomPaintImage() = default; |
60 | |
61 | static RefPtr<TypedOMCSSStyleValue> extractComputedProperty(const String& name, Element& element) |
62 | { |
63 | ComputedStyleExtractor extractor(&element); |
64 | |
65 | if (isCustomPropertyName(name)) { |
66 | auto value = extractor.customPropertyValue(name); |
67 | return StylePropertyMapReadOnly::customPropertyValueOrDefault(name, element.document(), value.get(), &element); |
68 | } |
69 | |
70 | CSSPropertyID propertyID = cssPropertyID(name); |
71 | if (!propertyID) |
72 | return nullptr; |
73 | |
74 | auto value = extractor.propertyValue(propertyID, DoNotUpdateLayout); |
75 | return StylePropertyMapReadOnly::reifyValue(value.get(), element.document(), &element); |
76 | } |
77 | |
78 | class HashMapStylePropertyMap final : public StylePropertyMap { |
79 | public: |
80 | static Ref<StylePropertyMap> create(HashMap<String, RefPtr<TypedOMCSSStyleValue>>&& map) |
81 | { |
82 | return adoptRef(*new HashMapStylePropertyMap(WTFMove(map))); |
83 | } |
84 | |
85 | static RefPtr<TypedOMCSSStyleValue> extractComputedProperty(const String& name, Element& element) |
86 | { |
87 | ComputedStyleExtractor extractor(&element); |
88 | |
89 | if (isCustomPropertyName(name)) { |
90 | auto value = extractor.customPropertyValue(name); |
91 | return StylePropertyMapReadOnly::customPropertyValueOrDefault(name, element.document(), value.get(), &element); |
92 | } |
93 | |
94 | CSSPropertyID propertyID = cssPropertyID(name); |
95 | if (!propertyID) |
96 | return nullptr; |
97 | |
98 | auto value = extractor.propertyValue(propertyID, DoNotUpdateLayout); |
99 | return StylePropertyMapReadOnly::reifyValue(value.get(), element.document(), &element); |
100 | } |
101 | |
102 | private: |
103 | explicit HashMapStylePropertyMap(HashMap<String, RefPtr<TypedOMCSSStyleValue>>&& map) |
104 | : m_map(WTFMove(map)) |
105 | { |
106 | } |
107 | |
108 | void clearElement() override { } |
109 | |
110 | RefPtr<TypedOMCSSStyleValue> get(const String& property) const final { return makeRefPtr(m_map.get(property)); } |
111 | |
112 | HashMap<String, RefPtr<TypedOMCSSStyleValue>> m_map; |
113 | }; |
114 | |
115 | ImageDrawResult CustomPaintImage::doCustomPaint(GraphicsContext& destContext, const FloatSize& destSize) |
116 | { |
117 | if (!m_element || !m_element->element() || !m_paintDefinition) |
118 | return ImageDrawResult::DidNothing; |
119 | |
120 | JSC::JSValue paintConstructor = m_paintDefinition->paintConstructor; |
121 | |
122 | if (!paintConstructor) |
123 | return ImageDrawResult::DidNothing; |
124 | |
125 | ASSERT(!m_element->needsLayout()); |
126 | ASSERT(!m_element->element()->document().needsStyleRecalc()); |
127 | |
128 | JSCSSPaintCallback& callback = static_cast<JSCSSPaintCallback&>(m_paintDefinition->paintCallback.get()); |
129 | auto* scriptExecutionContext = callback.scriptExecutionContext(); |
130 | if (!scriptExecutionContext) |
131 | return ImageDrawResult::DidNothing; |
132 | |
133 | auto canvas = CustomPaintCanvas::create(*scriptExecutionContext, destSize.width(), destSize.height()); |
134 | ExceptionOr<RefPtr<PaintRenderingContext2D>> contextOrException = canvas->getContext(); |
135 | |
136 | if (contextOrException.hasException()) |
137 | return ImageDrawResult::DidNothing; |
138 | auto context = contextOrException.releaseReturnValue(); |
139 | |
140 | HashMap<String, RefPtr<TypedOMCSSStyleValue>> propertyValues; |
141 | |
142 | if (auto* element = m_element->element()) { |
143 | for (auto& name : m_inputProperties) |
144 | propertyValues.add(name, extractComputedProperty(name, *element)); |
145 | } |
146 | |
147 | auto size = CSSPaintSize::create(destSize.width(), destSize.height()); |
148 | Ref<StylePropertyMapReadOnly> propertyMap = HashMapStylePropertyMap::create(WTFMove(propertyValues)); |
149 | |
150 | auto& vm = *paintConstructor.getObject()->vm(); |
151 | JSC::JSLockHolder lock(vm); |
152 | auto scope = DECLARE_THROW_SCOPE(vm); |
153 | auto& globalObject = *paintConstructor.getObject()->globalObject(); |
154 | |
155 | auto& state = *globalObject.globalExec(); |
156 | JSC::ArgList noArgs; |
157 | JSC::JSValue thisObject = { JSC::construct(&state, paintConstructor, noArgs, "Failed to construct paint class" ) }; |
158 | |
159 | if (UNLIKELY(scope.exception())) { |
160 | reportException(&state, scope.exception()); |
161 | return ImageDrawResult::DidNothing; |
162 | } |
163 | |
164 | auto result = callback.handleEvent(WTFMove(thisObject), *context, size, propertyMap, m_arguments); |
165 | if (result.type() != CallbackResultType::Success) |
166 | return ImageDrawResult::DidNothing; |
167 | |
168 | canvas->replayDisplayList(&destContext); |
169 | |
170 | return ImageDrawResult::DidDraw; |
171 | } |
172 | |
173 | ImageDrawResult CustomPaintImage::draw(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator compositeOp, BlendMode blendMode, DecodingMode, ImageOrientationDescription) |
174 | { |
175 | GraphicsContextStateSaver stateSaver(destContext); |
176 | destContext.setCompositeOperation(compositeOp, blendMode); |
177 | destContext.clip(destRect); |
178 | destContext.translate(destRect.location()); |
179 | if (destRect.size() != srcRect.size()) |
180 | destContext.scale(destRect.size() / srcRect.size()); |
181 | destContext.translate(-srcRect.location()); |
182 | return doCustomPaint(destContext, size()); |
183 | } |
184 | |
185 | void CustomPaintImage::drawPattern(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, const AffineTransform& patternTransform, |
186 | const FloatPoint& phase, const FloatSize& spacing, CompositeOperator compositeOp, BlendMode blendMode) |
187 | { |
188 | // Allow the generator to provide visually-equivalent tiling parameters for better performance. |
189 | FloatSize adjustedSize = size(); |
190 | FloatRect adjustedSrcRect = srcRect; |
191 | |
192 | // Factor in the destination context's scale to generate at the best resolution |
193 | AffineTransform destContextCTM = destContext.getCTM(GraphicsContext::DefinitelyIncludeDeviceScale); |
194 | double xScale = fabs(destContextCTM.xScale()); |
195 | double yScale = fabs(destContextCTM.yScale()); |
196 | AffineTransform adjustedPatternCTM = patternTransform; |
197 | adjustedPatternCTM.scale(1.0 / xScale, 1.0 / yScale); |
198 | adjustedSrcRect.scale(xScale, yScale); |
199 | |
200 | auto buffer = ImageBuffer::createCompatibleBuffer(adjustedSize, ColorSpaceSRGB, destContext); |
201 | if (!buffer) |
202 | return; |
203 | doCustomPaint(buffer->context(), adjustedSize); |
204 | |
205 | if (destContext.drawLuminanceMask()) |
206 | buffer->convertToLuminanceMask(); |
207 | |
208 | buffer->drawPattern(destContext, destRect, adjustedSrcRect, adjustedPatternCTM, phase, spacing, compositeOp, blendMode); |
209 | destContext.setDrawLuminanceMask(false); |
210 | } |
211 | |
212 | } |
213 | #endif |
214 | |