blob: 22f2d4f184846770aa9b6dfa764cbad22663d077 [file] [log] [blame] [edit]
// 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.
goog.module('webdriver.atoms.element.attribute');
goog.module.declareLegacyNamespace();
var TagName = goog.require('goog.dom.TagName');
var array = goog.require('goog.array');
var domCore = goog.require('bot.dom.core');
/**
* Common aliases for properties. This maps names that users use to the correct
* property name.
*
* @const {!Object<string, string>}
*/
var PROPERTY_ALIASES = {
'class': 'className',
'readonly': 'readOnly'
};
/**
* Used to determine whether we should return a boolean value from getAttribute.
* These are all extracted from the WHATWG spec:
*
* http://www.whatwg.org/specs/web-apps/current-work/
*
* These must all be lower-case.
*
* @const {!Array<string>}
*/
var BOOLEAN_PROPERTIES = [
'allowfullscreen',
'allowpaymentrequest',
'allowusermedia',
'async',
'autofocus',
'autoplay',
'checked',
'compact',
'complete',
'controls',
'declare',
'default',
'defaultchecked',
'defaultselected',
'defer',
'disabled',
'ended',
'formnovalidate',
'hidden',
'indeterminate',
'iscontenteditable',
'ismap',
'itemscope',
'loop',
'multiple',
'muted',
'nohref',
'nomodule',
'noresize',
'noshade',
'novalidate',
'nowrap',
'open',
'paused',
'playsinline',
'pubdate',
'readonly',
'required',
'reversed',
'scoped',
'seamless',
'seeking',
'selected',
'truespeed',
'typemustmatch',
'willvalidate'
];
/**
* Get the value of the given property or attribute. If the "attribute" is for
* a boolean property, we return null in the case where the value is false. If
* the attribute name is "style" an attempt to convert that style into a string
* is done.
*
* @param {!Element} element The element to use.
* @param {string} attribute The name of the attribute to look up.
* @return {?string} The string value of the attribute or property, or null.
* @suppress {reportUnknownTypes}
*/
exports.get = function(element, attribute) {
var value = null;
var name = attribute.toLowerCase();
if ('style' == name) {
value = element.style;
if (value && !goog.isString(value)) {
value = value.cssText;
}
return /** @type {?string} */ (value);
}
if (('selected' == name || 'checked' == name) &&
domCore.isSelectable(element)) {
return domCore.isSelected(element) ? 'true' : null;
}
// Our tests suggest that returning the attribute is desirable for
// the href attribute of <a> tags and the src attribute of <img> tags,
// but we normally attempt to get the property value before the attribute.
var isLink = domCore.isElement(element, TagName.A);
var isImg = domCore.isElement(element, TagName.IMG);
// Although the attribute matters, the property is consistent. Return that in
// preference to the attribute for links and images.
if ((isImg && name == 'src') || (isLink && name == 'href')) {
value = domCore.getAttribute(element, name);
if (value) {
// We want the full URL if present
value = domCore.getProperty(element, name);
}
return /** @type {?string} */ (value);
}
if ('spellcheck' == name) {
value = domCore.getAttribute(element, name);
if (!goog.isNull(value)) {
if (value.toLowerCase() == 'false') {
return 'false';
} else if (value.toLowerCase() == 'true') {
return 'true';
}
}
// coerce the property value to a string
return domCore.getProperty(element, name) + '';
}
var propName = PROPERTY_ALIASES[attribute] || attribute;
if (array.contains(BOOLEAN_PROPERTIES, name)) {
value = !goog.isNull(domCore.getAttribute(element, attribute)) ||
domCore.getProperty(element, propName);
return value ? 'true' : null;
}
var property;
try {
property = domCore.getProperty(element, propName);
} catch (e) {
// Leaves property undefined or null
}
// 1- Call getAttribute if getProperty fails,
// i.e. property is null or undefined.
// This happens for event handlers in Firefox.
// For example, calling getProperty for 'onclick' would
// fail while getAttribute for 'onclick' will succeed and
// return the JS code of the handler.
//
// 2- When property is an object we fall back to the
// actual attribute instead.
// See issue http://code.google.com/p/selenium/issues/detail?id=966
if (!goog.isDefAndNotNull(property) || goog.isObject(property)) {
value = domCore.getAttribute(element, attribute);
} else {
value = property;
}
// The empty string is a valid return value.
return goog.isDefAndNotNull(value) ? value.toString() : null;
};