@appium/images-plugin 1.3.6 → 2.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.
@@ -1,347 +1,403 @@
1
1
  "use strict";
2
-
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
-
5
- Object.defineProperty(exports, "__esModule", {
6
- value: true
7
- });
8
- exports.default = exports.W3C_ELEMENT_KEY = exports.MJSONWP_ELEMENT_KEY = exports.DEFAULT_SETTINGS = exports.DEFAULT_FIX_IMAGE_TEMPLATE_SCALE = void 0;
9
-
10
- require("source-map-support/register");
11
-
12
- var _lodash = _interopRequireDefault(require("lodash"));
13
-
14
- var _lruCache = _interopRequireDefault(require("lru-cache"));
15
-
16
- var _driver = require("appium/driver");
17
-
18
- var _support = require("appium/support");
19
-
20
- var _imageElement = require("./image-element");
21
-
22
- var _compare = require("./compare");
23
-
24
- var _logger = _interopRequireDefault(require("./logger"));
25
-
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_FIX_IMAGE_TEMPLATE_SCALE = exports.DEFAULT_SETTINGS = exports.MJSONWP_ELEMENT_KEY = exports.W3C_ELEMENT_KEY = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const lru_cache_1 = __importDefault(require("lru-cache"));
9
+ const driver_1 = require("appium/driver");
10
+ const support_1 = require("appium/support");
11
+ const image_element_1 = require("./image-element");
12
+ const compare_1 = require("./compare");
13
+ const logger_1 = __importDefault(require("./logger"));
26
14
  const MJSONWP_ELEMENT_KEY = 'ELEMENT';
27
15
  exports.MJSONWP_ELEMENT_KEY = MJSONWP_ELEMENT_KEY;
28
- const W3C_ELEMENT_KEY = _support.util.W3C_WEB_ELEMENT_IDENTIFIER;
16
+ const W3C_ELEMENT_KEY = support_1.util.W3C_WEB_ELEMENT_IDENTIFIER;
29
17
  exports.W3C_ELEMENT_KEY = W3C_ELEMENT_KEY;
30
18
  const DEFAULT_FIX_IMAGE_TEMPLATE_SCALE = 1;
31
19
  exports.DEFAULT_FIX_IMAGE_TEMPLATE_SCALE = DEFAULT_FIX_IMAGE_TEMPLATE_SCALE;
20
+ // Used to compare ratio and screen width
21
+ // Pixel is basically under 1080 for example. 100K is probably enough fo a while.
32
22
  const FLOAT_PRECISION = 100000;
33
23
  const MAX_CACHE_ITEMS = 100;
34
- const MAX_CACHE_SIZE_BYTES = 1024 * 1024 * 40;
24
+ const MAX_CACHE_SIZE_BYTES = 1024 * 1024 * 40; // 40mb
35
25
  const DEFAULT_SETTINGS = {
36
- imageMatchThreshold: _compare.DEFAULT_MATCH_THRESHOLD,
37
- imageMatchMethod: '',
38
- fixImageFindScreenshotDims: true,
39
- fixImageTemplateSize: false,
40
- fixImageTemplateScale: false,
41
- defaultImageTemplateScale: _imageElement.DEFAULT_TEMPLATE_IMAGE_SCALE,
42
- checkForImageElementStaleness: true,
43
- autoUpdateImageElementPosition: false,
44
- imageElementTapStrategy: _imageElement.IMAGE_EL_TAP_STRATEGY_W3C,
45
- getMatchedImageResult: false
26
+ // value between 0 and 1 representing match strength, below which an image
27
+ // element will not be found
28
+ imageMatchThreshold: compare_1.DEFAULT_MATCH_THRESHOLD,
29
+ // One of possible image matching methods.
30
+ // Read https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_template_matching/py_template_matching.html
31
+ // for more details.
32
+ // TM_CCOEFF_NORMED by default
33
+ imageMatchMethod: '',
34
+ // if the image returned by getScreenshot differs in size or aspect ratio
35
+ // from the screen, attempt to fix it automatically
36
+ fixImageFindScreenshotDims: true,
37
+ // whether Appium should ensure that an image template sent in during image
38
+ // element find should have its size adjusted so the match algorithm will not
39
+ // complain
40
+ fixImageTemplateSize: false,
41
+ // whether Appium should ensure that an image template sent in during image
42
+ // element find should have its scale adjusted to display size so the match
43
+ // algorithm will not complain.
44
+ // e.g. iOS has `width=375, height=667` window rect, but its screenshot is
45
+ // `width=750 × height=1334` pixels. This setting help to adjust the scale
46
+ // if a user use `width=750 × height=1334` pixels's base template image.
47
+ fixImageTemplateScale: false,
48
+ // Users might have scaled template image to reduce their storage size.
49
+ // This setting allows users to scale a template image they send to Appium server
50
+ // so that the Appium server compares the actual scale users originally had.
51
+ // e.g. If a user has an image of 270 x 32 pixels which was originally 1080 x 126 pixels,
52
+ // the user can set {defaultImageTemplateScale: 4.0} to scale the small image
53
+ // to the original one so that Appium can compare it as the original one.
54
+ defaultImageTemplateScale: image_element_1.DEFAULT_TEMPLATE_IMAGE_SCALE,
55
+ // whether Appium should re-check that an image element can be matched
56
+ // against the current screenshot before clicking it
57
+ checkForImageElementStaleness: true,
58
+ // whether before clicking on an image element Appium should re-determine the
59
+ // position of the element on screen
60
+ autoUpdateImageElementPosition: false,
61
+ // which method to use for tapping by coordinate for image elements. the
62
+ // options are 'w3c' or 'mjsonwp'
63
+ imageElementTapStrategy: image_element_1.IMAGE_EL_TAP_STRATEGY_W3C,
64
+ // which method to use to save the matched image area in ImageElement class.
65
+ // It is used for debugging purpose.
66
+ getMatchedImageResult: false,
46
67
  };
47
68
  exports.DEFAULT_SETTINGS = DEFAULT_SETTINGS;
48
-
69
+ const NO_OCCURRENCES_PATTERN = /Cannot find any occurrences/;
70
+ const CONDITION_UNMET_PATTERN = /Condition unmet/;
49
71
  class ImageElementFinder {
50
- driver;
51
- imgElCache;
52
-
53
- constructor(driver, maxSize = MAX_CACHE_SIZE_BYTES) {
54
- this.driver = driver;
55
- this.imgElCache = new _lruCache.default({
56
- max: MAX_CACHE_ITEMS,
57
- maxSize,
58
- sizeCalculation: el => el.template.length
59
- });
60
- }
61
-
62
- setDriver(driver) {
63
- this.driver = driver;
64
- }
65
-
66
- registerImageElement(imgEl) {
67
- this.imgElCache.set(imgEl.id, imgEl);
68
- const protoKey = this.driver.isW3CProtocol() ? W3C_ELEMENT_KEY : MJSONWP_ELEMENT_KEY;
69
- return imgEl.asElement(protoKey);
70
- }
71
-
72
- async findByImage(b64Template, {
73
- shouldCheckStaleness = false,
74
- multiple = false,
75
- ignoreDefaultImageTemplateScale = false
76
- }) {
77
- if (!this.driver) {
78
- throw new Error(`Can't find without a driver!`);
72
+ /**
73
+ *
74
+ * @param {ExternalDriver} driver
75
+ * @param {number} [maxSize]
76
+ */
77
+ constructor(driver, maxSize = MAX_CACHE_SIZE_BYTES) {
78
+ this.driver = driver;
79
+ this.imgElCache = new lru_cache_1.default({
80
+ max: MAX_CACHE_ITEMS,
81
+ maxSize,
82
+ sizeCalculation: (el) => el.template.length,
83
+ });
79
84
  }
80
-
81
- const settings = { ...DEFAULT_SETTINGS,
82
- ...this.driver.settings.getSettings()
83
- };
84
- const {
85
- imageMatchThreshold: threshold,
86
- imageMatchMethod,
87
- fixImageTemplateSize,
88
- fixImageTemplateScale,
89
- defaultImageTemplateScale,
90
- getMatchedImageResult: visualize
91
- } = settings;
92
-
93
- _logger.default.info(`Finding image element with match threshold ${threshold}`);
94
-
95
- if (!this.driver.getWindowSize) {
96
- throw new Error("This driver does not support the required 'getWindowSize' command");
85
+ setDriver(driver) {
86
+ this.driver = driver;
97
87
  }
98
-
99
- const {
100
- width: screenWidth,
101
- height: screenHeight
102
- } = await this.driver.getWindowSize();
103
-
104
- if (fixImageTemplateSize) {
105
- b64Template = await this.ensureTemplateSize(b64Template, screenWidth, screenHeight);
88
+ /**
89
+ * @param {ImageElement} imgEl
90
+ * @returns {Element}
91
+ */
92
+ registerImageElement(imgEl) {
93
+ this.imgElCache.set(imgEl.id, imgEl);
94
+ const protoKey = this.driver.isW3CProtocol() ? W3C_ELEMENT_KEY : MJSONWP_ELEMENT_KEY;
95
+ return imgEl.asElement(protoKey);
106
96
  }
107
-
108
- const results = [];
109
-
110
- const condition = async () => {
111
- try {
112
- const {
113
- b64Screenshot,
114
- scale
115
- } = await this.getScreenshotForImageFind(screenWidth, screenHeight);
116
- b64Template = await this.fixImageTemplateScale(b64Template, {
117
- defaultImageTemplateScale,
118
- ignoreDefaultImageTemplateScale,
119
- fixImageTemplateScale,
120
- ...scale
121
- });
122
- const comparisonOpts = {
123
- threshold,
124
- visualize,
125
- multiple
97
+ /**
98
+ * @typedef FindByImageOptions
99
+ * @property {boolean} [shouldCheckStaleness=false] - whether this call to find an
100
+ * image is merely to check staleness. If so we can bypass a lot of logic
101
+ * @property {boolean} [multiple=false] - Whether we are finding one element or
102
+ * multiple
103
+ * @property {boolean} [ignoreDefaultImageTemplateScale=false] - Whether we
104
+ * ignore defaultImageTemplateScale. It can be used when you would like to
105
+ * scale b64Template with defaultImageTemplateScale setting.
106
+ */
107
+ /**
108
+ * Find a screen rect represented by an ImageElement corresponding to an image
109
+ * template sent in by the client
110
+ *
111
+ * @param {string} b64Template - base64-encoded image used as a template to be
112
+ * matched in the screenshot
113
+ * @param {FindByImageOptions} opts - additional options
114
+ *
115
+ * @returns {Promise<Element|Element[]|ImageElement>} - WebDriver element with a special id prefix
116
+ */
117
+ async findByImage(b64Template, { shouldCheckStaleness = false, multiple = false, ignoreDefaultImageTemplateScale = false }) {
118
+ if (!this.driver) {
119
+ throw new Error(`Can't find without a driver!`);
120
+ }
121
+ const settings = { ...DEFAULT_SETTINGS, ...this.driver.settings.getSettings() };
122
+ const { imageMatchThreshold: threshold, imageMatchMethod, fixImageTemplateSize, fixImageTemplateScale, defaultImageTemplateScale, getMatchedImageResult: visualize, } = settings;
123
+ logger_1.default.info(`Finding image element with match threshold ${threshold}`);
124
+ if (!this.driver.getWindowSize) {
125
+ throw new Error("This driver does not support the required 'getWindowSize' command");
126
+ }
127
+ const { width: screenWidth, height: screenHeight } = await this.driver.getWindowSize();
128
+ // someone might have sent in a template that's larger than the screen
129
+ // dimensions. If so let's check and cut it down to size since the algorithm
130
+ // will not work unless we do. But because it requires some potentially
131
+ // expensive commands, only do this if the user has requested it in settings.
132
+ if (fixImageTemplateSize) {
133
+ b64Template = await this.ensureTemplateSize(b64Template, screenWidth, screenHeight);
134
+ }
135
+ const results = [];
136
+ const condition = async () => {
137
+ try {
138
+ const { b64Screenshot, scale } = await this.getScreenshotForImageFind(screenWidth, screenHeight);
139
+ b64Template = await this.fixImageTemplateScale(b64Template, {
140
+ defaultImageTemplateScale,
141
+ ignoreDefaultImageTemplateScale,
142
+ fixImageTemplateScale,
143
+ ...scale,
144
+ });
145
+ const comparisonOpts = {
146
+ threshold,
147
+ visualize,
148
+ multiple,
149
+ };
150
+ if (imageMatchMethod) {
151
+ comparisonOpts.method = imageMatchMethod;
152
+ }
153
+ if (multiple) {
154
+ results.push(...(await (0, compare_1.compareImages)(compare_1.MATCH_TEMPLATE_MODE, b64Screenshot, b64Template, comparisonOpts)));
155
+ }
156
+ else {
157
+ results.push(await (0, compare_1.compareImages)(compare_1.MATCH_TEMPLATE_MODE, b64Screenshot, b64Template, comparisonOpts));
158
+ }
159
+ return true;
160
+ }
161
+ catch (err) {
162
+ // if compareImages fails, we'll get a specific error, but we should
163
+ // retry, so trap that and just return false to trigger the next round of
164
+ // implicitly waiting. For other errors, throw them to get out of the
165
+ // implicit wait loop
166
+ if (NO_OCCURRENCES_PATTERN.test(err.message)) {
167
+ return false;
168
+ }
169
+ throw err;
170
+ }
126
171
  };
127
-
128
- if (imageMatchMethod) {
129
- comparisonOpts.method = imageMatchMethod;
172
+ try {
173
+ await this.driver.implicitWaitForCondition(condition);
130
174
  }
131
-
132
- if (multiple) {
133
- results.push(...(await (0, _compare.compareImages)(_compare.MATCH_TEMPLATE_MODE, b64Screenshot, b64Template, comparisonOpts)));
134
- } else {
135
- results.push(await (0, _compare.compareImages)(_compare.MATCH_TEMPLATE_MODE, b64Screenshot, b64Template, comparisonOpts));
175
+ catch (err) {
176
+ // this `implicitWaitForCondition` method will throw a 'Condition unmet'
177
+ // error if an element is not found eventually. In that case, we will
178
+ // handle the element not found response below. In the case where get some
179
+ // _other_ kind of error, it means something blew up totally apart from the
180
+ // implicit wait timeout. We should not mask that error and instead throw
181
+ // it straightaway
182
+ if (!CONDITION_UNMET_PATTERN.test(err.message)) {
183
+ throw err;
184
+ }
136
185
  }
137
-
138
- return true;
139
- } catch (err) {
140
- if (err.message.match(/Cannot find any occurrences/)) {
141
- return false;
186
+ if (lodash_1.default.isEmpty(results)) {
187
+ if (multiple) {
188
+ return [];
189
+ }
190
+ throw new driver_1.errors.NoSuchElementError();
142
191
  }
143
-
144
- throw err;
145
- }
146
- };
147
-
148
- try {
149
- await this.driver.implicitWaitForCondition(condition);
150
- } catch (err) {
151
- if (!err.message.match(/Condition unmet/)) {
152
- throw err;
153
- }
154
- }
155
-
156
- if (_lodash.default.isEmpty(results)) {
157
- if (multiple) {
158
- return [];
159
- }
160
-
161
- throw new _driver.errors.NoSuchElementError();
162
- }
163
-
164
- const elements = results.map(({
165
- rect,
166
- score,
167
- visualization
168
- }) => {
169
- _logger.default.info(`Image template matched: ${JSON.stringify(rect)}`);
170
-
171
- return new _imageElement.ImageElement(b64Template, rect, score, visualization, this);
172
- });
173
-
174
- if (shouldCheckStaleness) {
175
- return elements[0];
176
- }
177
-
178
- const registeredElements = elements.map(imgEl => this.registerImageElement(imgEl));
179
- return multiple ? registeredElements : registeredElements[0];
180
- }
181
-
182
- async ensureTemplateSize(b64Template, screenWidth, screenHeight) {
183
- let imgObj = await _support.imageUtil.getJimpImage(b64Template);
184
- let {
185
- width: tplWidth,
186
- height: tplHeight
187
- } = imgObj.bitmap;
188
-
189
- _logger.default.info(`Template image is ${tplWidth}x${tplHeight}. Screen size is ${screenWidth}x${screenHeight}`);
190
-
191
- if (tplWidth <= screenWidth && tplHeight <= screenHeight) {
192
- return b64Template;
193
- }
194
-
195
- _logger.default.info(`Scaling template image from ${tplWidth}x${tplHeight} to match ` + `screen at ${screenWidth}x${screenHeight}`);
196
-
197
- imgObj = imgObj.scaleToFit(screenWidth, screenHeight);
198
- return (await imgObj.getBuffer(_support.imageUtil.MIME_PNG)).toString('base64');
199
- }
200
-
201
- async getScreenshotForImageFind(screenWidth, screenHeight) {
202
- if (!this.driver.getScreenshot) {
203
- throw new Error("This driver does not support the required 'getScreenshot' command");
204
- }
205
-
206
- const settings = Object.assign({}, DEFAULT_SETTINGS, this.driver.settings.getSettings());
207
- const {
208
- fixImageFindScreenshotDims
209
- } = settings;
210
- let b64Screenshot = await this.driver.getScreenshot();
211
-
212
- if (!fixImageFindScreenshotDims) {
213
- _logger.default.info(`Not verifying screenshot dimensions match screen`);
214
-
215
- return {
216
- b64Screenshot
217
- };
218
- }
219
-
220
- if (screenWidth < 1 || screenHeight < 1) {
221
- _logger.default.warn(`The retrieved screen size ${screenWidth}x${screenHeight} does ` + `not seem to be valid. No changes will be applied to the screenshot`);
222
-
223
- return {
224
- b64Screenshot
225
- };
226
- }
227
-
228
- _logger.default.info('Verifying screenshot size and aspect ratio');
229
-
230
- let imgObj = await _support.imageUtil.getJimpImage(b64Screenshot);
231
- let {
232
- width: shotWidth,
233
- height: shotHeight
234
- } = imgObj.bitmap;
235
-
236
- if (shotWidth < 1 || shotHeight < 1) {
237
- _logger.default.warn(`The retrieved screenshot size ${shotWidth}x${shotHeight} does ` + `not seem to be valid. No changes will be applied to the screenshot`);
238
-
239
- return {
240
- b64Screenshot
241
- };
242
- }
243
-
244
- if (screenWidth === shotWidth && screenHeight === shotHeight) {
245
- _logger.default.info('Screenshot size matched screen size');
246
-
247
- return {
248
- b64Screenshot
249
- };
250
- }
251
-
252
- const scale = {
253
- xScale: 1.0,
254
- yScale: 1.0
255
- };
256
- const screenAR = screenWidth / screenHeight;
257
- const shotAR = shotWidth / shotHeight;
258
-
259
- if (Math.round(screenAR * FLOAT_PRECISION) === Math.round(shotAR * FLOAT_PRECISION)) {
260
- _logger.default.info(`Screenshot aspect ratio '${shotAR}' (${shotWidth}x${shotHeight}) matched ` + `screen aspect ratio '${screenAR}' (${screenWidth}x${screenHeight})`);
261
- } else {
262
- _logger.default.warn(`When trying to find an element, determined that the screen ` + `aspect ratio and screenshot aspect ratio are different. Screen ` + `is ${screenWidth}x${screenHeight} whereas screenshot is ` + `${shotWidth}x${shotHeight}.`);
263
-
264
- const xScale = 1.0 * shotWidth / screenWidth;
265
- const yScale = 1.0 * shotHeight / screenHeight;
266
- const scaleFactor = xScale >= yScale ? yScale : xScale;
267
-
268
- _logger.default.warn(`Resizing screenshot to ${shotWidth * scaleFactor}x${shotHeight * scaleFactor} to match ` + `screen aspect ratio so that image element coordinates have a ` + `greater chance of being correct.`);
269
-
270
- imgObj = imgObj.resize(shotWidth * scaleFactor, shotHeight * scaleFactor);
271
- scale.xScale *= scaleFactor;
272
- scale.yScale *= scaleFactor;
273
- shotWidth = imgObj.bitmap.width;
274
- shotHeight = imgObj.bitmap.height;
275
- }
276
-
277
- if (screenWidth !== shotWidth && screenHeight !== shotHeight) {
278
- _logger.default.info(`Scaling screenshot from ${shotWidth}x${shotHeight} to match ` + `screen at ${screenWidth}x${screenHeight}`);
279
-
280
- imgObj = imgObj.resize(screenWidth, screenHeight);
281
- scale.xScale *= 1.0 * screenWidth / shotWidth;
282
- scale.yScale *= 1.0 * screenHeight / shotHeight;
283
- }
284
-
285
- b64Screenshot = (await imgObj.getBuffer(_support.imageUtil.MIME_PNG)).toString('base64');
286
- return {
287
- b64Screenshot,
288
- scale
289
- };
290
- }
291
-
292
- async fixImageTemplateScale(b64Template, opts) {
293
- if (!opts) {
294
- return b64Template;
295
- }
296
-
297
- let {
298
- fixImageTemplateScale = false,
299
- defaultImageTemplateScale = _imageElement.DEFAULT_TEMPLATE_IMAGE_SCALE,
300
- ignoreDefaultImageTemplateScale = false,
301
- xScale = DEFAULT_FIX_IMAGE_TEMPLATE_SCALE,
302
- yScale = DEFAULT_FIX_IMAGE_TEMPLATE_SCALE
303
- } = opts;
304
-
305
- if (ignoreDefaultImageTemplateScale) {
306
- defaultImageTemplateScale = _imageElement.DEFAULT_TEMPLATE_IMAGE_SCALE;
307
- }
308
-
309
- if (defaultImageTemplateScale === _imageElement.DEFAULT_TEMPLATE_IMAGE_SCALE && !fixImageTemplateScale) {
310
- return b64Template;
192
+ const elements = results.map(({ rect, score, visualization }) => {
193
+ logger_1.default.info(`Image template matched: ${JSON.stringify(rect)}`);
194
+ return new image_element_1.ImageElement(b64Template, rect, score, visualization, this);
195
+ });
196
+ // if we're just checking staleness, return straightaway so we don't add
197
+ // a new element to the cache. shouldCheckStaleness does not support multiple
198
+ // elements, since it is a purely internal mechanism
199
+ if (shouldCheckStaleness) {
200
+ return elements[0];
201
+ }
202
+ const registeredElements = elements.map((imgEl) => this.registerImageElement(imgEl));
203
+ return multiple ? registeredElements : registeredElements[0];
311
204
  }
312
-
313
- if (fixImageTemplateScale) {
314
- xScale *= defaultImageTemplateScale;
315
- yScale *= defaultImageTemplateScale;
316
- } else {
317
- xScale = yScale = 1 * defaultImageTemplateScale;
205
+ /**
206
+ * Ensure that the image template sent in for a find is of a suitable size
207
+ *
208
+ * @param {string} b64Template - base64-encoded image
209
+ * @param {number} screenWidth - width of screen
210
+ * @param {number} screenHeight - height of screen
211
+ *
212
+ * @returns {Promise<string>} base64-encoded image, potentially resized
213
+ */
214
+ async ensureTemplateSize(b64Template, screenWidth, screenHeight) {
215
+ let imgObj = await support_1.imageUtil.getJimpImage(b64Template);
216
+ let { width: tplWidth, height: tplHeight } = imgObj.bitmap;
217
+ logger_1.default.info(`Template image is ${tplWidth}x${tplHeight}. Screen size is ${screenWidth}x${screenHeight}`);
218
+ // if the template fits inside the screen dimensions, we're good
219
+ if (tplWidth <= screenWidth && tplHeight <= screenHeight) {
220
+ return b64Template;
221
+ }
222
+ logger_1.default.info(`Scaling template image from ${tplWidth}x${tplHeight} to match ` +
223
+ `screen at ${screenWidth}x${screenHeight}`);
224
+ // otherwise, scale it to fit inside the screen dimensions
225
+ imgObj = imgObj.scaleToFit(screenWidth, screenHeight);
226
+ return (await imgObj.getBuffer(support_1.imageUtil.MIME_PNG)).toString('base64');
318
227
  }
319
-
320
- if (!parseFloat(String(xScale)) || !parseFloat(String(yScale))) {
321
- return b64Template;
228
+ /**
229
+ * Get the screenshot image that will be used for find by element, potentially
230
+ * altering it in various ways based on user-requested settings
231
+ *
232
+ * @param {number} screenWidth - width of screen
233
+ * @param {number} screenHeight - height of screen
234
+ *
235
+ * @returns {Promise<Screenshot & {scale?: ScreenshotScale}>} base64-encoded screenshot and ScreenshotScale
236
+ */
237
+ async getScreenshotForImageFind(screenWidth, screenHeight) {
238
+ if (!this.driver.getScreenshot) {
239
+ throw new Error("This driver does not support the required 'getScreenshot' command");
240
+ }
241
+ const settings = Object.assign({}, DEFAULT_SETTINGS, this.driver.settings.getSettings());
242
+ const { fixImageFindScreenshotDims } = settings;
243
+ let b64Screenshot = await this.driver.getScreenshot();
244
+ // if the user has requested not to correct for aspect or size differences
245
+ // between the screenshot and the screen, just return the screenshot now
246
+ if (!fixImageFindScreenshotDims) {
247
+ logger_1.default.info(`Not verifying screenshot dimensions match screen`);
248
+ return { b64Screenshot };
249
+ }
250
+ if (screenWidth < 1 || screenHeight < 1) {
251
+ logger_1.default.warn(`The retrieved screen size ${screenWidth}x${screenHeight} does ` +
252
+ `not seem to be valid. No changes will be applied to the screenshot`);
253
+ return { b64Screenshot };
254
+ }
255
+ // otherwise, do some verification on the screenshot to make sure it matches
256
+ // the screen size and aspect ratio
257
+ logger_1.default.info('Verifying screenshot size and aspect ratio');
258
+ let imgObj = await support_1.imageUtil.getJimpImage(b64Screenshot);
259
+ let { width: shotWidth, height: shotHeight } = imgObj.bitmap;
260
+ if (shotWidth < 1 || shotHeight < 1) {
261
+ logger_1.default.warn(`The retrieved screenshot size ${shotWidth}x${shotHeight} does ` +
262
+ `not seem to be valid. No changes will be applied to the screenshot`);
263
+ return { b64Screenshot };
264
+ }
265
+ if (screenWidth === shotWidth && screenHeight === shotHeight) {
266
+ // the height and width of the screenshot and the device screen match, which
267
+ // means we should be safe when doing template matches
268
+ logger_1.default.info('Screenshot size matched screen size');
269
+ return { b64Screenshot };
270
+ }
271
+ // otherwise, if they don't match, it could spell problems for the accuracy
272
+ // of coordinates returned by the image match algorithm, since we match based
273
+ // on the screenshot coordinates not the device coordinates themselves. There
274
+ // are two potential types of mismatch: aspect ratio mismatch and scale
275
+ // mismatch. We need to detect and fix both
276
+ const scale = { xScale: 1.0, yScale: 1.0 };
277
+ const screenAR = screenWidth / screenHeight;
278
+ const shotAR = shotWidth / shotHeight;
279
+ if (Math.round(screenAR * FLOAT_PRECISION) === Math.round(shotAR * FLOAT_PRECISION)) {
280
+ logger_1.default.info(`Screenshot aspect ratio '${shotAR}' (${shotWidth}x${shotHeight}) matched ` +
281
+ `screen aspect ratio '${screenAR}' (${screenWidth}x${screenHeight})`);
282
+ }
283
+ else {
284
+ logger_1.default.warn(`When trying to find an element, determined that the screen ` +
285
+ `aspect ratio and screenshot aspect ratio are different. Screen ` +
286
+ `is ${screenWidth}x${screenHeight} whereas screenshot is ` +
287
+ `${shotWidth}x${shotHeight}.`);
288
+ // In the case where the x-scale and y-scale are different, we need to decide
289
+ // which one to respect, otherwise the screenshot and template will end up
290
+ // being resized in a way that changes its aspect ratio (distorts it). For example, let's say:
291
+ // this.getScreenshot(shotWidth, shotHeight) is 540x397,
292
+ // this.getDeviceSize(screenWidth, screenHeight) is 1080x1920.
293
+ // The ratio would then be {xScale: 0.5, yScale: 0.2}.
294
+ // In this case, we must should `yScale: 0.2` as scaleFactor, because
295
+ // if we select the xScale, the height will be bigger than real screenshot size
296
+ // which is used to image comparison by OpenCV as a base image.
297
+ // All of this is primarily useful when the screenshot is a horizontal slice taken out of the
298
+ // screen (for example not including top/bottom nav bars)
299
+ const xScale = (1.0 * shotWidth) / screenWidth;
300
+ const yScale = (1.0 * shotHeight) / screenHeight;
301
+ const scaleFactor = xScale >= yScale ? yScale : xScale;
302
+ logger_1.default.warn(`Resizing screenshot to ${shotWidth * scaleFactor}x${shotHeight * scaleFactor} to match ` +
303
+ `screen aspect ratio so that image element coordinates have a ` +
304
+ `greater chance of being correct.`);
305
+ imgObj = imgObj.resize(shotWidth * scaleFactor, shotHeight * scaleFactor);
306
+ scale.xScale *= scaleFactor;
307
+ scale.yScale *= scaleFactor;
308
+ shotWidth = imgObj.bitmap.width;
309
+ shotHeight = imgObj.bitmap.height;
310
+ }
311
+ // Resize based on the screen dimensions only if both width and height are mismatched
312
+ // since except for that, it might be a situation which is different window rect and
313
+ // screenshot size like `@driver.window_rect #=>x=0, y=0, width=1080, height=1794` and
314
+ // `"deviceScreenSize"=>"1080x1920"`
315
+ if (screenWidth !== shotWidth && screenHeight !== shotHeight) {
316
+ logger_1.default.info(`Scaling screenshot from ${shotWidth}x${shotHeight} to match ` +
317
+ `screen at ${screenWidth}x${screenHeight}`);
318
+ imgObj = imgObj.resize(screenWidth, screenHeight);
319
+ scale.xScale *= (1.0 * screenWidth) / shotWidth;
320
+ scale.yScale *= (1.0 * screenHeight) / shotHeight;
321
+ }
322
+ b64Screenshot = (await imgObj.getBuffer(support_1.imageUtil.MIME_PNG)).toString('base64');
323
+ return { b64Screenshot, scale };
322
324
  }
323
-
324
- if (Math.round(xScale * FLOAT_PRECISION) === Math.round(DEFAULT_FIX_IMAGE_TEMPLATE_SCALE * FLOAT_PRECISION) && Math.round(Number(yScale * FLOAT_PRECISION === Math.round(DEFAULT_FIX_IMAGE_TEMPLATE_SCALE * FLOAT_PRECISION)))) {
325
- return b64Template;
325
+ /**
326
+ * @typedef ImageTemplateSettings
327
+ * @property {boolean} fixImageTemplateScale - fixImageTemplateScale in device-settings
328
+ * @property {number} defaultImageTemplateScale - defaultImageTemplateScale in device-settings
329
+ * @property {boolean} ignoreDefaultImageTemplateScale - Ignore defaultImageTemplateScale if it has true.
330
+ * If b64Template has been scaled to defaultImageTemplateScale or should ignore the scale,
331
+ * this parameter should be true. e.g. click in image-element module
332
+ * @property {number} xScale - Scale ratio for width
333
+ * @property {number} yScale - Scale ratio for height
334
+
335
+ */
336
+ /**
337
+ * Get a image that will be used for template maching.
338
+ * Returns scaled image if scale ratio is provided.
339
+ *
340
+ *
341
+ * @param {string} b64Template - base64-encoded image used as a template to be
342
+ * matched in the screenshot
343
+ * @param {ImageTemplateSettings} opts - Image template scale related options
344
+ *
345
+ * @returns {Promise<string>} base64-encoded scaled template screenshot
346
+ */
347
+ async fixImageTemplateScale(b64Template, opts) {
348
+ if (!opts) {
349
+ return b64Template;
350
+ }
351
+ let { fixImageTemplateScale = false, defaultImageTemplateScale = image_element_1.DEFAULT_TEMPLATE_IMAGE_SCALE, ignoreDefaultImageTemplateScale = false, xScale = DEFAULT_FIX_IMAGE_TEMPLATE_SCALE, yScale = DEFAULT_FIX_IMAGE_TEMPLATE_SCALE, } = opts;
352
+ if (ignoreDefaultImageTemplateScale) {
353
+ defaultImageTemplateScale = image_element_1.DEFAULT_TEMPLATE_IMAGE_SCALE;
354
+ }
355
+ // Default
356
+ if (defaultImageTemplateScale === image_element_1.DEFAULT_TEMPLATE_IMAGE_SCALE && !fixImageTemplateScale) {
357
+ return b64Template;
358
+ }
359
+ // Calculate xScale and yScale Appium should scale
360
+ if (fixImageTemplateScale) {
361
+ xScale *= defaultImageTemplateScale;
362
+ yScale *= defaultImageTemplateScale;
363
+ }
364
+ else {
365
+ xScale = yScale = 1 * defaultImageTemplateScale;
366
+ }
367
+ // xScale and yScale can be NaN if defaultImageTemplateScale is string, for example
368
+ if (!parseFloat(String(xScale)) || !parseFloat(String(yScale))) {
369
+ return b64Template;
370
+ }
371
+ // Return if the scale is default, 1, value
372
+ if (Math.round(xScale * FLOAT_PRECISION) ===
373
+ Math.round(DEFAULT_FIX_IMAGE_TEMPLATE_SCALE * FLOAT_PRECISION) &&
374
+ Math.round(Number(yScale * FLOAT_PRECISION ===
375
+ Math.round(DEFAULT_FIX_IMAGE_TEMPLATE_SCALE * FLOAT_PRECISION)))) {
376
+ return b64Template;
377
+ }
378
+ let imgTempObj = await support_1.imageUtil.getJimpImage(b64Template);
379
+ let { width: baseTempWidth, height: baseTempHeigh } = imgTempObj.bitmap;
380
+ const scaledWidth = baseTempWidth * xScale;
381
+ const scaledHeight = baseTempHeigh * yScale;
382
+ logger_1.default.info(`Scaling template image from ${baseTempWidth}x${baseTempHeigh}` +
383
+ ` to ${scaledWidth}x${scaledHeight}`);
384
+ logger_1.default.info(`The ratio is ${xScale} and ${yScale}`);
385
+ imgTempObj = await imgTempObj.resize(scaledWidth, scaledHeight);
386
+ return (await imgTempObj.getBuffer(support_1.imageUtil.MIME_PNG)).toString('base64');
326
387
  }
327
-
328
- let imgTempObj = await _support.imageUtil.getJimpImage(b64Template);
329
- let {
330
- width: baseTempWidth,
331
- height: baseTempHeigh
332
- } = imgTempObj.bitmap;
333
- const scaledWidth = baseTempWidth * xScale;
334
- const scaledHeight = baseTempHeigh * yScale;
335
-
336
- _logger.default.info(`Scaling template image from ${baseTempWidth}x${baseTempHeigh}` + ` to ${scaledWidth}x${scaledHeight}`);
337
-
338
- _logger.default.info(`The ratio is ${xScale} and ${yScale}`);
339
-
340
- imgTempObj = await imgTempObj.resize(scaledWidth, scaledHeight);
341
- return (await imgTempObj.getBuffer(_support.imageUtil.MIME_PNG)).toString('base64');
342
- }
343
-
344
388
  }
345
-
346
389
  exports.default = ImageElementFinder;
347
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJNSlNPTldQX0VMRU1FTlRfS0VZIiwiVzNDX0VMRU1FTlRfS0VZIiwidXRpbCIsIlczQ19XRUJfRUxFTUVOVF9JREVOVElGSUVSIiwiREVGQVVMVF9GSVhfSU1BR0VfVEVNUExBVEVfU0NBTEUiLCJGTE9BVF9QUkVDSVNJT04iLCJNQVhfQ0FDSEVfSVRFTVMiLCJNQVhfQ0FDSEVfU0laRV9CWVRFUyIsIkRFRkFVTFRfU0VUVElOR1MiLCJpbWFnZU1hdGNoVGhyZXNob2xkIiwiREVGQVVMVF9NQVRDSF9USFJFU0hPTEQiLCJpbWFnZU1hdGNoTWV0aG9kIiwiZml4SW1hZ2VGaW5kU2NyZWVuc2hvdERpbXMiLCJmaXhJbWFnZVRlbXBsYXRlU2l6ZSIsImZpeEltYWdlVGVtcGxhdGVTY2FsZSIsImRlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGUiLCJERUZBVUxUX1RFTVBMQVRFX0lNQUdFX1NDQUxFIiwiY2hlY2tGb3JJbWFnZUVsZW1lbnRTdGFsZW5lc3MiLCJhdXRvVXBkYXRlSW1hZ2VFbGVtZW50UG9zaXRpb24iLCJpbWFnZUVsZW1lbnRUYXBTdHJhdGVneSIsIklNQUdFX0VMX1RBUF9TVFJBVEVHWV9XM0MiLCJnZXRNYXRjaGVkSW1hZ2VSZXN1bHQiLCJJbWFnZUVsZW1lbnRGaW5kZXIiLCJkcml2ZXIiLCJpbWdFbENhY2hlIiwiY29uc3RydWN0b3IiLCJtYXhTaXplIiwiTFJVIiwibWF4Iiwic2l6ZUNhbGN1bGF0aW9uIiwiZWwiLCJ0ZW1wbGF0ZSIsImxlbmd0aCIsInNldERyaXZlciIsInJlZ2lzdGVySW1hZ2VFbGVtZW50IiwiaW1nRWwiLCJzZXQiLCJpZCIsInByb3RvS2V5IiwiaXNXM0NQcm90b2NvbCIsImFzRWxlbWVudCIsImZpbmRCeUltYWdlIiwiYjY0VGVtcGxhdGUiLCJzaG91bGRDaGVja1N0YWxlbmVzcyIsIm11bHRpcGxlIiwiaWdub3JlRGVmYXVsdEltYWdlVGVtcGxhdGVTY2FsZSIsIkVycm9yIiwic2V0dGluZ3MiLCJnZXRTZXR0aW5ncyIsInRocmVzaG9sZCIsInZpc3VhbGl6ZSIsImxvZyIsImluZm8iLCJnZXRXaW5kb3dTaXplIiwid2lkdGgiLCJzY3JlZW5XaWR0aCIsImhlaWdodCIsInNjcmVlbkhlaWdodCIsImVuc3VyZVRlbXBsYXRlU2l6ZSIsInJlc3VsdHMiLCJjb25kaXRpb24iLCJiNjRTY3JlZW5zaG90Iiwic2NhbGUiLCJnZXRTY3JlZW5zaG90Rm9ySW1hZ2VGaW5kIiwiY29tcGFyaXNvbk9wdHMiLCJtZXRob2QiLCJwdXNoIiwiY29tcGFyZUltYWdlcyIsIk1BVENIX1RFTVBMQVRFX01PREUiLCJlcnIiLCJtZXNzYWdlIiwibWF0Y2giLCJpbXBsaWNpdFdhaXRGb3JDb25kaXRpb24iLCJfIiwiaXNFbXB0eSIsImVycm9ycyIsIk5vU3VjaEVsZW1lbnRFcnJvciIsImVsZW1lbnRzIiwibWFwIiwicmVjdCIsInNjb3JlIiwidmlzdWFsaXphdGlvbiIsIkpTT04iLCJzdHJpbmdpZnkiLCJJbWFnZUVsZW1lbnQiLCJyZWdpc3RlcmVkRWxlbWVudHMiLCJpbWdPYmoiLCJpbWFnZVV0aWwiLCJnZXRKaW1wSW1hZ2UiLCJ0cGxXaWR0aCIsInRwbEhlaWdodCIsImJpdG1hcCIsInNjYWxlVG9GaXQiLCJnZXRCdWZmZXIiLCJNSU1FX1BORyIsInRvU3RyaW5nIiwiZ2V0U2NyZWVuc2hvdCIsIk9iamVjdCIsImFzc2lnbiIsIndhcm4iLCJzaG90V2lkdGgiLCJzaG90SGVpZ2h0IiwieFNjYWxlIiwieVNjYWxlIiwic2NyZWVuQVIiLCJzaG90QVIiLCJNYXRoIiwicm91bmQiLCJzY2FsZUZhY3RvciIsInJlc2l6ZSIsIm9wdHMiLCJwYXJzZUZsb2F0IiwiU3RyaW5nIiwiTnVtYmVyIiwiaW1nVGVtcE9iaiIsImJhc2VUZW1wV2lkdGgiLCJiYXNlVGVtcEhlaWdoIiwic2NhbGVkV2lkdGgiLCJzY2FsZWRIZWlnaHQiXSwic291cmNlcyI6WyIuLi8uLi9saWIvZmluZGVyLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBfIGZyb20gJ2xvZGFzaCc7XG5pbXBvcnQgTFJVIGZyb20gJ2xydS1jYWNoZSc7XG5pbXBvcnQge2Vycm9yc30gZnJvbSAnYXBwaXVtL2RyaXZlcic7XG5pbXBvcnQge3V0aWwsIGltYWdlVXRpbH0gZnJvbSAnYXBwaXVtL3N1cHBvcnQnO1xuaW1wb3J0IHtcbiAgSW1hZ2VFbGVtZW50LFxuICBERUZBVUxUX1RFTVBMQVRFX0lNQUdFX1NDQUxFLFxuICBJTUFHRV9FTF9UQVBfU1RSQVRFR1lfVzNDLFxufSBmcm9tICcuL2ltYWdlLWVsZW1lbnQnO1xuaW1wb3J0IHtNQVRDSF9URU1QTEFURV9NT0RFLCBjb21wYXJlSW1hZ2VzLCBERUZBVUxUX01BVENIX1RIUkVTSE9MRH0gZnJvbSAnLi9jb21wYXJlJztcbmltcG9ydCBsb2cgZnJvbSAnLi9sb2dnZXInO1xuXG5jb25zdCBNSlNPTldQX0VMRU1FTlRfS0VZID0gJ0VMRU1FTlQnO1xuY29uc3QgVzNDX0VMRU1FTlRfS0VZID0gdXRpbC5XM0NfV0VCX0VMRU1FTlRfSURFTlRJRklFUjtcbmNvbnN0IERFRkFVTFRfRklYX0lNQUdFX1RFTVBMQVRFX1NDQUxFID0gMTtcbi8vIFVzZWQgdG8gY29tcGFyZSByYXRpbyBhbmQgc2NyZWVuIHdpZHRoXG4vLyBQaXhlbCBpcyBiYXNpY2FsbHkgdW5kZXIgMTA4MCBmb3IgZXhhbXBsZS4gMTAwSyBpcyBwcm9iYWJseSBlbm91Z2ggZm8gYSB3aGlsZS5cbmNvbnN0IEZMT0FUX1BSRUNJU0lPTiA9IDEwMDAwMDtcbmNvbnN0IE1BWF9DQUNIRV9JVEVNUyA9IDEwMDtcbmNvbnN0IE1BWF9DQUNIRV9TSVpFX0JZVEVTID0gMTAyNCAqIDEwMjQgKiA0MDsgLy8gNDBtYlxuXG5jb25zdCBERUZBVUxUX1NFVFRJTkdTID0ge1xuICAvLyB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDEgcmVwcmVzZW50aW5nIG1hdGNoIHN0cmVuZ3RoLCBiZWxvdyB3aGljaCBhbiBpbWFnZVxuICAvLyBlbGVtZW50IHdpbGwgbm90IGJlIGZvdW5kXG4gIGltYWdlTWF0Y2hUaHJlc2hvbGQ6IERFRkFVTFRfTUFUQ0hfVEhSRVNIT0xELFxuXG4gIC8vIE9uZSBvZiBwb3NzaWJsZSBpbWFnZSBtYXRjaGluZyBtZXRob2RzLlxuICAvLyBSZWFkIGh0dHBzOi8vZG9jcy5vcGVuY3Yub3JnLzMuMC1iZXRhL2RvYy9weV90dXRvcmlhbHMvcHlfaW1ncHJvYy9weV90ZW1wbGF0ZV9tYXRjaGluZy9weV90ZW1wbGF0ZV9tYXRjaGluZy5odG1sXG4gIC8vIGZvciBtb3JlIGRldGFpbHMuXG4gIC8vIFRNX0NDT0VGRl9OT1JNRUQgYnkgZGVmYXVsdFxuICBpbWFnZU1hdGNoTWV0aG9kOiAnJyxcblxuICAvLyBpZiB0aGUgaW1hZ2UgcmV0dXJuZWQgYnkgZ2V0U2NyZWVuc2hvdCBkaWZmZXJzIGluIHNpemUgb3IgYXNwZWN0IHJhdGlvXG4gIC8vIGZyb20gdGhlIHNjcmVlbiwgYXR0ZW1wdCB0byBmaXggaXQgYXV0b21hdGljYWxseVxuICBmaXhJbWFnZUZpbmRTY3JlZW5zaG90RGltczogdHJ1ZSxcblxuICAvLyB3aGV0aGVyIEFwcGl1bSBzaG91bGQgZW5zdXJlIHRoYXQgYW4gaW1hZ2UgdGVtcGxhdGUgc2VudCBpbiBkdXJpbmcgaW1hZ2VcbiAgLy8gZWxlbWVudCBmaW5kIHNob3VsZCBoYXZlIGl0cyBzaXplIGFkanVzdGVkIHNvIHRoZSBtYXRjaCBhbGdvcml0aG0gd2lsbCBub3RcbiAgLy8gY29tcGxhaW5cbiAgZml4SW1hZ2VUZW1wbGF0ZVNpemU6IGZhbHNlLFxuXG4gIC8vIHdoZXRoZXIgQXBwaXVtIHNob3VsZCBlbnN1cmUgdGhhdCBhbiBpbWFnZSB0ZW1wbGF0ZSBzZW50IGluIGR1cmluZyBpbWFnZVxuICAvLyBlbGVtZW50IGZpbmQgc2hvdWxkIGhhdmUgaXRzIHNjYWxlIGFkanVzdGVkIHRvIGRpc3BsYXkgc2l6ZSBzbyB0aGUgbWF0Y2hcbiAgLy8gYWxnb3JpdGhtIHdpbGwgbm90IGNvbXBsYWluLlxuICAvLyBlLmcuIGlPUyBoYXMgYHdpZHRoPTM3NSwgaGVpZ2h0PTY2N2Agd2luZG93IHJlY3QsIGJ1dCBpdHMgc2NyZWVuc2hvdCBpc1xuICAvLyAgICAgIGB3aWR0aD03NTAgw5cgaGVpZ2h0PTEzMzRgIHBpeGVscy4gVGhpcyBzZXR0aW5nIGhlbHAgdG8gYWRqdXN0IHRoZSBzY2FsZVxuICAvLyAgICAgIGlmIGEgdXNlciB1c2UgYHdpZHRoPTc1MCDDlyBoZWlnaHQ9MTMzNGAgcGl4ZWxzJ3MgYmFzZSB0ZW1wbGF0ZSBpbWFnZS5cbiAgZml4SW1hZ2VUZW1wbGF0ZVNjYWxlOiBmYWxzZSxcblxuICAvLyBVc2VycyBtaWdodCBoYXZlIHNjYWxlZCB0ZW1wbGF0ZSBpbWFnZSB0byByZWR1Y2UgdGhlaXIgc3RvcmFnZSBzaXplLlxuICAvLyBUaGlzIHNldHRpbmcgYWxsb3dzIHVzZXJzIHRvIHNjYWxlIGEgdGVtcGxhdGUgaW1hZ2UgdGhleSBzZW5kIHRvIEFwcGl1bSBzZXJ2ZXJcbiAgLy8gc28gdGhhdCB0aGUgQXBwaXVtIHNlcnZlciBjb21wYXJlcyB0aGUgYWN0dWFsIHNjYWxlIHVzZXJzIG9yaWdpbmFsbHkgaGFkLlxuICAvLyBlLmcuIElmIGEgdXNlciBoYXMgYW4gaW1hZ2Ugb2YgMjcwIHggMzIgcGl4ZWxzIHdoaWNoIHdhcyBvcmlnaW5hbGx5IDEwODAgeCAxMjYgcGl4ZWxzLFxuICAvLyAgICAgIHRoZSB1c2VyIGNhbiBzZXQge2RlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGU6IDQuMH0gdG8gc2NhbGUgdGhlIHNtYWxsIGltYWdlXG4gIC8vICAgICAgdG8gdGhlIG9yaWdpbmFsIG9uZSBzbyB0aGF0IEFwcGl1bSBjYW4gY29tcGFyZSBpdCBhcyB0aGUgb3JpZ2luYWwgb25lLlxuICBkZWZhdWx0SW1hZ2VUZW1wbGF0ZVNjYWxlOiBERUZBVUxUX1RFTVBMQVRFX0lNQUdFX1NDQUxFLFxuXG4gIC8vIHdoZXRoZXIgQXBwaXVtIHNob3VsZCByZS1jaGVjayB0aGF0IGFuIGltYWdlIGVsZW1lbnQgY2FuIGJlIG1hdGNoZWRcbiAgLy8gYWdhaW5zdCB0aGUgY3VycmVudCBzY3JlZW5zaG90IGJlZm9yZSBjbGlja2luZyBpdFxuICBjaGVja0ZvckltYWdlRWxlbWVudFN0YWxlbmVzczogdHJ1ZSxcblxuICAvLyB3aGV0aGVyIGJlZm9yZSBjbGlja2luZyBvbiBhbiBpbWFnZSBlbGVtZW50IEFwcGl1bSBzaG91bGQgcmUtZGV0ZXJtaW5lIHRoZVxuICAvLyBwb3NpdGlvbiBvZiB0aGUgZWxlbWVudCBvbiBzY3JlZW5cbiAgYXV0b1VwZGF0ZUltYWdlRWxlbWVudFBvc2l0aW9uOiBmYWxzZSxcblxuICAvLyB3aGljaCBtZXRob2QgdG8gdXNlIGZvciB0YXBwaW5nIGJ5IGNvb3JkaW5hdGUgZm9yIGltYWdlIGVsZW1lbnRzLiB0aGVcbiAgLy8gb3B0aW9ucyBhcmUgJ3czYycgb3IgJ21qc29ud3AnXG4gIGltYWdlRWxlbWVudFRhcFN0cmF0ZWd5OiBJTUFHRV9FTF9UQVBfU1RSQVRFR1lfVzNDLFxuXG4gIC8vIHdoaWNoIG1ldGhvZCB0byB1c2UgdG8gc2F2ZSB0aGUgbWF0Y2hlZCBpbWFnZSBhcmVhIGluIEltYWdlRWxlbWVudCBjbGFzcy5cbiAgLy8gSXQgaXMgdXNlZCBmb3IgZGVidWdnaW5nIHB1cnBvc2UuXG4gIGdldE1hdGNoZWRJbWFnZVJlc3VsdDogZmFsc2UsXG59O1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBJbWFnZUVsZW1lbnRGaW5kZXIge1xuICAvKiogQHR5cGUge0V4dGVybmFsRHJpdmVyfSAqL1xuICBkcml2ZXI7XG5cbiAgLyoqIEB0eXBlIHtMUlU8c3RyaW5nLEltYWdlRWxlbWVudD59ICovXG4gIGltZ0VsQ2FjaGU7XG5cbiAgLyoqXG4gICAqXG4gICAqIEBwYXJhbSB7RXh0ZXJuYWxEcml2ZXJ9IGRyaXZlclxuICAgKiBAcGFyYW0ge251bWJlcn0gW21heFNpemVdXG4gICAqL1xuICBjb25zdHJ1Y3Rvcihkcml2ZXIsIG1heFNpemUgPSBNQVhfQ0FDSEVfU0laRV9CWVRFUykge1xuICAgIHRoaXMuZHJpdmVyID0gZHJpdmVyO1xuICAgIHRoaXMuaW1nRWxDYWNoZSA9IG5ldyBMUlUoe1xuICAgICAgbWF4OiBNQVhfQ0FDSEVfSVRFTVMsXG4gICAgICBtYXhTaXplLFxuICAgICAgc2l6ZUNhbGN1bGF0aW9uOiAoZWwpID0+IGVsLnRlbXBsYXRlLmxlbmd0aCxcbiAgICB9KTtcbiAgfVxuXG4gIHNldERyaXZlcihkcml2ZXIpIHtcbiAgICB0aGlzLmRyaXZlciA9IGRyaXZlcjtcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge0ltYWdlRWxlbWVudH0gaW1nRWxcbiAgICogQHJldHVybnMge0VsZW1lbnR9XG4gICAqL1xuICByZWdpc3RlckltYWdlRWxlbWVudChpbWdFbCkge1xuICAgIHRoaXMuaW1nRWxDYWNoZS5zZXQoaW1nRWwuaWQsIGltZ0VsKTtcbiAgICBjb25zdCBwcm90b0tleSA9IHRoaXMuZHJpdmVyLmlzVzNDUHJvdG9jb2woKSA/IFczQ19FTEVNRU5UX0tFWSA6IE1KU09OV1BfRUxFTUVOVF9LRVk7XG4gICAgcmV0dXJuIGltZ0VsLmFzRWxlbWVudChwcm90b0tleSk7XG4gIH1cblxuICAvKipcbiAgICogQHR5cGVkZWYgRmluZEJ5SW1hZ2VPcHRpb25zXG4gICAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gW3Nob3VsZENoZWNrU3RhbGVuZXNzPWZhbHNlXSAtIHdoZXRoZXIgdGhpcyBjYWxsIHRvIGZpbmQgYW5cbiAgICogaW1hZ2UgaXMgbWVyZWx5IHRvIGNoZWNrIHN0YWxlbmVzcy4gSWYgc28gd2UgY2FuIGJ5cGFzcyBhIGxvdCBvZiBsb2dpY1xuICAgKiBAcHJvcGVydHkge2Jvb2xlYW59IFttdWx0aXBsZT1mYWxzZV0gLSBXaGV0aGVyIHdlIGFyZSBmaW5kaW5nIG9uZSBlbGVtZW50IG9yXG4gICAqIG11bHRpcGxlXG4gICAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gW2lnbm9yZURlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGU9ZmFsc2VdIC0gV2hldGhlciB3ZVxuICAgKiBpZ25vcmUgZGVmYXVsdEltYWdlVGVtcGxhdGVTY2FsZS4gSXQgY2FuIGJlIHVzZWQgd2hlbiB5b3Ugd291bGQgbGlrZSB0b1xuICAgKiBzY2FsZSBiNjRUZW1wbGF0ZSB3aXRoIGRlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGUgc2V0dGluZy5cbiAgICovXG5cbiAgLyoqXG4gICAqIEZpbmQgYSBzY3JlZW4gcmVjdCByZXByZXNlbnRlZCBieSBhbiBJbWFnZUVsZW1lbnQgY29ycmVzcG9uZGluZyB0byBhbiBpbWFnZVxuICAgKiB0ZW1wbGF0ZSBzZW50IGluIGJ5IHRoZSBjbGllbnRcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IGI2NFRlbXBsYXRlIC0gYmFzZTY0LWVuY29kZWQgaW1hZ2UgdXNlZCBhcyBhIHRlbXBsYXRlIHRvIGJlXG4gICAqIG1hdGNoZWQgaW4gdGhlIHNjcmVlbnNob3RcbiAgICogQHBhcmFtIHtGaW5kQnlJbWFnZU9wdGlvbnN9IG9wdHMgLSBhZGRpdGlvbmFsIG9wdGlvbnNcbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2U8RWxlbWVudHxFbGVtZW50W118SW1hZ2VFbGVtZW50Pn0gLSBXZWJEcml2ZXIgZWxlbWVudCB3aXRoIGEgc3BlY2lhbCBpZCBwcmVmaXhcbiAgICovXG4gIGFzeW5jIGZpbmRCeUltYWdlKFxuICAgIGI2NFRlbXBsYXRlLFxuICAgIHtzaG91bGRDaGVja1N0YWxlbmVzcyA9IGZhbHNlLCBtdWx0aXBsZSA9IGZhbHNlLCBpZ25vcmVEZWZhdWx0SW1hZ2VUZW1wbGF0ZVNjYWxlID0gZmFsc2V9XG4gICkge1xuICAgIGlmICghdGhpcy5kcml2ZXIpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgQ2FuJ3QgZmluZCB3aXRob3V0IGEgZHJpdmVyIWApO1xuICAgIH1cbiAgICBjb25zdCBzZXR0aW5ncyA9IHsuLi5ERUZBVUxUX1NFVFRJTkdTLCAuLi50aGlzLmRyaXZlci5zZXR0aW5ncy5nZXRTZXR0aW5ncygpfTtcbiAgICBjb25zdCB7XG4gICAgICBpbWFnZU1hdGNoVGhyZXNob2xkOiB0aHJlc2hvbGQsXG4gICAgICBpbWFnZU1hdGNoTWV0aG9kLFxuICAgICAgZml4SW1hZ2VUZW1wbGF0ZVNpemUsXG4gICAgICBmaXhJbWFnZVRlbXBsYXRlU2NhbGUsXG4gICAgICBkZWZhdWx0SW1hZ2VUZW1wbGF0ZVNjYWxlLFxuICAgICAgZ2V0TWF0Y2hlZEltYWdlUmVzdWx0OiB2aXN1YWxpemUsXG4gICAgfSA9IHNldHRpbmdzO1xuXG4gICAgbG9nLmluZm8oYEZpbmRpbmcgaW1hZ2UgZWxlbWVudCB3aXRoIG1hdGNoIHRocmVzaG9sZCAke3RocmVzaG9sZH1gKTtcbiAgICBpZiAoIXRoaXMuZHJpdmVyLmdldFdpbmRvd1NpemUpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIlRoaXMgZHJpdmVyIGRvZXMgbm90IHN1cHBvcnQgdGhlIHJlcXVpcmVkICdnZXRXaW5kb3dTaXplJyBjb21tYW5kXCIpO1xuICAgIH1cbiAgICBjb25zdCB7d2lkdGg6IHNjcmVlbldpZHRoLCBoZWlnaHQ6IHNjcmVlbkhlaWdodH0gPSBhd2FpdCB0aGlzLmRyaXZlci5nZXRXaW5kb3dTaXplKCk7XG5cbiAgICAvLyBzb21lb25lIG1pZ2h0IGhhdmUgc2VudCBpbiBhIHRlbXBsYXRlIHRoYXQncyBsYXJnZXIgdGhhbiB0aGUgc2NyZWVuXG4gICAgLy8gZGltZW5zaW9ucy4gSWYgc28gbGV0J3MgY2hlY2sgYW5kIGN1dCBpdCBkb3duIHRvIHNpemUgc2luY2UgdGhlIGFsZ29yaXRobVxuICAgIC8vIHdpbGwgbm90IHdvcmsgdW5sZXNzIHdlIGRvLiBCdXQgYmVjYXVzZSBpdCByZXF1aXJlcyBzb21lIHBvdGVudGlhbGx5XG4gICAgLy8gZXhwZW5zaXZlIGNvbW1hbmRzLCBvbmx5IGRvIHRoaXMgaWYgdGhlIHVzZXIgaGFzIHJlcXVlc3RlZCBpdCBpbiBzZXR0aW5ncy5cbiAgICBpZiAoZml4SW1hZ2VUZW1wbGF0ZVNpemUpIHtcbiAgICAgIGI2NFRlbXBsYXRlID0gYXdhaXQgdGhpcy5lbnN1cmVUZW1wbGF0ZVNpemUoYjY0VGVtcGxhdGUsIHNjcmVlbldpZHRoLCBzY3JlZW5IZWlnaHQpO1xuICAgIH1cblxuICAgIGNvbnN0IHJlc3VsdHMgPSBbXTtcbiAgICBjb25zdCBjb25kaXRpb24gPSBhc3luYyAoKSA9PiB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCB7YjY0U2NyZWVuc2hvdCwgc2NhbGV9ID0gYXdhaXQgdGhpcy5nZXRTY3JlZW5zaG90Rm9ySW1hZ2VGaW5kKFxuICAgICAgICAgIHNjcmVlbldpZHRoLFxuICAgICAgICAgIHNjcmVlbkhlaWdodFxuICAgICAgICApO1xuXG4gICAgICAgIGI2NFRlbXBsYXRlID0gYXdhaXQgdGhpcy5maXhJbWFnZVRlbXBsYXRlU2NhbGUoYjY0VGVtcGxhdGUsIHtcbiAgICAgICAgICBkZWZhdWx0SW1hZ2VUZW1wbGF0ZVNjYWxlLFxuICAgICAgICAgIGlnbm9yZURlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGUsXG4gICAgICAgICAgZml4SW1hZ2VUZW1wbGF0ZVNjYWxlLFxuICAgICAgICAgIC4uLnNjYWxlLFxuICAgICAgICB9KTtcblxuICAgICAgICBjb25zdCBjb21wYXJpc29uT3B0cyA9IHtcbiAgICAgICAgICB0aHJlc2hvbGQsXG4gICAgICAgICAgdmlzdWFsaXplLFxuICAgICAgICAgIG11bHRpcGxlLFxuICAgICAgICB9O1xuICAgICAgICBpZiAoaW1hZ2VNYXRjaE1ldGhvZCkge1xuICAgICAgICAgIGNvbXBhcmlzb25PcHRzLm1ldGhvZCA9IGltYWdlTWF0Y2hNZXRob2Q7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKG11bHRpcGxlKSB7XG4gICAgICAgICAgcmVzdWx0cy5wdXNoKFxuICAgICAgICAgICAgLi4uKGF3YWl0IGNvbXBhcmVJbWFnZXMoXG4gICAgICAgICAgICAgIE1BVENIX1RFTVBMQVRFX01PREUsXG4gICAgICAgICAgICAgIGI2NFNjcmVlbnNob3QsXG4gICAgICAgICAgICAgIGI2NFRlbXBsYXRlLFxuICAgICAgICAgICAgICBjb21wYXJpc29uT3B0c1xuICAgICAgICAgICAgKSlcbiAgICAgICAgICApO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHJlc3VsdHMucHVzaChcbiAgICAgICAgICAgIGF3YWl0IGNvbXBhcmVJbWFnZXMoTUFUQ0hfVEVNUExBVEVfTU9ERSwgYjY0U2NyZWVuc2hvdCwgYjY0VGVtcGxhdGUsIGNvbXBhcmlzb25PcHRzKVxuICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgLy8gaWYgY29tcGFyZUltYWdlcyBmYWlscywgd2UnbGwgZ2V0IGEgc3BlY2lmaWMgZXJyb3IsIGJ1dCB3ZSBzaG91bGRcbiAgICAgICAgLy8gcmV0cnksIHNvIHRyYXAgdGhhdCBhbmQganVzdCByZXR1cm4gZmFsc2UgdG8gdHJpZ2dlciB0aGUgbmV4dCByb3VuZCBvZlxuICAgICAgICAvLyBpbXBsaWNpdGx5IHdhaXRpbmcuIEZvciBvdGhlciBlcnJvcnMsIHRocm93IHRoZW0gdG8gZ2V0IG91dCBvZiB0aGVcbiAgICAgICAgLy8gaW1wbGljaXQgd2FpdCBsb29wXG4gICAgICAgIGlmIChlcnIubWVzc2FnZS5tYXRjaCgvQ2Fubm90IGZpbmQgYW55IG9jY3VycmVuY2VzLykpIHtcbiAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgdGhyb3cgZXJyO1xuICAgICAgfVxuICAgIH07XG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgdGhpcy5kcml2ZXIuaW1wbGljaXRXYWl0Rm9yQ29uZGl0aW9uKGNvbmRpdGlvbik7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAvLyB0aGlzIGBpbXBsaWNpdFdhaXRGb3JDb25kaXRpb25gIG1ldGhvZCB3aWxsIHRocm93IGEgJ0NvbmRpdGlvbiB1bm1ldCdcbiAgICAgIC8vIGVycm9yIGlmIGFuIGVsZW1lbnQgaXMgbm90IGZvdW5kIGV2ZW50dWFsbHkuIEluIHRoYXQgY2FzZSwgd2Ugd2lsbFxuICAgICAgLy8gaGFuZGxlIHRoZSBlbGVtZW50IG5vdCBmb3VuZCByZXNwb25zZSBiZWxvdy4gSW4gdGhlIGNhc2Ugd2hlcmUgZ2V0IHNvbWVcbiAgICAgIC8vIF9vdGhlcl8ga2luZCBvZiBlcnJvciwgaXQgbWVhbnMgc29tZXRoaW5nIGJsZXcgdXAgdG90YWxseSBhcGFydCBmcm9tIHRoZVxuICAgICAgLy8gaW1wbGljaXQgd2FpdCB0aW1lb3V0LiBXZSBzaG91bGQgbm90IG1hc2sgdGhhdCBlcnJvciBhbmQgaW5zdGVhZCB0aHJvd1xuICAgICAgLy8gaXQgc3RyYWlnaHRhd2F5XG4gICAgICBpZiAoIWVyci5tZXNzYWdlLm1hdGNoKC9Db25kaXRpb24gdW5tZXQvKSkge1xuICAgICAgICB0aHJvdyBlcnI7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKF8uaXNFbXB0eShyZXN1bHRzKSkge1xuICAgICAgaWYgKG11bHRpcGxlKSB7XG4gICAgICAgIHJldHVybiBbXTtcbiAgICAgIH1cbiAgICAgIHRocm93IG5ldyBlcnJvcnMuTm9TdWNoRWxlbWVudEVycm9yKCk7XG4gICAgfVxuXG4gICAgY29uc3QgZWxlbWVudHMgPSByZXN1bHRzLm1hcCgoe3JlY3QsIHNjb3JlLCB2aXN1YWxpemF0aW9ufSkgPT4ge1xuICAgICAgbG9nLmluZm8oYEltYWdlIHRlbXBsYXRlIG1hdGNoZWQ6ICR7SlNPTi5zdHJpbmdpZnkocmVjdCl9YCk7XG4gICAgICByZXR1cm4gbmV3IEltYWdlRWxlbWVudChiNjRUZW1wbGF0ZSwgcmVjdCwgc2NvcmUsIHZpc3VhbGl6YXRpb24sIHRoaXMpO1xuICAgIH0pO1xuXG4gICAgLy8gaWYgd2UncmUganVzdCBjaGVja2luZyBzdGFsZW5lc3MsIHJldHVybiBzdHJhaWdodGF3YXkgc28gd2UgZG9uJ3QgYWRkXG4gICAgLy8gYSBuZXcgZWxlbWVudCB0byB0aGUgY2FjaGUuIHNob3VsZENoZWNrU3RhbGVuZXNzIGRvZXMgbm90IHN1cHBvcnQgbXVsdGlwbGVcbiAgICAvLyBlbGVtZW50cywgc2luY2UgaXQgaXMgYSBwdXJlbHkgaW50ZXJuYWwgbWVjaGFuaXNtXG4gICAgaWYgKHNob3VsZENoZWNrU3RhbGVuZXNzKSB7XG4gICAgICByZXR1cm4gZWxlbWVudHNbMF07XG4gICAgfVxuXG4gICAgY29uc3QgcmVnaXN0ZXJlZEVsZW1lbnRzID0gZWxlbWVudHMubWFwKChpbWdFbCkgPT4gdGhpcy5yZWdpc3RlckltYWdlRWxlbWVudChpbWdFbCkpO1xuXG4gICAgcmV0dXJuIG11bHRpcGxlID8gcmVnaXN0ZXJlZEVsZW1lbnRzIDogcmVnaXN0ZXJlZEVsZW1lbnRzWzBdO1xuICB9XG5cbiAgLyoqXG4gICAqIEVuc3VyZSB0aGF0IHRoZSBpbWFnZSB0ZW1wbGF0ZSBzZW50IGluIGZvciBhIGZpbmQgaXMgb2YgYSBzdWl0YWJsZSBzaXplXG4gICAqXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBiNjRUZW1wbGF0ZSAtIGJhc2U2NC1lbmNvZGVkIGltYWdlXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBzY3JlZW5XaWR0aCAtIHdpZHRoIG9mIHNjcmVlblxuICAgKiBAcGFyYW0ge251bWJlcn0gc2NyZWVuSGVpZ2h0IC0gaGVpZ2h0IG9mIHNjcmVlblxuICAgKlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBiYXNlNjQtZW5jb2RlZCBpbWFnZSwgcG90ZW50aWFsbHkgcmVzaXplZFxuICAgKi9cbiAgYXN5bmMgZW5zdXJlVGVtcGxhdGVTaXplKGI2NFRlbXBsYXRlLCBzY3JlZW5XaWR0aCwgc2NyZWVuSGVpZ2h0KSB7XG4gICAgbGV0IGltZ09iaiA9IGF3YWl0IGltYWdlVXRpbC5nZXRKaW1wSW1hZ2UoYjY0VGVtcGxhdGUpO1xuICAgIGxldCB7d2lkdGg6IHRwbFdpZHRoLCBoZWlnaHQ6IHRwbEhlaWdodH0gPSBpbWdPYmouYml0bWFwO1xuXG4gICAgbG9nLmluZm8oXG4gICAgICBgVGVtcGxhdGUgaW1hZ2UgaXMgJHt0cGxXaWR0aH14JHt0cGxIZWlnaHR9LiBTY3JlZW4gc2l6ZSBpcyAke3NjcmVlbldpZHRofXgke3NjcmVlbkhlaWdodH1gXG4gICAgKTtcbiAgICAvLyBpZiB0aGUgdGVtcGxhdGUgZml0cyBpbnNpZGUgdGhlIHNjcmVlbiBkaW1lbnNpb25zLCB3ZSdyZSBnb29kXG4gICAgaWYgKHRwbFdpZHRoIDw9IHNjcmVlbldpZHRoICYmIHRwbEhlaWdodCA8PSBzY3JlZW5IZWlnaHQpIHtcbiAgICAgIHJldHVybiBiNjRUZW1wbGF0ZTtcbiAgICB9XG5cbiAgICBsb2cuaW5mbyhcbiAgICAgIGBTY2FsaW5nIHRlbXBsYXRlIGltYWdlIGZyb20gJHt0cGxXaWR0aH14JHt0cGxIZWlnaHR9IHRvIG1hdGNoIGAgK1xuICAgICAgICBgc2NyZWVuIGF0ICR7c2NyZWVuV2lkdGh9eCR7c2NyZWVuSGVpZ2h0fWBcbiAgICApO1xuICAgIC8vIG90aGVyd2lzZSwgc2NhbGUgaXQgdG8gZml0IGluc2lkZSB0aGUgc2NyZWVuIGRpbWVuc2lvbnNcbiAgICBpbWdPYmogPSBpbWdPYmouc2NhbGVUb0ZpdChzY3JlZW5XaWR0aCwgc2NyZWVuSGVpZ2h0KTtcbiAgICByZXR1cm4gKGF3YWl0IGltZ09iai5nZXRCdWZmZXIoaW1hZ2VVdGlsLk1JTUVfUE5HKSkudG9TdHJpbmcoJ2Jhc2U2NCcpO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgc2NyZWVuc2hvdCBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCBmb3IgZmluZCBieSBlbGVtZW50LCBwb3RlbnRpYWxseVxuICAgKiBhbHRlcmluZyBpdCBpbiB2YXJpb3VzIHdheXMgYmFzZWQgb24gdXNlci1yZXF1ZXN0ZWQgc2V0dGluZ3NcbiAgICpcbiAgICogQHBhcmFtIHtudW1iZXJ9IHNjcmVlbldpZHRoIC0gd2lkdGggb2Ygc2NyZWVuXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBzY3JlZW5IZWlnaHQgLSBoZWlnaHQgb2Ygc2NyZWVuXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFNjcmVlbnNob3QgJiB7c2NhbGU/OiBTY3JlZW5zaG90U2NhbGV9Pn0gYmFzZTY0LWVuY29kZWQgc2NyZWVuc2hvdCBhbmQgU2NyZWVuc2hvdFNjYWxlXG4gICAqL1xuICBhc3luYyBnZXRTY3JlZW5zaG90Rm9ySW1hZ2VGaW5kKHNjcmVlbldpZHRoLCBzY3JlZW5IZWlnaHQpIHtcbiAgICBpZiAoIXRoaXMuZHJpdmVyLmdldFNjcmVlbnNob3QpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIlRoaXMgZHJpdmVyIGRvZXMgbm90IHN1cHBvcnQgdGhlIHJlcXVpcmVkICdnZXRTY3JlZW5zaG90JyBjb21tYW5kXCIpO1xuICAgIH1cbiAgICBjb25zdCBzZXR0aW5ncyA9IE9iamVjdC5hc3NpZ24oe30sIERFRkFVTFRfU0VUVElOR1MsIHRoaXMuZHJpdmVyLnNldHRpbmdzLmdldFNldHRpbmdzKCkpO1xuICAgIGNvbnN0IHtmaXhJbWFnZUZpbmRTY3JlZW5zaG90RGltc30gPSBzZXR0aW5ncztcblxuICAgIGxldCBiNjRTY3JlZW5zaG90ID0gYXdhaXQgdGhpcy5kcml2ZXIuZ2V0U2NyZWVuc2hvdCgpO1xuXG4gICAgLy8gaWYgdGhlIHVzZXIgaGFzIHJlcXVlc3RlZCBub3QgdG8gY29ycmVjdCBmb3IgYXNwZWN0IG9yIHNpemUgZGlmZmVyZW5jZXNcbiAgICAvLyBiZXR3ZWVuIHRoZSBzY3JlZW5zaG90IGFuZCB0aGUgc2NyZWVuLCBqdXN0IHJldHVybiB0aGUgc2NyZWVuc2hvdCBub3dcbiAgICBpZiAoIWZpeEltYWdlRmluZFNjcmVlbnNob3REaW1zKSB7XG4gICAgICBsb2cuaW5mbyhgTm90IHZlcmlmeWluZyBzY3JlZW5zaG90IGRpbWVuc2lvbnMgbWF0Y2ggc2NyZWVuYCk7XG4gICAgICByZXR1cm4ge2I2NFNjcmVlbnNob3R9O1xuICAgIH1cblxuICAgIGlmIChzY3JlZW5XaWR0aCA8IDEgfHwgc2NyZWVuSGVpZ2h0IDwgMSkge1xuICAgICAgbG9nLndhcm4oXG4gICAgICAgIGBUaGUgcmV0cmlldmVkIHNjcmVlbiBzaXplICR7c2NyZWVuV2lkdGh9eCR7c2NyZWVuSGVpZ2h0fSBkb2VzIGAgK1xuICAgICAgICAgIGBub3Qgc2VlbSB0byBiZSB2YWxpZC4gTm8gY2hhbmdlcyB3aWxsIGJlIGFwcGxpZWQgdG8gdGhlIHNjcmVlbnNob3RgXG4gICAgICApO1xuICAgICAgcmV0dXJuIHtiNjRTY3JlZW5zaG90fTtcbiAgICB9XG5cbiAgICAvLyBvdGhlcndpc2UsIGRvIHNvbWUgdmVyaWZpY2F0aW9uIG9uIHRoZSBzY3JlZW5zaG90IHRvIG1ha2Ugc3VyZSBpdCBtYXRjaGVzXG4gICAgLy8gdGhlIHNjcmVlbiBzaXplIGFuZCBhc3BlY3QgcmF0aW9cbiAgICBsb2cuaW5mbygnVmVyaWZ5aW5nIHNjcmVlbnNob3Qgc2l6ZSBhbmQgYXNwZWN0IHJhdGlvJyk7XG5cbiAgICBsZXQgaW1nT2JqID0gYXdhaXQgaW1hZ2VVdGlsLmdldEppbXBJbWFnZShiNjRTY3JlZW5zaG90KTtcbiAgICBsZXQge3dpZHRoOiBzaG90V2lkdGgsIGhlaWdodDogc2hvdEhlaWdodH0gPSBpbWdPYmouYml0bWFwO1xuXG4gICAgaWYgKHNob3RXaWR0aCA8IDEgfHwgc2hvdEhlaWdodCA8IDEpIHtcbiAgICAgIGxvZy53YXJuKFxuICAgICAgICBgVGhlIHJldHJpZXZlZCBzY3JlZW5zaG90IHNpemUgJHtzaG90V2lkdGh9eCR7c2hvdEhlaWdodH0gZG9lcyBgICtcbiAgICAgICAgICBgbm90IHNlZW0gdG8gYmUgdmFsaWQuIE5vIGNoYW5nZXMgd2lsbCBiZSBhcHBsaWVkIHRvIHRoZSBzY3JlZW5zaG90YFxuICAgICAgKTtcbiAgICAgIHJldHVybiB7YjY0U2NyZWVuc2hvdH07XG4gICAgfVxuXG4gICAgaWYgKHNjcmVlbldpZHRoID09PSBzaG90V2lkdGggJiYgc2NyZWVuSGVpZ2h0ID09PSBzaG90SGVpZ2h0KSB7XG4gICAgICAvLyB0aGUgaGVpZ2h0IGFuZCB3aWR0aCBvZiB0aGUgc2NyZWVuc2hvdCBhbmQgdGhlIGRldmljZSBzY3JlZW4gbWF0Y2gsIHdoaWNoXG4gICAgICAvLyBtZWFucyB3ZSBzaG91bGQgYmUgc2FmZSB3aGVuIGRvaW5nIHRlbXBsYXRlIG1hdGNoZXNcbiAgICAgIGxvZy5pbmZvKCdTY3JlZW5zaG90IHNpemUgbWF0Y2hlZCBzY3JlZW4gc2l6ZScpO1xuICAgICAgcmV0dXJuIHtiNjRTY3JlZW5zaG90fTtcbiAgICB9XG5cbiAgICAvLyBvdGhlcndpc2UsIGlmIHRoZXkgZG9uJ3QgbWF0Y2gsIGl0IGNvdWxkIHNwZWxsIHByb2JsZW1zIGZvciB0aGUgYWNjdXJhY3lcbiAgICAvLyBvZiBjb29yZGluYXRlcyByZXR1cm5lZCBieSB0aGUgaW1hZ2UgbWF0Y2ggYWxnb3JpdGhtLCBzaW5jZSB3ZSBtYXRjaCBiYXNlZFxuICAgIC8vIG9uIHRoZSBzY3JlZW5zaG90IGNvb3JkaW5hdGVzIG5vdCB0aGUgZGV2aWNlIGNvb3JkaW5hdGVzIHRoZW1zZWx2ZXMuIFRoZXJlXG4gICAgLy8gYXJlIHR3byBwb3RlbnRpYWwgdHlwZXMgb2YgbWlzbWF0Y2g6IGFzcGVjdCByYXRpbyBtaXNtYXRjaCBhbmQgc2NhbGVcbiAgICAvLyBtaXNtYXRjaC4gV2UgbmVlZCB0byBkZXRlY3QgYW5kIGZpeCBib3RoXG5cbiAgICBjb25zdCBzY2FsZSA9IHt4U2NhbGU6IDEuMCwgeVNjYWxlOiAxLjB9O1xuXG4gICAgY29uc3Qgc2NyZWVuQVIgPSBzY3JlZW5XaWR0aCAvIHNjcmVlbkhlaWdodDtcbiAgICBjb25zdCBzaG90QVIgPSBzaG90V2lkdGggLyBzaG90SGVpZ2h0O1xuICAgIGlmIChNYXRoLnJvdW5kKHNjcmVlbkFSICogRkxPQVRfUFJFQ0lTSU9OKSA9PT0gTWF0aC5yb3VuZChzaG90QVIgKiBGTE9BVF9QUkVDSVNJT04pKSB7XG4gICAgICBsb2cuaW5mbyhcbiAgICAgICAgYFNjcmVlbnNob3QgYXNwZWN0IHJhdGlvICcke3Nob3RBUn0nICgke3Nob3RXaWR0aH14JHtzaG90SGVpZ2h0fSkgbWF0Y2hlZCBgICtcbiAgICAgICAgICBgc2NyZWVuIGFzcGVjdCByYXRpbyAnJHtzY3JlZW5BUn0nICgke3NjcmVlbldpZHRofXgke3NjcmVlbkhlaWdodH0pYFxuICAgICAgKTtcbiAgICB9IGVsc2Uge1xuICAgICAgbG9nLndhcm4oXG4gICAgICAgIGBXaGVuIHRyeWluZyB0byBmaW5kIGFuIGVsZW1lbnQsIGRldGVybWluZWQgdGhhdCB0aGUgc2NyZWVuIGAgK1xuICAgICAgICAgIGBhc3BlY3QgcmF0aW8gYW5kIHNjcmVlbnNob3QgYXNwZWN0IHJhdGlvIGFyZSBkaWZmZXJlbnQuIFNjcmVlbiBgICtcbiAgICAgICAgICBgaXMgJHtzY3JlZW5XaWR0aH14JHtzY3JlZW5IZWlnaHR9IHdoZXJlYXMgc2NyZWVuc2hvdCBpcyBgICtcbiAgICAgICAgICBgJHtzaG90V2lkdGh9eCR7c2hvdEhlaWdodH0uYFxuICAgICAgKTtcblxuICAgICAgLy8gSW4gdGhlIGNhc2Ugd2hlcmUgdGhlIHgtc2NhbGUgYW5kIHktc2NhbGUgYXJlIGRpZmZlcmVudCwgd2UgbmVlZCB0byBkZWNpZGVcbiAgICAgIC8vIHdoaWNoIG9uZSB0byByZXNwZWN0LCBvdGhlcndpc2UgdGhlIHNjcmVlbnNob3QgYW5kIHRlbXBsYXRlIHdpbGwgZW5kIHVwXG4gICAgICAvLyBiZWluZyByZXNpemVkIGluIGEgd2F5IHRoYXQgY2hhbmdlcyBpdHMgYXNwZWN0IHJhdGlvIChkaXN0b3J0cyBpdCkuIEZvciBleGFtcGxlLCBsZXQncyBzYXk6XG4gICAgICAvLyB0aGlzLmdldFNjcmVlbnNob3Qoc2hvdFdpZHRoLCBzaG90SGVpZ2h0KSBpcyA1NDB4Mzk3LFxuICAgICAgLy8gdGhpcy5nZXREZXZpY2VTaXplKHNjcmVlbldpZHRoLCBzY3JlZW5IZWlnaHQpIGlzIDEwODB4MTkyMC5cbiAgICAgIC8vIFRoZSByYXRpbyB3b3VsZCB0aGVuIGJlIHt4U2NhbGU6IDAuNSwgeVNjYWxlOiAwLjJ9LlxuICAgICAgLy8gSW4gdGhpcyBjYXNlLCB3ZSBtdXN0IHNob3VsZCBgeVNjYWxlOiAwLjJgIGFzIHNjYWxlRmFjdG9yLCBiZWNhdXNlXG4gICAgICAvLyBpZiB3ZSBzZWxlY3QgdGhlIHhTY2FsZSwgdGhlIGhlaWdodCB3aWxsIGJlIGJpZ2dlciB0aGFuIHJlYWwgc2NyZWVuc2hvdCBzaXplXG4gICAgICAvLyB3aGljaCBpcyB1c2VkIHRvIGltYWdlIGNvbXBhcmlzb24gYnkgT3BlbkNWIGFzIGEgYmFzZSBpbWFnZS5cbiAgICAgIC8vIEFsbCBvZiB0aGlzIGlzIHByaW1hcmlseSB1c2VmdWwgd2hlbiB0aGUgc2NyZWVuc2hvdCBpcyBhIGhvcml6b250YWwgc2xpY2UgdGFrZW4gb3V0IG9mIHRoZVxuICAgICAgLy8gc2NyZWVuIChmb3IgZXhhbXBsZSBub3QgaW5jbHVkaW5nIHRvcC9ib3R0b20gbmF2IGJhcnMpXG4gICAgICBjb25zdCB4U2NhbGUgPSAoMS4wICogc2hvdFdpZHRoKSAvIHNjcmVlbldpZHRoO1xuICAgICAgY29uc3QgeVNjYWxlID0gKDEuMCAqIHNob3RIZWlnaHQpIC8gc2NyZWVuSGVpZ2h0O1xuICAgICAgY29uc3Qgc2NhbGVGYWN0b3IgPSB4U2NhbGUgPj0geVNjYWxlID8geVNjYWxlIDogeFNjYWxlO1xuXG4gICAgICBsb2cud2FybihcbiAgICAgICAgYFJlc2l6aW5nIHNjcmVlbnNob3QgdG8gJHtzaG90V2lkdGggKiBzY2FsZUZhY3Rvcn14JHtzaG90SGVpZ2h0ICogc2NhbGVGYWN0b3J9IHRvIG1hdGNoIGAgK1xuICAgICAgICAgIGBzY3JlZW4gYXNwZWN0IHJhdGlvIHNvIHRoYXQgaW1hZ2UgZWxlbWVudCBjb29yZGluYXRlcyBoYXZlIGEgYCArXG4gICAgICAgICAgYGdyZWF0ZXIgY2hhbmNlIG9mIGJlaW5nIGNvcnJlY3QuYFxuICAgICAgKTtcbiAgICAgIGltZ09iaiA9IGltZ09iai5yZXNpemUoc2hvdFdpZHRoICogc2NhbGVGYWN0b3IsIHNob3RIZWlnaHQgKiBzY2FsZUZhY3Rvcik7XG5cbiAgICAgIHNjYWxlLnhTY2FsZSAqPSBzY2FsZUZhY3RvcjtcbiAgICAgIHNjYWxlLnlTY2FsZSAqPSBzY2FsZUZhY3RvcjtcblxuICAgICAgc2hvdFdpZHRoID0gaW1nT2JqLmJpdG1hcC53aWR0aDtcbiAgICAgIHNob3RIZWlnaHQgPSBpbWdPYmouYml0bWFwLmhlaWdodDtcbiAgICB9XG5cbiAgICAvLyBSZXNpemUgYmFzZWQgb24gdGhlIHNjcmVlbiBkaW1lbnNpb25zIG9ubHkgaWYgYm90aCB3aWR0aCBhbmQgaGVpZ2h0IGFyZSBtaXNtYXRjaGVkXG4gICAgLy8gc2luY2UgZXhjZXB0IGZvciB0aGF0LCBpdCBtaWdodCBiZSBhIHNpdHVhdGlvbiB3aGljaCBpcyBkaWZmZXJlbnQgd2luZG93IHJlY3QgYW5kXG4gICAgLy8gc2NyZWVuc2hvdCBzaXplIGxpa2UgYEBkcml2ZXIud2luZG93X3JlY3QgIz0+eD0wLCB5PTAsIHdpZHRoPTEwODAsIGhlaWdodD0xNzk0YCBhbmRcbiAgICAvLyBgXCJkZXZpY2VTY3JlZW5TaXplXCI9PlwiMTA4MHgxOTIwXCJgXG4gICAgaWYgKHNjcmVlbldpZHRoICE9PSBzaG90V2lkdGggJiYgc2NyZWVuSGVpZ2h0ICE9PSBzaG90SGVpZ2h0KSB7XG4gICAgICBsb2cuaW5mbyhcbiAgICAgICAgYFNjYWxpbmcgc2NyZWVuc2hvdCBmcm9tICR7c2hvdFdpZHRofXgke3Nob3RIZWlnaHR9IHRvIG1hdGNoIGAgK1xuICAgICAgICAgIGBzY3JlZW4gYXQgJHtzY3JlZW5XaWR0aH14JHtzY3JlZW5IZWlnaHR9YFxuICAgICAgKTtcbiAgICAgIGltZ09iaiA9IGltZ09iai5yZXNpemUoc2NyZWVuV2lkdGgsIHNjcmVlbkhlaWdodCk7XG5cbiAgICAgIHNjYWxlLnhTY2FsZSAqPSAoMS4wICogc2NyZWVuV2lkdGgpIC8gc2hvdFdpZHRoO1xuICAgICAgc2NhbGUueVNjYWxlICo9ICgxLjAgKiBzY3JlZW5IZWlnaHQpIC8gc2hvdEhlaWdodDtcbiAgICB9XG5cbiAgICBiNjRTY3JlZW5zaG90ID0gKGF3YWl0IGltZ09iai5nZXRCdWZmZXIoaW1hZ2VVdGlsLk1JTUVfUE5HKSkudG9TdHJpbmcoJ2Jhc2U2NCcpO1xuICAgIHJldHVybiB7YjY0U2NyZWVuc2hvdCwgc2NhbGV9O1xuICB9XG5cbiAgLyoqXG4gICAqIEB0eXBlZGVmIEltYWdlVGVtcGxhdGVTZXR0aW5nc1xuICAgKiBAcHJvcGVydHkge2Jvb2xlYW59IGZpeEltYWdlVGVtcGxhdGVTY2FsZSAtIGZpeEltYWdlVGVtcGxhdGVTY2FsZSBpbiBkZXZpY2Utc2V0dGluZ3NcbiAgICogQHByb3BlcnR5IHtudW1iZXJ9IGRlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGUgLSBkZWZhdWx0SW1hZ2VUZW1wbGF0ZVNjYWxlIGluIGRldmljZS1zZXR0aW5nc1xuICAgKiBAcHJvcGVydHkge2Jvb2xlYW59IGlnbm9yZURlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGUgLSBJZ25vcmUgZGVmYXVsdEltYWdlVGVtcGxhdGVTY2FsZSBpZiBpdCBoYXMgdHJ1ZS5cbiAgICogSWYgYjY0VGVtcGxhdGUgaGFzIGJlZW4gc2NhbGVkIHRvIGRlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGUgb3Igc2hvdWxkIGlnbm9yZSB0aGUgc2NhbGUsXG4gICAqIHRoaXMgcGFyYW1ldGVyIHNob3VsZCBiZSB0cnVlLiBlLmcuIGNsaWNrIGluIGltYWdlLWVsZW1lbnQgbW9kdWxlXG4gICAqIEBwcm9wZXJ0eSB7bnVtYmVyfSB4U2NhbGUgLSBTY2FsZSByYXRpbyBmb3Igd2lkdGhcbiAgICogQHByb3BlcnR5IHtudW1iZXJ9IHlTY2FsZSAtIFNjYWxlIHJhdGlvIGZvciBoZWlnaHRcblxuICAgKi9cbiAgLyoqXG4gICAqIEdldCBhIGltYWdlIHRoYXQgd2lsbCBiZSB1c2VkIGZvciB0ZW1wbGF0ZSBtYWNoaW5nLlxuICAgKiBSZXR1cm5zIHNjYWxlZCBpbWFnZSBpZiBzY2FsZSByYXRpbyBpcyBwcm92aWRlZC5cbiAgICpcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IGI2NFRlbXBsYXRlIC0gYmFzZTY0LWVuY29kZWQgaW1hZ2UgdXNlZCBhcyBhIHRlbXBsYXRlIHRvIGJlXG4gICAqIG1hdGNoZWQgaW4gdGhlIHNjcmVlbnNob3RcbiAgICogQHBhcmFtIHtJbWFnZVRlbXBsYXRlU2V0dGluZ3N9IG9wdHMgLSBJbWFnZSB0ZW1wbGF0ZSBzY2FsZSByZWxhdGVkIG9wdGlvbnNcbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gYmFzZTY0LWVuY29kZWQgc2NhbGVkIHRlbXBsYXRlIHNjcmVlbnNob3RcbiAgICovXG4gIGFzeW5jIGZpeEltYWdlVGVtcGxhdGVTY2FsZShiNjRUZW1wbGF0ZSwgb3B0cykge1xuICAgIGlmICghb3B0cykge1xuICAgICAgcmV0dXJuIGI2NFRlbXBsYXRlO1xuICAgIH1cblxuICAgIGxldCB7XG4gICAgICBmaXhJbWFnZVRlbXBsYXRlU2NhbGUgPSBmYWxzZSxcbiAgICAgIGRlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGUgPSBERUZBVUxUX1RFTVBMQVRFX0lNQUdFX1NDQUxFLFxuICAgICAgaWdub3JlRGVmYXVsdEltYWdlVGVtcGxhdGVTY2FsZSA9IGZhbHNlLFxuICAgICAgeFNjYWxlID0gREVGQVVMVF9GSVhfSU1BR0VfVEVNUExBVEVfU0NBTEUsXG4gICAgICB5U2NhbGUgPSBERUZBVUxUX0ZJWF9JTUFHRV9URU1QTEFURV9TQ0FMRSxcbiAgICB9ID0gb3B0cztcblxuICAgIGlmIChpZ25vcmVEZWZhdWx0SW1hZ2VUZW1wbGF0ZVNjYWxlKSB7XG4gICAgICBkZWZhdWx0SW1hZ2VUZW1wbGF0ZVNjYWxlID0gREVGQVVMVF9URU1QTEFURV9JTUFHRV9TQ0FMRTtcbiAgICB9XG5cbiAgICAvLyBEZWZhdWx0XG4gICAgaWYgKGRlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGUgPT09IERFRkFVTFRfVEVNUExBVEVfSU1BR0VfU0NBTEUgJiYgIWZpeEltYWdlVGVtcGxhdGVTY2FsZSkge1xuICAgICAgcmV0dXJuIGI2NFRlbXBsYXRlO1xuICAgIH1cblxuICAgIC8vIENhbGN1bGF0ZSB4U2NhbGUgYW5kIHlTY2FsZSBBcHBpdW0gc2hvdWxkIHNjYWxlXG4gICAgaWYgKGZpeEltYWdlVGVtcGxhdGVTY2FsZSkge1xuICAgICAgeFNjYWxlICo9IGRlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGU7XG4gICAgICB5U2NhbGUgKj0gZGVmYXVsdEltYWdlVGVtcGxhdGVTY2FsZTtcbiAgICB9IGVsc2Uge1xuICAgICAgeFNjYWxlID0geVNjYWxlID0gMSAqIGRlZmF1bHRJbWFnZVRlbXBsYXRlU2NhbGU7XG4gICAgfVxuXG4gICAgLy8geFNjYWxlIGFuZCB5U2NhbGUgY2FuIGJlIE5hTiBpZiBkZWZhdWx0SW1hZ2VUZW1wbGF0ZVNjYWxlIGlzIHN0cmluZywgZm9yIGV4YW1wbGVcbiAgICBpZiAoIXBhcnNlRmxvYXQoU3RyaW5nKHhTY2FsZSkpIHx8ICFwYXJzZUZsb2F0KFN0cmluZyh5U2NhbGUpKSkge1xuICAgICAgcmV0dXJuIGI2NFRlbXBsYXRlO1xuICAgIH1cblxuICAgIC8vIFJldHVybiBpZiB0aGUgc2NhbGUgaXMgZGVmYXVsdCwgMSwgdmFsdWVcbiAgICBpZiAoXG4gICAgICBNYXRoLnJvdW5kKHhTY2FsZSAqIEZMT0FUX1BSRUNJU0lPTikgPT09XG4gICAgICAgIE1hdGgucm91bmQoREVGQVVMVF9GSVhfSU1BR0VfVEVNUExBVEVfU0NBTEUgKiBGTE9BVF9QUkVDSVNJT04pICYmXG4gICAgICBNYXRoLnJvdW5kKFxuICAgICAgICBOdW1iZXIoXG4gICAgICAgICAgeVNjYWxlICogRkxPQVRfUFJFQ0lTSU9OID09PVxuICAgICAgICAgICAgTWF0aC5yb3VuZChERUZBVUxUX0ZJWF9JTUFHRV9URU1QTEFURV9TQ0FMRSAqIEZMT0FUX1BSRUNJU0lPTilcbiAgICAgICAgKVxuICAgICAgKVxuICAgICkge1xuICAgICAgcmV0dXJuIGI2NFRlbXBsYXRlO1xuICAgIH1cblxuICAgIGxldCBpbWdUZW1wT2JqID0gYXdhaXQgaW1hZ2VVdGlsLmdldEppbXBJbWFnZShiNjRUZW1wbGF0ZSk7XG4gICAgbGV0IHt3aWR0aDogYmFzZVRlbXBXaWR0aCwgaGVpZ2h0OiBiYXNlVGVtcEhlaWdofSA9IGltZ1RlbXBPYmouYml0bWFwO1xuXG4gICAgY29uc3Qgc2NhbGVkV2lkdGggPSBiYXNlVGVtcFdpZHRoICogeFNjYWxlO1xuICAgIGNvbnN0IHNjYWxlZEhlaWdodCA9IGJhc2VUZW1wSGVpZ2ggKiB5U2NhbGU7XG4gICAgbG9nLmluZm8oXG4gICAgICBgU2NhbGluZyB0ZW1wbGF0ZSBpbWFnZSBmcm9tICR7YmFzZVRlbXBXaWR0aH14JHtiYXNlVGVtcEhlaWdofWAgK1xuICAgICAgICBgIHRvICR7c2NhbGVkV2lkdGh9eCR7c2NhbGVkSGVpZ2h0fWBcbiAgICApO1xuICAgIGxvZy5pbmZvKGBUaGUgcmF0aW8gaXMgJHt4U2NhbGV9IGFuZCAke3lTY2FsZX1gKTtcbiAgICBpbWdUZW1wT2JqID0gYXdhaXQgaW1nVGVtcE9iai5yZXNpemUoc2NhbGVkV2lkdGgsIHNjYWxlZEhlaWdodCk7XG4gICAgcmV0dXJuIChhd2FpdCBpbWdUZW1wT2JqLmdldEJ1ZmZlcihpbWFnZVV0aWwuTUlNRV9QTkcpKS50b1N0cmluZygnYmFzZTY0Jyk7XG4gIH1cbn1cblxuZXhwb3J0IHtXM0NfRUxFTUVOVF9LRVksIE1KU09OV1BfRUxFTUVOVF9LRVksIERFRkFVTFRfU0VUVElOR1MsIERFRkFVTFRfRklYX0lNQUdFX1RFTVBMQVRFX1NDQUxFfTtcblxuLyoqXG4gKiBAdHlwZWRlZiB7aW1wb3J0KCdAYXBwaXVtL3R5cGVzJykuRXh0ZXJuYWxEcml2ZXJ9IEV4dGVybmFsRHJpdmVyXG4gKiBAdHlwZWRlZiB7aW1wb3J0KCdAYXBwaXVtL3R5cGVzJykuRWxlbWVudH0gRWxlbWVudFxuICovXG5cbi8qKlxuICogQHR5cGVkZWYgU2NyZWVuc2hvdFxuICogQHByb3BlcnR5IHtzdHJpbmd9IGI2NFNjcmVlbnNob3QgLSBiYXNlNjQgYmFzZWQgc2NyZWVuc2hvdCBzdHJpbmdcbiAqL1xuXG4vKipcbiAqIEB0eXBlZGVmIFNjcmVlbnNob3RTY2FsZVxuICogQHByb3BlcnR5IHtudW1iZXJ9IHhTY2FsZSAtIFNjYWxlIHJhdGlvIGZvciB3aWR0aFxuICogQHByb3BlcnR5IHtudW1iZXJ9IHlTY2FsZSAtIFNjYWxlIHJhdGlvIGZvciBoZWlnaHRcbiAqL1xuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7OztBQUFBOztBQUNBOztBQUNBOztBQUNBOztBQUNBOztBQUtBOztBQUNBOztBQUVBLE1BQU1BLG1CQUFtQixHQUFHLFNBQTVCOztBQUNBLE1BQU1DLGVBQWUsR0FBR0MsYUFBQSxDQUFLQywwQkFBN0I7O0FBQ0EsTUFBTUMsZ0NBQWdDLEdBQUcsQ0FBekM7O0FBR0EsTUFBTUMsZUFBZSxHQUFHLE1BQXhCO0FBQ0EsTUFBTUMsZUFBZSxHQUFHLEdBQXhCO0FBQ0EsTUFBTUMsb0JBQW9CLEdBQUcsT0FBTyxJQUFQLEdBQWMsRUFBM0M7QUFFQSxNQUFNQyxnQkFBZ0IsR0FBRztFQUd2QkMsbUJBQW1CLEVBQUVDLGdDQUhFO0VBU3ZCQyxnQkFBZ0IsRUFBRSxFQVRLO0VBYXZCQywwQkFBMEIsRUFBRSxJQWJMO0VBa0J2QkMsb0JBQW9CLEVBQUUsS0FsQkM7RUEwQnZCQyxxQkFBcUIsRUFBRSxLQTFCQTtFQWtDdkJDLHlCQUF5QixFQUFFQywwQ0FsQ0o7RUFzQ3ZCQyw2QkFBNkIsRUFBRSxJQXRDUjtFQTBDdkJDLDhCQUE4QixFQUFFLEtBMUNUO0VBOEN2QkMsdUJBQXVCLEVBQUVDLHVDQTlDRjtFQWtEdkJDLHFCQUFxQixFQUFFO0FBbERBLENBQXpCOzs7QUFxRGUsTUFBTUMsa0JBQU4sQ0FBeUI7RUFFdENDLE1BQU07RUFHTkMsVUFBVTs7RUFPVkMsV0FBVyxDQUFDRixNQUFELEVBQVNHLE9BQU8sR0FBR25CLG9CQUFuQixFQUF5QztJQUNsRCxLQUFLZ0IsTUFBTCxHQUFjQSxNQUFkO0lBQ0EsS0FBS0MsVUFBTCxHQUFrQixJQUFJRyxpQkFBSixDQUFRO01BQ3hCQyxHQUFHLEVBQUV0QixlQURtQjtNQUV4Qm9CLE9BRndCO01BR3hCRyxlQUFlLEVBQUdDLEVBQUQsSUFB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
390
+ /**
391
+ * @typedef {import('@appium/types').ExternalDriver} ExternalDriver
392
+ * @typedef {import('@appium/types').Element} Element
393
+ */
394
+ /**
395
+ * @typedef Screenshot
396
+ * @property {string} b64Screenshot - base64 based screenshot string
397
+ */
398
+ /**
399
+ * @typedef ScreenshotScale
400
+ * @property {number} xScale - Scale ratio for width
401
+ * @property {number} yScale - Scale ratio for height
402
+ */
403
+ //# sourceMappingURL=finder.js.map