Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/atoms/locators/xpath.js
2884 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('goog.array');
40
goog.require('goog.dom');
41
goog.require('goog.dom.NodeType');
42
goog.require('goog.userAgent');
43
goog.require('goog.userAgent.product');
44
45
/**
46
* XPathResult enum values. These are defined separately since
47
* the context running this script may not support the XPathResult
48
* type.
49
* @enum {number}
50
* @see http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathResult
51
* @private
52
*/
53
// TODO: Move this enum back to bot.locators.xpath namespace.
54
// The problem is that we alias bot.locators.xpath in locators.js, while
55
// we set the flag --collapse_properties (http://goo.gl/5W6cP).
56
// The compiler should have thrown the error anyways, it's a bug that it fails
57
// only when introducing this enum.
58
// Solution: remove --collapse_properties from the js_binary rule or
59
// use goog.exportSymbol to export the public methods and get rid of the alias.
60
bot.locators.XPathResult_ = {
61
ORDERED_NODE_SNAPSHOT_TYPE: 7,
62
FIRST_ORDERED_NODE_TYPE: 9
63
};
64
65
66
/**
67
* Default XPath namespace resolver.
68
* @private
69
*/
70
bot.locators.xpath.DEFAULT_RESOLVER_ = (function () {
71
var namespaces = { svg: 'http://www.w3.org/2000/svg' };
72
return function (prefix) {
73
return namespaces[prefix] || null;
74
};
75
})();
76
77
78
/**
79
* Evaluates an XPath expression using a W3 XPathEvaluator.
80
* @param {!(Document|Element)} node The document or element to perform the
81
* search under.
82
* @param {string} path The xpath to search for.
83
* @param {!bot.locators.XPathResult_} resultType The desired result type.
84
* @return {XPathResult} The XPathResult or null if the root's ownerDocument
85
* does not support XPathEvaluators.
86
* @private
87
* @see http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathEvaluator-evaluate
88
*/
89
bot.locators.xpath.evaluate_ = function (node, path, resultType) {
90
var doc = goog.dom.getOwnerDocument(node);
91
92
if (!doc.documentElement) {
93
// document is not loaded yet
94
return null;
95
}
96
97
try {
98
var resolver = doc.createNSResolver ?
99
doc.createNSResolver(doc.documentElement) :
100
bot.locators.xpath.DEFAULT_RESOLVER_;
101
102
if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(7)) {
103
// IE6, and only IE6, has an issue where calling a custom function
104
// directly attached to the document object does not correctly propagate
105
// thrown errors. So in that case *only* we will use apply().
106
return doc.evaluate.call(doc, path, node, resolver, resultType, null);
107
108
} else {
109
if (!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9)) {
110
var reversedNamespaces = {};
111
var allNodes = doc.getElementsByTagName("*");
112
for (var i = 0; i < allNodes.length; ++i) {
113
var n = allNodes[i];
114
var ns = n.namespaceURI;
115
if (ns && !reversedNamespaces[ns]) {
116
var prefix = n.lookupPrefix(ns);
117
if (!prefix) {
118
var m = ns.match('.*/(\\w+)/?$');
119
if (m) {
120
prefix = m[1];
121
} else {
122
prefix = 'xhtml';
123
}
124
}
125
reversedNamespaces[ns] = prefix;
126
}
127
}
128
var namespaces = {};
129
for (var key in reversedNamespaces) {
130
namespaces[reversedNamespaces[key]] = key;
131
}
132
resolver = function (prefix) {
133
return namespaces[prefix] || null;
134
};
135
}
136
137
try {
138
return doc.evaluate(path, node, resolver, resultType, null);
139
} catch (te) {
140
if (te.name === 'TypeError') {
141
// fallback to simplified implementation
142
resolver = doc.createNSResolver ?
143
doc.createNSResolver(doc.documentElement) :
144
bot.locators.xpath.DEFAULT_RESOLVER_;
145
return doc.evaluate(path, node, resolver, resultType, null);
146
} else {
147
throw te;
148
}
149
}
150
}
151
} catch (ex) {
152
// The Firefox XPath evaluator can throw an exception if the document is
153
// queried while it's in the midst of reloading, so we ignore it. In all
154
// other cases, we assume an invalid xpath has caused the exception.
155
if (!(goog.userAgent.GECKO && ex.name == 'NS_ERROR_ILLEGAL_VALUE')) {
156
throw new bot.Error(bot.ErrorCode.INVALID_SELECTOR_ERROR,
157
'Unable to locate an element with the xpath expression ' + path +
158
' because of the following error:\n' + ex);
159
}
160
}
161
};
162
163
164
/**
165
* @param {Node|undefined} node Node to check whether it is an Element.
166
* @param {string} path XPath expression to include in the error message.
167
* @private
168
*/
169
bot.locators.xpath.checkElement_ = function (node, path) {
170
if (!node || node.nodeType != goog.dom.NodeType.ELEMENT) {
171
throw new bot.Error(bot.ErrorCode.INVALID_SELECTOR_ERROR,
172
'The result of the xpath expression "' + path +
173
'" is: ' + node + '. It should be an element.');
174
}
175
};
176
177
178
/**
179
* Find an element by using an xpath expression
180
* @param {string} target The xpath to search for.
181
* @param {!(Document|Element)} root The document or element to perform the
182
* search under.
183
* @return {Element} The first matching element found in the DOM, or null if no
184
* such element could be found.
185
*/
186
bot.locators.xpath.single = function (target, root) {
187
188
function selectSingleNode() {
189
var result = bot.locators.xpath.evaluate_(root, target,
190
bot.locators.XPathResult_.FIRST_ORDERED_NODE_TYPE);
191
192
if (result) {
193
var node = result.singleNodeValue;
194
return node || null;
195
} else if (root.selectSingleNode) {
196
var doc = goog.dom.getOwnerDocument(root);
197
if (doc.setProperty) {
198
doc.setProperty('SelectionLanguage', 'XPath');
199
}
200
return root.selectSingleNode(target);
201
}
202
return null;
203
}
204
205
var node = selectSingleNode();
206
if (!goog.isNull(node)) {
207
bot.locators.xpath.checkElement_(node, target);
208
}
209
return /** @type {Element} */ (node);
210
};
211
212
213
/**
214
* Find elements by using an xpath expression
215
* @param {string} target The xpath to search for.
216
* @param {!(Document|Element)} root The document or element to perform the
217
* search under.
218
* @return {!IArrayLike} All matching elements, or an empty list.
219
*/
220
bot.locators.xpath.many = function (target, root) {
221
222
function selectNodes() {
223
var result = bot.locators.xpath.evaluate_(root, target,
224
bot.locators.XPathResult_.ORDERED_NODE_SNAPSHOT_TYPE);
225
if (result) {
226
var count = result.snapshotLength;
227
var results = [];
228
for (var i = 0; i < count; ++i) {
229
results.push(result.snapshotItem(i));
230
}
231
return results;
232
} else if (root.selectNodes) {
233
var doc = goog.dom.getOwnerDocument(root);
234
if (doc.setProperty) {
235
doc.setProperty('SelectionLanguage', 'XPath');
236
}
237
return root.selectNodes(target);
238
}
239
return [];
240
}
241
242
var nodes = selectNodes();
243
goog.array.forEach(nodes, function (n) {
244
bot.locators.xpath.checkElement_(n, target);
245
});
246
return /** @type {!IArrayLike} */ (nodes);
247
};
248
249