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
26WI.RecordingState = class RecordingState
27{
28 constructor(data, {source} = {})
29 {
30 this._data = data;
31 this._source = source || null;
32 }
33
34 // Static
35
36 static deriveFromContext(type, context, options = {})
37 {
38 if (type === WI.Recording.Type.Canvas2D) {
39 let matrix = context.getTransform();
40
41 let data = {};
42
43 if (WI.ImageUtilities.supportsCanvasPathDebugging()) {
44 data.currentX = context.currentX;
45 data.currentY = context.currentY;
46 }
47
48 data.direction = context.direction;
49 data.fillStyle = context.fillStyle;
50 data.font = context.font;
51 data.globalAlpha = context.globalAlpha;
52 data.globalCompositeOperation = context.globalCompositeOperation;
53 data.imageSmoothingEnabled = context.imageSmoothingEnabled;
54 data.imageSmoothingQuality = context.imageSmoothingQuality;
55 data.lineCap = context.lineCap;
56 data.lineDash = context.getLineDash();
57 data.lineDashOffset = context.lineDashOffset;
58 data.lineJoin = context.lineJoin;
59 data.lineWidth = context.lineWidth;
60 data.miterLimit = context.miterLimit;
61 data.shadowBlur = context.shadowBlur;
62 data.shadowColor = context.shadowColor;
63 data.shadowOffsetX = context.shadowOffsetX;
64 data.shadowOffsetY = context.shadowOffsetY;
65 data.strokeStyle = context.strokeStyle;
66 data.textAlign = context.textAlign;
67 data.textBaseline = context.textBaseline;
68 data.transform = [matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f];
69 data.webkitImageSmoothingEnabled = context.webkitImageSmoothingEnabled;
70 data.webkitLineDash = context.webkitLineDash;
71 data.webkitLineDashOffset = context.webkitLineDashOffset;
72
73 if (WI.ImageUtilities.supportsCanvasPathDebugging())
74 data.setPath = [context.getPath()];
75
76 return new WI.RecordingState(data, options);
77 }
78
79 return null;
80 }
81
82 static async swizzleInitialState(recording, initialState)
83 {
84 if (recording.type === WI.Recording.Type.Canvas2D) {
85 let swizzledState = {};
86
87 for (let [name, value] of Object.entries(initialState)) {
88 // COMPATIBILITY (iOS 12.0): Recording.InitialState.states did not exist yet
89 let nameIndex = parseInt(name);
90 if (!isNaN(nameIndex))
91 name = await recording.swizzle(nameIndex, WI.Recording.Swizzle.String);
92
93 switch (name) {
94 case "setTransform":
95 value = [await recording.swizzle(value, WI.Recording.Swizzle.DOMMatrix)];
96 break;
97
98 case "fillStyle":
99 case "strokeStyle":
100 var [gradient, pattern, string] = await Promise.all([
101 recording.swizzle(value, WI.Recording.Swizzle.CanvasGradient),
102 recording.swizzle(value, WI.Recording.Swizzle.CanvasPattern),
103 recording.swizzle(value, WI.Recording.Swizzle.String),
104 ]);
105 if (gradient && !pattern)
106 value = gradient;
107 else if (pattern && !gradient)
108 value = pattern;
109 else
110 value = string;
111 break;
112
113 case "direction":
114 case "font":
115 case "globalCompositeOperation":
116 case "imageSmoothingQuality":
117 case "lineCap":
118 case "lineJoin":
119 case "shadowColor":
120 case "textAlign":
121 case "textBaseline":
122 value = await recording.swizzle(value, WI.Recording.Swizzle.String);
123 break;
124
125 case "globalAlpha":
126 case "lineWidth":
127 case "miterLimit":
128 case "shadowOffsetX":
129 case "shadowOffsetY":
130 case "shadowBlur":
131 case "lineDashOffset":
132 value = await recording.swizzle(value, WI.Recording.Swizzle.Number);
133 break;
134
135 case "setPath":
136 value = [await recording.swizzle(value[0], WI.Recording.Swizzle.Path2D)];
137 break;
138 }
139
140 if (value === undefined || (Array.isArray(value) && value.includes(undefined)))
141 continue;
142
143 swizzledState[name] = value;
144 }
145
146 return new WI.RecordingState(swizzledState);
147 }
148
149 return null;
150 }
151
152 // Public
153
154 get source() { return this._source; }
155
156 has(name)
157 {
158 return name in this._data;
159 }
160
161 get(name)
162 {
163 return this._data[name];
164 }
165
166 apply(type, context)
167 {
168 for (let [name, value] of this) {
169 if (!(name in context))
170 continue;
171
172 // Skip internal state used for path debugging.
173 if (name === "currentX" || name === "currentY")
174 continue;
175
176 try {
177 if (WI.RecordingAction.isFunctionForType(type, name))
178 context[name](...value);
179 else
180 context[name] = value;
181 } catch { }
182 }
183 }
184
185 toJSON()
186 {
187 return this._data;
188 }
189
190 [Symbol.iterator]()
191 {
192 return Object.entries(this._data)[Symbol.iterator]();
193 }
194};