1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
pprint: "chrome://remote/content/shared/Format.sys.mjs",
});
/**
* The class provides a mapping between DOM nodes and unique element
* references by using `ContentDOMReference` identifiers.
*/
export class NodeCache {
#domRefs;
#sharedIds;
constructor() {
// ContentDOMReference id => shared unique id
this.#sharedIds = new Map();
// shared unique id => ContentDOMReference
this.#domRefs = new Map();
}
/**
* Get the number of elements in the cache.
*/
get size() {
return this.#sharedIds.size;
}
/**
* Add a DOM element to the cache if not known yet.
*
* @param {Element} el
* The DOM Element to be added.
*
* @return {string}
* The shared id to uniquely identify the DOM element.
*/
add(el) {
let domRef, sharedId;
try {
// Evaluation of code will take place in mutable sandboxes, which are
// created to waive xrays by default. As such DOM elements have to be
// unwaived before accessing the ownerGlobal if possible, which is
// needed by ContentDOMReference.
domRef = lazy.ContentDOMReference.get(Cu.unwaiveXrays(el));
} catch (e) {
throw new lazy.error.UnknownError(
lazy.pprint`Failed to create element reference for ${el}: ${e.message}`
);
}
if (this.#sharedIds.has(domRef.id)) {
// For already known elements retrieve the cached shared id.
sharedId = this.#sharedIds.get(domRef.id);
} else {
// For new elements generate a unique id without curly braces.
sharedId = Services.uuid
.generateUUID()
.toString()
.slice(1, -1);
this.#sharedIds.set(domRef.id, sharedId);
this.#domRefs.set(sharedId, domRef);
}
return sharedId;
}
/**
* Clears all known DOM elements.
*
* @param {Object=} options
* @param {boolean=} options.all
* Clear all references from any browsing context. Defaults to false.
* @param {BrowsingContext=} browsingContext
* Clear all references living in that browsing context.
*/
clear(options = {}) {
const { all = false, browsingContext } = options;
if (all) {
this.#sharedIds.clear();
this.#domRefs.clear();
return;
}
if (browsingContext) {
for (const [sharedId, domRef] of this.#domRefs.entries()) {
if (domRef.browsingContextId === browsingContext.id) {
this.#sharedIds.delete(domRef.id);
this.#domRefs.delete(sharedId);
}
}
return;
}
throw new Error(`Requires "browsingContext" or "all" to be set.`);
}
/**
* Wrapper around ContentDOMReference.resolve with additional error handling
* specific to WebDriver.
*
* @param {string} sharedId
* The unique identifier for the DOM element.
*
* @return {Element|null}
* The DOM element that the unique identifier was generated for or
* `null` if the element does not exist anymore.
*
* @throws {NoSuchElementError}
* If the DOM element as represented by the unique WebElement reference
* <var>sharedId</var> isn't known.
*/
resolve(sharedId) {
const domRef = this.#domRefs.get(sharedId);
if (domRef == undefined) {
throw new lazy.error.NoSuchElementError(
`Unknown element with id ${sharedId}`
);
}
return lazy.ContentDOMReference.resolve(domRef);
}
}
|