// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617/**18* @fileoverview Defines the core DOM querying library for the atoms, with a19* minimal set of dependencies. Notably, this file should never have a20* dependency on CSS libraries such as sizzle.21*/2223goog.provide('bot.dom.core');2425goog.require('bot.Error');26goog.require('bot.ErrorCode');27goog.require('bot.userAgent');28goog.require('goog.array');29goog.require('goog.dom');30goog.require('goog.dom.NodeType');31goog.require('goog.dom.TagName');323334/**35* Get the user-specified value of the given attribute of the element, or null36* if the attribute is not present.37*38* <p>For boolean attributes such as "selected" or "checked", this method39* returns the value of element.getAttribute(attributeName) cast to a String40* when attribute is present. For modern browsers, this will be the string the41* attribute is given in the HTML, but for IE8 it will be the name of the42* attribute, and for IE7, it will be the string "true". To test whether a43* boolean attribute is present, test whether the return value is non-null, the44* same as one would for non-boolean attributes. Specifically, do *not* test45* whether the boolean evaluation of the return value is true, because the value46* of a boolean attribute that is present will often be the empty string.47*48* <p>For the style attribute, it standardizes the value by lower-casing the49* property names and always including a trailing semicolon.50*51* @param {!Element} element The element to use.52* @param {string} attributeName The name of the attribute to return.53* @return {?string} The value of the attribute or "null" if entirely missing.54*/55bot.dom.core.getAttribute = function (element, attributeName) {56attributeName = attributeName.toLowerCase();5758// The style attribute should be a css text string that includes only what59// the HTML element specifies itself (excluding what is inherited from parent60// elements or style sheets). We standardize the format of this string via the61// standardizeStyleAttribute method.62if (attributeName == 'style') {63return bot.dom.core.standardizeStyleAttribute_(element.style.cssText);64}6566// In IE doc mode < 8, the "value" attribute of an <input> is only accessible67// as a property.68if (bot.userAgent.IE_DOC_PRE8 && attributeName == 'value' &&69bot.dom.core.isElement(element, goog.dom.TagName.INPUT)) {70return element['value'];71}7273// In IE < 9, element.getAttributeNode will return null for some boolean74// attributes that are present, such as the selected attribute on <option>75// elements. This if-statement is sufficient if these cases are restricted76// to boolean attributes whose reflected property names are all lowercase77// (as attributeName is by this point), like "selected". We have not78// found a boolean attribute for which this does not work.79if (bot.userAgent.IE_DOC_PRE9 && element[attributeName] === true) {80return String(element.getAttribute(attributeName));81}8283// When the attribute is not present, either attr will be null or84// attr.specified will be false.85var attr = element.getAttributeNode(attributeName);86return (attr && attr.specified) ? attr.value : null;87};888990/**91* Regex to split on semicolons, but not when enclosed in parens or quotes.92* Helper for {@link bot.dom.core.standardizeStyleAttribute_}.93* If the style attribute ends with a semicolon this will include an empty94* string at the end of the array95* @private {!RegExp}96* @const97*/98bot.dom.core.SPLIT_STYLE_ATTRIBUTE_ON_SEMICOLONS_REGEXP_ =99new RegExp('[;]+' +100'(?=(?:(?:[^"]*"){2})*[^"]*$)' +101'(?=(?:(?:[^\']*\'){2})*[^\']*$)' +102'(?=(?:[^()]*\\([^()]*\\))*[^()]*$)');103104105/**106* Standardize a style attribute value, which includes:107* (1) converting all property names lowercase108* (2) ensuring it ends in a trailing semicolon109* @param {string} value The style attribute value.110* @return {string} The identical value, with the formatting rules described111* above applied.112* @private113*/114bot.dom.core.standardizeStyleAttribute_ = function (value) {115var styleArray = value.split(116bot.dom.core.SPLIT_STYLE_ATTRIBUTE_ON_SEMICOLONS_REGEXP_);117var css = [];118goog.array.forEach(styleArray, function (pair) {119var i = pair.indexOf(':');120if (i > 0) {121var keyValue = [pair.slice(0, i), pair.slice(i + 1)];122if (keyValue.length == 2) {123css.push(keyValue[0].toLowerCase(), ':', keyValue[1], ';');124}125}126});127css = css.join('');128css = css.charAt(css.length - 1) == ';' ? css : css + ';';129return css;130};131132133/**134* Looks up the given property (not to be confused with an attribute) on the135* given element.136*137* @param {!Element} element The element to use.138* @param {string} propertyName The name of the property.139* @return {*} The value of the property.140*/141bot.dom.core.getProperty = function (element, propertyName) {142// When an <option>'s value attribute is not set, its value property should be143// its text content, but IE < 8 does not adhere to that behavior, so fix it.144// http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-value-OPTION145if (bot.userAgent.IE_DOC_PRE8 && propertyName == 'value' &&146bot.dom.core.isElement(element, goog.dom.TagName.OPTION) &&147goog.isNull(bot.dom.core.getAttribute(element, 'value'))) {148return goog.dom.getRawTextContent(element);149}150return element[propertyName];151};152153154155/**156* Returns whether the given node is an element and, optionally, whether it has157* the given tag name. If the tag name is not provided, returns true if the node158* is an element, regardless of the tag name.h159*160* @template T161* @param {Node} node The node to test.162* @param {(goog.dom.TagName<!T>|string)=} opt_tagName Tag name to test the node for.163* @return {boolean} Whether the node is an element with the given tag name.164*/165bot.dom.core.isElement = function (node, opt_tagName) {166// because we call this with deprecated tags such as SHADOW167if (opt_tagName && (typeof opt_tagName !== 'string')) {168opt_tagName = opt_tagName.toString();169}170// because node.tagName.toUpperCase() fails when tagName is "tagName"171if (node instanceof HTMLFormElement) {172return !!node && node.nodeType == goog.dom.NodeType.ELEMENT &&173(!opt_tagName || "FORM" == opt_tagName);174}175return !!node && node.nodeType == goog.dom.NodeType.ELEMENT &&176(!opt_tagName || node.tagName.toUpperCase() == opt_tagName);177};178179180/**181* Returns whether the element can be checked or selected.182*183* @param {!Element} element The element to check.184* @return {boolean} Whether the element could be checked or selected.185*/186bot.dom.core.isSelectable = function (element) {187if (bot.dom.core.isElement(element, goog.dom.TagName.OPTION)) {188return true;189}190191if (bot.dom.core.isElement(element, goog.dom.TagName.INPUT)) {192var type = element.type.toLowerCase();193return type == 'checkbox' || type == 'radio';194}195196return false;197};198199200/**201* Returns whether the element is checked or selected.202*203* @param {!Element} element The element to check.204* @return {boolean} Whether the element is checked or selected.205*/206bot.dom.core.isSelected = function (element) {207if (!bot.dom.core.isSelectable(element)) {208throw new bot.Error(bot.ErrorCode.ELEMENT_NOT_SELECTABLE,209'Element is not selectable');210}211212var propertyName = 'selected';213var type = element.type && element.type.toLowerCase();214if ('checkbox' == type || 'radio' == type) {215propertyName = 'checked';216}217218return !!bot.dom.core.getProperty(element, propertyName);219};220221222