obsidian-integration-testing 16.0.0 → 17.0.0
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/lib/cjs/adb.cjs +2 -2
- package/dist/lib/cjs/adb.d.cts +10 -0
- package/dist/lib/cjs/avd-name-channel.cjs +74 -0
- package/dist/lib/cjs/avd-name-channel.d.cts +152 -0
- package/dist/lib/cjs/avd-probe-verdict.cjs +11 -4
- package/dist/lib/cjs/avd-probe-verdict.d.cts +7 -0
- package/dist/lib/cjs/device-screenshot.cjs +1 -1
- package/dist/lib/cjs/device-screenshot.d.cts +3 -1
- package/dist/lib/cjs/emulator-backend.cjs +22 -1
- package/dist/lib/cjs/emulator-backend.d.cts +81 -0
- package/dist/lib/cjs/emulator-reclaim.cjs +78 -34
- package/dist/lib/cjs/emulator-reclaim.d.cts +57 -9
- package/dist/lib/cjs/enable-plugin.cjs +17 -8
- package/dist/lib/cjs/enable-plugin.d.cts +3 -1
- package/dist/lib/cjs/host-process-query-verdict.cjs +103 -0
- package/dist/lib/cjs/host-process-query-verdict.d.cts +196 -0
- package/dist/lib/cjs/index.cjs +14 -1
- package/dist/lib/cjs/index.d.cts +3 -1
- package/dist/lib/cjs/launched-device-selection.cjs +46 -0
- package/dist/lib/cjs/launched-device-selection.d.cts +112 -0
- package/dist/lib/cjs/library.cjs +1 -1
- package/dist/lib/cjs/paint-out-status-bar.cjs +170 -0
- package/dist/lib/cjs/paint-out-status-bar.d.cts +236 -0
- package/dist/lib/cjs/port-owner-query-verdict.cjs +94 -0
- package/dist/lib/cjs/port-owner-query-verdict.d.cts +161 -0
- package/dist/lib/cjs/resolve-emulator-device-id.cjs +27 -4
- package/dist/lib/cjs/sharp-loader.cjs +1 -1
- package/dist/lib/cjs/sharp-loader.d.cts +67 -2
- package/dist/lib/cjs/soft-keyboard-geometry.cjs +12 -2
- package/dist/lib/cjs/soft-keyboard-geometry.d.cts +17 -2
- package/dist/lib/cjs/soft-keyboard.cjs +130 -28
- package/dist/lib/cjs/soft-keyboard.d.cts +65 -6
- package/dist/lib/cjs/transport-appium.cjs +74 -4
- package/dist/lib/cjs/transport-appium.d.cts +49 -2
- package/dist/lib/cjs/transport-factory.cjs +162 -42
- package/dist/lib/esm/adb.d.mts +10 -0
- package/dist/lib/esm/adb.mjs +2 -2
- package/dist/lib/esm/avd-name-channel.d.mts +152 -0
- package/dist/lib/esm/avd-name-channel.mjs +45 -0
- package/dist/lib/esm/avd-probe-verdict.d.mts +7 -0
- package/dist/lib/esm/avd-probe-verdict.mjs +15 -4
- package/dist/lib/esm/device-screenshot.d.mts +3 -1
- package/dist/lib/esm/device-screenshot.mjs +1 -1
- package/dist/lib/esm/emulator-backend.d.mts +81 -0
- package/dist/lib/esm/emulator-backend.mjs +19 -1
- package/dist/lib/esm/emulator-reclaim.d.mts +57 -9
- package/dist/lib/esm/emulator-reclaim.mjs +84 -36
- package/dist/lib/esm/enable-plugin.d.mts +3 -1
- package/dist/lib/esm/enable-plugin.mjs +17 -8
- package/dist/lib/esm/host-process-query-verdict.d.mts +196 -0
- package/dist/lib/esm/host-process-query-verdict.mjs +74 -0
- package/dist/lib/esm/index.d.mts +3 -1
- package/dist/lib/esm/index.mjs +15 -1
- package/dist/lib/esm/launched-device-selection.d.mts +112 -0
- package/dist/lib/esm/launched-device-selection.mjs +21 -0
- package/dist/lib/esm/library.mjs +1 -1
- package/dist/lib/esm/paint-out-status-bar.d.mts +236 -0
- package/dist/lib/esm/paint-out-status-bar.mjs +142 -0
- package/dist/lib/esm/port-owner-query-verdict.d.mts +161 -0
- package/dist/lib/esm/port-owner-query-verdict.mjs +73 -0
- package/dist/lib/esm/resolve-emulator-device-id.mjs +32 -4
- package/dist/lib/esm/sharp-loader.d.mts +67 -2
- package/dist/lib/esm/sharp-loader.mjs +1 -1
- package/dist/lib/esm/soft-keyboard-geometry.d.mts +17 -2
- package/dist/lib/esm/soft-keyboard-geometry.mjs +11 -2
- package/dist/lib/esm/soft-keyboard.d.mts +65 -6
- package/dist/lib/esm/soft-keyboard.mjs +131 -28
- package/dist/lib/esm/transport-appium.d.mts +49 -2
- package/dist/lib/esm/transport-appium.mjs +74 -4
- package/dist/lib/esm/transport-factory.mjs +176 -44
- package/package.json +1 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Paints the device's status bar out of a captured frame, so a device capture
|
|
5
|
+
* can be committed.
|
|
6
|
+
*
|
|
7
|
+
* A framebuffer carries the status bar, and a status bar carries a wall clock, a
|
|
8
|
+
* battery that charges while the emulator runs and a radio that comes and goes.
|
|
9
|
+
* So re-running a capture suite on an unchanged tree rewrites its PNGs with
|
|
10
|
+
* content nobody can commit, and the churn is invisible until someone diffs two
|
|
11
|
+
* runs. `device-screenshot`'s header has always said a device capture is not
|
|
12
|
+
* byte-reproducible; this is what makes one.
|
|
13
|
+
*
|
|
14
|
+
* **Pinning the bar was tried first, and is NOT sufficient — that finding is the
|
|
15
|
+
* reason this helper exists rather than a `withSystemUiDemoMode` one.** SystemUI
|
|
16
|
+
* demo mode (`sysui_demo_allowed`, plus one `com.android.systemui.demo`
|
|
17
|
+
* broadcast per area: clock at a fixed `hhmm`, battery full and unplugged,
|
|
18
|
+
* radios hidden, notification icons off) held the clock at `12:00` and two runs
|
|
19
|
+
* still disagreed on **1823 full-contrast pixels**, because the bar's leading
|
|
20
|
+
* group is laid out at a different offset between emulator boots —
|
|
21
|
+
* intermittently, roughly one boot in four. The Tuner's `icon_blacklist` does
|
|
22
|
+
* not help either: demo mode draws its own icon layer and ignores it. Measured
|
|
23
|
+
* on a real AVD across five paired capture runs for `obsidian-link-picker`'s
|
|
24
|
+
* store frames, which is where this recipe was worked out before it moved here.
|
|
25
|
+
*
|
|
26
|
+
* Removing the band rather than pinning it also retires every device setting
|
|
27
|
+
* that pinning needed, and nothing in a status bar is evidence about a plugin. A
|
|
28
|
+
* listing frame without one is what a listing frame normally looks like.
|
|
29
|
+
*
|
|
30
|
+
* **The band's height is the caller's to measure, and there is deliberately no
|
|
31
|
+
* default.** A wrong height does not fail loudly by itself — it paints over the
|
|
32
|
+
* product. What stands between the two is {@link resolveStatusBarBand}: the row
|
|
33
|
+
* just under the bar's own content must be one flat color all the way across,
|
|
34
|
+
* which is what an empty band looks like and is also where the fill color is
|
|
35
|
+
* sampled from. A taller bar, or an Obsidian that draws higher, fails the
|
|
36
|
+
* capture instead.
|
|
37
|
+
*
|
|
38
|
+
* Compositing over a captured frame is not a new liberty: `labelScreenshot`
|
|
39
|
+
* already draws the caption band across the bottom of the same image.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Parameters for {@link buildStatusBarBandFailureMessage}.
|
|
43
|
+
*/
|
|
44
|
+
export interface BuildStatusBarBandFailureMessageParams {
|
|
45
|
+
/**
|
|
46
|
+
* How far in from each edge the row was scanned, in pixels.
|
|
47
|
+
*/
|
|
48
|
+
readonly edgeMarginInPixels: number;
|
|
49
|
+
/**
|
|
50
|
+
* The height of the band that was about to be painted, in pixels.
|
|
51
|
+
*/
|
|
52
|
+
readonly heightInPixels: number;
|
|
53
|
+
/**
|
|
54
|
+
* The verdict being reported.
|
|
55
|
+
*/
|
|
56
|
+
readonly verdict: StatusBarBandVerdict;
|
|
57
|
+
/**
|
|
58
|
+
* The frame's width, in pixels.
|
|
59
|
+
*/
|
|
60
|
+
readonly widthInPixels: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Options for {@link paintOutStatusBar}.
|
|
64
|
+
*/
|
|
65
|
+
export interface PaintOutStatusBarOptions {
|
|
66
|
+
/**
|
|
67
|
+
* How far in from each edge the clear-of-chrome check reads.
|
|
68
|
+
*
|
|
69
|
+
* @default {@link DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS}
|
|
70
|
+
*/
|
|
71
|
+
readonly edgeMarginInPixels?: number;
|
|
72
|
+
/**
|
|
73
|
+
* How tall the device's status bar is, in framebuffer pixels.
|
|
74
|
+
*
|
|
75
|
+
* **Measured, never assumed, and deliberately without a default.** 24dp at a
|
|
76
|
+
* density of 320 is 48px, and that is one AVD's number rather than a constant
|
|
77
|
+
* of Android. Measure it on a frame: the bar's own content occupies the top
|
|
78
|
+
* rows, a few empty rows follow, and the app's top chrome starts at exactly
|
|
79
|
+
* this offset.
|
|
80
|
+
*/
|
|
81
|
+
readonly heightInPixels: number;
|
|
82
|
+
/**
|
|
83
|
+
* How far above the band's bottom edge the fill color is sampled, in pixels.
|
|
84
|
+
*
|
|
85
|
+
* @default {@link DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS}
|
|
86
|
+
*/
|
|
87
|
+
readonly sampleInsetInPixels?: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parameters for {@link resolveStatusBarBand}.
|
|
91
|
+
*/
|
|
92
|
+
export interface ResolveStatusBarBandParams {
|
|
93
|
+
/**
|
|
94
|
+
* How many bytes each pixel occupies in {@link ResolveStatusBarBandParams.pixels} — 3 for RGB, 4 for RGBA.
|
|
95
|
+
*/
|
|
96
|
+
readonly channelCount: number;
|
|
97
|
+
/**
|
|
98
|
+
* How far in from each edge to start reading, in pixels.
|
|
99
|
+
*
|
|
100
|
+
* A device's rounded corners darken a handful of pixels at both ends of every
|
|
101
|
+
* row in the band, constantly and harmlessly; the check is about what the APP
|
|
102
|
+
* draws, so it ignores them.
|
|
103
|
+
*
|
|
104
|
+
* @default {@link DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS}
|
|
105
|
+
*/
|
|
106
|
+
readonly edgeMarginInPixels?: number;
|
|
107
|
+
/**
|
|
108
|
+
* The height of the band, in pixels — the same number {@link paintOutStatusBar} would paint.
|
|
109
|
+
*/
|
|
110
|
+
readonly heightInPixels: number;
|
|
111
|
+
/**
|
|
112
|
+
* The frame's raw pixels, row-major, as `sharp`'s `raw()` reports them.
|
|
113
|
+
*/
|
|
114
|
+
readonly pixels: Uint8Array;
|
|
115
|
+
/**
|
|
116
|
+
* How far above the band's bottom edge the row is read from, in pixels.
|
|
117
|
+
*
|
|
118
|
+
* The bar's own content stops short of the band's bottom edge, so the rows
|
|
119
|
+
* immediately under it are empty; this picks one of them.
|
|
120
|
+
*
|
|
121
|
+
* @default {@link DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS}
|
|
122
|
+
*/
|
|
123
|
+
readonly sampleInsetInPixels?: number;
|
|
124
|
+
/**
|
|
125
|
+
* The frame's width, in pixels.
|
|
126
|
+
*/
|
|
127
|
+
readonly widthInPixels: number;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* A color read out of a frame, channel by channel.
|
|
131
|
+
*
|
|
132
|
+
* This package's own type rather than `sharp`'s, because it is part of the
|
|
133
|
+
* public surface and `sharp` is an OPTIONAL peer: a consumer that never installs
|
|
134
|
+
* it still names this. Structurally what `sharp`'s `background` takes, which is
|
|
135
|
+
* how the fill block is created from one with no conversion in between.
|
|
136
|
+
*/
|
|
137
|
+
export interface StatusBarBandColor {
|
|
138
|
+
/**
|
|
139
|
+
* The blue channel, 0-255.
|
|
140
|
+
*/
|
|
141
|
+
readonly b: number;
|
|
142
|
+
/**
|
|
143
|
+
* The green channel, 0-255.
|
|
144
|
+
*/
|
|
145
|
+
readonly g: number;
|
|
146
|
+
/**
|
|
147
|
+
* The red channel, 0-255.
|
|
148
|
+
*/
|
|
149
|
+
readonly r: number;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* What the row under the status bar's content turned out to hold.
|
|
153
|
+
*
|
|
154
|
+
* Verdict-as-data, like the compatibility and teardown checks: the reading and
|
|
155
|
+
* the throwing are separate, so a caller can ask what a frame looks like without
|
|
156
|
+
* having to catch an error to find out.
|
|
157
|
+
*/
|
|
158
|
+
export interface StatusBarBandVerdict {
|
|
159
|
+
/**
|
|
160
|
+
* The color the band would be filled with — read from the first scanned column of the sample row.
|
|
161
|
+
*/
|
|
162
|
+
readonly backgroundColor: StatusBarBandColor;
|
|
163
|
+
/**
|
|
164
|
+
* How many distinct colors the scanned row holds. One is what an empty band looks like.
|
|
165
|
+
*/
|
|
166
|
+
readonly distinctColorCount: number;
|
|
167
|
+
/**
|
|
168
|
+
* The color at {@link StatusBarBandVerdict.firstDifferentColumnXInPixels}, or `null` when the row is flat.
|
|
169
|
+
*/
|
|
170
|
+
readonly firstDifferentColor: null | StatusBarBandColor;
|
|
171
|
+
/**
|
|
172
|
+
* The first scanned column that differs from {@link StatusBarBandVerdict.backgroundColor}, or `null` when the row is flat.
|
|
173
|
+
*/
|
|
174
|
+
readonly firstDifferentColumnXInPixels: null | number;
|
|
175
|
+
/**
|
|
176
|
+
* Whether the row is one flat color, and so whether the band holds nothing but device chrome.
|
|
177
|
+
*/
|
|
178
|
+
readonly isClear: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Which row was read, as a distance from the top of the frame in pixels.
|
|
181
|
+
*/
|
|
182
|
+
readonly sampleRowYInPixels: number;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* How far in from each edge the clear-of-chrome check reads, in pixels.
|
|
186
|
+
*
|
|
187
|
+
* Exported because a caller tightening or loosening it wants to say so relative
|
|
188
|
+
* to this rather than in the abstract.
|
|
189
|
+
*/
|
|
190
|
+
export declare const DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS = 100;
|
|
191
|
+
/**
|
|
192
|
+
* How far above the band's bottom edge the sample row sits, in pixels.
|
|
193
|
+
*
|
|
194
|
+
* Four rows up: far enough to clear the bar's own content, close enough to still
|
|
195
|
+
* be inside the band on every device this has been measured on.
|
|
196
|
+
*/
|
|
197
|
+
export declare const DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS = 4;
|
|
198
|
+
/**
|
|
199
|
+
* Builds the message a band that is not clear is refused with.
|
|
200
|
+
*
|
|
201
|
+
* Separate from the throwing so it can be asserted on directly: what makes this
|
|
202
|
+
* failure actionable is the measurement, not the wording around it.
|
|
203
|
+
*
|
|
204
|
+
* @param params - The verdict and the geometry it was read with.
|
|
205
|
+
* @returns The message, ready to throw.
|
|
206
|
+
*/
|
|
207
|
+
export declare function buildStatusBarBandFailureMessage(params: BuildStatusBarBandFailureMessageParams): string;
|
|
208
|
+
/**
|
|
209
|
+
* Fills the status-bar band with the background behind it, so the frame carries no device chrome.
|
|
210
|
+
*
|
|
211
|
+
* The image keeps its dimensions exactly: the band is composited OVER the frame,
|
|
212
|
+
* never cropped off it, because a store listing expects a specific size and a
|
|
213
|
+
* cropped frame is a different picture.
|
|
214
|
+
*
|
|
215
|
+
* @param bytes - The device framebuffer, as `captureDeviceScreenshot` returned it.
|
|
216
|
+
* @param options - How tall the band is, and how the check reads it.
|
|
217
|
+
* @returns A {@link Promise} that resolves to the same frame with the band painted out, as PNG bytes.
|
|
218
|
+
* @throws Error if `sharp` is not installed, the band is as tall as the frame or taller, or the row under
|
|
219
|
+
* the bar's content is not one flat color — see {@link buildStatusBarBandFailureMessage}.
|
|
220
|
+
*/
|
|
221
|
+
export declare function paintOutStatusBar(bytes: Uint8Array, options: PaintOutStatusBarOptions): Promise<Uint8Array>;
|
|
222
|
+
/**
|
|
223
|
+
* Reads the row under the status bar's content and says whether the band is clear of the app.
|
|
224
|
+
*
|
|
225
|
+
* The band's height is a number a caller measured once, and a measured number
|
|
226
|
+
* can go stale — a taller status bar, or an Obsidian that draws higher, would
|
|
227
|
+
* have the capture quietly paint over the product. So the row just below the
|
|
228
|
+
* bar's own content is required to be one flat color all the way across: that
|
|
229
|
+
* is what an empty band looks like, and it is what the fill color is read from.
|
|
230
|
+
*
|
|
231
|
+
* @param params - The frame's raw pixels and the band's geometry.
|
|
232
|
+
* @returns What the scanned row holds.
|
|
233
|
+
* @throws Error if the geometry is degenerate — a non-positive dimension, a sample row above the top of
|
|
234
|
+
* the frame, a margin that leaves nothing to scan, or a buffer too short to hold the row.
|
|
235
|
+
*/
|
|
236
|
+
export declare function resolveStatusBarBand(params: ResolveStatusBarBandParams): StatusBarBandVerdict;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { importSharp } from "./sharp-loader.mjs";
|
|
2
|
+
const DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS = 100;
|
|
3
|
+
const DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS = 4;
|
|
4
|
+
const BLUE_CHANNEL_OFFSET = 2;
|
|
5
|
+
const FILL_CHANNEL_COUNT = 3;
|
|
6
|
+
const GREEN_CHANNEL_OFFSET = 1;
|
|
7
|
+
const MINIMUM_SCANNED_COLUMN_COUNT = 2;
|
|
8
|
+
const RED_CHANNEL_OFFSET = 0;
|
|
9
|
+
function buildStatusBarBandFailureMessage(params) {
|
|
10
|
+
const {
|
|
11
|
+
edgeMarginInPixels,
|
|
12
|
+
heightInPixels,
|
|
13
|
+
verdict,
|
|
14
|
+
widthInPixels
|
|
15
|
+
} = params;
|
|
16
|
+
const lines = [
|
|
17
|
+
`paintOutStatusBar: the row under the status bar holds more than one color, so a band of ${String(heightInPixels)}px would paint over the app rather than over the device.`,
|
|
18
|
+
`band=${String(heightInPixels)}px sampleRow=y${String(verdict.sampleRowYInPixels)} scanned x${String(edgeMarginInPixels)}..${String(widthInPixels - edgeMarginInPixels)} of ${String(widthInPixels)}px distinctColors=${String(verdict.distinctColorCount)}`,
|
|
19
|
+
`sampled ${formatColor(verdict.backgroundColor)}, but x=${String(verdict.firstDifferentColumnXInPixels)} is ${formatColor(verdict.firstDifferentColor)}`,
|
|
20
|
+
"A taller status bar, or an Obsidian that draws higher, both look like this. Measure the band on a fresh frame and pass the height you measured: enlarging it until the check passes is how a capture paints over the product."
|
|
21
|
+
];
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
|
24
|
+
async function paintOutStatusBar(bytes, options) {
|
|
25
|
+
const { heightInPixels } = options;
|
|
26
|
+
const edgeMarginInPixels = options.edgeMarginInPixels ?? DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS;
|
|
27
|
+
const sharp = await importSharp("paintOutStatusBar");
|
|
28
|
+
const { data, info } = await sharp(bytes).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
29
|
+
if (heightInPixels >= info.height) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`paintOutStatusBar: a band of ${String(heightInPixels)}px would cover a frame that is only ${String(info.height)}px tall. That is a measurement of some other device, not of this frame.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const verdict = resolveStatusBarBand({
|
|
35
|
+
channelCount: info.channels,
|
|
36
|
+
edgeMarginInPixels,
|
|
37
|
+
heightInPixels,
|
|
38
|
+
pixels: data,
|
|
39
|
+
...options.sampleInsetInPixels !== void 0 && { sampleInsetInPixels: options.sampleInsetInPixels },
|
|
40
|
+
widthInPixels: info.width
|
|
41
|
+
});
|
|
42
|
+
if (!verdict.isClear) {
|
|
43
|
+
throw new Error(buildStatusBarBandFailureMessage({
|
|
44
|
+
edgeMarginInPixels,
|
|
45
|
+
heightInPixels,
|
|
46
|
+
verdict,
|
|
47
|
+
widthInPixels: info.width
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
const layer = {
|
|
51
|
+
input: {
|
|
52
|
+
create: {
|
|
53
|
+
background: verdict.backgroundColor,
|
|
54
|
+
channels: FILL_CHANNEL_COUNT,
|
|
55
|
+
height: heightInPixels,
|
|
56
|
+
width: info.width
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
left: 0,
|
|
60
|
+
top: 0
|
|
61
|
+
};
|
|
62
|
+
const painted = await sharp(bytes).composite([layer]).png().toBuffer();
|
|
63
|
+
return new Uint8Array(painted);
|
|
64
|
+
}
|
|
65
|
+
function resolveStatusBarBand(params) {
|
|
66
|
+
const {
|
|
67
|
+
channelCount,
|
|
68
|
+
heightInPixels,
|
|
69
|
+
pixels,
|
|
70
|
+
widthInPixels
|
|
71
|
+
} = params;
|
|
72
|
+
const edgeMarginInPixels = params.edgeMarginInPixels ?? DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS;
|
|
73
|
+
const sampleInsetInPixels = params.sampleInsetInPixels ?? DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS;
|
|
74
|
+
assertPositive("widthInPixels", widthInPixels);
|
|
75
|
+
assertPositive("heightInPixels", heightInPixels);
|
|
76
|
+
assertPositive("channelCount", channelCount);
|
|
77
|
+
const sampleRowYInPixels = heightInPixels - sampleInsetInPixels;
|
|
78
|
+
if (sampleRowYInPixels < 0) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`resolveStatusBarBand: a sample inset of ${String(sampleInsetInPixels)}px puts the sample row above the top of the frame for a band of ${String(heightInPixels)}px.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const lastScannedColumnXInPixels = widthInPixels - edgeMarginInPixels;
|
|
84
|
+
if (lastScannedColumnXInPixels - edgeMarginInPixels < MINIMUM_SCANNED_COLUMN_COUNT) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`resolveStatusBarBand: an edge margin of ${String(edgeMarginInPixels)}px leaves fewer than ${String(MINIMUM_SCANNED_COLUMN_COUNT)} columns of a ${String(widthInPixels)}px frame to scan, which is a check that cannot fail.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const rowStartOffset = sampleRowYInPixels * widthInPixels * channelCount;
|
|
90
|
+
const requiredLength = rowStartOffset + widthInPixels * channelCount;
|
|
91
|
+
if (pixels.length < requiredLength) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`resolveStatusBarBand: the frame holds ${String(pixels.length)} bytes, too few for row y${String(sampleRowYInPixels)} of a ${String(widthInPixels)}px-wide frame at ${String(channelCount)} channels, which needs ${String(requiredLength)}.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const backgroundColor = readColor(pixels, rowStartOffset + edgeMarginInPixels * channelCount);
|
|
97
|
+
const backgroundKey = formatColor(backgroundColor);
|
|
98
|
+
const colorKeys = /* @__PURE__ */ new Set();
|
|
99
|
+
let firstDifferentColor = null;
|
|
100
|
+
let firstDifferentColumnXInPixels = null;
|
|
101
|
+
for (let x = edgeMarginInPixels; x < lastScannedColumnXInPixels; x++) {
|
|
102
|
+
const color = readColor(pixels, rowStartOffset + x * channelCount);
|
|
103
|
+
const colorKey = formatColor(color);
|
|
104
|
+
colorKeys.add(colorKey);
|
|
105
|
+
if (firstDifferentColumnXInPixels !== null || colorKey === backgroundKey) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
firstDifferentColor = color;
|
|
109
|
+
firstDifferentColumnXInPixels = x;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
backgroundColor,
|
|
113
|
+
distinctColorCount: colorKeys.size,
|
|
114
|
+
firstDifferentColor,
|
|
115
|
+
firstDifferentColumnXInPixels,
|
|
116
|
+
isClear: colorKeys.size === 1,
|
|
117
|
+
sampleRowYInPixels
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function assertPositive(name, value) {
|
|
121
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
122
|
+
throw new Error(`resolveStatusBarBand: ${name} must be a positive number, got ${String(value)}.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function formatColor(color) {
|
|
126
|
+
return color === null ? "(none)" : `rgb(${String(color.r)},${String(color.g)},${String(color.b)})`;
|
|
127
|
+
}
|
|
128
|
+
function readColor(pixels, offset) {
|
|
129
|
+
return {
|
|
130
|
+
b: pixels[offset + BLUE_CHANNEL_OFFSET] ?? 0,
|
|
131
|
+
g: pixels[offset + GREEN_CHANNEL_OFFSET] ?? 0,
|
|
132
|
+
r: pixels[offset + RED_CHANNEL_OFFSET] ?? 0
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS,
|
|
137
|
+
DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS,
|
|
138
|
+
buildStatusBarBandFailureMessage,
|
|
139
|
+
paintOutStatusBar,
|
|
140
|
+
resolveStatusBarBand
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3BhaW50LW91dC1zdGF0dXMtYmFyLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogUGFpbnRzIHRoZSBkZXZpY2UncyBzdGF0dXMgYmFyIG91dCBvZiBhIGNhcHR1cmVkIGZyYW1lLCBzbyBhIGRldmljZSBjYXB0dXJlXG4gKiBjYW4gYmUgY29tbWl0dGVkLlxuICpcbiAqIEEgZnJhbWVidWZmZXIgY2FycmllcyB0aGUgc3RhdHVzIGJhciwgYW5kIGEgc3RhdHVzIGJhciBjYXJyaWVzIGEgd2FsbCBjbG9jaywgYVxuICogYmF0dGVyeSB0aGF0IGNoYXJnZXMgd2hpbGUgdGhlIGVtdWxhdG9yIHJ1bnMgYW5kIGEgcmFkaW8gdGhhdCBjb21lcyBhbmQgZ29lcy5cbiAqIFNvIHJlLXJ1bm5pbmcgYSBjYXB0dXJlIHN1aXRlIG9uIGFuIHVuY2hhbmdlZCB0cmVlIHJld3JpdGVzIGl0cyBQTkdzIHdpdGhcbiAqIGNvbnRlbnQgbm9ib2R5IGNhbiBjb21taXQsIGFuZCB0aGUgY2h1cm4gaXMgaW52aXNpYmxlIHVudGlsIHNvbWVvbmUgZGlmZnMgdHdvXG4gKiBydW5zLiBgZGV2aWNlLXNjcmVlbnNob3RgJ3MgaGVhZGVyIGhhcyBhbHdheXMgc2FpZCBhIGRldmljZSBjYXB0dXJlIGlzIG5vdFxuICogYnl0ZS1yZXByb2R1Y2libGU7IHRoaXMgaXMgd2hhdCBtYWtlcyBvbmUuXG4gKlxuICogKipQaW5uaW5nIHRoZSBiYXIgd2FzIHRyaWVkIGZpcnN0LCBhbmQgaXMgTk9UIHN1ZmZpY2llbnQgXHUyMDE0IHRoYXQgZmluZGluZyBpcyB0aGVcbiAqIHJlYXNvbiB0aGlzIGhlbHBlciBleGlzdHMgcmF0aGVyIHRoYW4gYSBgd2l0aFN5c3RlbVVpRGVtb01vZGVgIG9uZS4qKiBTeXN0ZW1VSVxuICogZGVtbyBtb2RlIChgc3lzdWlfZGVtb19hbGxvd2VkYCwgcGx1cyBvbmUgYGNvbS5hbmRyb2lkLnN5c3RlbXVpLmRlbW9gXG4gKiBicm9hZGNhc3QgcGVyIGFyZWE6IGNsb2NrIGF0IGEgZml4ZWQgYGhobW1gLCBiYXR0ZXJ5IGZ1bGwgYW5kIHVucGx1Z2dlZCxcbiAqIHJhZGlvcyBoaWRkZW4sIG5vdGlmaWNhdGlvbiBpY29ucyBvZmYpIGhlbGQgdGhlIGNsb2NrIGF0IGAxMjowMGAgYW5kIHR3byBydW5zXG4gKiBzdGlsbCBkaXNhZ3JlZWQgb24gKioxODIzIGZ1bGwtY29udHJhc3QgcGl4ZWxzKiosIGJlY2F1c2UgdGhlIGJhcidzIGxlYWRpbmdcbiAqIGdyb3VwIGlzIGxhaWQgb3V0IGF0IGEgZGlmZmVyZW50IG9mZnNldCBiZXR3ZWVuIGVtdWxhdG9yIGJvb3RzIFx1MjAxNFxuICogaW50ZXJtaXR0ZW50bHksIHJvdWdobHkgb25lIGJvb3QgaW4gZm91ci4gVGhlIFR1bmVyJ3MgYGljb25fYmxhY2tsaXN0YCBkb2VzXG4gKiBub3QgaGVscCBlaXRoZXI6IGRlbW8gbW9kZSBkcmF3cyBpdHMgb3duIGljb24gbGF5ZXIgYW5kIGlnbm9yZXMgaXQuIE1lYXN1cmVkXG4gKiBvbiBhIHJlYWwgQVZEIGFjcm9zcyBmaXZlIHBhaXJlZCBjYXB0dXJlIHJ1bnMgZm9yIGBvYnNpZGlhbi1saW5rLXBpY2tlcmAnc1xuICogc3RvcmUgZnJhbWVzLCB3aGljaCBpcyB3aGVyZSB0aGlzIHJlY2lwZSB3YXMgd29ya2VkIG91dCBiZWZvcmUgaXQgbW92ZWQgaGVyZS5cbiAqXG4gKiBSZW1vdmluZyB0aGUgYmFuZCByYXRoZXIgdGhhbiBwaW5uaW5nIGl0IGFsc28gcmV0aXJlcyBldmVyeSBkZXZpY2Ugc2V0dGluZ1xuICogdGhhdCBwaW5uaW5nIG5lZWRlZCwgYW5kIG5vdGhpbmcgaW4gYSBzdGF0dXMgYmFyIGlzIGV2aWRlbmNlIGFib3V0IGEgcGx1Z2luLiBBXG4gKiBsaXN0aW5nIGZyYW1lIHdpdGhvdXQgb25lIGlzIHdoYXQgYSBsaXN0aW5nIGZyYW1lIG5vcm1hbGx5IGxvb2tzIGxpa2UuXG4gKlxuICogKipUaGUgYmFuZCdzIGhlaWdodCBpcyB0aGUgY2FsbGVyJ3MgdG8gbWVhc3VyZSwgYW5kIHRoZXJlIGlzIGRlbGliZXJhdGVseSBub1xuICogZGVmYXVsdC4qKiBBIHdyb25nIGhlaWdodCBkb2VzIG5vdCBmYWlsIGxvdWRseSBieSBpdHNlbGYgXHUyMDE0IGl0IHBhaW50cyBvdmVyIHRoZVxuICogcHJvZHVjdC4gV2hhdCBzdGFuZHMgYmV0d2VlbiB0aGUgdHdvIGlzIHtAbGluayByZXNvbHZlU3RhdHVzQmFyQmFuZH06IHRoZSByb3dcbiAqIGp1c3QgdW5kZXIgdGhlIGJhcidzIG93biBjb250ZW50IG11c3QgYmUgb25lIGZsYXQgY29sb3IgYWxsIHRoZSB3YXkgYWNyb3NzLFxuICogd2hpY2ggaXMgd2hhdCBhbiBlbXB0eSBiYW5kIGxvb2tzIGxpa2UgYW5kIGlzIGFsc28gd2hlcmUgdGhlIGZpbGwgY29sb3IgaXNcbiAqIHNhbXBsZWQgZnJvbS4gQSB0YWxsZXIgYmFyLCBvciBhbiBPYnNpZGlhbiB0aGF0IGRyYXdzIGhpZ2hlciwgZmFpbHMgdGhlXG4gKiBjYXB0dXJlIGluc3RlYWQuXG4gKlxuICogQ29tcG9zaXRpbmcgb3ZlciBhIGNhcHR1cmVkIGZyYW1lIGlzIG5vdCBhIG5ldyBsaWJlcnR5OiBgbGFiZWxTY3JlZW5zaG90YFxuICogYWxyZWFkeSBkcmF3cyB0aGUgY2FwdGlvbiBiYW5kIGFjcm9zcyB0aGUgYm90dG9tIG9mIHRoZSBzYW1lIGltYWdlLlxuICovXG5cbmltcG9ydCB0eXBlIHsgU2hhcnBDb21wb3NpdGVMYXllciB9IGZyb20gJy4vc2hhcnAtbG9hZGVyLm1qcyc7XG5cbmltcG9ydCB7IGltcG9ydFNoYXJwIH0gZnJvbSAnLi9zaGFycC1sb2FkZXIubWpzJztcblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgYnVpbGRTdGF0dXNCYXJCYW5kRmFpbHVyZU1lc3NhZ2V9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEJ1aWxkU3RhdHVzQmFyQmFuZEZhaWx1cmVNZXNzYWdlUGFyYW1zIHtcbiAgLyoqXG4gICAqIEhvdyBmYXIgaW4gZnJvbSBlYWNoIGVkZ2UgdGhlIHJvdyB3YXMgc2Nhbm5lZCwgaW4gcGl4ZWxzLlxuICAgKi9cbiAgcmVhZG9ubHkgZWRnZU1hcmdpbkluUGl4ZWxzOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIFRoZSBoZWlnaHQgb2YgdGhlIGJhbmQgdGhhdCB3YXMgYWJvdXQgdG8gYmUgcGFpbnRlZCwgaW4gcGl4ZWxzLlxuICAgKi9cbiAgcmVhZG9ubHkgaGVpZ2h0SW5QaXhlbHM6IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIHZlcmRpY3QgYmVpbmcgcmVwb3J0ZWQuXG4gICAqL1xuICByZWFkb25seSB2ZXJkaWN0OiBTdGF0dXNCYXJCYW5kVmVyZGljdDtcblxuICAvKipcbiAgICogVGhlIGZyYW1lJ3Mgd2lkdGgsIGluIHBpeGVscy5cbiAgICovXG4gIHJlYWRvbmx5IHdpZHRoSW5QaXhlbHM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBPcHRpb25zIGZvciB7QGxpbmsgcGFpbnRPdXRTdGF0dXNCYXJ9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFBhaW50T3V0U3RhdHVzQmFyT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBIb3cgZmFyIGluIGZyb20gZWFjaCBlZGdlIHRoZSBjbGVhci1vZi1jaHJvbWUgY2hlY2sgcmVhZHMuXG4gICAqXG4gICAqIEBkZWZhdWx0IHtAbGluayBERUZBVUxUX1NUQVRVU19CQVJfRURHRV9NQVJHSU5fSU5fUElYRUxTfVxuICAgKi9cbiAgcmVhZG9ubHkgZWRnZU1hcmdpbkluUGl4ZWxzPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBIb3cgdGFsbCB0aGUgZGV2aWNlJ3Mgc3RhdHVzIGJhciBpcywgaW4gZnJhbWVidWZmZXIgcGl4ZWxzLlxuICAgKlxuICAgKiAqKk1lYXN1cmVkLCBuZXZlciBhc3N1bWVkLCBhbmQgZGVsaWJlcmF0ZWx5IHdpdGhvdXQgYSBkZWZhdWx0LioqIDI0ZHAgYXQgYVxuICAgKiBkZW5zaXR5IG9mIDMyMCBpcyA0OHB4LCBhbmQgdGhhdCBpcyBvbmUgQVZEJ3MgbnVtYmVyIHJhdGhlciB0aGFuIGEgY29uc3RhbnRcbiAgICogb2YgQW5kcm9pZC4gTWVhc3VyZSBpdCBvbiBhIGZyYW1lOiB0aGUgYmFyJ3Mgb3duIGNvbnRlbnQgb2NjdXBpZXMgdGhlIHRvcFxuICAgKiByb3dzLCBhIGZldyBlbXB0eSByb3dzIGZvbGxvdywgYW5kIHRoZSBhcHAncyB0b3AgY2hyb21lIHN0YXJ0cyBhdCBleGFjdGx5XG4gICAqIHRoaXMgb2Zmc2V0LlxuICAgKi9cbiAgcmVhZG9ubHkgaGVpZ2h0SW5QaXhlbHM6IG51bWJlcjtcblxuICAvKipcbiAgICogSG93IGZhciBhYm92ZSB0aGUgYmFuZCdzIGJvdHRvbSBlZGdlIHRoZSBmaWxsIGNvbG9yIGlzIHNhbXBsZWQsIGluIHBpeGVscy5cbiAgICpcbiAgICogQGRlZmF1bHQge0BsaW5rIERFRkFVTFRfU1RBVFVTX0JBUl9TQU1QTEVfSU5TRVRfSU5fUElYRUxTfVxuICAgKi9cbiAgcmVhZG9ubHkgc2FtcGxlSW5zZXRJblBpeGVscz86IG51bWJlcjtcbn1cblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgcmVzb2x2ZVN0YXR1c0JhckJhbmR9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc29sdmVTdGF0dXNCYXJCYW5kUGFyYW1zIHtcbiAgLyoqXG4gICAqIEhvdyBtYW55IGJ5dGVzIGVhY2ggcGl4ZWwgb2NjdXBpZXMgaW4ge0BsaW5rIFJlc29sdmVTdGF0dXNCYXJCYW5kUGFyYW1zLnBpeGVsc30gXHUyMDE0IDMgZm9yIFJHQiwgNCBmb3IgUkdCQS5cbiAgICovXG4gIHJlYWRvbmx5IGNoYW5uZWxDb3VudDogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBIb3cgZmFyIGluIGZyb20gZWFjaCBlZGdlIHRvIHN0YXJ0IHJlYWRpbmcsIGluIHBpeGVscy5cbiAgICpcbiAgICogQSBkZXZpY2UncyByb3VuZGVkIGNvcm5lcnMgZGFya2VuIGEgaGFuZGZ1bCBvZiBwaXhlbHMgYXQgYm90aCBlbmRzIG9mIGV2ZXJ5XG4gICAqIHJvdyBpbiB0aGUgYmFuZCwgY29uc3RhbnRseSBhbmQgaGFybWxlc3NseTsgdGhlIGNoZWNrIGlzIGFib3V0IHdoYXQgdGhlIEFQUFxuICAgKiBkcmF3cywgc28gaXQgaWdub3JlcyB0aGVtLlxuICAgKlxuICAgKiBAZGVmYXVsdCB7QGxpbmsgREVGQVVMVF9TVEFUVVNfQkFSX0VER0VfTUFSR0lOX0lOX1BJWEVMU31cbiAgICovXG4gIHJlYWRvbmx5IGVkZ2VNYXJnaW5JblBpeGVscz86IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGhlaWdodCBvZiB0aGUgYmFuZCwgaW4gcGl4ZWxzIFx1MjAxNCB0aGUgc2FtZSBudW1iZXIge0BsaW5rIHBhaW50T3V0U3RhdHVzQmFyfSB3b3VsZCBwYWludC5cbiAgICovXG4gIHJlYWRvbmx5IGhlaWdodEluUGl4ZWxzOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIFRoZSBmcmFtZSdzIHJhdyBwaXhlbHMsIHJvdy1tYWpvciwgYXMgYHNoYXJwYCdzIGByYXcoKWAgcmVwb3J0cyB0aGVtLlxuICAgKi9cbiAgcmVhZG9ubHkgcGl4ZWxzOiBVaW50OEFycmF5O1xuXG4gIC8qKlxuICAgKiBIb3cgZmFyIGFib3ZlIHRoZSBiYW5kJ3MgYm90dG9tIGVkZ2UgdGhlIHJvdyBpcyByZWFkIGZyb20sIGluIHBpeGVscy5cbiAgICpcbiAgICogVGhlIGJhcidzIG93biBjb250ZW50IHN0b3BzIHNob3J0IG9mIHRoZSBiYW5kJ3MgYm90dG9tIGVkZ2UsIHNvIHRoZSByb3dzXG4gICAqIGltbWVkaWF0ZWx5IHVuZGVyIGl0IGFyZSBlbXB0eTsgdGhpcyBwaWNrcyBvbmUgb2YgdGhlbS5cbiAgICpcbiAgICogQGRlZmF1bHQge0BsaW5rIERFRkFVTFRfU1RBVFVTX0JBUl9TQU1QTEVfSU5TRVRfSU5fUElYRUxTfVxuICAgKi9cbiAgcmVhZG9ubHkgc2FtcGxlSW5zZXRJblBpeGVscz86IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGZyYW1lJ3Mgd2lkdGgsIGluIHBpeGVscy5cbiAgICovXG4gIHJlYWRvbmx5IHdpZHRoSW5QaXhlbHM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBBIGNvbG9yIHJlYWQgb3V0IG9mIGEgZnJhbWUsIGNoYW5uZWwgYnkgY2hhbm5lbC5cbiAqXG4gKiBUaGlzIHBhY2thZ2UncyBvd24gdHlwZSByYXRoZXIgdGhhbiBgc2hhcnBgJ3MsIGJlY2F1c2UgaXQgaXMgcGFydCBvZiB0aGVcbiAqIHB1YmxpYyBzdXJmYWNlIGFuZCBgc2hhcnBgIGlzIGFuIE9QVElPTkFMIHBlZXI6IGEgY29uc3VtZXIgdGhhdCBuZXZlciBpbnN0YWxsc1xuICogaXQgc3RpbGwgbmFtZXMgdGhpcy4gU3RydWN0dXJhbGx5IHdoYXQgYHNoYXJwYCdzIGBiYWNrZ3JvdW5kYCB0YWtlcywgd2hpY2ggaXNcbiAqIGhvdyB0aGUgZmlsbCBibG9jayBpcyBjcmVhdGVkIGZyb20gb25lIHdpdGggbm8gY29udmVyc2lvbiBpbiBiZXR3ZWVuLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFN0YXR1c0JhckJhbmRDb2xvciB7XG4gIC8qKlxuICAgKiBUaGUgYmx1ZSBjaGFubmVsLCAwLTI1NS5cbiAgICovXG4gIHJlYWRvbmx5IGI6IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGdyZWVuIGNoYW5uZWwsIDAtMjU1LlxuICAgKi9cbiAgcmVhZG9ubHkgZzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBUaGUgcmVkIGNoYW5uZWwsIDAtMjU1LlxuICAgKi9cbiAgcmVhZG9ubHkgcjogbnVtYmVyO1xufVxuXG4vKipcbiAqIFdoYXQgdGhlIHJvdyB1bmRlciB0aGUgc3RhdHVzIGJhcidzIGNvbnRlbnQgdHVybmVkIG91dCB0byBob2xkLlxuICpcbiAqIFZlcmRpY3QtYXMtZGF0YSwgbGlrZSB0aGUgY29tcGF0aWJpbGl0eSBhbmQgdGVhcmRvd24gY2hlY2tzOiB0aGUgcmVhZGluZyBhbmRcbiAqIHRoZSB0aHJvd2luZyBhcmUgc2VwYXJhdGUsIHNvIGEgY2FsbGVyIGNhbiBhc2sgd2hhdCBhIGZyYW1lIGxvb2tzIGxpa2Ugd2l0aG91dFxuICogaGF2aW5nIHRvIGNhdGNoIGFuIGVycm9yIHRvIGZpbmQgb3V0LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFN0YXR1c0JhckJhbmRWZXJkaWN0IHtcbiAgLyoqXG4gICAqIFRoZSBjb2xvciB0aGUgYmFuZCB3b3VsZCBiZSBmaWxsZWQgd2l0aCBcdTIwMTQgcmVhZCBmcm9tIHRoZSBmaXJzdCBzY2FubmVkIGNvbHVtbiBvZiB0aGUgc2FtcGxlIHJvdy5cbiAgICovXG4gIHJlYWRvbmx5IGJhY2tncm91bmRDb2xvcjogU3RhdHVzQmFyQmFuZENvbG9yO1xuXG4gIC8qKlxuICAgKiBIb3cgbWFueSBkaXN0aW5jdCBjb2xvcnMgdGhlIHNjYW5uZWQgcm93IGhvbGRzLiBPbmUgaXMgd2hhdCBhbiBlbXB0eSBiYW5kIGxvb2tzIGxpa2UuXG4gICAqL1xuICByZWFkb25seSBkaXN0aW5jdENvbG9yQ291bnQ6IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGNvbG9yIGF0IHtAbGluayBTdGF0dXNCYXJCYW5kVmVyZGljdC5maXJzdERpZmZlcmVudENvbHVtblhJblBpeGVsc30sIG9yIGBudWxsYCB3aGVuIHRoZSByb3cgaXMgZmxhdC5cbiAgICovXG4gIHJlYWRvbmx5IGZpcnN0RGlmZmVyZW50Q29sb3I6IG51bGwgfCBTdGF0dXNCYXJCYW5kQ29sb3I7XG5cbiAgLyoqXG4gICAqIFRoZSBmaXJzdCBzY2FubmVkIGNvbHVtbiB0aGF0IGRpZmZlcnMgZnJvbSB7QGxpbmsgU3RhdHVzQmFyQmFuZFZlcmRpY3QuYmFja2dyb3VuZENvbG9yfSwgb3IgYG51bGxgIHdoZW4gdGhlIHJvdyBpcyBmbGF0LlxuICAgKi9cbiAgcmVhZG9ubHkgZmlyc3REaWZmZXJlbnRDb2x1bW5YSW5QaXhlbHM6IG51bGwgfCBudW1iZXI7XG5cbiAgLyoqXG4gICAqIFdoZXRoZXIgdGhlIHJvdyBpcyBvbmUgZmxhdCBjb2xvciwgYW5kIHNvIHdoZXRoZXIgdGhlIGJhbmQgaG9sZHMgbm90aGluZyBidXQgZGV2aWNlIGNocm9tZS5cbiAgICovXG4gIHJlYWRvbmx5IGlzQ2xlYXI6IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIFdoaWNoIHJvdyB3YXMgcmVhZCwgYXMgYSBkaXN0YW5jZSBmcm9tIHRoZSB0b3Agb2YgdGhlIGZyYW1lIGluIHBpeGVscy5cbiAgICovXG4gIHJlYWRvbmx5IHNhbXBsZVJvd1lJblBpeGVsczogbnVtYmVyO1xufVxuXG4vKipcbiAqIEhvdyBmYXIgaW4gZnJvbSBlYWNoIGVkZ2UgdGhlIGNsZWFyLW9mLWNocm9tZSBjaGVjayByZWFkcywgaW4gcGl4ZWxzLlxuICpcbiAqIEV4cG9ydGVkIGJlY2F1c2UgYSBjYWxsZXIgdGlnaHRlbmluZyBvciBsb29zZW5pbmcgaXQgd2FudHMgdG8gc2F5IHNvIHJlbGF0aXZlXG4gKiB0byB0aGlzIHJhdGhlciB0aGFuIGluIHRoZSBhYnN0cmFjdC5cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfU1RBVFVTX0JBUl9FREdFX01BUkdJTl9JTl9QSVhFTFMgPSAxMDA7XG5cbi8qKlxuICogSG93IGZhciBhYm92ZSB0aGUgYmFuZCdzIGJvdHRvbSBlZGdlIHRoZSBzYW1wbGUgcm93IHNpdHMsIGluIHBpeGVscy5cbiAqXG4gKiBGb3VyIHJvd3MgdXA6IGZhciBlbm91Z2ggdG8gY2xlYXIgdGhlIGJhcidzIG93biBjb250ZW50LCBjbG9zZSBlbm91Z2ggdG8gc3RpbGxcbiAqIGJlIGluc2lkZSB0aGUgYmFuZCBvbiBldmVyeSBkZXZpY2UgdGhpcyBoYXMgYmVlbiBtZWFzdXJlZCBvbi5cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfU1RBVFVTX0JBUl9TQU1QTEVfSU5TRVRfSU5fUElYRUxTID0gNDtcblxuLyoqXG4gKiBCeXRlIG9mZnNldCBvZiB0aGUgYmx1ZSBjaGFubmVsIHdpdGhpbiBvbmUgcGl4ZWwgb2YgYSByYXcgZnJhbWUuXG4gKi9cbmNvbnN0IEJMVUVfQ0hBTk5FTF9PRkZTRVQgPSAyO1xuXG4vKipcbiAqIEhvdyBtYW55IGNoYW5uZWxzIHRoZSBmaWxsIGJsb2NrIGlzIGNyZWF0ZWQgd2l0aCBcdTIwMTQgUkdCLCB3aXRoIG5vIGFscGhhLCBiZWNhdXNlIGl0IGlzIG9wYXF1ZSBieSBjb25zdHJ1Y3Rpb24uXG4gKi9cbmNvbnN0IEZJTExfQ0hBTk5FTF9DT1VOVCA9IDM7XG5cbi8qKlxuICogQnl0ZSBvZmZzZXQgb2YgdGhlIGdyZWVuIGNoYW5uZWwgd2l0aGluIG9uZSBwaXhlbCBvZiBhIHJhdyBmcmFtZS5cbiAqL1xuY29uc3QgR1JFRU5fQ0hBTk5FTF9PRkZTRVQgPSAxO1xuXG4vKipcbiAqIFRoZSBmZXdlc3QgY29sdW1ucyBhIG1lYW5pbmdmdWwgc2NhbiBjYW4gY292ZXIuXG4gKlxuICogT25lIGNvbHVtbiBpcyB0cml2aWFsbHkgZmxhdCwgc28gYSBtYXJnaW4gdGhhdCBsZWF2ZXMgZmV3ZXIgdGhhbiB0d28gaXMgYVxuICogY2hlY2sgdGhhdCBjYW5ub3QgZmFpbCBcdTIwMTQgdGhlIGV4YWN0IHNoYXBlIHRoaXMgaGVscGVyIGV4aXN0cyB0byBhdm9pZC5cbiAqL1xuY29uc3QgTUlOSU1VTV9TQ0FOTkVEX0NPTFVNTl9DT1VOVCA9IDI7XG5cbi8qKlxuICogQnl0ZSBvZmZzZXQgb2YgdGhlIHJlZCBjaGFubmVsIHdpdGhpbiBvbmUgcGl4ZWwgb2YgYSByYXcgZnJhbWUuXG4gKi9cbmNvbnN0IFJFRF9DSEFOTkVMX09GRlNFVCA9IDA7XG5cbi8qKlxuICogQnVpbGRzIHRoZSBtZXNzYWdlIGEgYmFuZCB0aGF0IGlzIG5vdCBjbGVhciBpcyByZWZ1c2VkIHdpdGguXG4gKlxuICogU2VwYXJhdGUgZnJvbSB0aGUgdGhyb3dpbmcgc28gaXQgY2FuIGJlIGFzc2VydGVkIG9uIGRpcmVjdGx5OiB3aGF0IG1ha2VzIHRoaXNcbiAqIGZhaWx1cmUgYWN0aW9uYWJsZSBpcyB0aGUgbWVhc3VyZW1lbnQsIG5vdCB0aGUgd29yZGluZyBhcm91bmQgaXQuXG4gKlxuICogQHBhcmFtIHBhcmFtcyAtIFRoZSB2ZXJkaWN0IGFuZCB0aGUgZ2VvbWV0cnkgaXQgd2FzIHJlYWQgd2l0aC5cbiAqIEByZXR1cm5zIFRoZSBtZXNzYWdlLCByZWFkeSB0byB0aHJvdy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkU3RhdHVzQmFyQmFuZEZhaWx1cmVNZXNzYWdlKHBhcmFtczogQnVpbGRTdGF0dXNCYXJCYW5kRmFpbHVyZU1lc3NhZ2VQYXJhbXMpOiBzdHJpbmcge1xuICBjb25zdCB7XG4gICAgZWRnZU1hcmdpbkluUGl4ZWxzLFxuICAgIGhlaWdodEluUGl4ZWxzLFxuICAgIHZlcmRpY3QsXG4gICAgd2lkdGhJblBpeGVsc1xuICB9ID0gcGFyYW1zO1xuXG4gIGNvbnN0IGxpbmVzID0gW1xuICAgICdwYWludE91dFN0YXR1c0JhcjogdGhlIHJvdyB1bmRlciB0aGUgc3RhdHVzIGJhciBob2xkcyBtb3JlIHRoYW4gb25lIGNvbG9yLCBzbyBhIGJhbmQgb2YgJ1xuICAgICsgYCR7U3RyaW5nKGhlaWdodEluUGl4ZWxzKX1weCB3b3VsZCBwYWludCBvdmVyIHRoZSBhcHAgcmF0aGVyIHRoYW4gb3ZlciB0aGUgZGV2aWNlLmAsXG4gICAgYGJhbmQ9JHtTdHJpbmcoaGVpZ2h0SW5QaXhlbHMpfXB4IHNhbXBsZVJvdz15JHtTdHJpbmcodmVyZGljdC5zYW1wbGVSb3dZSW5QaXhlbHMpfSBgXG4gICAgKyBgc2Nhbm5lZCB4JHtTdHJpbmcoZWRnZU1hcmdpbkluUGl4ZWxzKX0uLiR7U3RyaW5nKHdpZHRoSW5QaXhlbHMgLSBlZGdlTWFyZ2luSW5QaXhlbHMpfSBgXG4gICAgKyBgb2YgJHtTdHJpbmcod2lkdGhJblBpeGVscyl9cHggZGlzdGluY3RDb2xvcnM9JHtTdHJpbmcodmVyZGljdC5kaXN0aW5jdENvbG9yQ291bnQpfWAsXG4gICAgYHNhbXBsZWQgJHtmb3JtYXRDb2xvcih2ZXJkaWN0LmJhY2tncm91bmRDb2xvcil9LCBidXQgeD0ke1N0cmluZyh2ZXJkaWN0LmZpcnN0RGlmZmVyZW50Q29sdW1uWEluUGl4ZWxzKX0gYFxuICAgICsgYGlzICR7Zm9ybWF0Q29sb3IodmVyZGljdC5maXJzdERpZmZlcmVudENvbG9yKX1gLFxuICAgICdBIHRhbGxlciBzdGF0dXMgYmFyLCBvciBhbiBPYnNpZGlhbiB0aGF0IGRyYXdzIGhpZ2hlciwgYm90aCBsb29rIGxpa2UgdGhpcy4gTWVhc3VyZSB0aGUgYmFuZCBvbiBhIGZyZXNoICdcbiAgICArICdmcmFtZSBhbmQgcGFzcyB0aGUgaGVpZ2h0IHlvdSBtZWFzdXJlZDogZW5sYXJnaW5nIGl0IHVudGlsIHRoZSBjaGVjayBwYXNzZXMgaXMgaG93IGEgY2FwdHVyZSBwYWludHMgJ1xuICAgICsgJ292ZXIgdGhlIHByb2R1Y3QuJ1xuICBdO1xuXG4gIHJldHVybiBsaW5lcy5qb2luKCdcXG4nKTtcbn1cblxuLyoqXG4gKiBGaWxscyB0aGUgc3RhdHVzLWJhciBiYW5kIHdpdGggdGhlIGJhY2tncm91bmQgYmVoaW5kIGl0LCBzbyB0aGUgZnJhbWUgY2FycmllcyBubyBkZXZpY2UgY2hyb21lLlxuICpcbiAqIFRoZSBpbWFnZSBrZWVwcyBpdHMgZGltZW5zaW9ucyBleGFjdGx5OiB0aGUgYmFuZCBpcyBjb21wb3NpdGVkIE9WRVIgdGhlIGZyYW1lLFxuICogbmV2ZXIgY3JvcHBlZCBvZmYgaXQsIGJlY2F1c2UgYSBzdG9yZSBsaXN0aW5nIGV4cGVjdHMgYSBzcGVjaWZpYyBzaXplIGFuZCBhXG4gKiBjcm9wcGVkIGZyYW1lIGlzIGEgZGlmZmVyZW50IHBpY3R1cmUuXG4gKlxuICogQHBhcmFtIGJ5dGVzIC0gVGhlIGRldmljZSBmcmFtZWJ1ZmZlciwgYXMgYGNhcHR1cmVEZXZpY2VTY3JlZW5zaG90YCByZXR1cm5lZCBpdC5cbiAqIEBwYXJhbSBvcHRpb25zIC0gSG93IHRhbGwgdGhlIGJhbmQgaXMsIGFuZCBob3cgdGhlIGNoZWNrIHJlYWRzIGl0LlxuICogQHJldHVybnMgQSB7QGxpbmsgUHJvbWlzZX0gdGhhdCByZXNvbHZlcyB0byB0aGUgc2FtZSBmcmFtZSB3aXRoIHRoZSBiYW5kIHBhaW50ZWQgb3V0LCBhcyBQTkcgYnl0ZXMuXG4gKiBAdGhyb3dzIEVycm9yIGlmIGBzaGFycGAgaXMgbm90IGluc3RhbGxlZCwgdGhlIGJhbmQgaXMgYXMgdGFsbCBhcyB0aGUgZnJhbWUgb3IgdGFsbGVyLCBvciB0aGUgcm93IHVuZGVyXG4gKiAgIHRoZSBiYXIncyBjb250ZW50IGlzIG5vdCBvbmUgZmxhdCBjb2xvciBcdTIwMTQgc2VlIHtAbGluayBidWlsZFN0YXR1c0JhckJhbmRGYWlsdXJlTWVzc2FnZX0uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBwYWludE91dFN0YXR1c0JhcihieXRlczogVWludDhBcnJheSwgb3B0aW9uczogUGFpbnRPdXRTdGF0dXNCYXJPcHRpb25zKTogUHJvbWlzZTxVaW50OEFycmF5PiB7XG4gIGNvbnN0IHsgaGVpZ2h0SW5QaXhlbHMgfSA9IG9wdGlvbnM7XG4gIGNvbnN0IGVkZ2VNYXJnaW5JblBpeGVscyA9IG9wdGlvbnMuZWRnZU1hcmdpbkluUGl4ZWxzID8/IERFRkFVTFRfU1RBVFVTX0JBUl9FREdFX01BUkdJTl9JTl9QSVhFTFM7XG4gIGNvbnN0IHNoYXJwID0gYXdhaXQgaW1wb3J0U2hhcnAoJ3BhaW50T3V0U3RhdHVzQmFyJyk7XG4gIGNvbnN0IHsgZGF0YSwgaW5mbyB9ID0gYXdhaXQgc2hhcnAoYnl0ZXMpXG4gICAgLmVuc3VyZUFscGhhKClcbiAgICAucmF3KClcbiAgICAudG9CdWZmZXIoeyByZXNvbHZlV2l0aE9iamVjdDogdHJ1ZSB9KTtcblxuICBpZiAoaGVpZ2h0SW5QaXhlbHMgPj0gaW5mby5oZWlnaHQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgcGFpbnRPdXRTdGF0dXNCYXI6IGEgYmFuZCBvZiAke1N0cmluZyhoZWlnaHRJblBpeGVscyl9cHggd291bGQgY292ZXIgYSBmcmFtZSB0aGF0IGlzIG9ubHkgYFxuICAgICAgICArIGAke1N0cmluZyhpbmZvLmhlaWdodCl9cHggdGFsbC4gVGhhdCBpcyBhIG1lYXN1cmVtZW50IG9mIHNvbWUgb3RoZXIgZGV2aWNlLCBub3Qgb2YgdGhpcyBmcmFtZS5gXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IHZlcmRpY3QgPSByZXNvbHZlU3RhdHVzQmFyQmFuZCh7XG4gICAgY2hhbm5lbENvdW50OiBpbmZvLmNoYW5uZWxzLFxuICAgIGVkZ2VNYXJnaW5JblBpeGVscyxcbiAgICBoZWlnaHRJblBpeGVscyxcbiAgICBwaXhlbHM6IGRhdGEsXG4gICAgLi4uKG9wdGlvbnMuc2FtcGxlSW5zZXRJblBpeGVscyAhPT0gdW5kZWZpbmVkICYmIHsgc2FtcGxlSW5zZXRJblBpeGVsczogb3B0aW9ucy5zYW1wbGVJbnNldEluUGl4ZWxzIH0pLFxuICAgIHdpZHRoSW5QaXhlbHM6IGluZm8ud2lkdGhcbiAgfSk7XG5cbiAgaWYgKCF2ZXJkaWN0LmlzQ2xlYXIpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYnVpbGRTdGF0dXNCYXJCYW5kRmFpbHVyZU1lc3NhZ2Uoe1xuICAgICAgZWRnZU1hcmdpbkluUGl4ZWxzLFxuICAgICAgaGVpZ2h0SW5QaXhlbHMsXG4gICAgICB2ZXJkaWN0LFxuICAgICAgd2lkdGhJblBpeGVsczogaW5mby53aWR0aFxuICAgIH0pKTtcbiAgfVxuXG4gIGNvbnN0IGxheWVyOiBTaGFycENvbXBvc2l0ZUxheWVyID0ge1xuICAgIGlucHV0OiB7XG4gICAgICBjcmVhdGU6IHtcbiAgICAgICAgYmFja2dyb3VuZDogdmVyZGljdC5iYWNrZ3JvdW5kQ29sb3IsXG4gICAgICAgIGNoYW5uZWxzOiBGSUxMX0NIQU5ORUxfQ09VTlQsXG4gICAgICAgIGhlaWdodDogaGVpZ2h0SW5QaXhlbHMsXG4gICAgICAgIHdpZHRoOiBpbmZvLndpZHRoXG4gICAgICB9XG4gICAgfSxcbiAgICBsZWZ0OiAwLFxuICAgIHRvcDogMFxuICB9O1xuXG4gIGNvbnN0IHBhaW50ZWQgPSBhd2FpdCBzaGFycChieXRlcylcbiAgICAuY29tcG9zaXRlKFtsYXllcl0pXG4gICAgLnBuZygpXG4gICAgLnRvQnVmZmVyKCk7XG5cbiAgcmV0dXJuIG5ldyBVaW50OEFycmF5KHBhaW50ZWQpO1xufVxuXG4vKipcbiAqIFJlYWRzIHRoZSByb3cgdW5kZXIgdGhlIHN0YXR1cyBiYXIncyBjb250ZW50IGFuZCBzYXlzIHdoZXRoZXIgdGhlIGJhbmQgaXMgY2xlYXIgb2YgdGhlIGFwcC5cbiAqXG4gKiBUaGUgYmFuZCdzIGhlaWdodCBpcyBhIG51bWJlciBhIGNhbGxlciBtZWFzdXJlZCBvbmNlLCBhbmQgYSBtZWFzdXJlZCBudW1iZXJcbiAqIGNhbiBnbyBzdGFsZSBcdTIwMTQgYSB0YWxsZXIgc3RhdHVzIGJhciwgb3IgYW4gT2JzaWRpYW4gdGhhdCBkcmF3cyBoaWdoZXIsIHdvdWxkXG4gKiBoYXZlIHRoZSBjYXB0dXJlIHF1aWV0bHkgcGFpbnQgb3ZlciB0aGUgcHJvZHVjdC4gU28gdGhlIHJvdyBqdXN0IGJlbG93IHRoZVxuICogYmFyJ3Mgb3duIGNvbnRlbnQgaXMgcmVxdWlyZWQgdG8gYmUgb25lIGZsYXQgY29sb3IgYWxsIHRoZSB3YXkgYWNyb3NzOiB0aGF0XG4gKiBpcyB3aGF0IGFuIGVtcHR5IGJhbmQgbG9va3MgbGlrZSwgYW5kIGl0IGlzIHdoYXQgdGhlIGZpbGwgY29sb3IgaXMgcmVhZCBmcm9tLlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZnJhbWUncyByYXcgcGl4ZWxzIGFuZCB0aGUgYmFuZCdzIGdlb21ldHJ5LlxuICogQHJldHVybnMgV2hhdCB0aGUgc2Nhbm5lZCByb3cgaG9sZHMuXG4gKiBAdGhyb3dzIEVycm9yIGlmIHRoZSBnZW9tZXRyeSBpcyBkZWdlbmVyYXRlIFx1MjAxNCBhIG5vbi1wb3NpdGl2ZSBkaW1lbnNpb24sIGEgc2FtcGxlIHJvdyBhYm92ZSB0aGUgdG9wIG9mXG4gKiAgIHRoZSBmcmFtZSwgYSBtYXJnaW4gdGhhdCBsZWF2ZXMgbm90aGluZyB0byBzY2FuLCBvciBhIGJ1ZmZlciB0b28gc2hvcnQgdG8gaG9sZCB0aGUgcm93LlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVN0YXR1c0JhckJhbmQocGFyYW1zOiBSZXNvbHZlU3RhdHVzQmFyQmFuZFBhcmFtcyk6IFN0YXR1c0JhckJhbmRWZXJkaWN0IHtcbiAgY29uc3Qge1xuICAgIGNoYW5uZWxDb3VudCxcbiAgICBoZWlnaHRJblBpeGVscyxcbiAgICBwaXhlbHMsXG4gICAgd2lkdGhJblBpeGVsc1xuICB9ID0gcGFyYW1zO1xuICBjb25zdCBlZGdlTWFyZ2luSW5QaXhlbHMgPSBwYXJhbXMuZWRnZU1hcmdpbkluUGl4ZWxzID8/IERFRkFVTFRfU1RBVFVTX0JBUl9FREdFX01BUkdJTl9JTl9QSVhFTFM7XG4gIGNvbnN0IHNhbXBsZUluc2V0SW5QaXhlbHMgPSBwYXJhbXMuc2FtcGxlSW5zZXRJblBpeGVscyA/PyBERUZBVUxUX1NUQVRVU19CQVJfU0FNUExFX0lOU0VUX0lOX1BJWEVMUztcblxuICBhc3NlcnRQb3NpdGl2ZSgnd2lkdGhJblBpeGVscycsIHdpZHRoSW5QaXhlbHMpO1xuICBhc3NlcnRQb3NpdGl2ZSgnaGVpZ2h0SW5QaXhlbHMnLCBoZWlnaHRJblBpeGVscyk7XG4gIGFzc2VydFBvc2l0aXZlKCdjaGFubmVsQ291bnQnLCBjaGFubmVsQ291bnQpO1xuXG4gIGNvbnN0IHNhbXBsZVJvd1lJblBpeGVscyA9IGhlaWdodEluUGl4ZWxzIC0gc2FtcGxlSW5zZXRJblBpeGVscztcblxuICBpZiAoc2FtcGxlUm93WUluUGl4ZWxzIDwgMCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGByZXNvbHZlU3RhdHVzQmFyQmFuZDogYSBzYW1wbGUgaW5zZXQgb2YgJHtTdHJpbmcoc2FtcGxlSW5zZXRJblBpeGVscyl9cHggcHV0cyB0aGUgc2FtcGxlIHJvdyBhYm92ZSB0aGUgdG9wIGBcbiAgICAgICAgKyBgb2YgdGhlIGZyYW1lIGZvciBhIGJhbmQgb2YgJHtTdHJpbmcoaGVpZ2h0SW5QaXhlbHMpfXB4LmBcbiAgICApO1xuICB9XG5cbiAgY29uc3QgbGFzdFNjYW5uZWRDb2x1bW5YSW5QaXhlbHMgPSB3aWR0aEluUGl4ZWxzIC0gZWRnZU1hcmdpbkluUGl4ZWxzO1xuXG4gIGlmIChsYXN0U2Nhbm5lZENvbHVtblhJblBpeGVscyAtIGVkZ2VNYXJnaW5JblBpeGVscyA8IE1JTklNVU1fU0NBTk5FRF9DT0xVTU5fQ09VTlQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgcmVzb2x2ZVN0YXR1c0JhckJhbmQ6IGFuIGVkZ2UgbWFyZ2luIG9mICR7U3RyaW5nKGVkZ2VNYXJnaW5JblBpeGVscyl9cHggbGVhdmVzIGZld2VyIHRoYW4gYFxuICAgICAgICArIGAke1N0cmluZyhNSU5JTVVNX1NDQU5ORURfQ09MVU1OX0NPVU5UKX0gY29sdW1ucyBvZiBhICR7U3RyaW5nKHdpZHRoSW5QaXhlbHMpfXB4IGZyYW1lIHRvIHNjYW4sIGBcbiAgICAgICAgKyAnd2hpY2ggaXMgYSBjaGVjayB0aGF0IGNhbm5vdCBmYWlsLidcbiAgICApO1xuICB9XG5cbiAgY29uc3Qgcm93U3RhcnRPZmZzZXQgPSBzYW1wbGVSb3dZSW5QaXhlbHMgKiB3aWR0aEluUGl4ZWxzICogY2hhbm5lbENvdW50O1xuICBjb25zdCByZXF1aXJlZExlbmd0aCA9IHJvd1N0YXJ0T2Zmc2V0ICsgd2lkdGhJblBpeGVscyAqIGNoYW5uZWxDb3VudDtcblxuICBpZiAocGl4ZWxzLmxlbmd0aCA8IHJlcXVpcmVkTGVuZ3RoKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYHJlc29sdmVTdGF0dXNCYXJCYW5kOiB0aGUgZnJhbWUgaG9sZHMgJHtTdHJpbmcocGl4ZWxzLmxlbmd0aCl9IGJ5dGVzLCB0b28gZmV3IGZvciByb3cgYFxuICAgICAgICArIGB5JHtTdHJpbmcoc2FtcGxlUm93WUluUGl4ZWxzKX0gb2YgYSAke1N0cmluZyh3aWR0aEluUGl4ZWxzKX1weC13aWRlIGZyYW1lIGF0IGBcbiAgICAgICAgKyBgJHtTdHJpbmcoY2hhbm5lbENvdW50KX0gY2hhbm5lbHMsIHdoaWNoIG5lZWRzICR7U3RyaW5nKHJlcXVpcmVkTGVuZ3RoKX0uYFxuICAgICk7XG4gIH1cblxuICBjb25zdCBiYWNrZ3JvdW5kQ29sb3IgPSByZWFkQ29sb3IocGl4ZWxzLCByb3dTdGFydE9mZnNldCArIGVkZ2VNYXJnaW5JblBpeGVscyAqIGNoYW5uZWxDb3VudCk7XG4gIGNvbnN0IGJhY2tncm91bmRLZXkgPSBmb3JtYXRDb2xvcihiYWNrZ3JvdW5kQ29sb3IpO1xuICBjb25zdCBjb2xvcktleXMgPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgbGV0IGZpcnN0RGlmZmVyZW50Q29sb3I6IG51bGwgfCBTdGF0dXNCYXJCYW5kQ29sb3IgPSBudWxsO1xuICBsZXQgZmlyc3REaWZmZXJlbnRDb2x1bW5YSW5QaXhlbHM6IG51bGwgfCBudW1iZXIgPSBudWxsO1xuXG4gIGZvciAobGV0IHggPSBlZGdlTWFyZ2luSW5QaXhlbHM7IHggPCBsYXN0U2Nhbm5lZENvbHVtblhJblBpeGVsczsgeCsrKSB7XG4gICAgY29uc3QgY29sb3IgPSByZWFkQ29sb3IocGl4ZWxzLCByb3dTdGFydE9mZnNldCArIHggKiBjaGFubmVsQ291bnQpO1xuICAgIGNvbnN0IGNvbG9yS2V5ID0gZm9ybWF0Q29sb3IoY29sb3IpO1xuICAgIGNvbG9yS2V5cy5hZGQoY29sb3JLZXkpO1xuXG4gICAgaWYgKGZpcnN0RGlmZmVyZW50Q29sdW1uWEluUGl4ZWxzICE9PSBudWxsIHx8IGNvbG9yS2V5ID09PSBiYWNrZ3JvdW5kS2V5KSB7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBmaXJzdERpZmZlcmVudENvbG9yID0gY29sb3I7XG4gICAgZmlyc3REaWZmZXJlbnRDb2x1bW5YSW5QaXhlbHMgPSB4O1xuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBiYWNrZ3JvdW5kQ29sb3IsXG4gICAgZGlzdGluY3RDb2xvckNvdW50OiBjb2xvcktleXMuc2l6ZSxcbiAgICBmaXJzdERpZmZlcmVudENvbG9yLFxuICAgIGZpcnN0RGlmZmVyZW50Q29sdW1uWEluUGl4ZWxzLFxuICAgIGlzQ2xlYXI6IGNvbG9yS2V5cy5zaXplID09PSAxLFxuICAgIHNhbXBsZVJvd1lJblBpeGVsc1xuICB9O1xufVxuXG4vKipcbiAqIFJlZnVzZXMgYSBkaW1lbnNpb24gdGhhdCBjYW5ub3QgZGVzY3JpYmUgYSBmcmFtZS5cbiAqXG4gKiBAcGFyYW0gbmFtZSAtIFRoZSBwYXJhbWV0ZXIgYmVpbmcgY2hlY2tlZCwgc28gdGhlIG1lc3NhZ2UgbmFtZXMgaXQuXG4gKiBAcGFyYW0gdmFsdWUgLSBXaGF0IHdhcyBwYXNzZWQuXG4gKiBAdGhyb3dzIEVycm9yIGlmIHRoZSB2YWx1ZSBpcyBub3QgYSBwb3NpdGl2ZSBmaW5pdGUgbnVtYmVyLlxuICovXG5mdW5jdGlvbiBhc3NlcnRQb3NpdGl2ZShuYW1lOiBzdHJpbmcsIHZhbHVlOiBudW1iZXIpOiB2b2lkIHtcbiAgaWYgKCFOdW1iZXIuaXNGaW5pdGUodmFsdWUpIHx8IHZhbHVlIDw9IDApIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYHJlc29sdmVTdGF0dXNCYXJCYW5kOiAke25hbWV9IG11c3QgYmUgYSBwb3NpdGl2ZSBudW1iZXIsIGdvdCAke1N0cmluZyh2YWx1ZSl9LmApO1xuICB9XG59XG5cbi8qKlxuICogUmVuZGVycyBhIGNvbG9yIGFzIHRleHQsIGZvciBjb21wYXJpc29uIGFuZCBmb3IgdGhlIGRpYWdub3N0aWMuXG4gKlxuICogVGhlIHNhbWUgcmVuZGVyaW5nIGRvZXMgYm90aCBqb2JzIG9uIHB1cnBvc2U6IHdoYXQgYSByZWFkZXIgaXMgc2hvd24gaXMgdGhlXG4gKiB2YWx1ZSB0aGUgY29tcGFyaXNvbiB3YXMgbWFkZSBvbiwgcmF0aGVyIHRoYW4gYSByZS1mb3JtYXR0aW5nIG9mIGl0LlxuICpcbiAqIEBwYXJhbSBjb2xvciAtIFRoZSBjb2xvciwgb3IgYG51bGxgIHdoZW4gdGhlcmUgd2FzIG5vbmUgdG8gcmVwb3J0LlxuICogQHJldHVybnMgVGhlIGNvbG9yIGFzIGByZ2IocixnLGIpYCwgb3IgYChub25lKWAuXG4gKi9cbmZ1bmN0aW9uIGZvcm1hdENvbG9yKGNvbG9yOiBudWxsIHwgU3RhdHVzQmFyQmFuZENvbG9yKTogc3RyaW5nIHtcbiAgcmV0dXJuIGNvbG9yID09PSBudWxsID8gJyhub25lKScgOiBgcmdiKCR7U3RyaW5nKGNvbG9yLnIpfSwke1N0cmluZyhjb2xvci5nKX0sJHtTdHJpbmcoY29sb3IuYil9KWA7XG59XG5cbi8qKlxuICogUmVhZHMgb25lIHBpeGVsJ3MgY29sb3Igb3V0IG9mIGEgcmF3IGZyYW1lLCBpZ25vcmluZyBhbnkgYWxwaGEgY2hhbm5lbC5cbiAqXG4gKiBAcGFyYW0gcGl4ZWxzIC0gVGhlIGZyYW1lJ3MgcmF3IGJ5dGVzLlxuICogQHBhcmFtIG9mZnNldCAtIFRoZSBieXRlIG9mZnNldCBvZiB0aGUgcGl4ZWwuXG4gKiBAcmV0dXJucyBUaGUgY29sb3IgYXQgdGhhdCBvZmZzZXQuXG4gKi9cbmZ1bmN0aW9uIHJlYWRDb2xvcihwaXhlbHM6IFVpbnQ4QXJyYXksIG9mZnNldDogbnVtYmVyKTogU3RhdHVzQmFyQmFuZENvbG9yIHtcbiAgLyogdjggaWdub3JlIHN0YXJ0IC0tIFRoZSBmYWxsYmFja3MgYXJlIHVucmVhY2hhYmxlOiBldmVyeSByZWFkIGlzIGluc2lkZSBhIHJvdyBgcmVzb2x2ZVN0YXR1c0JhckJhbmRgIGhhcyBhbHJlYWR5IHByb3ZlZCB0aGUgYnVmZmVyIGxvbmcgZW5vdWdoIHRvIGhvbGQuIFRoZXkgYXJlIGhlcmUgYmVjYXVzZSBhbiBpbmRleCByZWFkIGlzIHR5cGVkIGBudW1iZXIgfCB1bmRlZmluZWRgLCBub3QgYmVjYXVzZSBhIHBpeGVsIGNhbiBiZSBtaXNzaW5nLiAqL1xuICByZXR1cm4ge1xuICAgIGI6IHBpeGVsc1tvZmZzZXQgKyBCTFVFX0NIQU5ORUxfT0ZGU0VUXSA/PyAwLFxuICAgIGc6IHBpeGVsc1tvZmZzZXQgKyBHUkVFTl9DSEFOTkVMX09GRlNFVF0gPz8gMCxcbiAgICByOiBwaXhlbHNbb2Zmc2V0ICsgUkVEX0NIQU5ORUxfT0ZGU0VUXSA/PyAwXG4gIH07XG4gIC8qIHY4IGlnbm9yZSBzdG9wICovXG59XG4iXSwKICAibWFwcGluZ3MiOiAiQUEyQ0EsU0FBUyxtQkFBbUI7QUE2S3JCLE1BQU0sMkNBQTJDO0FBUWpELE1BQU0sNENBQTRDO0FBS3pELE1BQU0sc0JBQXNCO0FBSzVCLE1BQU0scUJBQXFCO0FBSzNCLE1BQU0sdUJBQXVCO0FBUTdCLE1BQU0sK0JBQStCO0FBS3JDLE1BQU0scUJBQXFCO0FBV3BCLFNBQVMsaUNBQWlDLFFBQXdEO0FBQ3ZHLFFBQU07QUFBQSxJQUNKO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsRUFDRixJQUFJO0FBRUosUUFBTSxRQUFRO0FBQUEsSUFDWiwyRkFDSyxPQUFPLGNBQWMsQ0FBQztBQUFBLElBQzNCLFFBQVEsT0FBTyxjQUFjLENBQUMsaUJBQWlCLE9BQU8sUUFBUSxrQkFBa0IsQ0FBQyxhQUNuRSxPQUFPLGtCQUFrQixDQUFDLEtBQUssT0FBTyxnQkFBZ0Isa0JBQWtCLENBQUMsT0FDL0UsT0FBTyxhQUFhLENBQUMscUJBQXFCLE9BQU8sUUFBUSxrQkFBa0IsQ0FBQztBQUFBLElBQ3BGLFdBQVcsWUFBWSxRQUFRLGVBQWUsQ0FBQyxXQUFXLE9BQU8sUUFBUSw2QkFBNkIsQ0FBQyxPQUMvRixZQUFZLFFBQVEsbUJBQW1CLENBQUM7QUFBQSxJQUNoRDtBQUFBLEVBR0Y7QUFFQSxTQUFPLE1BQU0sS0FBSyxJQUFJO0FBQ3hCO0FBZUEsZUFBc0Isa0JBQWtCLE9BQW1CLFNBQXdEO0FBQ2pILFFBQU0sRUFBRSxlQUFlLElBQUk7QUFDM0IsUUFBTSxxQkFBcUIsUUFBUSxzQkFBc0I7QUFDekQsUUFBTSxRQUFRLE1BQU0sWUFBWSxtQkFBbUI7QUFDbkQsUUFBTSxFQUFFLE1BQU0sS0FBSyxJQUFJLE1BQU0sTUFBTSxLQUFLLEVBQ3JDLFlBQVksRUFDWixJQUFJLEVBQ0osU0FBUyxFQUFFLG1CQUFtQixLQUFLLENBQUM7QUFFdkMs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
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Pure classification of the port-owner query — `netstat -ano` on Windows,
|
|
5
|
+
* `lsof -ti tcp:<port>` elsewhere — deciding, from how it ended, whether it
|
|
6
|
+
* **answered** or **failed**, and saying which failure in one line.
|
|
7
|
+
*
|
|
8
|
+
* The query is the only real escalation the Appium teardown has: the PID the
|
|
9
|
+
* harness holds is the `shell: true` wrapper it spawned, so what survives a kill
|
|
10
|
+
* is found by asking who still listens on the port (**L46**). It used to resolve
|
|
11
|
+
* an empty list for every error, which made three different endings one silent
|
|
12
|
+
* value:
|
|
13
|
+
*
|
|
14
|
+
* - `lsof` exiting 1 with nothing on either stream — which is how it says
|
|
15
|
+
* nothing holds the port*, a legitimate empty answer;
|
|
16
|
+
* - the budget running out on a contended host;
|
|
17
|
+
* - the child dying.
|
|
18
|
+
*
|
|
19
|
+
* Only the first is an answer. The other two disarm the escalation exactly the
|
|
20
|
+
* way a silently failed process listing disarmed the emulator's
|
|
21
|
+
* (`host-process-query-verdict.ts`), so they are classified by the same rules
|
|
22
|
+
* and reported, not swallowed.
|
|
23
|
+
*
|
|
24
|
+
* **Why a module of its own rather than an option on the process-listing
|
|
25
|
+
* verdict.** The two queries disagree in two places, not one. Zero owners is a
|
|
26
|
+
* legitimate answer here, so `zero-rows` never applies; and `lsof`'s silent exit
|
|
27
|
+
* 1 means *nothing listens*, where for `tasklist` a silent non-zero exit is a
|
|
28
|
+
* crash. The failure branches themselves are delegated to
|
|
29
|
+
* {@link resolveHostQueryFailure}, so there is still one definition of
|
|
30
|
+
* `crashed`, `timed-out` and `refused`.
|
|
31
|
+
*/
|
|
32
|
+
import type { HostQueryFailure } from './host-process-query-verdict.mjs';
|
|
33
|
+
/**
|
|
34
|
+
* Parameters for {@link buildPortOwnerQueryMessage}.
|
|
35
|
+
*/
|
|
36
|
+
export interface BuildPortOwnerQueryMessageParams {
|
|
37
|
+
/**
|
|
38
|
+
The command that was run, for the reader to try by hand — e.g. `netstat -ano`.
|
|
39
|
+
*/
|
|
40
|
+
readonly command: string;
|
|
41
|
+
/**
|
|
42
|
+
How long the call took before it ended, in milliseconds.
|
|
43
|
+
*/
|
|
44
|
+
readonly elapsedInMilliseconds: number;
|
|
45
|
+
/**
|
|
46
|
+
The child's exit code, when it exited rather than failing to start or being killed.
|
|
47
|
+
*/
|
|
48
|
+
readonly exitCode: null | number;
|
|
49
|
+
/**
|
|
50
|
+
The verdict from {@link resolvePortOwnerQueryOutcome}.
|
|
51
|
+
*/
|
|
52
|
+
readonly outcome: PortOwnerQueryOutcome;
|
|
53
|
+
/**
|
|
54
|
+
The port whose owners were asked for.
|
|
55
|
+
*/
|
|
56
|
+
readonly port: number;
|
|
57
|
+
/**
|
|
58
|
+
The signal the child was killed with, when it was killed.
|
|
59
|
+
*/
|
|
60
|
+
readonly signal: null | string;
|
|
61
|
+
/**
|
|
62
|
+
Whatever the child wrote to stderr.
|
|
63
|
+
*/
|
|
64
|
+
readonly standardError: string;
|
|
65
|
+
/**
|
|
66
|
+
The budget the call was given, in milliseconds.
|
|
67
|
+
*/
|
|
68
|
+
readonly timeoutInMilliseconds: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The fields {@link normalizeSyncExecError} extracts from an `execFileSync` throw.
|
|
72
|
+
*/
|
|
73
|
+
export interface NormalizedSyncExecError {
|
|
74
|
+
/**
|
|
75
|
+
The error code in `execFile`'s vocabulary: the numeric exit code, or a string such as `ENOENT` or `ERR_CHILD_PROCESS_STDIO_MAXBUFFER`.
|
|
76
|
+
*/
|
|
77
|
+
readonly errorCode: null | number | string;
|
|
78
|
+
/**
|
|
79
|
+
The child's exit code, when it exited.
|
|
80
|
+
*/
|
|
81
|
+
readonly exitCode: null | number;
|
|
82
|
+
/**
|
|
83
|
+
Whether the budget expired and the child was killed.
|
|
84
|
+
*/
|
|
85
|
+
readonly isKilled: boolean;
|
|
86
|
+
/**
|
|
87
|
+
The signal the child was killed with.
|
|
88
|
+
*/
|
|
89
|
+
readonly signal: null | string;
|
|
90
|
+
/**
|
|
91
|
+
Whatever the child wrote to stderr.
|
|
92
|
+
*/
|
|
93
|
+
readonly standardError: string;
|
|
94
|
+
/**
|
|
95
|
+
Whatever the child wrote to stdout before it ended.
|
|
96
|
+
*/
|
|
97
|
+
readonly standardOutput: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* How the port-owner query ended.
|
|
101
|
+
*
|
|
102
|
+
* `answered` covers both a listener found and none found — the query succeeded
|
|
103
|
+
* either way. The rest are {@link resolveHostQueryFailure}'s failures.
|
|
104
|
+
*/
|
|
105
|
+
export type PortOwnerQueryOutcome = 'answered' | HostQueryFailure;
|
|
106
|
+
/**
|
|
107
|
+
* Parameters for {@link resolvePortOwnerQueryOutcome}.
|
|
108
|
+
*/
|
|
109
|
+
export interface ResolvePortOwnerQueryOutcomeParams {
|
|
110
|
+
/**
|
|
111
|
+
`execFile`'s `error.code`: the numeric exit code, or a string like `ENOENT` when the child never ran.
|
|
112
|
+
*/
|
|
113
|
+
readonly errorCode: null | number | string;
|
|
114
|
+
/**
|
|
115
|
+
Whether `execFile` reported an error at all.
|
|
116
|
+
*/
|
|
117
|
+
readonly hasFailed: boolean;
|
|
118
|
+
/**
|
|
119
|
+
`execFile`'s `error.killed`: whether the budget expired and the child was killed.
|
|
120
|
+
*/
|
|
121
|
+
readonly isKilled: boolean;
|
|
122
|
+
/**
|
|
123
|
+
The host platform, which decides whether a silent exit 1 is `lsof` saying *nothing listens*.
|
|
124
|
+
*/
|
|
125
|
+
readonly platform: NodeJS.Platform;
|
|
126
|
+
/**
|
|
127
|
+
Whatever the child wrote to stderr.
|
|
128
|
+
*/
|
|
129
|
+
readonly standardError: string;
|
|
130
|
+
/**
|
|
131
|
+
Whatever the child wrote to stdout.
|
|
132
|
+
*/
|
|
133
|
+
readonly standardOutput: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Builds the single line the query logs for an outcome.
|
|
137
|
+
*
|
|
138
|
+
* @param params - The outcome plus the command, the port, the budget and what the child left behind.
|
|
139
|
+
* @returns The warning to log, or `undefined` when the query answered.
|
|
140
|
+
*/
|
|
141
|
+
export declare function buildPortOwnerQueryMessage(params: BuildPortOwnerQueryMessageParams): string | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Extracts what an `execFileSync` throw carries, in `execFile`'s vocabulary.
|
|
144
|
+
*
|
|
145
|
+
* The synchronous API reports the same endings with different fields: an
|
|
146
|
+
* expired budget is `code: 'ETIMEDOUT'` rather than `killed: true`, an overrun
|
|
147
|
+
* buffer is `ENOBUFS`, and the exit code is `status` rather than `code`.
|
|
148
|
+
* Normalizing here lets the sync teardown classify with the same verdict as the
|
|
149
|
+
* async one.
|
|
150
|
+
*
|
|
151
|
+
* @param error - Whatever `execFileSync` threw.
|
|
152
|
+
* @returns The normalized fields; an unrecognizable throw reads as a crash with no evidence.
|
|
153
|
+
*/
|
|
154
|
+
export declare function normalizeSyncExecError(error: unknown): NormalizedSyncExecError;
|
|
155
|
+
/**
|
|
156
|
+
* Decides how the port-owner query ended.
|
|
157
|
+
*
|
|
158
|
+
* @param params - What `execFile` reported, and the platform it ran on.
|
|
159
|
+
* @returns The outcome.
|
|
160
|
+
*/
|
|
161
|
+
export declare function resolvePortOwnerQueryOutcome(params: ResolvePortOwnerQueryOutcomeParams): PortOwnerQueryOutcome;
|