Skip to content

Commit

Permalink
[atoms] Cleaning up getAttribute dependencies, reducing size from 36K…
Browse files Browse the repository at this point in the history
… to 7K

The getAttribute atom had a transitive dependency on wgxpath, which was
never being flagged for dead-code removal by the Closure compiler. This
unnecessarily bloats the atom.

As a workaround, move the atom's definition from
webdriver.atoms.element.getAttribute to a new module:
webdriver.atoms.element.attribute (with a single exported "get"
function). Also moving all of the new module's bot.dom dependencies to a
new namespace, bot.dom.core. Having to rearrange the code like this is
a bit unfortunate, but I cannot figure out why the compiler is not pruning
wgxpath from the atom.
  • Loading branch information
jleyba committed Sep 2, 2016
1 parent 3d82e9e commit 703cffb
Show file tree
Hide file tree
Showing 8 changed files with 452 additions and 319 deletions.
29 changes: 29 additions & 0 deletions javascript/atoms/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,28 @@ js_library(name = 'devices',
)


js_library(name = 'domcore',
srcs = [
"domcore.js",
],
deps = [
':errors',
':useragent',
'//third_party/closure:closure',
],
visibility = [
'//javascript/...',
],
)


js_library(name = 'dom',
srcs = [
"dom.js",
],
deps = [
':bot',
':domcore',
':color',
':json',
':xpath',
Expand Down Expand Up @@ -160,6 +176,19 @@ js_library(name = 'locators',
)


js_library(name = 'useragent',
srcs = [
'userAgent.js',
],
deps = [
'//third_party/closure:closure',
],
visibility = [
'//javascript/...',
],
)


js_library(name = 'window',
srcs = [
'frame.js',
Expand Down
1 change: 1 addition & 0 deletions javascript/atoms/build.desc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ js_library(name = "all_js",
js_library(name = "dom",
srcs = [
"dom.js",
"domcore.js",
])

js_library(name = "error_lib",
Expand Down
166 changes: 10 additions & 156 deletions javascript/atoms/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ goog.provide('bot.dom');

goog.require('bot');
goog.require('bot.color');
goog.require('bot.dom.core');
goog.require('bot.locators.xpath');
goog.require('bot.userAgent');
goog.require('goog.array');
Expand Down Expand Up @@ -66,18 +67,9 @@ bot.dom.getActiveElement = function(nodeOrWindow) {


/**
* 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
*
* @param {Node} node The node to test.
* @param {string=} opt_tagName Tag name to test the node for.
* @return {boolean} Whether the node is an element with the given tag name.
* @const
*/
bot.dom.isElement = function(node, opt_tagName) {
return !!node && node.nodeType == goog.dom.NodeType.ELEMENT &&
(!opt_tagName || node.tagName.toUpperCase() == opt_tagName);
};
bot.dom.isElement = bot.dom.core.isElement;


/**
Expand Down Expand Up @@ -113,45 +105,15 @@ bot.dom.hasPointerEventsDisabled_ = function(element) {


/**
* 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.
* @const
*/
bot.dom.isSelectable = function(element) {
if (bot.dom.isElement(element, goog.dom.TagName.OPTION)) {
return true;
}

if (bot.dom.isElement(element, goog.dom.TagName.INPUT)) {
var type = element.type.toLowerCase();
return type == 'checkbox' || type == 'radio';
}

return false;
};
bot.dom.isSelectable = bot.dom.core.isSelectable;


/**
* Returns whether the element is checked or selected.
*
* @param {!Element} element The element to check.
* @return {boolean} Whether the element is checked or selected.
* @const
*/
bot.dom.isSelected = function(element) {
if (!bot.dom.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.getProperty(element, propertyName);
};
bot.dom.isSelected = bot.dom.core.isSelected;


/**
Expand Down Expand Up @@ -190,123 +152,15 @@ bot.dom.isFocusable = function(element) {


/**
* 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.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.isElement(element, goog.dom.TagName.OPTION) &&
goog.isNull(bot.dom.getAttribute(element, 'value'))) {
return goog.dom.getRawTextContent(element);
}
return element[propertyName];
};


/**
* Regex to split on semicolons, but not when enclosed in parens or quotes.
* Helper for {@link bot.dom.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.SPLIT_STYLE_ATTRIBUTE_ON_SEMICOLONS_REGEXP_ =
new RegExp('[;]+' +
'(?=(?:(?:[^"]*"){2})*[^"]*$)' +
'(?=(?:(?:[^\']*\'){2})*[^\']*$)' +
'(?=(?:[^()]*\\([^()]*\\))*[^()]*$)');
bot.dom.getProperty = bot.dom.core.getProperty;


/**
* Standardize a style attribute value, which includes:
* (1) converting all property names lowercase
* (2) ensuring it ends in a trailing semi-colon
* @param {string} value The style attribute value.
* @return {string} The identical value, with the formatting rules described
* above applied.
* @private
*/
bot.dom.standardizeStyleAttribute_ = function(value) {
var styleArray = value.split(
bot.dom.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;
};


/**
* 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 semi-colon.
*
* @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.
* @const
*/
bot.dom.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.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.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;
};
bot.dom.getAttribute = bot.dom.core.getAttribute;


/**
Expand Down
Loading

0 comments on commit 703cffb

Please sign in to comment.