@reticlehq/browser 3.8.0 → 3.8.1

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.
@@ -10,13 +10,15 @@ export function visibleCanvases(doc) {
10
10
  const height = doc.defaultView?.innerHeight ?? 0;
11
11
  let count = 0;
12
12
  for (const canvas of doc.querySelectorAll(CANVAS_SELECTOR)) {
13
- if (isReticleUi(canvas))
13
+ if (isReticleUi(canvas)) {
14
14
  continue;
15
+ }
15
16
  const box = canvas.getBoundingClientRect();
16
17
  const sized = box.width > 0 && box.height > 0;
17
18
  const onScreen = box.bottom > 0 && box.right > 0 && box.top < height && box.left < width;
18
- if (sized && onScreen)
19
+ if (sized && onScreen) {
19
20
  count += 1;
21
+ }
20
22
  }
21
23
  return count;
22
24
  }
@@ -27,22 +29,28 @@ export function anchorOf(el) {
27
29
  if (testid !== undefined) {
28
30
  out.testid = testid;
29
31
  const shared = elementsMatching({ by: QueryBy.TESTID, value: testid }).length;
30
- if (1 < shared)
32
+ if (1 < shared) {
31
33
  out.testidOf = shared;
34
+ }
32
35
  }
33
36
  const component = info?.componentStack[0];
34
- if (component !== undefined)
37
+ if (component !== undefined) {
35
38
  out.component = component;
36
- if (info?.source !== undefined)
39
+ }
40
+ if (info?.source !== undefined) {
37
41
  out.source = info.source;
42
+ }
38
43
  const role = getRole(el);
39
44
  const name = getAccessibleName(el);
40
- if (role.length > 0)
45
+ if (role.length > 0) {
41
46
  out.role = role;
42
- if (name.length > 0)
47
+ }
48
+ if (name.length > 0) {
43
49
  out.name = name;
44
- if (isSensitiveField(el))
50
+ }
51
+ if (isSensitiveField(el)) {
45
52
  out.secret = true;
53
+ }
46
54
  if (role.length > 0 && name.length > 0) {
47
55
  const same = elementsMatching({ by: QueryBy.ROLE, value: role, name });
48
56
  const at = same.indexOf(el);
@@ -63,25 +71,35 @@ export const result = (ref, action, effect, settled, settleReason, anchor, warni
63
71
  settleReason,
64
72
  effect,
65
73
  };
66
- if (anchor.testid !== undefined)
74
+ if (anchor.testid !== undefined) {
67
75
  base.testid = anchor.testid;
68
- if (anchor.testidOf !== undefined)
76
+ }
77
+ if (anchor.testidOf !== undefined) {
69
78
  base.testidOf = anchor.testidOf;
70
- if (anchor.component !== undefined)
79
+ }
80
+ if (anchor.component !== undefined) {
71
81
  base.component = anchor.component;
72
- if (anchor.source !== undefined)
82
+ }
83
+ if (anchor.source !== undefined) {
73
84
  base.source = anchor.source;
74
- if (anchor.role !== undefined)
85
+ }
86
+ if (anchor.role !== undefined) {
75
87
  base.role = anchor.role;
76
- if (anchor.name !== undefined)
88
+ }
89
+ if (anchor.name !== undefined) {
77
90
  base.name = anchor.name;
78
- if (anchor.nth !== undefined)
91
+ }
92
+ if (anchor.nth !== undefined) {
79
93
  base.nth = anchor.nth;
80
- if (anchor.of !== undefined)
94
+ }
95
+ if (anchor.of !== undefined) {
81
96
  base.of = anchor.of;
82
- if (anchor.secret !== undefined)
97
+ }
98
+ if (anchor.secret !== undefined) {
83
99
  base.secret = anchor.secret;
84
- if (warning !== undefined)
100
+ }
101
+ if (warning !== undefined) {
85
102
  base.warning = warning;
103
+ }
86
104
  return base;
87
105
  };
@@ -1,11 +1,12 @@
1
1
  import { ActionType, ActionWarning, clampHoldMs, DANGEROUS_ACTION_CONFIRM_ARG, ElementState, NATIVE_INPUT_ARG, SettleReason, } from '@reticlehq/core';
2
- import { isHiddenNativeOption } from '../dom/query.js';
2
+ import { isHiddenNativeOption } from '../dom/query-finders.js';
3
3
  import { isReticleUi } from '../dom/dom-ignore.js';
4
4
  import { asSyntheticInput } from './synthetic/synthetic-input.js';
5
5
  import { echoRef, refs } from '../dom/addressing/refs.js';
6
6
  import { requiresDangerousConfirmation, assertOutwardAllowed, assertPaymentEntryAllowed, submitControlFor, } from './danger-context.js';
7
7
  import { assertEditable, assertNotRichText, isAutofillSafeTarget, setNativeValue, } from './value-input.js';
8
- import { isVisible, getStates } from '../dom/a11y.js';
8
+ import { getStates } from '../dom/a11y.js';
9
+ import { isVisible } from '../dom/visibility.js';
9
10
  import { elementHasHoverHandlers } from '../registry/stores/adapters.js';
10
11
  import { isActionTarget, isForm, isInput, isSelect, isTextArea, } from '../dom/realm.js';
11
12
  import { asSettleBudget, nativeSetTimeout, settle } from '../timers/native/native-timers.js';
@@ -19,10 +20,12 @@ function asString(value, fallback = '') {
19
20
  }
20
21
  function requireElement(ref) {
21
22
  const el = refs.resolve(ref);
22
- if (null === el)
23
+ if (null === el) {
23
24
  throw new Error(`ref '${echoRef(ref)}' no longer resolves to an element`);
24
- if (!isActionTarget(el))
25
+ }
26
+ if (!isActionTarget(el)) {
25
27
  throw new Error(`ref '${echoRef(ref)}' is not an HTML or SVG element`);
28
+ }
26
29
  return el;
27
30
  }
28
31
  const FILL_LIKE = new Set([
@@ -51,8 +54,9 @@ const GENERIC_ACTION_ARG_KEYS = new Set([
51
54
  function assertUploadArgs(args) {
52
55
  const keys = Object.keys(args);
53
56
  const dropped = keys.filter((k) => !UPLOAD_ARG_KEYS.has(k) && !GENERIC_ACTION_ARG_KEYS.has(k));
54
- if (0 === dropped.length && keys.some((k) => UPLOAD_ARG_KEYS.has(k)))
57
+ if (0 === dropped.length && keys.some((k) => UPLOAD_ARG_KEYS.has(k))) {
55
58
  return;
59
+ }
56
60
  const detail = 0 === dropped.length
57
61
  ? 'no file was described'
58
62
  : `upload does not read ${dropped.join(', ')}, so it would be dropped`;
@@ -70,10 +74,12 @@ function assertActionAllowed(el, action, args) {
70
74
  (action === ActionType.PRESS && 'Enter' === pressKey(args));
71
75
  const dragTarget = action === ActionType.DRAG ? refs.resolve(dragTargetRef(args)) : null;
72
76
  const submitter = action === ActionType.PRESS && 'Enter' === pressKey(args) ? submitControlFor(el) : null;
73
- if (canTrigger)
77
+ if (canTrigger) {
74
78
  assertOutwardAllowed([el, submitter, isActionTarget(dragTarget) ? dragTarget : null]);
75
- if (action === ActionType.FILL || action === ActionType.TYPE)
79
+ }
80
+ if (action === ActionType.FILL || action === ActionType.TYPE) {
76
81
  assertPaymentEntryAllowed(el);
82
+ }
77
83
  const sourceDangerous = requiresDangerousConfirmation(el) ||
78
84
  (submitter !== null && requiresDangerousConfirmation(submitter));
79
85
  const targetDangerous = isActionTarget(dragTarget) && requiresDangerousConfirmation(dragTarget);
@@ -87,8 +93,9 @@ function assertActionAllowed(el, action, args) {
87
93
  }
88
94
  }
89
95
  function optionValueFor(options, named) {
90
- if (options.some((option) => option.value === named))
96
+ if (options.some((option) => option.value === named)) {
91
97
  return named;
98
+ }
92
99
  const byLabel = options.filter((option) => option.label.trim() === named.trim());
93
100
  return 1 === byLabel.length ? (byLabel[0]?.value ?? named) : named;
94
101
  }
@@ -106,8 +113,9 @@ function valueOf(el) {
106
113
  }
107
114
  function activeRef(el) {
108
115
  const active = el.ownerDocument.activeElement;
109
- if (null === active || active === el.ownerDocument.body)
116
+ if (null === active || active === el.ownerDocument.body) {
110
117
  return null;
118
+ }
111
119
  return refs.refFor(active);
112
120
  }
113
121
  async function dispatchFor(el, action, args) {
@@ -143,8 +151,9 @@ async function dispatchOther(el, action, args) {
143
151
  firePointer(el, 'pointermove', from);
144
152
  const moved = el.dispatchEvent(mouseEventFor(el, 'mousemove', { bubbles: true, cancelable: true }));
145
153
  const holdMs = clampHoldMs(args['holdMs']);
146
- if (holdMs > 0)
154
+ if (holdMs > 0) {
147
155
  await sleep(holdMs);
156
+ }
148
157
  return !moved;
149
158
  }
150
159
  case ActionType.FOCUS:
@@ -168,8 +177,9 @@ async function dispatchOther(el, action, args) {
168
177
  'To empty a field on purpose use the `clear` action, or fill with an explicit "".');
169
178
  }
170
179
  assertEditable(el, 'fill');
171
- if (!isAutofillSafeTarget(el))
180
+ if (!isAutofillSafeTarget(el)) {
172
181
  el.focus();
182
+ }
173
183
  return setNativeValue(el, args['value']);
174
184
  }
175
185
  assertNotRichText(el, 'fill');
@@ -221,8 +231,9 @@ async function dispatchOther(el, action, args) {
221
231
  if ('radio' === el.type && action === ActionType.UNCHECK) {
222
232
  throw new Error('cannot uncheck a radio button — a real user could not; select another radio in the group');
223
233
  }
224
- if (alreadyAtCheckedState(el, action))
234
+ if (alreadyAtCheckedState(el, action)) {
225
235
  return false;
236
+ }
226
237
  const event = mouseEventFor(el, 'click', {
227
238
  bubbles: true,
228
239
  cancelable: true,
@@ -233,25 +244,29 @@ async function dispatchOther(el, action, args) {
233
244
  }
234
245
  case ActionType.SUBMIT: {
235
246
  const form = isForm(el) ? el : el.closest('form');
236
- if (null === form)
247
+ if (null === form) {
237
248
  throw new Error('no form to submit');
249
+ }
238
250
  form.requestSubmit();
239
251
  return false;
240
252
  }
241
253
  case ActionType.PRESS: {
242
254
  const mods = pressModifiers(args);
243
255
  const combo = pressKeys(args);
244
- if (combo.length > 0)
256
+ if (combo.length > 0) {
245
257
  return await pressCombo(el, combo, mods, clampHoldMs(args['holdMs']));
258
+ }
246
259
  const key = pressKey(args);
247
260
  const code = pressCode(args, key);
248
261
  const down = asSyntheticInput(() => el.dispatchEvent(keyboardEvent('keydown', { key, code, bubbles: true, cancelable: true, ...mods })));
249
262
  closeModalOnEscape(el, key, down);
250
- if (down)
263
+ if (down) {
251
264
  dispatchKeypress(el, key, code, mods);
265
+ }
252
266
  const hold = clampHoldMs(args['holdMs']);
253
- if (hold > 0)
267
+ if (hold > 0) {
254
268
  await holdKey(el, key, code, mods, hold);
269
+ }
255
270
  asSyntheticInput(() => el.dispatchEvent(keyboardEvent('keyup', { key, code, bubbles: true, ...mods })));
256
271
  return !down;
257
272
  }
@@ -277,8 +292,9 @@ async function dispatchOther(el, action, args) {
277
292
  if (true === args['__base64']) {
278
293
  const bin = atob(rawContent);
279
294
  const arr = new Uint8Array(bin.length);
280
- for (let i = 0; i < bin.length; i++)
295
+ for (let i = 0; i < bin.length; i++) {
281
296
  arr[i] = bin.charCodeAt(i);
297
+ }
282
298
  fileBody = arr.buffer.slice(0);
283
299
  }
284
300
  const file = new File([fileBody], asString(args['name'], 'file.txt'), {
@@ -293,8 +309,9 @@ async function dispatchOther(el, action, args) {
293
309
  }
294
310
  case ActionType.DRAG: {
295
311
  const toRef = dragTargetRef(args);
296
- if ('' === toRef)
312
+ if ('' === toRef) {
297
313
  return await dragElement(el, null, args['data']);
314
+ }
298
315
  const resolved = refs.resolve(toRef);
299
316
  if (!isActionTarget(resolved)) {
300
317
  throw new Error(`drag target '${echoRef(toRef)}' did not resolve to an element — pass a ref from ` +
@@ -309,10 +326,12 @@ async function dispatchOther(el, action, args) {
309
326
  export async function executeAction(ref, action, args = {}) {
310
327
  const named = 0 < ref.length;
311
328
  const el = isReflessDocumentPress(ref, action, args) ? focusedOrDocument() : requireElement(ref);
312
- if (named)
329
+ if (named) {
313
330
  assertActionAllowed(el, action, args);
314
- if (named)
331
+ }
332
+ if (named) {
315
333
  assertNotLeaving(el);
334
+ }
316
335
  const anchor = named ? anchorOf(el) : {};
317
336
  const visible = isVisible(el);
318
337
  const enabled = enabledOf(el);
@@ -376,15 +395,19 @@ export async function executeAction(ref, action, args = {}) {
376
395
  ...(alreadyAtValue ? { alreadyAtValue: true } : {}),
377
396
  ...canvasesOnScreen(),
378
397
  };
379
- const warning = !named
380
- ? ActionWarning.GLOBAL_PRESS
381
- : geometry.occluded
382
- ? ActionWarning.CLICK_OCCLUDED
383
- : action === ActionType.HOVER && elementHasHoverHandlers(el)
384
- ? ActionWarning.HOVER_NATIVE_ENTER_LEAVE
385
- : hitsNoControl
386
- ? ActionWarning.CLICK_NOT_A_CONTROL
387
- : undefined;
398
+ let warning;
399
+ if (!named) {
400
+ warning = ActionWarning.GLOBAL_PRESS;
401
+ }
402
+ else if (geometry.occluded) {
403
+ warning = ActionWarning.CLICK_OCCLUDED;
404
+ }
405
+ else if (action === ActionType.HOVER && elementHasHoverHandlers(el)) {
406
+ warning = ActionWarning.HOVER_NATIVE_ENTER_LEAVE;
407
+ }
408
+ else if (hitsNoControl) {
409
+ warning = ActionWarning.CLICK_NOT_A_CONTROL;
410
+ }
388
411
  const done = result(ref, action, effect, settled, settleReason, anchor, warning);
389
412
  return isReticleUi(el) ? { ...done, reticleUi: true } : done;
390
413
  }
@@ -406,20 +429,27 @@ export async function executeSequence(steps) {
406
429
  settled: res.settled,
407
430
  settleReason: res.settleReason,
408
431
  };
409
- if (res.testid !== undefined)
432
+ if (res.testid !== undefined) {
410
433
  stepBase.testid = res.testid;
411
- if (res.component !== undefined)
434
+ }
435
+ if (res.component !== undefined) {
412
436
  stepBase.component = res.component;
413
- if (res.role !== undefined)
437
+ }
438
+ if (res.role !== undefined) {
414
439
  stepBase.role = res.role;
415
- if (res.name !== undefined)
440
+ }
441
+ if (res.name !== undefined) {
416
442
  stepBase.name = res.name;
417
- if (res.source !== undefined)
443
+ }
444
+ if (res.source !== undefined) {
418
445
  stepBase.source = res.source;
419
- if (res.secret !== undefined)
446
+ }
447
+ if (res.secret !== undefined) {
420
448
  stepBase.secret = res.secret;
421
- if (res.warning !== undefined)
449
+ }
450
+ if (res.warning !== undefined) {
422
451
  stepBase.warning = res.warning;
452
+ }
423
453
  stepResults.push(stepBase);
424
454
  }
425
455
  return { ok: true, count: steps.length, effects, steps: stepResults };
@@ -1,4 +1,4 @@
1
- import { OutwardAction, isDangerousActionText, isInertHref, isDangerousNavigation, isPaymentField, outwardActionOf, outwardRefusal, } from '@reticlehq/core';
1
+ import { OutwardAction, isDangerousActionText, isDangerousNavigation, isInertHref, isPaymentField, outwardActionOf, outwardRefusal, withoutFilenameTokens, } from '@reticlehq/core';
2
2
  import { getAccessibleName, getRole } from '../dom/a11y.js';
3
3
  import { isHtmlElement } from '../dom/realm.js';
4
4
  const LABEL_VALUED_INPUT_TYPES = new Set([
@@ -8,34 +8,39 @@ const LABEL_VALUED_INPUT_TYPES = new Set([
8
8
  'image',
9
9
  ]);
10
10
  function holdsUserText(el) {
11
- if (isHtmlElement(el) && el.isContentEditable)
11
+ if (isHtmlElement(el) && el.isContentEditable) {
12
12
  return true;
13
- if (el instanceof HTMLTextAreaElement)
13
+ }
14
+ if (el instanceof HTMLTextAreaElement) {
14
15
  return true;
15
- if (el instanceof HTMLInputElement)
16
+ }
17
+ if (el instanceof HTMLInputElement) {
16
18
  return !LABEL_VALUED_INPUT_TYPES.has(el.type.toLowerCase());
19
+ }
17
20
  return false;
18
21
  }
19
22
  const ANCHOR_TAG = 'A';
20
23
  function navigatingHref(el) {
21
- if (ANCHOR_TAG !== el.tagName)
24
+ if (ANCHOR_TAG !== el.tagName) {
22
25
  return undefined;
26
+ }
23
27
  const href = el.getAttribute('href');
24
28
  return null === href || isInertHref(href) ? undefined : href;
25
29
  }
26
30
  export function dangerousActionContext(el) {
27
31
  const href = navigatingHref(el);
28
- if (href !== undefined)
32
+ if (href !== undefined) {
29
33
  return href;
34
+ }
30
35
  const form = el.closest('form');
31
36
  const ownText = holdsUserText(el)
32
37
  ? ['', '']
33
38
  : [el.textContent ?? '', el.getAttribute('value') ?? ''];
34
39
  return [
35
- getAccessibleName(el),
36
- ...ownText,
37
- el.getAttribute('title') ?? '',
38
- el.getAttribute('aria-label') ?? '',
40
+ withoutFilenameTokens(getAccessibleName(el)),
41
+ withoutFilenameTokens(ownText.join(' ')),
42
+ withoutFilenameTokens(el.getAttribute('title') ?? ''),
43
+ withoutFilenameTokens(el.getAttribute('aria-label') ?? ''),
39
44
  el.getAttribute('href') ?? '',
40
45
  form?.getAttribute('action') ?? '',
41
46
  ].join(' ');
@@ -47,8 +52,9 @@ export function submitControlFor(el) {
47
52
  }
48
53
  export function requiresDangerousConfirmation(el) {
49
54
  const href = navigatingHref(el);
50
- if (href !== undefined)
55
+ if (href !== undefined) {
51
56
  return isDangerousNavigation(href);
57
+ }
52
58
  return isDangerousActionText(dangerousActionContext(el), getRole(el));
53
59
  }
54
60
  let outwardAllowed;
@@ -56,19 +62,23 @@ export function setOutwardPolicy(allowed) {
56
62
  outwardAllowed = allowed;
57
63
  }
58
64
  export function assertOutwardAllowed(targets) {
59
- if (outwardAllowed === undefined)
65
+ if (outwardAllowed === undefined) {
60
66
  return;
67
+ }
61
68
  for (const el of targets) {
62
- if (null === el)
69
+ if (null === el) {
63
70
  continue;
71
+ }
64
72
  const kind = outwardActionOf(dangerousActionContext(el), getRole(el));
65
- if (kind !== undefined && !outwardAllowed.includes(kind))
73
+ if (kind !== undefined && !outwardAllowed.includes(kind)) {
66
74
  throw new Error(outwardRefusal(kind));
75
+ }
67
76
  }
68
77
  }
69
78
  export function assertPaymentEntryAllowed(el) {
70
- if (outwardAllowed === undefined || outwardAllowed.includes(OutwardAction.PAY))
79
+ if (outwardAllowed === undefined || outwardAllowed.includes(OutwardAction.PAY)) {
71
80
  return;
81
+ }
72
82
  const facts = {
73
83
  autocomplete: el.getAttribute('autocomplete') ?? undefined,
74
84
  name: el.getAttribute('name') ?? undefined,
@@ -76,6 +86,7 @@ export function assertPaymentEntryAllowed(el) {
76
86
  inputMode: el.getAttribute('inputmode') ?? undefined,
77
87
  label: `${getAccessibleName(el)} ${el.getAttribute('placeholder') ?? ''}`,
78
88
  };
79
- if (isPaymentField(facts))
89
+ if (isPaymentField(facts)) {
80
90
  throw new Error(outwardRefusal(OutwardAction.PAY));
91
+ }
81
92
  }
@@ -11,6 +11,8 @@ interface ScrollResult {
11
11
  atLeftEnd: boolean;
12
12
  atRightEnd: boolean;
13
13
  }
14
+ export declare const SCROLL_FRACTION_RANGE_ERROR_PREFIX = "scroll fraction must be between 0 and 1, got ";
15
+ export declare function fractionOutOfRangeMessage(fraction: number): string;
14
16
  export declare function scrollContainer(ref: string | undefined, dy: number | undefined, fraction?: number, dx?: number): ScrollResult;
15
17
  export declare function scrollFrom(base: Element | undefined, dy: number | undefined, fraction?: number, dx?: number): ScrollResult;
16
18
  export {};
@@ -1,6 +1,10 @@
1
1
  import { refs } from '../dom/addressing/refs.js';
2
2
  const FALLBACK_STEP_PX = 400;
3
3
  const VIEWPORT_FRACTION = 0.8;
4
+ export const SCROLL_FRACTION_RANGE_ERROR_PREFIX = 'scroll fraction must be between 0 and 1, got ';
5
+ export function fractionOutOfRangeMessage(fraction) {
6
+ return `${SCROLL_FRACTION_RANGE_ERROR_PREFIX}${fraction}`;
7
+ }
4
8
  function nearestScrollable(el, axis) {
5
9
  let cur = el;
6
10
  while (cur !== null && cur !== document.body) {
@@ -18,18 +22,22 @@ export function scrollContainer(ref, dy, fraction, dx) {
18
22
  return scrollFrom(base instanceof Element ? base : undefined, dy, fraction, dx);
19
23
  }
20
24
  export function scrollFrom(base, dy, fraction, dx) {
25
+ if (fraction !== undefined && (fraction < 0 || fraction > 1 || Number.isNaN(fraction))) {
26
+ throw new RangeError(fractionOutOfRangeMessage(fraction));
27
+ }
21
28
  const axis = dx !== undefined && dx !== 0 && (dy === undefined || 0 === dy) ? 'x' : 'y';
22
29
  const target = base !== undefined
23
30
  ? nearestScrollable(base, axis)
24
31
  : (document.scrollingElement ?? document.documentElement);
25
32
  const before = target.scrollTop;
26
33
  const beforeLeft = target.scrollLeft;
27
- if (fraction !== undefined && fraction >= 0 && fraction <= 1) {
34
+ if (fraction !== undefined) {
28
35
  target.scrollTop = Math.round(target.scrollHeight * fraction);
29
36
  }
30
37
  else {
31
- if (dx !== undefined && dx !== 0)
38
+ if (dx !== undefined && dx !== 0) {
32
39
  target.scrollLeft = beforeLeft + dx;
40
+ }
33
41
  if (dx === undefined || (dy !== undefined && 0 !== dy)) {
34
42
  const step = dy ?? (Math.round(target.clientHeight * VIEWPORT_FRACTION) || FALLBACK_STEP_PX);
35
43
  target.scrollTop = before + step;
@@ -4,8 +4,4 @@ export declare function getAccessibleName(el: Element): string;
4
4
  export declare function getStates(el: Element, visible?: boolean): ElementState[];
5
5
  export declare function isSensitiveField(el: Element): boolean;
6
6
  export declare function getValue(el: Element): string | undefined;
7
- export declare function isHiddenByAriaOnly(el: Element): boolean;
8
- export declare function isRenderedOutsideAriaHidden(el: Element): boolean;
9
- export declare function isInViewport(el: Element, memo?: Map<Element, boolean>): boolean;
10
- export declare function isVisible(el: Element, memo?: Map<Element, boolean>): boolean;
11
7
  export declare function describe(el: Element, memo?: Map<Element, boolean>): ElementDescriptor;