Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/atoms/locators/xpath.js
4502 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
/**
19
* @fileoverview Functions to locate elements by XPath.
20
*
21
* <p>The locator implementations below differ from the Closure functions
22
* goog.dom.xml.{selectSingleNode,selectNodes} in three important ways:
23
* <ol>
24
* <li>they do not refer to "document" which is undefined in the context of a
25
* Firefox extension;
26
* <li> they use a default NsResolver for browsers that do not provide
27
* document.createNSResolver (e.g. Android); and
28
* <li> they prefer document.evaluate to node.{selectSingleNode,selectNodes}
29
* because the latter silently return nothing when the xpath resolves to a
30
* non-Node type, limiting the error-checking the implementation can provide.
31
* </ol>
32
*/
33
34
goog.provide('bot.locators.xpath');
35
36
goog.require('bot');
37
goog.require('bot.Error');
38
goog.require('bot.ErrorCode');
39
goog.require('bot.locators');
40
goog.require('goog.array');
41
goog.require('goog.dom');
42
goog.require('goog.dom.NodeType');
43
goog.require('goog.userAgent');
44
goog.require('goog.userAgent.product');
45
46
/**
47
* XPathResult enum values. These are defined separately since
48
* the context running this script may not support the XPathResult
49
* type.
50
* @enum {number}
51
* @see http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathResult
52
* @private
53
*/
54
// TODO: Move this enum back to bot.locators.xpath namespace.
55
// The problem is that we alias bot.locators.xpath in locators.js, while
56
// we set the flag --collapse_properties (http://goo.gl/5W6cP).
57
// The compiler should have thrown the error anyways, it's a bug that it fails
58
// only when introducing this enum.
59
// Solution: remove --collapse_properties from the js_binary rule or
60
// use goog.exportSymbol to export the public methods and get rid of the alias.
61
bot.locators.XPathResult_ = {
62
ORDERED_NODE_SNAPSHOT_TYPE: 7,
63
FIRST_ORDERED_NODE_TYPE: 9
64
};
65
66
67
/**
68
* Default XPath namespace resolver.
69
* @private
70
*/
71
bot.locators.xpath.DEFAULT_RESOLVER_ = (function () {
72
var namespaces = { svg: 'http://www.w3.org/2000/svg' };
73
return function (prefix) {
74
return namespaces[prefix] || null;
75
};
76
})();
77
78
79
/**
80
* Evaluates an XPath expression using a W3 XPathEvaluator.
81
* @param {!(Document|Element)} node The document or element to perform the
82
* search under.
83
* @param {string} path The xpath to search for.
84
* @param {!bot.locators.XPathResult_} resultType The desired result type.
85
* @return {XPathResult} The XPathResult or null if the root's ownerDocument
86
* does not support XPathEvaluators.
87
* @private
88
* @see http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathEvaluator-evaluate
89
*/
90
bot.locators.xpath.evaluate_ = function (node, path, resultType) {
91
var doc = goog.dom.getOwnerDocument(node);
92
93
if (!doc.documentElement) {
94
// document is not loaded yet
95
return null;
96
}
97
98
try {
99
var resolver = doc.createNSResolver ?
100
doc.createNSResolver(doc.documentElement) :
101
bot.locators.xpath.DEFAULT_RESOLVER_;
102
103
if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(7)) {
104
// IE6, and only IE6, has an issue where calling a custom function
105
// directly attached to the document object does not correctly propagate
106
// thrown errors. So in that case *only* we will use apply().
107
return doc.evaluate.call(doc, path, node, resolver, resultType, null);
108
109
} else {
110
if (!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9)) {
111
var reversedNamespaces = {};
112
var allNodes = doc.getElementsByTagName("*");
113
for (var i = 0; i < allNodes.length; ++i) {
114
var n = allNodes[i];
115
var ns = n.namespaceURI;
116
if (ns && !reversedNamespaces[ns]) {
117
var prefix = n.lookupPrefix(ns);
118
if (!prefix) {
119
var m = ns.match('.*/(\\w+)/?$');
120
if (m) {
121
prefix = m[1];
122
} else {
123
prefix = 'xhtml';
124
}
125
}
126
reversedNamespaces[ns] = prefix;
127
}
128
}
129
var namespaces = {};
130
for (var key in reversedNamespaces) {
131
namespaces[reversedNamespaces[key]] = key;
132
}
133
resolver = function (prefix) {
134
return namespaces[prefix] || null;
135
};
136
}
137
138
try {
139
return doc.evaluate(path, node, resolver, resultType, null);
140
} catch (te) {
141
if (te.name === 'TypeError') {
142
// fallback to simplified implementation
143
resolver = doc.createNSResolver ?
144
doc.createNSResolver(doc.documentElement) :
145
bot.locators.xpath.DEFAULT_RESOLVER_;
146
return doc.evaluate(path, node, resolver, resultType, null);
147
} else {
148
throw te;
149
}
150
}
151
}
152
} catch (ex) {
153
// The Firefox XPath evaluator can throw an exception if the document is
154
// queried while it's in the midst of reloading, so we ignore it. In all
155
// other cases, we assume an invalid xpath has caused the exception.
156
if (!(goog.userAgent.GECKO && ex.name == 'NS_ERROR_ILLEGAL_VALUE')) {
157
throw new bot.Error(bot.ErrorCode.INVALID_SELECTOR_ERROR,
158
'Unable to locate an element with the xpath expression ' + path +
159
' because of the following error:\n' + ex);
160
}
161
}
162
};
163
164
165
/**
166
* @param {Node|undefined} node Node to check whether it is an Element.
167
* @param {string} path XPath expression to include in the error message.
168
* @private
169
*/
170
bot.locators.xpath.checkElement_ = function (node, path) {
171
if (!node || node.nodeType != goog.dom.NodeType.ELEMENT) {
172
throw new bot.Error(bot.ErrorCode.INVALID_SELECTOR_ERROR,
173
'The result of the xpath expression "' + path +
174
'" is: ' + node + '. It should be an element.');
175
}
176
};
177
178
179
/**
180
* Find an element by using an xpath expression
181
* @param {string} target The xpath to search for.
182
* @param {!(Document|Element)} root The document or element to perform the
183
* search under.
184
* @return {Element} The first matching element found in the DOM, or null if no
185
* such element could be found.
186
*/
187
bot.locators.xpath.single = function (target, root) {
188
189
function selectSingleNode() {
190
var result = bot.locators.xpath.evaluate_(root, target,
191
bot.locators.XPathResult_.FIRST_ORDERED_NODE_TYPE);
192
193
if (result) {
194
var node = result.singleNodeValue;
195
return node || null;
196
} else if (root.selectSingleNode) {
197
var doc = goog.dom.getOwnerDocument(root);
198
if (doc.setProperty) {
199
doc.setProperty('SelectionLanguage', 'XPath');
200
}
201
return root.selectSingleNode(target);
202
}
203
return null;
204
}
205
206
var node = selectSingleNode();
207
if (node !== null) {
208
bot.locators.xpath.checkElement_(node, target);
209
}
210
return /** @type {Element} */ (node);
211
};
212
213
214
/**
215
* Find elements by using an xpath expression
216
* @param {string} target The xpath to search for.
217
* @param {!(Document|Element)} root The document or element to perform the
218
* search under.
219
* @return {!IArrayLike} All matching elements, or an empty list.
220
*/
221
bot.locators.xpath.many = function (target, root) {
222
223
function selectNodes() {
224
var result = bot.locators.xpath.evaluate_(root, target,
225
bot.locators.XPathResult_.ORDERED_NODE_SNAPSHOT_TYPE);
226
if (result) {
227
var count = result.snapshotLength;
228
var results = [];
229
for (var i = 0; i < count; ++i) {
230
results.push(result.snapshotItem(i));
231
}
232
return results;
233
} else if (root.selectNodes) {
234
var doc = goog.dom.getOwnerDocument(root);
235
if (doc.setProperty) {
236
doc.setProperty('SelectionLanguage', 'XPath');
237
}
238
return root.selectNodes(target);
239
}
240
return [];
241
}
242
243
var nodes = selectNodes();
244
goog.array.forEach(nodes, function (n) {
245
bot.locators.xpath.checkElement_(n, target);
246
});
247
return /** @type {!IArrayLike} */ (nodes);
248
};
249
250