-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
/
Copy pathdomcore.js
216 lines (190 loc) · 7.95 KB
/
domcore.js
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
/**
* @fileoverview Defines the core DOM querying library for the atoms, with a
* minimal set of dependencies. Notably, this file should never have a
* dependency on CSS or XPath polyfill libraries (sizzle and wgxpath,
* respectively).
*/
goog.provide('bot.dom.core');
goog.require('bot.Error');
goog.require('bot.ErrorCode');
goog.require('bot.userAgent');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.TagName');
/**
* Get the user-specified value of the given attribute of the element, or null
* if the attribute is not present.
*
* <p>For boolean attributes such as "selected" or "checked", this method
* returns the value of element.getAttribute(attributeName) cast to a String
* when attribute is present. For modern browsers, this will be the string the
* attribute is given in the HTML, but for IE8 it will be the name of the
* attribute, and for IE7, it will be the string "true". To test whether a
* boolean attribute is present, test whether the return value is non-null, the
* same as one would for non-boolean attributes. Specifically, do *not* test
* whether the boolean evaluation of the return value is true, because the value
* of a boolean attribute that is present will often be the empty string.
*
* <p>For the style attribute, it standardizes the value by lower-casing the
* property names and always including a trailing semicolon.
*
* @param {!Element} element The element to use.
* @param {string} attributeName The name of the attribute to return.
* @return {?string} The value of the attribute or "null" if entirely missing.
*/
bot.dom.core.getAttribute = function (element, attributeName) {
attributeName = attributeName.toLowerCase();
// The style attribute should be a css text string that includes only what
// the HTML element specifies itself (excluding what is inherited from parent
// elements or style sheets). We standardize the format of this string via the
// standardizeStyleAttribute method.
if (attributeName == 'style') {
return bot.dom.core.standardizeStyleAttribute_(element.style.cssText);
}
// In IE doc mode < 8, the "value" attribute of an <input> is only accessible
// as a property.
if (bot.userAgent.IE_DOC_PRE8 && attributeName == 'value' &&
bot.dom.core.isElement(element, goog.dom.TagName.INPUT)) {
return element['value'];
}
// In IE < 9, element.getAttributeNode will return null for some boolean
// attributes that are present, such as the selected attribute on <option>
// elements. This if-statement is sufficient if these cases are restricted
// to boolean attributes whose reflected property names are all lowercase
// (as attributeName is by this point), like "selected". We have not
// found a boolean attribute for which this does not work.
if (bot.userAgent.IE_DOC_PRE9 && element[attributeName] === true) {
return String(element.getAttribute(attributeName));
}
// When the attribute is not present, either attr will be null or
// attr.specified will be false.
var attr = element.getAttributeNode(attributeName);
return (attr && attr.specified) ? attr.value : null;
};
/**
* Regex to split on semicolons, but not when enclosed in parens or quotes.
* Helper for {@link bot.dom.core.standardizeStyleAttribute_}.
* If the style attribute ends with a semicolon this will include an empty
* string at the end of the array
* @private {!RegExp}
* @const
*/
bot.dom.core.SPLIT_STYLE_ATTRIBUTE_ON_SEMICOLONS_REGEXP_ =
new RegExp('[;]+' +
'(?=(?:(?:[^"]*"){2})*[^"]*$)' +
'(?=(?:(?:[^\']*\'){2})*[^\']*$)' +
'(?=(?:[^()]*\\([^()]*\\))*[^()]*$)');
/**
* Standardize a style attribute value, which includes:
* (1) converting all property names lowercase
* (2) ensuring it ends in a trailing semicolon
* @param {string} value The style attribute value.
* @return {string} The identical value, with the formatting rules described
* above applied.
* @private
*/
bot.dom.core.standardizeStyleAttribute_ = function (value) {
var styleArray = value.split(
bot.dom.core.SPLIT_STYLE_ATTRIBUTE_ON_SEMICOLONS_REGEXP_);
var css = [];
goog.array.forEach(styleArray, function (pair) {
var i = pair.indexOf(':');
if (i > 0) {
var keyValue = [pair.slice(0, i), pair.slice(i + 1)];
if (keyValue.length == 2) {
css.push(keyValue[0].toLowerCase(), ':', keyValue[1], ';');
}
}
});
css = css.join('');
css = css.charAt(css.length - 1) == ';' ? css : css + ';';
return css;
};
/**
* Looks up the given property (not to be confused with an attribute) on the
* given element.
*
* @param {!Element} element The element to use.
* @param {string} propertyName The name of the property.
* @return {*} The value of the property.
*/
bot.dom.core.getProperty = function (element, propertyName) {
// When an <option>'s value attribute is not set, its value property should be
// its text content, but IE < 8 does not adhere to that behavior, so fix it.
// http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-value-OPTION
if (bot.userAgent.IE_DOC_PRE8 && propertyName == 'value' &&
bot.dom.core.isElement(element, goog.dom.TagName.OPTION) &&
goog.isNull(bot.dom.core.getAttribute(element, 'value'))) {
return goog.dom.getRawTextContent(element);
}
return element[propertyName];
};
/**
* Returns whether the given node is an element and, optionally, whether it has
* the given tag name. If the tag name is not provided, returns true if the node
* is an element, regardless of the tag name.h
*
* @template T
* @param {Node} node The node to test.
* @param {(goog.dom.TagName<!T>|string)=} opt_tagName Tag name to test the node for.
* @return {boolean} Whether the node is an element with the given tag name.
*/
bot.dom.core.isElement = function (node, opt_tagName) {
// because we call this with deprecated tags such as SHADOW
if (opt_tagName && (typeof opt_tagName !== 'string')) {
opt_tagName = opt_tagName.toString();
}
return !!node && node.nodeType == goog.dom.NodeType.ELEMENT &&
(!opt_tagName || node.tagName.toUpperCase() == opt_tagName);
};
/**
* Returns whether the element can be checked or selected.
*
* @param {!Element} element The element to check.
* @return {boolean} Whether the element could be checked or selected.
*/
bot.dom.core.isSelectable = function (element) {
if (bot.dom.core.isElement(element, goog.dom.TagName.OPTION)) {
return true;
}
if (bot.dom.core.isElement(element, goog.dom.TagName.INPUT)) {
var type = element.type.toLowerCase();
return type == 'checkbox' || type == 'radio';
}
return false;
};
/**
* Returns whether the element is checked or selected.
*
* @param {!Element} element The element to check.
* @return {boolean} Whether the element is checked or selected.
*/
bot.dom.core.isSelected = function (element) {
if (!bot.dom.core.isSelectable(element)) {
throw new bot.Error(bot.ErrorCode.ELEMENT_NOT_SELECTABLE,
'Element is not selectable');
}
var propertyName = 'selected';
var type = element.type && element.type.toLowerCase();
if ('checkbox' == type || 'radio' == type) {
propertyName = 'checked';
}
return !!bot.dom.core.getProperty(element, propertyName);
};