1 | /* |
2 | * Copyright (C) 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 "DOMMatrixReadOnly.h" |
28 | |
29 | #include "CSSParser.h" |
30 | #include "CSSToLengthConversionData.h" |
31 | #include "DOMMatrix.h" |
32 | #include "DOMPoint.h" |
33 | #include "ScriptExecutionContext.h" |
34 | #include "StyleProperties.h" |
35 | #include "TransformFunctions.h" |
36 | #include <JavaScriptCore/GenericTypedArrayViewInlines.h> |
37 | #include <JavaScriptCore/HeapInlines.h> |
38 | #include <JavaScriptCore/JSGenericTypedArrayViewInlines.h> |
39 | #include <wtf/IsoMallocInlines.h> |
40 | #include <wtf/text/StringBuilder.h> |
41 | |
42 | namespace WebCore { |
43 | |
44 | WTF_MAKE_ISO_ALLOCATED_IMPL(DOMMatrixReadOnly); |
45 | |
46 | // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-dommatrixreadonly |
47 | ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::create(ScriptExecutionContext& scriptExecutionContext, Optional<Variant<String, Vector<double>>>&& init) |
48 | { |
49 | if (!init) |
50 | return adoptRef(*new DOMMatrixReadOnly); |
51 | |
52 | return WTF::switchOn(init.value(), |
53 | [&scriptExecutionContext](const String& init) -> ExceptionOr<Ref<DOMMatrixReadOnly>> { |
54 | if (!scriptExecutionContext.isDocument()) |
55 | return Exception { TypeError }; |
56 | |
57 | auto parseResult = parseStringIntoAbstractMatrix(init); |
58 | if (parseResult.hasException()) |
59 | return parseResult.releaseException(); |
60 | |
61 | return adoptRef(*new DOMMatrixReadOnly(parseResult.returnValue().matrix, parseResult.returnValue().is2D ? Is2D::Yes : Is2D::No)); |
62 | }, |
63 | [](const Vector<double>& init) -> ExceptionOr<Ref<DOMMatrixReadOnly>> { |
64 | if (init.size() == 6) { |
65 | return adoptRef(*new DOMMatrixReadOnly(TransformationMatrix { |
66 | init[0], init[1], init[2], init[3], init[4], init[5] |
67 | }, Is2D::Yes)); |
68 | } |
69 | if (init.size() == 16) { |
70 | return adoptRef(*new DOMMatrixReadOnly(TransformationMatrix { |
71 | init[0], init[1], init[2], init[3], |
72 | init[4], init[5], init[6], init[7], |
73 | init[8], init[9], init[10], init[11], |
74 | init[12], init[13], init[14], init[15] |
75 | }, Is2D::No)); |
76 | } |
77 | return Exception { TypeError }; |
78 | } |
79 | ); |
80 | } |
81 | |
82 | DOMMatrixReadOnly::DOMMatrixReadOnly(const TransformationMatrix& matrix, Is2D is2D) |
83 | : m_matrix(matrix) |
84 | , m_is2D(is2D == Is2D::Yes) |
85 | { |
86 | ASSERT(!m_is2D || m_matrix.isAffine()); |
87 | } |
88 | |
89 | DOMMatrixReadOnly::DOMMatrixReadOnly(TransformationMatrix&& matrix, Is2D is2D) |
90 | : m_matrix(WTFMove(matrix)) |
91 | , m_is2D(is2D == Is2D::Yes) |
92 | { |
93 | ASSERT(!m_is2D || m_matrix.isAffine()); |
94 | } |
95 | |
96 | inline Ref<DOMMatrix> DOMMatrixReadOnly::cloneAsDOMMatrix() const |
97 | { |
98 | return DOMMatrix::create(m_matrix, m_is2D ? Is2D::Yes : Is2D::No); |
99 | } |
100 | |
101 | // https://tc39.github.io/ecma262/#sec-samevaluezero |
102 | static bool sameValueZero(double a, double b) |
103 | { |
104 | if (std::isnan(a) && std::isnan(b)) |
105 | return true; |
106 | return a == b; |
107 | } |
108 | |
109 | // https://drafts.fxtf.org/geometry/#matrix-validate-and-fixup |
110 | ExceptionOr<void> DOMMatrixReadOnly::validateAndFixup(DOMMatrix2DInit& init) |
111 | { |
112 | if (init.a && init.m11 && !sameValueZero(init.a.value(), init.m11.value())) |
113 | return Exception { TypeError, "init.a and init.m11 do not match"_s }; |
114 | if (init.b && init.m12 && !sameValueZero(init.b.value(), init.m12.value())) |
115 | return Exception { TypeError, "init.b and init.m12 do not match"_s }; |
116 | if (init.c && init.m21 && !sameValueZero(init.c.value(), init.m21.value())) |
117 | return Exception { TypeError, "init.c and init.m21 do not match"_s }; |
118 | if (init.d && init.m22 && !sameValueZero(init.d.value(), init.m22.value())) |
119 | return Exception { TypeError, "init.d and init.m22 do not match"_s }; |
120 | if (init.e && init.m41 && !sameValueZero(init.e.value(), init.m41.value())) |
121 | return Exception { TypeError, "init.e and init.m41 do not match"_s }; |
122 | if (init.f && init.m42 && !sameValueZero(init.f.value(), init.m42.value())) |
123 | return Exception { TypeError, "init.f and init.m42 do not match"_s }; |
124 | |
125 | if (!init.m11) |
126 | init.m11 = init.a.valueOr(1); |
127 | if (!init.m12) |
128 | init.m12 = init.b.valueOr(0); |
129 | if (!init.m21) |
130 | init.m21 = init.c.valueOr(0); |
131 | if (!init.m22) |
132 | init.m22 = init.d.valueOr(1); |
133 | if (!init.m41) |
134 | init.m41 = init.e.valueOr(0); |
135 | if (!init.m42) |
136 | init.m42 = init.f.valueOr(0); |
137 | |
138 | return { }; |
139 | } |
140 | |
141 | ExceptionOr<void> DOMMatrixReadOnly::validateAndFixup(DOMMatrixInit& init) |
142 | { |
143 | auto validate2D = validateAndFixup(static_cast<DOMMatrix2DInit&>(init)); |
144 | if (validate2D.hasException()) |
145 | return validate2D.releaseException(); |
146 | |
147 | if (init.is2D && init.is2D.value()) { |
148 | if (init.m13) |
149 | return Exception { TypeError, "m13 should be 0 for a 2D matrix"_s }; |
150 | if (init.m14) |
151 | return Exception { TypeError, "m14 should be 0 for a 2D matrix"_s }; |
152 | if (init.m23) |
153 | return Exception { TypeError, "m23 should be 0 for a 2D matrix"_s }; |
154 | if (init.m24) |
155 | return Exception { TypeError, "m24 should be 0 for a 2D matrix"_s }; |
156 | if (init.m31) |
157 | return Exception { TypeError, "m31 should be 0 for a 2D matrix"_s }; |
158 | if (init.m32) |
159 | return Exception { TypeError, "m32 should be 0 for a 2D matrix"_s }; |
160 | if (init.m34) |
161 | return Exception { TypeError, "m34 should be 0 for a 2D matrix"_s }; |
162 | if (init.m43) |
163 | return Exception { TypeError, "m43 should be 0 for a 2D matrix"_s }; |
164 | if (init.m33 != 1) |
165 | return Exception { TypeError, "m33 should be 1 for a 2D matrix"_s }; |
166 | if (init.m44 != 1) |
167 | return Exception { TypeError, "m44 should be 1 for a 2D matrix"_s }; |
168 | } |
169 | |
170 | if (!init.is2D) { |
171 | if (init.m13 || init.m14 || init.m23 || init.m24 || init.m31 || init.m32 || init.m34 || init.m43 || init.m33 != 1 || init.m44 != 1) |
172 | init.is2D = false; |
173 | else |
174 | init.is2D = true; |
175 | } |
176 | return { }; |
177 | } |
178 | |
179 | ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromMatrix(DOMMatrixInit&& init) |
180 | { |
181 | return fromMatrixHelper<DOMMatrixReadOnly>(WTFMove(init)); |
182 | } |
183 | |
184 | ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromFloat32Array(Ref<Float32Array>&& array32) |
185 | { |
186 | if (array32->length() == 6) |
187 | return DOMMatrixReadOnly::create(TransformationMatrix(array32->item(0), array32->item(1), array32->item(2), array32->item(3), array32->item(4), array32->item(5)), Is2D::Yes); |
188 | |
189 | if (array32->length() == 16) { |
190 | return DOMMatrixReadOnly::create(TransformationMatrix( |
191 | array32->item(0), array32->item(1), array32->item(2), array32->item(3), |
192 | array32->item(4), array32->item(5), array32->item(6), array32->item(7), |
193 | array32->item(8), array32->item(9), array32->item(10), array32->item(11), |
194 | array32->item(12), array32->item(13), array32->item(14), array32->item(15) |
195 | ), Is2D::No); |
196 | } |
197 | |
198 | return Exception { TypeError }; |
199 | } |
200 | |
201 | ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromFloat64Array(Ref<Float64Array>&& array64) |
202 | { |
203 | if (array64->length() == 6) |
204 | return DOMMatrixReadOnly::create(TransformationMatrix(array64->item(0), array64->item(1), array64->item(2), array64->item(3), array64->item(4), array64->item(5)), Is2D::Yes); |
205 | |
206 | if (array64->length() == 16) { |
207 | return DOMMatrixReadOnly::create(TransformationMatrix( |
208 | array64->item(0), array64->item(1), array64->item(2), array64->item(3), |
209 | array64->item(4), array64->item(5), array64->item(6), array64->item(7), |
210 | array64->item(8), array64->item(9), array64->item(10), array64->item(11), |
211 | array64->item(12), array64->item(13), array64->item(14), array64->item(15) |
212 | ), Is2D::No); |
213 | } |
214 | |
215 | return Exception { TypeError }; |
216 | } |
217 | |
218 | bool DOMMatrixReadOnly::isIdentity() const |
219 | { |
220 | return m_matrix.isIdentity(); |
221 | } |
222 | |
223 | ExceptionOr<DOMMatrixReadOnly::AbstractMatrix> DOMMatrixReadOnly::parseStringIntoAbstractMatrix(const String& string) |
224 | { |
225 | if (string.isEmpty()) |
226 | return AbstractMatrix { }; |
227 | |
228 | auto styleDeclaration = MutableStyleProperties::create(); |
229 | if (CSSParser::parseValue(styleDeclaration, CSSPropertyTransform, string, true, HTMLStandardMode) == CSSParser::ParseResult::Error) |
230 | return Exception { SyntaxError }; |
231 | |
232 | // Convert to TransformOperations. This can fail if a property requires style (i.e., param uses 'ems' or 'exs') |
233 | auto value = styleDeclaration->getPropertyCSSValue(CSSPropertyTransform); |
234 | |
235 | // Check for a "none" or empty transform. In these cases we can use the default identity matrix. |
236 | if (!value || (is<CSSPrimitiveValue>(*value) && downcast<CSSPrimitiveValue>(*value).valueID() == CSSValueNone)) |
237 | return AbstractMatrix { }; |
238 | |
239 | TransformOperations operations; |
240 | if (!transformsForValue(*value, CSSToLengthConversionData(), operations)) |
241 | return Exception { SyntaxError }; |
242 | |
243 | AbstractMatrix matrix; |
244 | for (auto& operation : operations.operations()) { |
245 | if (operation->apply(matrix.matrix, { 0, 0 })) |
246 | return Exception { SyntaxError }; |
247 | if (operation->is3DOperation()) |
248 | matrix.is2D = false; |
249 | } |
250 | |
251 | return matrix; |
252 | } |
253 | |
254 | // https://drafts.fxtf.org/geometry/#dom-dommatrix-setmatrixvalue |
255 | ExceptionOr<void> DOMMatrixReadOnly::setMatrixValue(const String& string) |
256 | { |
257 | auto parseResult = parseStringIntoAbstractMatrix(string); |
258 | if (parseResult.hasException()) |
259 | return parseResult.releaseException(); |
260 | |
261 | m_is2D = parseResult.returnValue().is2D; |
262 | m_matrix = parseResult.returnValue().matrix; |
263 | return { }; |
264 | } |
265 | |
266 | Ref<DOMMatrix> DOMMatrixReadOnly::translate(double tx, double ty, double tz) |
267 | { |
268 | auto matrix = cloneAsDOMMatrix(); |
269 | return matrix->translateSelf(tx, ty, tz); |
270 | } |
271 | |
272 | // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-flipx |
273 | Ref<DOMMatrix> DOMMatrixReadOnly::flipX() |
274 | { |
275 | auto matrix = cloneAsDOMMatrix(); |
276 | matrix->m_matrix.flipX(); |
277 | return matrix; |
278 | } |
279 | |
280 | // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-flipy |
281 | Ref<DOMMatrix> DOMMatrixReadOnly::flipY() |
282 | { |
283 | auto matrix = cloneAsDOMMatrix(); |
284 | matrix->m_matrix.flipY(); |
285 | return matrix; |
286 | } |
287 | |
288 | ExceptionOr<Ref<DOMMatrix>> DOMMatrixReadOnly::multiply(DOMMatrixInit&& other) const |
289 | { |
290 | auto matrix = cloneAsDOMMatrix(); |
291 | return matrix->multiplySelf(WTFMove(other)); |
292 | } |
293 | |
294 | Ref<DOMMatrix> DOMMatrixReadOnly::scale(double scaleX, Optional<double> scaleY, double scaleZ, double originX, double originY, double originZ) |
295 | { |
296 | auto matrix = cloneAsDOMMatrix(); |
297 | return matrix->scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ); |
298 | } |
299 | |
300 | Ref<DOMMatrix> DOMMatrixReadOnly::scale3d(double scale, double originX, double originY, double originZ) |
301 | { |
302 | auto matrix = cloneAsDOMMatrix(); |
303 | return matrix->scale3dSelf(scale, originX, originY, originZ); |
304 | } |
305 | |
306 | Ref<DOMMatrix> DOMMatrixReadOnly::rotate(double rotX, Optional<double> rotY, Optional<double> rotZ) |
307 | { |
308 | auto matrix = cloneAsDOMMatrix(); |
309 | return matrix->rotateSelf(rotX, rotY, rotZ); |
310 | } |
311 | |
312 | Ref<DOMMatrix> DOMMatrixReadOnly::rotateFromVector(double x, double y) |
313 | { |
314 | auto matrix = cloneAsDOMMatrix(); |
315 | return matrix->rotateFromVectorSelf(x, y); |
316 | } |
317 | |
318 | Ref<DOMMatrix> DOMMatrixReadOnly::rotateAxisAngle(double x, double y, double z, double angle) |
319 | { |
320 | auto matrix = cloneAsDOMMatrix(); |
321 | return matrix->rotateAxisAngleSelf(x, y, z, angle); |
322 | } |
323 | |
324 | Ref<DOMMatrix> DOMMatrixReadOnly::skewX(double sx) |
325 | { |
326 | auto matrix = cloneAsDOMMatrix(); |
327 | return matrix->skewXSelf(sx); |
328 | } |
329 | |
330 | Ref<DOMMatrix> DOMMatrixReadOnly::skewY(double sy) |
331 | { |
332 | auto matrix = cloneAsDOMMatrix(); |
333 | return matrix->skewYSelf(sy); |
334 | } |
335 | |
336 | Ref<DOMMatrix> DOMMatrixReadOnly::inverse() const |
337 | { |
338 | auto matrix = cloneAsDOMMatrix(); |
339 | return matrix->invertSelf(); |
340 | } |
341 | |
342 | // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-transformpoint |
343 | Ref<DOMPoint> DOMMatrixReadOnly::transformPoint(DOMPointInit&& pointInit) |
344 | { |
345 | m_matrix.map4ComponentPoint(pointInit.x, pointInit.y, pointInit.z, pointInit.w); |
346 | return DOMPoint::create(pointInit.x, pointInit.y, pointInit.z, pointInit.w); |
347 | } |
348 | |
349 | ExceptionOr<Ref<Float32Array>> DOMMatrixReadOnly::toFloat32Array() const |
350 | { |
351 | auto array32 = Float32Array::tryCreateUninitialized(16); |
352 | if (!array32) |
353 | return Exception { UnknownError, "Out of memory"_s }; |
354 | |
355 | unsigned index = 0; |
356 | array32->set(index++, m_matrix.m11()); |
357 | array32->set(index++, m_matrix.m12()); |
358 | array32->set(index++, m_matrix.m13()); |
359 | array32->set(index++, m_matrix.m14()); |
360 | array32->set(index++, m_matrix.m21()); |
361 | array32->set(index++, m_matrix.m22()); |
362 | array32->set(index++, m_matrix.m23()); |
363 | array32->set(index++, m_matrix.m24()); |
364 | array32->set(index++, m_matrix.m31()); |
365 | array32->set(index++, m_matrix.m32()); |
366 | array32->set(index++, m_matrix.m33()); |
367 | array32->set(index++, m_matrix.m34()); |
368 | array32->set(index++, m_matrix.m41()); |
369 | array32->set(index++, m_matrix.m42()); |
370 | array32->set(index++, m_matrix.m43()); |
371 | array32->set(index, m_matrix.m44()); |
372 | return array32.releaseNonNull(); |
373 | } |
374 | |
375 | ExceptionOr<Ref<Float64Array>> DOMMatrixReadOnly::toFloat64Array() const |
376 | { |
377 | auto array64 = Float64Array::tryCreateUninitialized(16); |
378 | if (!array64) |
379 | return Exception { UnknownError, "Out of memory"_s }; |
380 | |
381 | unsigned index = 0; |
382 | array64->set(index++, m_matrix.m11()); |
383 | array64->set(index++, m_matrix.m12()); |
384 | array64->set(index++, m_matrix.m13()); |
385 | array64->set(index++, m_matrix.m14()); |
386 | array64->set(index++, m_matrix.m21()); |
387 | array64->set(index++, m_matrix.m22()); |
388 | array64->set(index++, m_matrix.m23()); |
389 | array64->set(index++, m_matrix.m24()); |
390 | array64->set(index++, m_matrix.m31()); |
391 | array64->set(index++, m_matrix.m32()); |
392 | array64->set(index++, m_matrix.m33()); |
393 | array64->set(index++, m_matrix.m34()); |
394 | array64->set(index++, m_matrix.m41()); |
395 | array64->set(index++, m_matrix.m42()); |
396 | array64->set(index++, m_matrix.m43()); |
397 | array64->set(index, m_matrix.m44()); |
398 | return array64.releaseNonNull(); |
399 | } |
400 | |
401 | // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-stringifier |
402 | ExceptionOr<String> DOMMatrixReadOnly::toString() const |
403 | { |
404 | if (!m_matrix.containsOnlyFiniteValues()) |
405 | return Exception { InvalidStateError, "Matrix contains non-finite values"_s }; |
406 | |
407 | StringBuilder builder; |
408 | if (is2D()) { |
409 | builder.appendLiteral("matrix(" ); |
410 | builder.appendECMAScriptNumber(m_matrix.a()); |
411 | builder.appendLiteral(", " ); |
412 | builder.appendECMAScriptNumber(m_matrix.b()); |
413 | builder.appendLiteral(", " ); |
414 | builder.appendECMAScriptNumber(m_matrix.c()); |
415 | builder.appendLiteral(", " ); |
416 | builder.appendECMAScriptNumber(m_matrix.d()); |
417 | builder.appendLiteral(", " ); |
418 | builder.appendECMAScriptNumber(m_matrix.e()); |
419 | builder.appendLiteral(", " ); |
420 | builder.appendECMAScriptNumber(m_matrix.f()); |
421 | } else { |
422 | builder.appendLiteral("matrix3d(" ); |
423 | builder.appendECMAScriptNumber(m_matrix.m11()); |
424 | builder.appendLiteral(", " ); |
425 | builder.appendECMAScriptNumber(m_matrix.m12()); |
426 | builder.appendLiteral(", " ); |
427 | builder.appendECMAScriptNumber(m_matrix.m13()); |
428 | builder.appendLiteral(", " ); |
429 | builder.appendECMAScriptNumber(m_matrix.m14()); |
430 | builder.appendLiteral(", " ); |
431 | builder.appendECMAScriptNumber(m_matrix.m21()); |
432 | builder.appendLiteral(", " ); |
433 | builder.appendECMAScriptNumber(m_matrix.m22()); |
434 | builder.appendLiteral(", " ); |
435 | builder.appendECMAScriptNumber(m_matrix.m23()); |
436 | builder.appendLiteral(", " ); |
437 | builder.appendECMAScriptNumber(m_matrix.m24()); |
438 | builder.appendLiteral(", " ); |
439 | builder.appendECMAScriptNumber(m_matrix.m31()); |
440 | builder.appendLiteral(", " ); |
441 | builder.appendECMAScriptNumber(m_matrix.m32()); |
442 | builder.appendLiteral(", " ); |
443 | builder.appendECMAScriptNumber(m_matrix.m33()); |
444 | builder.appendLiteral(", " ); |
445 | builder.appendECMAScriptNumber(m_matrix.m34()); |
446 | builder.appendLiteral(", " ); |
447 | builder.appendECMAScriptNumber(m_matrix.m41()); |
448 | builder.appendLiteral(", " ); |
449 | builder.appendECMAScriptNumber(m_matrix.m42()); |
450 | builder.appendLiteral(", " ); |
451 | builder.appendECMAScriptNumber(m_matrix.m43()); |
452 | builder.appendLiteral(", " ); |
453 | builder.appendECMAScriptNumber(m_matrix.m44()); |
454 | } |
455 | builder.append(')'); |
456 | return builder.toString(); |
457 | } |
458 | |
459 | } // namespace WebCore |
460 | |