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
42namespace WebCore {
43
44WTF_MAKE_ISO_ALLOCATED_IMPL(DOMMatrixReadOnly);
45
46// https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-dommatrixreadonly
47ExceptionOr<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
82DOMMatrixReadOnly::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
89DOMMatrixReadOnly::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
96inline 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
102static 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
110ExceptionOr<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
141ExceptionOr<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
179ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromMatrix(DOMMatrixInit&& init)
180{
181 return fromMatrixHelper<DOMMatrixReadOnly>(WTFMove(init));
182}
183
184ExceptionOr<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
201ExceptionOr<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
218bool DOMMatrixReadOnly::isIdentity() const
219{
220 return m_matrix.isIdentity();
221}
222
223ExceptionOr<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
255ExceptionOr<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
266Ref<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
273Ref<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
281Ref<DOMMatrix> DOMMatrixReadOnly::flipY()
282{
283 auto matrix = cloneAsDOMMatrix();
284 matrix->m_matrix.flipY();
285 return matrix;
286}
287
288ExceptionOr<Ref<DOMMatrix>> DOMMatrixReadOnly::multiply(DOMMatrixInit&& other) const
289{
290 auto matrix = cloneAsDOMMatrix();
291 return matrix->multiplySelf(WTFMove(other));
292}
293
294Ref<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
300Ref<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
306Ref<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
312Ref<DOMMatrix> DOMMatrixReadOnly::rotateFromVector(double x, double y)
313{
314 auto matrix = cloneAsDOMMatrix();
315 return matrix->rotateFromVectorSelf(x, y);
316}
317
318Ref<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
324Ref<DOMMatrix> DOMMatrixReadOnly::skewX(double sx)
325{
326 auto matrix = cloneAsDOMMatrix();
327 return matrix->skewXSelf(sx);
328}
329
330Ref<DOMMatrix> DOMMatrixReadOnly::skewY(double sy)
331{
332 auto matrix = cloneAsDOMMatrix();
333 return matrix->skewYSelf(sy);
334}
335
336Ref<DOMMatrix> DOMMatrixReadOnly::inverse() const
337{
338 auto matrix = cloneAsDOMMatrix();
339 return matrix->invertSelf();
340}
341
342// https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-transformpoint
343Ref<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
349ExceptionOr<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
375ExceptionOr<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
402ExceptionOr<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