@swf2es/player-hosts 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/thread/player/display/bounds.js +105 -0
- package/dist/thread/player/display/drawing.js +25 -10
- package/dist/thread/player/display/shapes.js +72 -54
- package/dist/thread/player/display/textobjects.js +34 -1
- package/dist/thread/player/player.js +4 -3
- package/dist/thread/player/playerglobal/flash/display/DisplayObject.js +2 -2
- package/dist/thread/player/playerglobal/flash/net/NetConnection.js +150 -5
- package/dist/thread/player/playerglobal/flash/net/local-values.js +133 -67
- package/dist/thread/player/playerglobal/flash/net/remoting.js +162 -0
- package/dist/thread/player/scripting/lifecycle.js +39 -19
- package/dist/thread/player/scripting.js +4 -3
- package/package.json +2 -2
|
@@ -25,6 +25,7 @@ export function ownBounds(d, lines) {
|
|
|
25
25
|
: null;
|
|
26
26
|
}
|
|
27
27
|
if (d instanceof TextObject) {
|
|
28
|
+
d.settle();
|
|
28
29
|
return { xMin: d.left, yMin: d.top, xMax: d.left + d.width, yMax: d.top + d.height };
|
|
29
30
|
}
|
|
30
31
|
if (d instanceof VideoObject) {
|
|
@@ -95,12 +96,116 @@ export function placedRect(d, r, drawn = false) {
|
|
|
95
96
|
const box = transformRect(r, own);
|
|
96
97
|
return back ? boxThrough(box, back) : box;
|
|
97
98
|
}
|
|
99
|
+
// Scratch matrices for boundsIn's flat case: getRect and getBounds are asked of every moved
|
|
100
|
+
// sprite, every frame, by some movies, and these leave nothing behind for the collector.
|
|
101
|
+
const scratchUp = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
102
|
+
const scratchDown = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
103
|
+
const scratchBoth = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
104
|
+
/** `m` set to `p`'s six numbers. */
|
|
105
|
+
function copyInto(m, p) {
|
|
106
|
+
m.a = p.a;
|
|
107
|
+
m.b = p.b;
|
|
108
|
+
m.c = p.c;
|
|
109
|
+
m.d = p.d;
|
|
110
|
+
m.tx = p.tx;
|
|
111
|
+
m.ty = p.ty;
|
|
112
|
+
}
|
|
113
|
+
/** `m` then `p` into `out`, as concat multiplies two flat matrices; `out` may be `m`. */
|
|
114
|
+
function concatInto(out, m, p) {
|
|
115
|
+
const a = m.a * p.a + m.b * p.c;
|
|
116
|
+
const b = m.a * p.b + m.b * p.d;
|
|
117
|
+
const c = m.c * p.a + m.d * p.c;
|
|
118
|
+
const d = m.c * p.b + m.d * p.d;
|
|
119
|
+
const tx = m.tx * p.a + m.ty * p.c + p.tx;
|
|
120
|
+
const ty = m.tx * p.b + m.ty * p.d + p.ty;
|
|
121
|
+
out.a = a;
|
|
122
|
+
out.b = b;
|
|
123
|
+
out.c = c;
|
|
124
|
+
out.d = d;
|
|
125
|
+
out.tx = tx;
|
|
126
|
+
out.ty = ty;
|
|
127
|
+
}
|
|
128
|
+
// `placed` of one with a scrollRect, shifted as geometry's shifted shifts it, without a new matrix.
|
|
129
|
+
const scratchPlaced = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
130
|
+
/** `d.placed` if flat, into scratchPlaced where a scroll shifts it; null for a projective one. */
|
|
131
|
+
function flatPlaced(d) {
|
|
132
|
+
if (d.space) {
|
|
133
|
+
const m = d.placed;
|
|
134
|
+
return isProjective(m) ? null : m;
|
|
135
|
+
}
|
|
136
|
+
const m = d.matrix;
|
|
137
|
+
if (isProjective(m)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const r = d.scroll;
|
|
141
|
+
if (!r) {
|
|
142
|
+
return m;
|
|
143
|
+
}
|
|
144
|
+
copyInto(scratchPlaced, m);
|
|
145
|
+
scratchPlaced.tx = m.tx - m.a * r.xMin - m.c * r.yMin;
|
|
146
|
+
scratchPlaced.ty = m.ty - m.b * r.xMin - m.d * r.yMin;
|
|
147
|
+
return scratchPlaced;
|
|
148
|
+
}
|
|
149
|
+
/** toStage into `out`, if every matrix on the way is flat; false, `out` spoilt, otherwise. */
|
|
150
|
+
function flatToStage(d, stage, out) {
|
|
151
|
+
const own = flatPlaced(d);
|
|
152
|
+
if (!own) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
copyInto(out, own);
|
|
156
|
+
for (let p = d.parent; p && p !== stage; p = p.parent) {
|
|
157
|
+
const m = flatPlaced(p);
|
|
158
|
+
if (!m) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
concatInto(out, out, m);
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
/** `m` inverted in place, as invert does; left as it is, as invert's null is, when flat. */
|
|
166
|
+
function invertFlat(m) {
|
|
167
|
+
const det = m.a * m.d - m.b * m.c;
|
|
168
|
+
if (det === 0) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const a = m.d / det;
|
|
172
|
+
const b = -m.b / det;
|
|
173
|
+
const c = -m.c / det;
|
|
174
|
+
const d = m.a / det;
|
|
175
|
+
const tx = (m.c * m.ty - m.d * m.tx) / det;
|
|
176
|
+
const ty = (m.b * m.tx - m.a * m.ty) / det;
|
|
177
|
+
m.a = a;
|
|
178
|
+
m.b = b;
|
|
179
|
+
m.c = c;
|
|
180
|
+
m.d = d;
|
|
181
|
+
m.tx = tx;
|
|
182
|
+
m.ty = ty;
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
98
185
|
/** `d`'s bounds in `target`'s space (its own for null), through the stage. */
|
|
99
186
|
export function boundsIn(d, target, stage, lines) {
|
|
100
187
|
const own = bounds(d, lines) ?? { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
|
|
101
188
|
if (!target || target === d) {
|
|
102
189
|
return own;
|
|
103
190
|
}
|
|
191
|
+
// Flat all the way, the common case, the same arithmetic as below's in scratch matrices.
|
|
192
|
+
if (flatToStage(d, stage, scratchUp)) {
|
|
193
|
+
if (target === stage) {
|
|
194
|
+
copyInto(scratchDown, IDENTITY);
|
|
195
|
+
}
|
|
196
|
+
else if (!flatToStage(target, stage, scratchDown)) {
|
|
197
|
+
return boundsInThrough(d, target, stage, own);
|
|
198
|
+
}
|
|
199
|
+
else if (!invertFlat(scratchDown)) {
|
|
200
|
+
copyInto(scratchDown, IDENTITY);
|
|
201
|
+
}
|
|
202
|
+
concatInto(scratchBoth, scratchUp, scratchDown);
|
|
203
|
+
return transformRect(own, scratchBoth);
|
|
204
|
+
}
|
|
205
|
+
return boundsInThrough(d, target, stage, own);
|
|
206
|
+
}
|
|
207
|
+
/** boundsIn where a plane is on the way: up or down through a projective matrix. */
|
|
208
|
+
function boundsInThrough(d, target, stage, own) {
|
|
104
209
|
const up = toStage(d, stage);
|
|
105
210
|
const down = target === stage ? IDENTITY : (invert(toStage(target, stage)) ?? IDENTITY);
|
|
106
211
|
if (!isProjective(up)) {
|
|
@@ -20,6 +20,13 @@ function fillsOnly(layer) {
|
|
|
20
20
|
const LINE_POINTS = [0, 0];
|
|
21
21
|
const CURVE_POINTS = [0, 0, 0, 0];
|
|
22
22
|
const CUBIC_POINTS = [0, 0, 0, 0, 0, 0];
|
|
23
|
+
// Each quarter's start and end directions, (sx, sy) and (ex, ey), for arc.
|
|
24
|
+
const QUARTERS = [
|
|
25
|
+
[1, 0, 0, 1],
|
|
26
|
+
[0, 1, -1, 0],
|
|
27
|
+
[-1, 0, 0, -1],
|
|
28
|
+
[0, -1, 1, 0],
|
|
29
|
+
];
|
|
23
30
|
/** `kind` and `points` onto the last of `paths`. */
|
|
24
31
|
function append(paths, kind, points) {
|
|
25
32
|
const path = paths[paths.length - 1];
|
|
@@ -40,8 +47,13 @@ export class Drawing {
|
|
|
40
47
|
/** Where the open fill's entries begin in its layer, which fills before it may share. */
|
|
41
48
|
fillStart = 0;
|
|
42
49
|
stroke = null;
|
|
43
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* The stroke whose last closing line closes the open contour, or the one
|
|
52
|
+
* endFill closed, while a line drawn on may take it away.
|
|
53
|
+
*/
|
|
44
54
|
closing = null;
|
|
55
|
+
/** The closing line last taken away, for the next: a filled, lined pen makes one a segment. */
|
|
56
|
+
spareClose = null;
|
|
45
57
|
/**
|
|
46
58
|
* A point of the open contour off its start, for whether it leaves the
|
|
47
59
|
* line through them: its offset from the start, while `heading`.
|
|
@@ -205,12 +217,11 @@ export class Drawing {
|
|
|
205
217
|
if (rx === 0 && ry === 0) {
|
|
206
218
|
return;
|
|
207
219
|
}
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
][quarter];
|
|
220
|
+
const turn = QUARTERS[quarter];
|
|
221
|
+
const sx = turn[0];
|
|
222
|
+
const sy = turn[1];
|
|
223
|
+
const ex = turn[2];
|
|
224
|
+
const ey = turn[3];
|
|
214
225
|
this.curveTo(cx + (sx + ex * TAN_22_5) * rx, cy + (sy + ey * TAN_22_5) * ry, cx + (sx + ex) * COS_45 * rx, cy + (sy + ey) * COS_45 * ry);
|
|
215
226
|
this.curveTo(cx + (ex + sx * TAN_22_5) * rx, cy + (ey + sy * TAN_22_5) * ry, cx + ex * rx, cy + ey * ry);
|
|
216
227
|
}
|
|
@@ -591,7 +602,7 @@ export class Drawing {
|
|
|
591
602
|
unclose() {
|
|
592
603
|
if (this.closing) {
|
|
593
604
|
this.version++;
|
|
594
|
-
this.closing.
|
|
605
|
+
this.spareClose = this.closing.closes?.pop() ?? null;
|
|
595
606
|
this.closing = null;
|
|
596
607
|
}
|
|
597
608
|
}
|
|
@@ -603,10 +614,14 @@ export class Drawing {
|
|
|
603
614
|
return;
|
|
604
615
|
}
|
|
605
616
|
if (contour[1] !== this.x || contour[2] !== this.y) {
|
|
606
|
-
const close = { at:
|
|
617
|
+
const close = this.spareClose ?? { at: 0, x: 0, y: 0 };
|
|
618
|
+
this.spareClose = null;
|
|
619
|
+
close.at = stroke.paths.length - 1;
|
|
620
|
+
close.x = contour[1];
|
|
621
|
+
close.y = contour[2];
|
|
607
622
|
stroke.closes = stroke.closes ?? [];
|
|
608
623
|
stroke.closes.push(close);
|
|
609
|
-
this.closing =
|
|
624
|
+
this.closing = stroke;
|
|
610
625
|
this.version++;
|
|
611
626
|
}
|
|
612
627
|
}
|
|
@@ -58,21 +58,60 @@ export function inside(points, px, py) {
|
|
|
58
58
|
}
|
|
59
59
|
return hit;
|
|
60
60
|
}
|
|
61
|
+
// The running box of extent: one for every call, as bounds are asked of every drawn shape.
|
|
62
|
+
const box = new Float64Array(4);
|
|
63
|
+
function take(x, y) {
|
|
64
|
+
box[0] = Math.min(box[0], x);
|
|
65
|
+
box[1] = Math.min(box[1], y);
|
|
66
|
+
box[2] = Math.max(box[2], x);
|
|
67
|
+
box[3] = Math.max(box[3], y);
|
|
68
|
+
}
|
|
69
|
+
/** The quadratic curve from (x, y) through (cx, cy) to (ax, ay), at `t`, into the box. */
|
|
70
|
+
function takeCurveAt(t, x, y, cx, cy, ax, ay) {
|
|
71
|
+
const u = 1 - t;
|
|
72
|
+
take(u * u * x + 2 * u * t * cx + t * t * ax, u * u * y + 2 * u * t * cy + t * t * ay);
|
|
73
|
+
}
|
|
74
|
+
/** The cubic curve from (x, y) by (c1x, c1y) and (c2x, c2y) to (ax, ay), at `t`, into the box. */
|
|
75
|
+
function takeCubicAt(t, x, y, c1x, c1y, c2x, c2y, ax, ay) {
|
|
76
|
+
if (!(t > 0 && t < 1)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const u = 1 - t;
|
|
80
|
+
take(u * u * u * x + 3 * u * u * t * c1x + 3 * u * t * t * c2x + t * t * t * ax, u * u * u * y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * ay);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Where the cubic's derivative on one axis, from p0 by c1 and c2 to p3, is
|
|
84
|
+
* zero within it: the roots of a t² + b t + c, each into the box.
|
|
85
|
+
*/
|
|
86
|
+
function takeCubicTurns(p0, c1, c2, p3, x, y, c1x, c1y, c2x, c2y, ax, ay) {
|
|
87
|
+
const a = -p0 + 3 * c1 - 3 * c2 + p3;
|
|
88
|
+
const b = 2 * (p0 - 2 * c1 + c2);
|
|
89
|
+
const c = c1 - p0;
|
|
90
|
+
if (a === 0) {
|
|
91
|
+
if (b !== 0) {
|
|
92
|
+
takeCubicAt(-c / b, x, y, c1x, c1y, c2x, c2y, ax, ay);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const d = b * b - 4 * a * c;
|
|
97
|
+
if (d < 0) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const s = Math.sqrt(d);
|
|
101
|
+
takeCubicAt((-b + s) / (2 * a), x, y, c1x, c1y, c2x, c2y, ax, ay);
|
|
102
|
+
takeCubicAt((-b - s) / (2 * a), x, y, c1x, c1y, c2x, c2y, ax, ay);
|
|
103
|
+
}
|
|
61
104
|
/**
|
|
62
105
|
* The path's extent, each curve at its true extremes, as [xMin, yMin,
|
|
63
|
-
* xMax, yMax]; null for a path with no points.
|
|
106
|
+
* xMax, yMax]; null for a path with no points. It allocates only its
|
|
107
|
+
* answer: the bounds of every shape drawn, a pen's redrawn each frame
|
|
108
|
+
* among them, are asked again and again.
|
|
64
109
|
*/
|
|
65
110
|
export function extent(path) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const take = (x, y) => {
|
|
71
|
-
xMin = Math.min(xMin, x);
|
|
72
|
-
yMin = Math.min(yMin, y);
|
|
73
|
-
xMax = Math.max(xMax, x);
|
|
74
|
-
yMax = Math.max(yMax, y);
|
|
75
|
-
};
|
|
111
|
+
box[0] = Number.POSITIVE_INFINITY;
|
|
112
|
+
box[1] = Number.POSITIVE_INFINITY;
|
|
113
|
+
box[2] = Number.NEGATIVE_INFINITY;
|
|
114
|
+
box[3] = Number.NEGATIVE_INFINITY;
|
|
76
115
|
let x = 0;
|
|
77
116
|
let y = 0;
|
|
78
117
|
for (let i = 0; i < path.length;) {
|
|
@@ -86,49 +125,36 @@ export function extent(path) {
|
|
|
86
125
|
}
|
|
87
126
|
take(x, y);
|
|
88
127
|
if (command === CURVE) {
|
|
89
|
-
const
|
|
128
|
+
const cx = path[i + 1];
|
|
129
|
+
const cy = path[i + 2];
|
|
130
|
+
const ax = path[i + 3];
|
|
131
|
+
const ay = path[i + 4];
|
|
90
132
|
take(ax, ay);
|
|
91
133
|
// Where the derivative is zero on either axis, if within the curve.
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const t = denominator === 0 ? -1 : (p0 - c) / denominator;
|
|
102
|
-
if (t > 0 && t < 1) {
|
|
103
|
-
at(t);
|
|
104
|
-
}
|
|
134
|
+
const dx = x - 2 * cx + ax;
|
|
135
|
+
const tx = dx === 0 ? -1 : (x - cx) / dx;
|
|
136
|
+
if (tx > 0 && tx < 1) {
|
|
137
|
+
takeCurveAt(tx, x, y, cx, cy, ax, ay);
|
|
138
|
+
}
|
|
139
|
+
const dy = y - 2 * cy + ay;
|
|
140
|
+
const ty = dy === 0 ? -1 : (y - cy) / dy;
|
|
141
|
+
if (ty > 0 && ty < 1) {
|
|
142
|
+
takeCurveAt(ty, x, y, cx, cy, ax, ay);
|
|
105
143
|
}
|
|
106
144
|
x = ax;
|
|
107
145
|
y = ay;
|
|
108
146
|
i += 5;
|
|
109
147
|
}
|
|
110
148
|
else if (command === CUBIC) {
|
|
111
|
-
const
|
|
149
|
+
const c1x = path[i + 1];
|
|
150
|
+
const c1y = path[i + 2];
|
|
151
|
+
const c2x = path[i + 3];
|
|
152
|
+
const c2y = path[i + 4];
|
|
153
|
+
const ax = path[i + 5];
|
|
154
|
+
const ay = path[i + 6];
|
|
112
155
|
take(ax, ay);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
take(u * u * u * x + 3 * u * u * t * c1x + 3 * u * t * t * c2x + t * t * t * ax, u * u * u * y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * ay);
|
|
116
|
-
};
|
|
117
|
-
for (const [p0, c1, c2, p3] of [
|
|
118
|
-
[x, c1x, c2x, ax],
|
|
119
|
-
[y, c1y, c2y, ay],
|
|
120
|
-
]) {
|
|
121
|
-
// The derivative's roots: a t² + b t + c = 0.
|
|
122
|
-
const a = -p0 + 3 * c1 - 3 * c2 + p3;
|
|
123
|
-
const b = 2 * (p0 - 2 * c1 + c2);
|
|
124
|
-
const c = c1 - p0;
|
|
125
|
-
const roots = a === 0 ? (b === 0 ? [] : [-c / b]) : quadraticRoots(a, b, c);
|
|
126
|
-
for (const t of roots) {
|
|
127
|
-
if (t > 0 && t < 1) {
|
|
128
|
-
at(t);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
156
|
+
takeCubicTurns(x, c1x, c2x, ax, x, y, c1x, c1y, c2x, c2y, ax, ay);
|
|
157
|
+
takeCubicTurns(y, c1y, c2y, ay, x, y, c1x, c1y, c2x, c2y, ax, ay);
|
|
132
158
|
x = ax;
|
|
133
159
|
y = ay;
|
|
134
160
|
i += 7;
|
|
@@ -140,15 +166,7 @@ export function extent(path) {
|
|
|
140
166
|
i += 3;
|
|
141
167
|
}
|
|
142
168
|
}
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
function quadraticRoots(a, b, c) {
|
|
146
|
-
const d = b * b - 4 * a * c;
|
|
147
|
-
if (d < 0) {
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
const s = Math.sqrt(d);
|
|
151
|
-
return [(-b + s) / (2 * a), (-b - s) / (2 * a)];
|
|
169
|
+
return box[0] <= box[2] ? [box[0], box[1], box[2], box[3]] : null;
|
|
152
170
|
}
|
|
153
171
|
/** Layers as they draw mirrored, as of a version: the renderer and the hit tests ask on each redraw. */
|
|
154
172
|
const mirroredLayers = new WeakMap();
|
|
@@ -30,6 +30,15 @@ export class StaticTextObject extends DisplayObject {
|
|
|
30
30
|
return this.laidGlyphs;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
/** The fields whose fit a set size left pending, for the frame to settle before it is drawn. */
|
|
34
|
+
const pendingFits = new Set();
|
|
35
|
+
/** Every pending fit applied, as Flash applies them before it draws. */
|
|
36
|
+
export function settleFits() {
|
|
37
|
+
for (const field of pendingFits) {
|
|
38
|
+
field.settle();
|
|
39
|
+
}
|
|
40
|
+
pendingFits.clear();
|
|
41
|
+
}
|
|
33
42
|
/**
|
|
34
43
|
* A TextField: placed by DefineEditText, or made by a script. Its text and
|
|
35
44
|
* formats are a TextModel; a timeline's starts with its tag's text, read
|
|
@@ -55,6 +64,12 @@ export class TextObject extends DisplayObject {
|
|
|
55
64
|
type = "dynamic";
|
|
56
65
|
embedFonts = false;
|
|
57
66
|
autoSize = "none";
|
|
67
|
+
/**
|
|
68
|
+
* A script set the size of a field that fits itself: as in Flash, the fit
|
|
69
|
+
* waits for its size to be asked, its transform to change or the frame to
|
|
70
|
+
* be drawn, and a wordWrap set meanwhile wraps at the size set (adl).
|
|
71
|
+
*/
|
|
72
|
+
fitPending = false;
|
|
58
73
|
selectable = true;
|
|
59
74
|
maxChars = 0;
|
|
60
75
|
displayAsPassword = false;
|
|
@@ -165,7 +180,7 @@ export class TextObject extends DisplayObject {
|
|
|
165
180
|
* centre or right edge where it was.
|
|
166
181
|
*/
|
|
167
182
|
fit() {
|
|
168
|
-
if (this.autoSize === "none") {
|
|
183
|
+
if (this.autoSize === "none" || this.fitPending) {
|
|
169
184
|
return;
|
|
170
185
|
}
|
|
171
186
|
const layout = this.layout;
|
|
@@ -186,6 +201,24 @@ export class TextObject extends DisplayObject {
|
|
|
186
201
|
this.height = height;
|
|
187
202
|
this.invalidate(CONTENT);
|
|
188
203
|
}
|
|
204
|
+
/** A script set its size: under autoSize, the fit waits (fitPending). */
|
|
205
|
+
sizeSet() {
|
|
206
|
+
if (this.autoSize !== "none") {
|
|
207
|
+
this.fitPending = true;
|
|
208
|
+
pendingFits.add(this);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** The fit a set size left pending, applied: its size asked, its transform changed, the frame drawn. */
|
|
212
|
+
settle() {
|
|
213
|
+
if (this.fitPending) {
|
|
214
|
+
this.fitPending = false;
|
|
215
|
+
this.fit();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
touch() {
|
|
219
|
+
super.touch();
|
|
220
|
+
this.settle();
|
|
221
|
+
}
|
|
189
222
|
/** The text, \r between its lines. */
|
|
190
223
|
get text() {
|
|
191
224
|
return this.model.text;
|
|
@@ -10,6 +10,7 @@ import { decodeImages, decodeInBrowser } from "./bitmap/images.js";
|
|
|
10
10
|
import { drawKernelFrame, drawKernelLetGo, drawKernelUsed } from "./bitmap/kernel.js";
|
|
11
11
|
import { BUTTON, CLIP, Container, MovieClip, OTHER, unloads, walkList, } from "./display/display.js";
|
|
12
12
|
import { setStageSize } from "./display/perspective.js";
|
|
13
|
+
import { settleFits } from "./display/textobjects.js";
|
|
13
14
|
import { readLibrary } from "./display/timeline.js";
|
|
14
15
|
import { customContextMenu } from "./input/context-menu.js";
|
|
15
16
|
import { KeyboardInput } from "./input/keyboard.js";
|
|
@@ -607,9 +608,7 @@ export class Player {
|
|
|
607
608
|
}
|
|
608
609
|
};
|
|
609
610
|
collect(this.stage);
|
|
610
|
-
|
|
611
|
-
collect(orphan);
|
|
612
|
-
}
|
|
611
|
+
this.scripting?.lifecycle.eachOrphanRoot(collect);
|
|
613
612
|
// Let go of once advanced, so that the kept list holds none past the tick.
|
|
614
613
|
try {
|
|
615
614
|
for (let i = 0; i < count; i++) {
|
|
@@ -624,5 +623,7 @@ export class Player {
|
|
|
624
623
|
this.scripting.frame(this.stage, true, render);
|
|
625
624
|
this.frameRate = this.scripting.frameRate;
|
|
626
625
|
}
|
|
626
|
+
// The fits a set size left pending happen before the frame is drawn, as in Flash.
|
|
627
|
+
settleFits();
|
|
627
628
|
}
|
|
628
629
|
}
|
|
@@ -340,7 +340,7 @@ export function displayObjectNatives(s) {
|
|
|
340
340
|
if (d instanceof TextObject) {
|
|
341
341
|
d.width = value / (Math.abs(d.scaleX) || 1);
|
|
342
342
|
d.invalidate(CONTENT);
|
|
343
|
-
d.
|
|
343
|
+
d.sizeSet();
|
|
344
344
|
return;
|
|
345
345
|
}
|
|
346
346
|
const r = bounds(d, true);
|
|
@@ -375,7 +375,7 @@ export function displayObjectNatives(s) {
|
|
|
375
375
|
if (d instanceof TextObject) {
|
|
376
376
|
d.height = value / (Math.abs(d.scaleY) || 1);
|
|
377
377
|
d.invalidate(CONTENT);
|
|
378
|
-
d.
|
|
378
|
+
d.sizeSet();
|
|
379
379
|
return;
|
|
380
380
|
}
|
|
381
381
|
const r = bounds(d, true);
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// flash.net.NetConnection in its local mode, connect(null), which Flash
|
|
2
2
|
// keeps for progressive video, with its properties and status events as
|
|
3
|
-
// Flash gives them;
|
|
4
|
-
// and a
|
|
3
|
+
// Flash gives them; and Flash Remoting over an HTTP URI: each call queued,
|
|
4
|
+
// and a frame's calls POSTed together at its end as one packet
|
|
5
|
+
// (remoting.ts), the connection's headers with them, each answer, /n/
|
|
6
|
+
// onResult or /n/onStatus, going to its Responder, as adl does.
|
|
7
|
+
// Responder holds its callbacks.
|
|
5
8
|
import { avm2 } from "../../../../runtime/index.js";
|
|
6
9
|
import { dispatchEvent } from "../../../scripting/events.js";
|
|
10
|
+
import { remoteArguments, remoteMessages, remotePacket, remoteValue, } from "./remoting.js";
|
|
7
11
|
/** NetConnection's private invoke codes, as playerglobal's constants have them. */
|
|
8
12
|
const CLOSE = 1;
|
|
9
13
|
const CALL = 2;
|
|
@@ -32,8 +36,10 @@ export function netConnectionNatives(s) {
|
|
|
32
36
|
/** Null is the local connection, at once and connected; an HTTP URI is only kept. A connection made goes first. */
|
|
33
37
|
connect(command) {
|
|
34
38
|
const self = this;
|
|
35
|
-
|
|
39
|
+
// A gateway connected before is closed as a local one is (the corpus's netconnection_send_remote).
|
|
40
|
+
if (this.$connected || this.$remote) {
|
|
36
41
|
this.$connected = false;
|
|
42
|
+
this.$remote = null;
|
|
37
43
|
status(self, ["code", "NetConnection.Connect.Closed", "level", "status"]);
|
|
38
44
|
}
|
|
39
45
|
if (command === null || command === undefined || s.rt.toString(command) === "null") {
|
|
@@ -43,6 +49,16 @@ export function netConnectionNatives(s) {
|
|
|
43
49
|
return;
|
|
44
50
|
}
|
|
45
51
|
this.$uri = s.rt.toString(command);
|
|
52
|
+
if (/^https?:\/\//i.test(this.$uri)) {
|
|
53
|
+
this.$remote = {
|
|
54
|
+
url: this.$uri,
|
|
55
|
+
headers: [],
|
|
56
|
+
queue: [],
|
|
57
|
+
next: 1,
|
|
58
|
+
responders: new Map(),
|
|
59
|
+
scheduled: false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
46
62
|
}
|
|
47
63
|
"flash.net:NetConnection::invoke"(code) {
|
|
48
64
|
if (s.rt.toUint(code) === CLOSE) {
|
|
@@ -51,13 +67,16 @@ export function netConnectionNatives(s) {
|
|
|
51
67
|
}
|
|
52
68
|
throw s.rt.unsupported(`NetConnection invoke ${s.rt.toUint(code)}`);
|
|
53
69
|
}
|
|
54
|
-
"flash.net:NetConnection::invokeWithArgsArray"(code,
|
|
70
|
+
"flash.net:NetConnection::invokeWithArgsArray"(code, args) {
|
|
55
71
|
const which = s.rt.toUint(code);
|
|
72
|
+
const values = (args?.$a ?? []);
|
|
56
73
|
if (which === ADD_HEADER) {
|
|
74
|
+
addHeader(this, values);
|
|
57
75
|
return undefined;
|
|
58
76
|
}
|
|
59
77
|
if (which === CALL) {
|
|
60
|
-
|
|
78
|
+
call(this, values);
|
|
79
|
+
return undefined;
|
|
61
80
|
}
|
|
62
81
|
throw s.rt.unsupported(`NetConnection invoke ${which}`);
|
|
63
82
|
}
|
|
@@ -135,6 +154,7 @@ export function netConnectionNatives(s) {
|
|
|
135
154
|
const wasConnected = o.$connected ?? false;
|
|
136
155
|
o.$connected = false;
|
|
137
156
|
o.$uri = null;
|
|
157
|
+
o.$remote = null;
|
|
138
158
|
if (wasConnected) {
|
|
139
159
|
status(o, ["code", "NetConnection.Connect.Closed", "level", "status"]);
|
|
140
160
|
}
|
|
@@ -143,6 +163,131 @@ export function netConnectionNatives(s) {
|
|
|
143
163
|
status(o, ["code", "", "description", "", "details", "", "level", "status"]);
|
|
144
164
|
}
|
|
145
165
|
};
|
|
166
|
+
/**
|
|
167
|
+
* A call queued for the frame's end, numbered on its connection; nothing
|
|
168
|
+
* on a local connection or none, which send nothing, as in adl.
|
|
169
|
+
*/
|
|
170
|
+
const call = (o, values) => {
|
|
171
|
+
const remote = o.$remote;
|
|
172
|
+
if (!remote) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const method = s.rt.toString(values[0]);
|
|
176
|
+
const responder = (values[1] ?? null);
|
|
177
|
+
const id = remote.next++;
|
|
178
|
+
const encoding = o.$objectEncoding ?? defaultEncoding;
|
|
179
|
+
remote.queue.push({ method, id, body: remoteArguments(s.rt, values.slice(2), encoding) });
|
|
180
|
+
remote.responders.set(id, responder);
|
|
181
|
+
if (!remote.scheduled) {
|
|
182
|
+
remote.scheduled = true;
|
|
183
|
+
s.afterExitFrame.push(() => send(o, remote, encoding));
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
/** One header of a name, its case aside: the last set, a value of null its value (corpus). */
|
|
187
|
+
const addHeader = (o, values) => {
|
|
188
|
+
const remote = o.$remote;
|
|
189
|
+
if (!remote) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const name = s.rt.toString(values[0]);
|
|
193
|
+
const encoding = o.$objectEncoding ?? defaultEncoding;
|
|
194
|
+
const value = remoteValue(s.rt, values[2] ?? null, encoding);
|
|
195
|
+
remote.headers = remote.headers.filter((h) => h.name.toLowerCase() !== name.toLowerCase());
|
|
196
|
+
remote.headers.push({ name, mustUnderstand: s.rt.toBoolean(values[1]), value });
|
|
197
|
+
};
|
|
198
|
+
/** The frame's calls, POSTed as one packet; their answers come in a later frame. */
|
|
199
|
+
const send = (o, remote, encoding) => {
|
|
200
|
+
remote.scheduled = false;
|
|
201
|
+
const calls = remote.queue;
|
|
202
|
+
remote.queue = [];
|
|
203
|
+
const fetch = s.fetch;
|
|
204
|
+
// A connection closed or replaced since sends nothing.
|
|
205
|
+
if (o.$remote !== remote || calls.length === 0 || !fetch) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const body = remotePacket(encoding, remote.headers, calls);
|
|
209
|
+
const request = fetch({
|
|
210
|
+
url: remote.url,
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: [["Content-Type", "application/x-amf"]],
|
|
213
|
+
body,
|
|
214
|
+
purpose: "data",
|
|
215
|
+
}, new AbortController().signal).then((result) => result, () => ({ bytes: null, status: 0, headers: [] }));
|
|
216
|
+
s.loads.trackRequest(request.then((result) => {
|
|
217
|
+
s.loads.deferHostEvent(() => {
|
|
218
|
+
try {
|
|
219
|
+
answered(o, remote, result.bytes, result.status);
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
// Its calls are done with, answered or not: their responders go.
|
|
223
|
+
for (const call of calls) {
|
|
224
|
+
remote.responders.delete(call.id);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}));
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* An answer: each message to the responder of the call it names; an
|
|
232
|
+
* HTTP error or no answer as Call.Failed, one that is no packet as
|
|
233
|
+
* Call.BadVersion, an empty one as nothing, as adl reports them.
|
|
234
|
+
*/
|
|
235
|
+
const answered = (o, remote, bytes, httpStatus) => {
|
|
236
|
+
if (o.$remote !== remote) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (bytes === null) {
|
|
240
|
+
const description = httpStatus >= 300 ? `HTTP: Status ${httpStatus}` : "HTTP: Failed";
|
|
241
|
+
status(o, [
|
|
242
|
+
"code",
|
|
243
|
+
"NetConnection.Call.Failed",
|
|
244
|
+
"description",
|
|
245
|
+
description,
|
|
246
|
+
"details",
|
|
247
|
+
remote.url,
|
|
248
|
+
"level",
|
|
249
|
+
"error",
|
|
250
|
+
]);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (bytes.length === 0) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const messages = remoteMessages(s.rt, bytes);
|
|
257
|
+
if (messages === "version") {
|
|
258
|
+
status(o, [
|
|
259
|
+
"code",
|
|
260
|
+
"NetConnection.Call.BadVersion",
|
|
261
|
+
"description",
|
|
262
|
+
"",
|
|
263
|
+
"details",
|
|
264
|
+
"",
|
|
265
|
+
"level",
|
|
266
|
+
"error",
|
|
267
|
+
]);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
for (const [target, value] of messages) {
|
|
271
|
+
const m = /^\/(\d+)\/(onResult|onStatus)$/.exec(target);
|
|
272
|
+
if (!m) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const id = Number(m[1]);
|
|
276
|
+
const responder = remote.responders.get(id);
|
|
277
|
+
remote.responders.delete(id);
|
|
278
|
+
const handler = responder
|
|
279
|
+
? responder[m[2] === "onResult" ? "$result" : "$status"]
|
|
280
|
+
: null;
|
|
281
|
+
if (typeof handler === "function" || (handler && typeof handler === "object")) {
|
|
282
|
+
try {
|
|
283
|
+
s.rt.call(handler, null, value);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
s.reportUncaught(error);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
146
291
|
class ResponderNatives {
|
|
147
292
|
"flash.net:Responder::ctor"(result, status) {
|
|
148
293
|
this.$result = result;
|
|
@@ -6,12 +6,29 @@ import * as A from "../../../avm1/amf.js";
|
|
|
6
6
|
import { LOCAL_MESSAGE_LIMIT } from "../../../localconnection.js";
|
|
7
7
|
const encoder = new TextEncoder();
|
|
8
8
|
const decoder = new TextDecoder();
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
/**
|
|
10
|
+
* AS3 values as AMF0, through the AS2 codec's writer: LocalConnection's
|
|
11
|
+
* arguments, and Flash Remoting's in AMF0. `limit` bounds strings and
|
|
12
|
+
* arrays (LocalConnection's message size); `next` is the reference the
|
|
13
|
+
* first object takes: 1 where the strict array of arguments is itself the
|
|
14
|
+
* first, as LocalConnection numbers them, 0 for remoting's (adl).
|
|
15
|
+
*/
|
|
16
|
+
export class Amf0Writer {
|
|
17
|
+
rt;
|
|
18
|
+
limit;
|
|
19
|
+
next;
|
|
20
|
+
what;
|
|
21
|
+
out = new A.Writer(() => null, 8, null, null);
|
|
22
|
+
references = new Map();
|
|
23
|
+
constructor(rt, limit = LOCAL_MESSAGE_LIMIT, next = 1, what = "LocalConnection") {
|
|
24
|
+
this.rt = rt;
|
|
25
|
+
this.limit = limit;
|
|
26
|
+
this.next = next;
|
|
27
|
+
this.what = what;
|
|
28
|
+
}
|
|
29
|
+
value(value, depth = 0) {
|
|
30
|
+
const out = this.out;
|
|
31
|
+
const rt = this.rt;
|
|
15
32
|
if (depth >= A.MAX_DEPTH - 2) {
|
|
16
33
|
out.bytes(A.UNDEFINED);
|
|
17
34
|
return;
|
|
@@ -37,8 +54,8 @@ export function writeLocalValues(rt, args) {
|
|
|
37
54
|
}
|
|
38
55
|
if (traits.name === "XML" || traits.name === "flash.xml::XMLDocument") {
|
|
39
56
|
const text = rt.toString(rt.callProperty(value, rt.publicName(traits.name === "XML" ? "toXMLString" : "toString")));
|
|
40
|
-
if (text.length >
|
|
41
|
-
throw new RangeError(
|
|
57
|
+
if (text.length > this.limit) {
|
|
58
|
+
throw new RangeError(`${this.what} message too large`);
|
|
42
59
|
}
|
|
43
60
|
const bytes = encoder.encode(text);
|
|
44
61
|
out.bytes(A.XML);
|
|
@@ -46,13 +63,13 @@ export function writeLocalValues(rt, args) {
|
|
|
46
63
|
out.push(bytes);
|
|
47
64
|
return;
|
|
48
65
|
}
|
|
49
|
-
const seen = references.get(value);
|
|
66
|
+
const seen = this.references.get(value);
|
|
50
67
|
if (seen !== undefined) {
|
|
51
68
|
out.bytes(A.REFERENCE);
|
|
52
69
|
out.u16(seen);
|
|
53
70
|
return;
|
|
54
71
|
}
|
|
55
|
-
references.set(value, next++);
|
|
72
|
+
this.references.set(value, this.next++);
|
|
56
73
|
if (value.$a) {
|
|
57
74
|
const array = value.$a;
|
|
58
75
|
const properties = value.$d;
|
|
@@ -61,22 +78,22 @@ export function writeLocalValues(rt, args) {
|
|
|
61
78
|
out.u32(array.length);
|
|
62
79
|
for (const key of Object.keys(array)) {
|
|
63
80
|
out.name(key);
|
|
64
|
-
|
|
81
|
+
this.value(array[Number(key)], depth + 1);
|
|
65
82
|
}
|
|
66
83
|
for (const [key, field] of properties) {
|
|
67
84
|
out.name(key);
|
|
68
|
-
|
|
85
|
+
this.value(field, depth + 1);
|
|
69
86
|
}
|
|
70
87
|
out.bytes(0, 0, A.OBJECT_END);
|
|
71
88
|
return;
|
|
72
89
|
}
|
|
73
|
-
if (array.length >
|
|
74
|
-
throw new RangeError(
|
|
90
|
+
if (array.length > this.limit) {
|
|
91
|
+
throw new RangeError(`${this.what} message too large`);
|
|
75
92
|
}
|
|
76
93
|
out.bytes(A.STRICT_ARRAY);
|
|
77
94
|
out.u32(array.length);
|
|
78
95
|
for (let i = 0; i < array.length; i++) {
|
|
79
|
-
|
|
96
|
+
this.value(array[i], depth + 1);
|
|
80
97
|
}
|
|
81
98
|
return;
|
|
82
99
|
}
|
|
@@ -106,80 +123,122 @@ export function writeLocalValues(rt, args) {
|
|
|
106
123
|
continue;
|
|
107
124
|
}
|
|
108
125
|
out.name(name);
|
|
109
|
-
|
|
126
|
+
this.value(field, depth + 1);
|
|
110
127
|
}
|
|
111
128
|
out.bytes(0, 0, A.OBJECT_END);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function writeLocalValues(rt, args) {
|
|
132
|
+
// The original AMF3 payload is already bounded; AMF0 can expand small integers.
|
|
133
|
+
const w = new Amf0Writer(rt);
|
|
134
|
+
w.out.bytes(A.STRICT_ARRAY);
|
|
135
|
+
w.out.u32(args.length);
|
|
115
136
|
for (const value of args) {
|
|
116
|
-
|
|
137
|
+
w.value(value);
|
|
117
138
|
}
|
|
118
|
-
return out.finish();
|
|
139
|
+
return w.out.finish();
|
|
119
140
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
141
|
+
/** AMF0's marker for an AMF3 value after it, which Flash Remoting writes and reads. */
|
|
142
|
+
export const AVMPLUS = 0x11;
|
|
143
|
+
/**
|
|
144
|
+
* AMF0 read as AS3 values from `at`: LocalConnection's arguments, and
|
|
145
|
+
* Flash Remoting's answers, whose AMF3 values after 0x11 (with `amf3`)
|
|
146
|
+
* share one AMF3 reader's tables.
|
|
147
|
+
*/
|
|
148
|
+
export class Amf0Reader {
|
|
149
|
+
rt;
|
|
150
|
+
bytes;
|
|
151
|
+
amf3;
|
|
152
|
+
what;
|
|
153
|
+
at;
|
|
154
|
+
view;
|
|
155
|
+
references = [];
|
|
156
|
+
amf3Reader = null;
|
|
157
|
+
amf3Bytes = null;
|
|
158
|
+
constructor(rt, bytes, at = 0, amf3 = false, what = "LocalConnection") {
|
|
159
|
+
this.rt = rt;
|
|
160
|
+
this.bytes = bytes;
|
|
161
|
+
this.amf3 = amf3;
|
|
162
|
+
this.what = what;
|
|
163
|
+
this.at = at;
|
|
164
|
+
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
165
|
+
}
|
|
166
|
+
u16() {
|
|
167
|
+
const n = this.view.getUint16(this.at);
|
|
168
|
+
this.at += 2;
|
|
127
169
|
return n;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const n = view.getUint32(at);
|
|
131
|
-
at += 4;
|
|
170
|
+
}
|
|
171
|
+
u32() {
|
|
172
|
+
const n = this.view.getUint32(this.at);
|
|
173
|
+
this.at += 4;
|
|
132
174
|
return n;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (length > bytes.length - at) {
|
|
136
|
-
throw new RangeError(
|
|
175
|
+
}
|
|
176
|
+
utf(length) {
|
|
177
|
+
if (length > this.bytes.length - this.at) {
|
|
178
|
+
throw new RangeError(`${this.what} message ended early`);
|
|
137
179
|
}
|
|
138
|
-
const text = decoder.decode(bytes.subarray(at, at + length));
|
|
139
|
-
at += length;
|
|
180
|
+
const text = decoder.decode(this.bytes.subarray(this.at, this.at + length));
|
|
181
|
+
this.at += length;
|
|
140
182
|
return text;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
183
|
+
}
|
|
184
|
+
name() {
|
|
185
|
+
return this.utf(this.u16());
|
|
186
|
+
}
|
|
187
|
+
/** An AMF3 value at the reader's place, its tables those of the values before it. */
|
|
188
|
+
amf3Value() {
|
|
189
|
+
if (this.amf3Bytes === null) {
|
|
190
|
+
this.amf3Bytes = new avm2.Bytes(this.rt, {});
|
|
191
|
+
this.amf3Bytes.write(this.bytes);
|
|
192
|
+
this.amf3Reader = new avm2.Amf3Reader(this.rt, this.amf3Bytes);
|
|
193
|
+
}
|
|
194
|
+
this.amf3Bytes.position = this.at;
|
|
195
|
+
const value = this.amf3Reader.value();
|
|
196
|
+
this.at = this.amf3Bytes.position;
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
value(depth = 0) {
|
|
200
|
+
const rt = this.rt;
|
|
201
|
+
const bytes = this.bytes;
|
|
202
|
+
const view = this.view;
|
|
144
203
|
if (depth >= A.MAX_DEPTH) {
|
|
145
|
-
throw new RangeError(
|
|
204
|
+
throw new RangeError(`${this.what} arguments nested too deeply`);
|
|
146
205
|
}
|
|
147
|
-
const marker = bytes[at++];
|
|
206
|
+
const marker = bytes[this.at++];
|
|
148
207
|
switch (marker) {
|
|
149
208
|
case A.NUMBER: {
|
|
150
|
-
const n = view.getFloat64(at);
|
|
151
|
-
at += 8;
|
|
209
|
+
const n = view.getFloat64(this.at);
|
|
210
|
+
this.at += 8;
|
|
152
211
|
return n;
|
|
153
212
|
}
|
|
154
213
|
case A.BOOLEAN:
|
|
155
|
-
return bytes[at++] !== 0;
|
|
214
|
+
return bytes[this.at++] !== 0;
|
|
156
215
|
case A.STRING:
|
|
157
|
-
return name();
|
|
216
|
+
return this.name();
|
|
158
217
|
case A.LONG_STRING:
|
|
159
|
-
return utf(u32());
|
|
218
|
+
return this.utf(this.u32());
|
|
160
219
|
case A.NULL:
|
|
161
220
|
return null;
|
|
162
221
|
case A.UNDEFINED:
|
|
163
222
|
case A.UNSUPPORTED:
|
|
164
223
|
return undefined;
|
|
165
224
|
case A.REFERENCE:
|
|
166
|
-
return references[u16()];
|
|
225
|
+
return this.references[this.u16()];
|
|
167
226
|
case A.DATE: {
|
|
168
|
-
const time = view.getFloat64(at);
|
|
169
|
-
at += 10;
|
|
227
|
+
const time = view.getFloat64(this.at);
|
|
228
|
+
this.at += 10;
|
|
170
229
|
return rt.construct(rt.classNamed("Date"), time);
|
|
171
230
|
}
|
|
172
231
|
case A.XML:
|
|
173
|
-
return rt.construct(rt.classNamed("flash.xml::XMLDocument"), utf(u32()));
|
|
232
|
+
return rt.construct(rt.classNamed("flash.xml::XMLDocument"), this.utf(this.u32()));
|
|
174
233
|
case A.STRICT_ARRAY: {
|
|
175
|
-
const count = u32();
|
|
176
|
-
if (count > bytes.length - at) {
|
|
177
|
-
throw new RangeError(
|
|
234
|
+
const count = this.u32();
|
|
235
|
+
if (count > bytes.length - this.at) {
|
|
236
|
+
throw new RangeError(`${this.what} message ended early`);
|
|
178
237
|
}
|
|
179
238
|
const result = rt.newArray([]);
|
|
180
|
-
references.push(result);
|
|
239
|
+
this.references.push(result);
|
|
181
240
|
for (let i = 0; i < count; i++) {
|
|
182
|
-
result.$a.push(
|
|
241
|
+
result.$a.push(this.value(depth + 1));
|
|
183
242
|
}
|
|
184
243
|
return result;
|
|
185
244
|
}
|
|
@@ -188,30 +247,37 @@ export function readLocalValues(rt, bytes) {
|
|
|
188
247
|
case A.ECMA_ARRAY: {
|
|
189
248
|
let result;
|
|
190
249
|
if (marker === A.TYPED_OBJECT) {
|
|
191
|
-
result = rt.allocate(rt.classByAlias(name(), true)) ?? rt.newObject([]);
|
|
250
|
+
result = rt.allocate(rt.classByAlias(this.name(), true)) ?? rt.newObject([]);
|
|
192
251
|
}
|
|
193
252
|
else {
|
|
194
253
|
result = marker === A.ECMA_ARRAY ? rt.newArray([]) : rt.newObject([]);
|
|
195
254
|
}
|
|
196
|
-
const length = marker === A.ECMA_ARRAY ? u32() : 0;
|
|
197
|
-
references.push(result);
|
|
255
|
+
const length = marker === A.ECMA_ARRAY ? this.u32() : 0;
|
|
256
|
+
this.references.push(result);
|
|
198
257
|
for (;;) {
|
|
199
|
-
const key = name();
|
|
200
|
-
if (key === "" && bytes[at] === A.OBJECT_END) {
|
|
201
|
-
at++;
|
|
258
|
+
const key = this.name();
|
|
259
|
+
if (key === "" && bytes[this.at] === A.OBJECT_END) {
|
|
260
|
+
this.at++;
|
|
202
261
|
break;
|
|
203
262
|
}
|
|
204
|
-
rt.setProperty(result, rt.publicName(key),
|
|
263
|
+
rt.setProperty(result, rt.publicName(key), this.value(depth + 1));
|
|
205
264
|
}
|
|
206
265
|
if (marker === A.ECMA_ARRAY && length > result.$a.length) {
|
|
207
266
|
result.$a.length = length;
|
|
208
267
|
}
|
|
209
268
|
return result;
|
|
210
269
|
}
|
|
270
|
+
case AVMPLUS:
|
|
271
|
+
if (this.amf3) {
|
|
272
|
+
return this.amf3Value();
|
|
273
|
+
}
|
|
274
|
+
throw new RangeError(`Invalid ${this.what} AMF0 marker`);
|
|
211
275
|
default:
|
|
212
|
-
throw new RangeError(
|
|
276
|
+
throw new RangeError(`Invalid ${this.what} AMF0 marker`);
|
|
213
277
|
}
|
|
214
|
-
}
|
|
215
|
-
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
export function readLocalValues(rt, bytes) {
|
|
281
|
+
const result = new Amf0Reader(rt, bytes).value();
|
|
216
282
|
return result.$a;
|
|
217
283
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Flash Remoting's packets for AS3's NetConnection.call, as adl sends and
|
|
2
|
+
// reads them. A packet is its version (0, or 3 where the connection's
|
|
3
|
+
// objectEncoding is AMF3), its headers and its calls, each call's
|
|
4
|
+
// arguments a strict array. In AMF0 every value is AMF0, the arguments
|
|
5
|
+
// sharing one table of references whose first object is 0. In AMF3 the
|
|
6
|
+
// strings, numbers, booleans, null and undefined are still AMF0, and every
|
|
7
|
+
// other value is AMF0's 0x11 followed by AMF3, the arguments of a call
|
|
8
|
+
// sharing one AMF3 writer's tables; a header's value is one of its own.
|
|
9
|
+
import { avm2 } from "../../../../runtime/index.js";
|
|
10
|
+
import * as A from "../../../avm1/amf.js";
|
|
11
|
+
import { Amf0Reader, Amf0Writer, AVMPLUS } from "./local-values.js";
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
const decoder = new TextDecoder();
|
|
14
|
+
/** Whether AMF3 writes `v` as AMF0 all the same: what has no AMF3 reference to share. */
|
|
15
|
+
function primitive(v) {
|
|
16
|
+
return (v === null || (typeof v !== "object" && typeof v !== "function") || v instanceof avm2.FloatValue);
|
|
17
|
+
}
|
|
18
|
+
/** `v`, a primitive, in AMF0 at the end of `out`. */
|
|
19
|
+
function writeAmf0Primitive(out, v) {
|
|
20
|
+
const w = new A.Writer(() => null, 8, null, null);
|
|
21
|
+
w.value(v instanceof avm2.FloatValue ? v.value : v);
|
|
22
|
+
out.write(w.finish());
|
|
23
|
+
}
|
|
24
|
+
/** AMF3 values, each after 0x11, into one buffer, sharing one AMF3 writer's tables. */
|
|
25
|
+
class Amf3Values {
|
|
26
|
+
bytes;
|
|
27
|
+
writer;
|
|
28
|
+
constructor(rt) {
|
|
29
|
+
this.bytes = new avm2.Bytes(rt, {});
|
|
30
|
+
this.writer = new avm2.Amf3Writer(rt, this.bytes);
|
|
31
|
+
}
|
|
32
|
+
value(v) {
|
|
33
|
+
if (primitive(v)) {
|
|
34
|
+
writeAmf0Primitive(this.bytes, v);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.bytes.write(Uint8Array.of(AVMPLUS));
|
|
38
|
+
this.writer.value(v);
|
|
39
|
+
}
|
|
40
|
+
finish() {
|
|
41
|
+
return this.bytes.buffer.slice(0, this.bytes.length);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** A header's value: AMF0, itself its table's first object; or in AMF3, with tables of its own. */
|
|
45
|
+
export function remoteValue(rt, v, encoding) {
|
|
46
|
+
if (encoding === 3) {
|
|
47
|
+
const w = new Amf3Values(rt);
|
|
48
|
+
w.value(v);
|
|
49
|
+
return w.finish();
|
|
50
|
+
}
|
|
51
|
+
const w = new Amf0Writer(rt, Number.POSITIVE_INFINITY, 0, "Remoting");
|
|
52
|
+
w.value(v);
|
|
53
|
+
return w.out.finish();
|
|
54
|
+
}
|
|
55
|
+
/** A call's arguments: a strict array of them, in the connection's encoding. */
|
|
56
|
+
export function remoteArguments(rt, args, encoding) {
|
|
57
|
+
if (encoding === 3) {
|
|
58
|
+
const w = new Amf3Values(rt);
|
|
59
|
+
const head = new Uint8Array(5);
|
|
60
|
+
head[0] = A.STRICT_ARRAY;
|
|
61
|
+
new DataView(head.buffer).setUint32(1, args.length);
|
|
62
|
+
w.bytes.write(head);
|
|
63
|
+
for (const v of args) {
|
|
64
|
+
w.value(v);
|
|
65
|
+
}
|
|
66
|
+
return w.finish();
|
|
67
|
+
}
|
|
68
|
+
const w = new Amf0Writer(rt, Number.POSITIVE_INFINITY, 0, "Remoting");
|
|
69
|
+
w.out.bytes(A.STRICT_ARRAY);
|
|
70
|
+
w.out.u32(args.length);
|
|
71
|
+
for (const v of args) {
|
|
72
|
+
w.value(v);
|
|
73
|
+
}
|
|
74
|
+
return w.out.finish();
|
|
75
|
+
}
|
|
76
|
+
/** Bytes, joined once. */
|
|
77
|
+
function join(chunks) {
|
|
78
|
+
let length = 0;
|
|
79
|
+
for (const chunk of chunks) {
|
|
80
|
+
length += chunk.length;
|
|
81
|
+
}
|
|
82
|
+
const out = new Uint8Array(length);
|
|
83
|
+
let at = 0;
|
|
84
|
+
for (const chunk of chunks) {
|
|
85
|
+
out.set(chunk, at);
|
|
86
|
+
at += chunk.length;
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/** A u16-long UTF-8 string, no marker. */
|
|
91
|
+
function utf(text) {
|
|
92
|
+
const b = encoder.encode(text).subarray(0, 0xffff);
|
|
93
|
+
return join([Uint8Array.of(b.length >> 8, b.length & 0xff), b]);
|
|
94
|
+
}
|
|
95
|
+
const u16 = (n) => Uint8Array.of((n >> 8) & 0xff, n & 0xff);
|
|
96
|
+
const u32 = (n) => Uint8Array.of((n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff);
|
|
97
|
+
/** A packet of the calls, each answered at /n, its number on the connection. */
|
|
98
|
+
export function remotePacket(encoding, headers, calls) {
|
|
99
|
+
const out = [u16(encoding === 3 ? 3 : 0), u16(headers.length)];
|
|
100
|
+
for (const h of headers) {
|
|
101
|
+
out.push(utf(h.name), Uint8Array.of(h.mustUnderstand ? 1 : 0), u32(h.value.length), h.value);
|
|
102
|
+
}
|
|
103
|
+
out.push(u16(calls.length));
|
|
104
|
+
for (const call of calls) {
|
|
105
|
+
out.push(utf(call.method), utf(`/${call.id}`), u32(call.body.length), call.body);
|
|
106
|
+
}
|
|
107
|
+
return join(out);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* An answer's messages, their targets and values; "version" for one that
|
|
111
|
+
* is no packet of version 0 or 3, which Flash reports as BadVersion.
|
|
112
|
+
* Each value is read on its own, AMF3 after 0x11 included; a message's
|
|
113
|
+
* length, where it gives one, says where the next begins.
|
|
114
|
+
*/
|
|
115
|
+
export function remoteMessages(rt, bytes) {
|
|
116
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
117
|
+
try {
|
|
118
|
+
const version = view.getUint16(0);
|
|
119
|
+
if (version !== 0 && version !== 3) {
|
|
120
|
+
return "version";
|
|
121
|
+
}
|
|
122
|
+
let at = 2;
|
|
123
|
+
const text = () => {
|
|
124
|
+
const n = view.getUint16(at);
|
|
125
|
+
at += 2;
|
|
126
|
+
if (at + n > bytes.length) {
|
|
127
|
+
throw new RangeError("past the end");
|
|
128
|
+
}
|
|
129
|
+
const s = decoder.decode(bytes.subarray(at, at + n));
|
|
130
|
+
at += n;
|
|
131
|
+
return s;
|
|
132
|
+
};
|
|
133
|
+
const value = () => {
|
|
134
|
+
const length = view.getUint32(at);
|
|
135
|
+
at += 4;
|
|
136
|
+
const start = at;
|
|
137
|
+
const reader = new Amf0Reader(rt, bytes, at, true, "Remoting");
|
|
138
|
+
const v = reader.value();
|
|
139
|
+
at = length !== 0xffffffff && start + length <= bytes.length ? start + length : reader.at;
|
|
140
|
+
return v;
|
|
141
|
+
};
|
|
142
|
+
const headers = view.getUint16(at);
|
|
143
|
+
at += 2;
|
|
144
|
+
for (let i = 0; i < headers; i++) {
|
|
145
|
+
text();
|
|
146
|
+
at += 1;
|
|
147
|
+
value();
|
|
148
|
+
}
|
|
149
|
+
const count = view.getUint16(at);
|
|
150
|
+
at += 2;
|
|
151
|
+
const messages = [];
|
|
152
|
+
for (let i = 0; i < count; i++) {
|
|
153
|
+
const target = text();
|
|
154
|
+
text();
|
|
155
|
+
messages.push([target, value()]);
|
|
156
|
+
}
|
|
157
|
+
return messages;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return "version";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -45,7 +45,7 @@ export class Lifecycle {
|
|
|
45
45
|
* order, as Flash dispatches them.
|
|
46
46
|
*/
|
|
47
47
|
added(display) {
|
|
48
|
-
this.
|
|
48
|
+
this.dropOrphan(display.serial);
|
|
49
49
|
this.dispatch(display.object, "added", true);
|
|
50
50
|
if (this.onStage(display)) {
|
|
51
51
|
this.eachObject(display, (o) => this.dispatch(o, "addedToStage", false));
|
|
@@ -136,6 +136,7 @@ export class Lifecycle {
|
|
|
136
136
|
orphan(display, keep = true) {
|
|
137
137
|
if (display.object && !this.orphans.has(display.serial)) {
|
|
138
138
|
this.orphans.set(display.serial, { ref: display.ref, keep, since: this.s.frames });
|
|
139
|
+
this.sortedOrphans = null;
|
|
139
140
|
scriptWork.changes++;
|
|
140
141
|
}
|
|
141
142
|
}
|
|
@@ -152,24 +153,42 @@ export class Lifecycle {
|
|
|
152
153
|
display.fresh = true;
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
|
-
/**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
/**
|
|
157
|
+
* The orphans, newest first, as Flash runs their frames: kept sorted
|
|
158
|
+
* until one comes or goes, as the player walks them twice a frame.
|
|
159
|
+
*/
|
|
160
|
+
sortedOrphans = null;
|
|
161
|
+
dropOrphan(serial) {
|
|
162
|
+
if (this.orphans.delete(serial)) {
|
|
163
|
+
this.sortedOrphans = null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Each orphan still there, newest first, to `f`, which must leave the orphans as they are. */
|
|
167
|
+
eachOrphanRoot(f) {
|
|
168
|
+
let sorted = this.sortedOrphans;
|
|
169
|
+
if (!sorted) {
|
|
170
|
+
sorted = [];
|
|
171
|
+
for (const [serial, orphan] of this.orphans) {
|
|
172
|
+
sorted.push({ serial, ref: orphan.ref });
|
|
173
|
+
}
|
|
174
|
+
sorted.sort((a, b) => b.serial - a.serial);
|
|
175
|
+
this.sortedOrphans = sorted;
|
|
176
|
+
}
|
|
177
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
178
|
+
const display = sorted[i].ref.deref();
|
|
160
179
|
if (!display) {
|
|
161
|
-
this.
|
|
180
|
+
this.dropOrphan(sorted[i].serial);
|
|
162
181
|
continue;
|
|
163
182
|
}
|
|
164
|
-
|
|
183
|
+
f(display);
|
|
165
184
|
}
|
|
166
|
-
return roots.sort((a, b) => b.serial - a.serial);
|
|
167
185
|
}
|
|
168
186
|
/** Whether `display` or anything under it listens for a frame's broadcast events. */
|
|
169
187
|
hearsFrames(display) {
|
|
170
188
|
if (display.object) {
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
const sets = this.s.broadcastSets;
|
|
190
|
+
for (let i = 0; i < sets.length; i++) {
|
|
191
|
+
if (sets[i].has(display.object)) {
|
|
173
192
|
return true;
|
|
174
193
|
}
|
|
175
194
|
}
|
|
@@ -187,7 +206,7 @@ export class Lifecycle {
|
|
|
187
206
|
* does: timelines stopped, frame broadcasts no longer heard, no orphan.
|
|
188
207
|
*/
|
|
189
208
|
stopAll(display) {
|
|
190
|
-
this.
|
|
209
|
+
this.dropOrphan(display.serial);
|
|
191
210
|
stopTimelineSoundsUnder(this.s, display);
|
|
192
211
|
const stop = (o) => {
|
|
193
212
|
if (o instanceof MovieClip) {
|
|
@@ -230,24 +249,25 @@ export class Lifecycle {
|
|
|
230
249
|
// What the timeline took off this frame has had its frame; it stops here. So
|
|
231
250
|
// does one that has played ORPHAN_FRAMES off the list, but for one that
|
|
232
251
|
// listens for a frame's events, which hold it in Flash too.
|
|
233
|
-
for
|
|
252
|
+
// forEach, as for-of makes an entry for each of what may be thousands, every frame.
|
|
253
|
+
this.orphans.forEach((orphan, serial) => {
|
|
234
254
|
if (!orphan.keep) {
|
|
235
|
-
this.
|
|
236
|
-
|
|
255
|
+
this.dropOrphan(serial);
|
|
256
|
+
return;
|
|
237
257
|
}
|
|
238
258
|
if (this.s.frames - orphan.since < ORPHAN_FRAMES) {
|
|
239
|
-
|
|
259
|
+
return;
|
|
240
260
|
}
|
|
241
261
|
const display = orphan.ref.deref();
|
|
242
262
|
if (display && this.hearsFrames(display)) {
|
|
243
263
|
orphan.since = this.s.frames;
|
|
244
|
-
|
|
264
|
+
return;
|
|
245
265
|
}
|
|
246
|
-
this.
|
|
266
|
+
this.dropOrphan(serial);
|
|
247
267
|
if (display) {
|
|
248
268
|
stopTimelineSoundsUnder(this.s, display);
|
|
249
269
|
}
|
|
250
|
-
}
|
|
270
|
+
});
|
|
251
271
|
// What scripts made this frame and left off the display list plays on as an orphan.
|
|
252
272
|
const made = this.madeThisFrame.splice(0);
|
|
253
273
|
if (made.length > 0) {
|
|
@@ -203,6 +203,8 @@ export class Scripting {
|
|
|
203
203
|
inFrameScript = null;
|
|
204
204
|
/** The display objects listening for each frame event, in the order they first listened; a broadcast reaches these. */
|
|
205
205
|
broadcasts = new Map();
|
|
206
|
+
/** The same sets, for a walk by index that makes no iterator; the map never lets one go. */
|
|
207
|
+
broadcastSets = [];
|
|
206
208
|
/** The dispatchers listening for ACTIVATE or DEACTIVATE, held weakly. */
|
|
207
209
|
activation = new Map();
|
|
208
210
|
/** What flash.display.Stage reports and sets; the player copies the frame rate back each frame. */
|
|
@@ -579,6 +581,7 @@ export class Scripting {
|
|
|
579
581
|
if (!targets) {
|
|
580
582
|
targets = new Set();
|
|
581
583
|
this.broadcasts.set(type, targets);
|
|
584
|
+
this.broadcastSets.push(targets);
|
|
582
585
|
}
|
|
583
586
|
return targets;
|
|
584
587
|
}
|
|
@@ -792,9 +795,7 @@ export class Scripting {
|
|
|
792
795
|
}
|
|
793
796
|
}
|
|
794
797
|
};
|
|
795
|
-
|
|
796
|
-
visit(orphan);
|
|
797
|
-
}
|
|
798
|
+
this.lifecycle.eachOrphanRoot(visit);
|
|
798
799
|
visit(root);
|
|
799
800
|
for (const display of this.lifecycle.fresh) {
|
|
800
801
|
visit(display);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swf2es/player-hosts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Optional Node and WebSocket transports, worker threads, an IndexedDB module cache and precompiled modules for the swf2es player",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"swf",
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
"LICENSE.third-party"
|
|
61
61
|
],
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@swf2es/player": "0.1.
|
|
63
|
+
"@swf2es/player": "0.1.7"
|
|
64
64
|
}
|
|
65
65
|
}
|