Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/atoms/domcore.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 Defines the core DOM querying library for the atoms, with a
20
* minimal set of dependencies. Notably, this file should never have a
21
* dependency on CSS libraries such as sizzle.
22
*/
23
24
goog.provide('bot.dom.core');
25
26
goog.require('bot.Error');
27
goog.require('bot.ErrorCode');
28
goog.require('bot.userAgent');
29
goog.require('goog.array');
30
goog.require('goog.dom');
31
goog.require('goog.dom.NodeType');
32
goog.require('goog.dom.TagName');
33
34
35
/**
36
* Get the user-specified value of the given attribute of the element, or null
37
* if the attribute is not present.
38
*
39
* <p>For boolean attributes such as "selected" or "checked", this method
40
* returns the value of element.getAttribute(attributeName) cast to a String
41
* when attribute is present. For modern browsers, this will be the string the
42
* attribute is given in the HTML, but for IE8 it will be the name of the
43
* attribute, and for IE7, it will be the string "true". To test whether a
44
* boolean attribute is present, test whether the return value is non-null, the
45
* same as one would for non-boolean attributes. Specifically, do *not* test
46
* whether the boolean evaluation of the return value is true, because the value
47
* of a boolean attribute that is present will often be the empty string.
48
*
49
* <p>For the style attribute, it standardizes the value by lower-casing the
50
* property names and always including a trailing semicolon.
51
*
52
* @param {!Element} element The element to use.
53
* @param {string} attributeName The name of the attribute to return.
54
* @return {?string} The value of the attribute or "null" if entirely missing.
55
*/
56
bot.dom.core.getAttribute = function (element, attributeName) {
57
attributeName = attributeName.toLowerCase();
58
59
// The style attribute should be a css text string that includes only what
60
// the HTML element specifies itself (excluding what is inherited from parent
61
// elements or style sheets). We standardize the format of this string via the
62
// standardizeStyleAttribute method.
63
if (attributeName == 'style') {
64
return bot.dom.core.standardizeStyleAttribute_(element.style.cssText);
65
}
66
67
// In IE doc mode < 8, the "value" attribute of an <input> is only accessible
68
// as a property.
69
if (bot.userAgent.IE_DOC_PRE8 && attributeName == 'value' &&
70
bot.dom.core.isElement(element, goog.dom.TagName.INPUT)) {
71
return element['value'];
72
}
73
74
// In IE < 9, element.getAttributeNode will return null for some boolean
75
// attributes that are present, such as the selected attribute on <option>
76
// elements. This if-statement is sufficient if these cases are restricted
77
// to boolean attributes whose reflected property names are all lowercase
78
// (as attributeName is by this point), like "selected". We have not
79
// found a boolean attribute for which this does not work.
80
if (bot.userAgent.IE_DOC_PRE9 && element[attributeName] === true) {
81
return String(element.getAttribute(attributeName));
82
}
83
84
// When the attribute is not present, either attr will be null or
85
// attr.specified will be false.
86
var attr = element.getAttributeNode(attributeName);
87
return (attr && attr.specified) ? attr.value : null;
88
};
89
90
91
/**
92
* Regex to split on semicolons, but not when enclosed in parens or quotes.
93
* Helper for {@link bot.dom.core.standardizeStyleAttribute_}.
94
* If the style attribute ends with a semicolon this will include an empty
95
* string at the end of the array
96
* @private {!RegExp}
97
* @const
98
*/
99
bot.dom.core.SPLIT_STYLE_ATTRIBUTE_ON_SEMICOLONS_REGEXP_ =
100
new RegExp('[;]+' +
101
'(?=(?:(?:[^"]*"){2})*[^"]*$)' +
102
'(?=(?:(?:[^\']*\'){2})*[^\']*$)' +
103
'(?=(?:[^()]*\\([^()]*\\))*[^()]*$)');
104
105
106
/**
107
* Standardize a style attribute value, which includes:
108
* (1) converting all property names lowercase
109
* (2) ensuring it ends in a trailing semicolon
110
* @param {string} value The style attribute value.
111
* @return {string} The identical value, with the formatting rules described
112
* above applied.
113
* @private
114
*/
115
bot.dom.core.standardizeStyleAttribute_ = function (value) {
116
var styleArray = value.split(
117
bot.dom.core.SPLIT_STYLE_ATTRIBUTE_ON_SEMICOLONS_REGEXP_);
118
var css = [];
119
goog.array.forEach(styleArray, function (pair) {
120
var i = pair.indexOf(':');
121
if (i > 0) {
122
var keyValue = [pair.slice(0, i), pair.slice(i + 1)];
123
if (keyValue.length == 2) {
124
css.push(keyValue[0].toLowerCase(), ':', keyValue[1], ';');
125
}
126
}
127
});
128
css = css.join('');
129
css = css.charAt(css.length - 1) == ';' ? css : css + ';';
130
return css;
131
};
132
133
134
/**
135
* Looks up the given property (not to be confused with an attribute) on the
136
* given element.
137
*
138
* @param {!Element} element The element to use.
139
* @param {string} propertyName The name of the property.
140
* @return {*} The value of the property.
141
*/
142
bot.dom.core.getProperty = function (element, propertyName) {
143
// When an <option>'s value attribute is not set, its value property should be
144
// its text content, but IE < 8 does not adhere to that behavior, so fix it.
145
// http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-value-OPTION
146
if (bot.userAgent.IE_DOC_PRE8 && propertyName == 'value' &&
147
bot.dom.core.isElement(element, goog.dom.TagName.OPTION) &&
148
goog.isNull(bot.dom.core.getAttribute(element, 'value'))) {
149
return goog.dom.getRawTextContent(element);
150
}
151
return element[propertyName];
152
};
153
154
155
156
/**
157
* Returns whether the given node is an element and, optionally, whether it has
158
* the given tag name. If the tag name is not provided, returns true if the node
159
* is an element, regardless of the tag name.h
160
*
161
* @template T
162
* @param {Node} node The node to test.
163
* @param {(goog.dom.TagName<!T>|string)=} opt_tagName Tag name to test the node for.
164
* @return {boolean} Whether the node is an element with the given tag name.
165
*/
166
bot.dom.core.isElement = function (node, opt_tagName) {
167
// because we call this with deprecated tags such as SHADOW
168
if (opt_tagName && (typeof opt_tagName !== 'string')) {
169
opt_tagName = opt_tagName.toString();
170
}
171
// because node.tagName.toUpperCase() fails when tagName is "tagName"
172
if (node instanceof HTMLFormElement) {
173
return !!node && node.nodeType == goog.dom.NodeType.ELEMENT &&
174
(!opt_tagName || "FORM" == opt_tagName);
175
}
176
return !!node && node.nodeType == goog.dom.NodeType.ELEMENT &&
177
(!opt_tagName || node.tagName.toUpperCase() == opt_tagName);
178
};
179
180
181
/**
182
* Returns whether the element can be checked or selected.
183
*
184
* @param {!Element} element The element to check.
185
* @return {boolean} Whether the element could be checked or selected.
186
*/
187
bot.dom.core.isSelectable = function (element) {
188
if (bot.dom.core.isElement(element, goog.dom.TagName.OPTION)) {
189
return true;
190
}
191
192
if (bot.dom.core.isElement(element, goog.dom.TagName.INPUT)) {
193
var type = element.type.toLowerCase();
194
return type == 'checkbox' || type == 'radio';
195
}
196
197
return false;
198
};
199
200
201
/**
202
* Returns whether the element is checked or selected.
203
*
204
* @param {!Element} element The element to check.
205
* @return {boolean} Whether the element is checked or selected.
206
*/
207
bot.dom.core.isSelected = function (element) {
208
if (!bot.dom.core.isSelectable(element)) {
209
throw new bot.Error(bot.ErrorCode.ELEMENT_NOT_SELECTABLE,
210
'Element is not selectable');
211
}
212
213
var propertyName = 'selected';
214
var type = element.type && element.type.toLowerCase();
215
if ('checkbox' == type || 'radio' == type) {
216
propertyName = 'checked';
217
}
218
219
return !!bot.dom.core.getProperty(element, propertyName);
220
};
221
222