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