UNPKG

accessibility-developer-tools

Version:

This is a library of accessibility-related testing and utility code.

1,352 lines (1,182 loc) 75.8 kB
// Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed 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 Utilities for element styles. * * @author arv@google.com (Erik Arvidsson) * @author eae@google.com (Emil A Eklund) * @see ../demos/inline_block_quirks.html * @see ../demos/inline_block_standards.html * @see ../demos/style_viewport.html */ goog.provide('goog.style'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.NodeType'); goog.require('goog.dom.TagName'); goog.require('goog.dom.vendor'); goog.require('goog.html.SafeStyleSheet'); goog.require('goog.html.legacyconversions'); goog.require('goog.math.Box'); goog.require('goog.math.Coordinate'); goog.require('goog.math.Rect'); goog.require('goog.math.Size'); goog.require('goog.object'); goog.require('goog.reflect'); goog.require('goog.string'); goog.require('goog.userAgent'); goog.forwardDeclare('goog.events.BrowserEvent'); goog.forwardDeclare('goog.events.Event'); /** * Sets a style value on an element. * * This function is not indended to patch issues in the browser's style * handling, but to allow easy programmatic access to setting dash-separated * style properties. An example is setting a batch of properties from a data * object without overwriting old styles. When possible, use native APIs: * elem.style.propertyKey = 'value' or (if obliterating old styles is fine) * elem.style.cssText = 'property1: value1; property2: value2'. * * @param {Element} element The element to change. * @param {string|Object} style If a string, a style name. If an object, a hash * of style names to style values. * @param {string|number|boolean=} opt_value If style was a string, then this * should be the value. */ goog.style.setStyle = function(element, style, opt_value) { if (goog.isString(style)) { goog.style.setStyle_(element, opt_value, style); } else { for (var key in style) { goog.style.setStyle_(element, style[key], key); } } }; /** * Sets a style value on an element, with parameters swapped to work with * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when * necessary. * @param {Element} element The element to change. * @param {string|number|boolean|undefined} value Style value. * @param {string} style Style name. * @private */ goog.style.setStyle_ = function(element, value, style) { var propertyName = goog.style.getVendorJsStyleName_(element, style); if (propertyName) { // TODO(johnlenz): coerce to string? element.style[propertyName] = /** @type {?} */ (value); } }; /** * Style name cache that stores previous property name lookups. * * This is used by setStyle to speed up property lookups, entries look like: * { StyleName: ActualPropertyName } * * @private {!Object<string, string>} */ goog.style.styleNameCache_ = {}; /** * Returns the style property name in camel-case. If it does not exist and a * vendor-specific version of the property does exist, then return the vendor- * specific property name instead. * @param {Element} element The element to change. * @param {string} style Style name. * @return {string} Vendor-specific style. * @private */ goog.style.getVendorJsStyleName_ = function(element, style) { var propertyName = goog.style.styleNameCache_[style]; if (!propertyName) { var camelStyle = goog.string.toCamelCase(style); propertyName = camelStyle; if (element.style[camelStyle] === undefined) { var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + goog.string.toTitleCase(camelStyle); if (element.style[prefixedStyle] !== undefined) { propertyName = prefixedStyle; } } goog.style.styleNameCache_[style] = propertyName; } return propertyName; }; /** * Returns the style property name in CSS notation. If it does not exist and a * vendor-specific version of the property does exist, then return the vendor- * specific property name instead. * @param {Element} element The element to change. * @param {string} style Style name. * @return {string} Vendor-specific style. * @private */ goog.style.getVendorStyleName_ = function(element, style) { var camelStyle = goog.string.toCamelCase(style); if (element.style[camelStyle] === undefined) { var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + goog.string.toTitleCase(camelStyle); if (element.style[prefixedStyle] !== undefined) { return goog.dom.vendor.getVendorPrefix() + '-' + style; } } return style; }; /** * Retrieves an explicitly-set style value of a node. This returns '' if there * isn't a style attribute on the element or if this style property has not been * explicitly set in script. * * @param {Element} element Element to get style of. * @param {string} property Property to get, css-style (if you have a camel-case * property, use element.style[style]). * @return {string} Style value. */ goog.style.getStyle = function(element, property) { // element.style is '' for well-known properties which are unset. // For for browser specific styles as 'filter' is undefined // so we need to return '' explicitly to make it consistent across // browsers. var styleValue = element.style[goog.string.toCamelCase(property)]; // Using typeof here because of a bug in Safari 5.1, where this value // was undefined, but === undefined returned false. if (typeof(styleValue) !== 'undefined') { return styleValue; } return element.style[goog.style.getVendorJsStyleName_(element, property)] || ''; }; /** * Retrieves a computed style value of a node. It returns empty string if the * value cannot be computed (which will be the case in Internet Explorer) or * "none" if the property requested is an SVG one and it has not been * explicitly set (firefox and webkit). * * @param {Element} element Element to get style of. * @param {string} property Property to get (camel-case). * @return {string} Style value. */ goog.style.getComputedStyle = function(element, property) { var doc = goog.dom.getOwnerDocument(element); if (doc.defaultView && doc.defaultView.getComputedStyle) { var styles = doc.defaultView.getComputedStyle(element, null); if (styles) { // element.style[..] is undefined for browser specific styles // as 'filter'. return styles[property] || styles.getPropertyValue(property) || ''; } } return ''; }; /** * Gets the cascaded style value of a node, or null if the value cannot be * computed (only Internet Explorer can do this). * * @param {Element} element Element to get style of. * @param {string} style Property to get (camel-case). * @return {string} Style value. */ goog.style.getCascadedStyle = function(element, style) { // TODO(nicksantos): This should be documented to return null. #fixTypes return element.currentStyle ? element.currentStyle[style] : null; }; /** * Cross-browser pseudo get computed style. It returns the computed style where * available. If not available it tries the cascaded style value (IE * currentStyle) and in worst case the inline style value. It shouldn't be * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for * discussion. * * @param {Element} element Element to get style of. * @param {string} style Property to get (must be camelCase, not css-style.). * @return {string} Style value. * @private */ goog.style.getStyle_ = function(element, style) { return goog.style.getComputedStyle(element, style) || goog.style.getCascadedStyle(element, style) || (element.style && element.style[style]); }; /** * Retrieves the computed value of the box-sizing CSS attribute. * Browser support: http://caniuse.com/css3-boxsizing. * @param {!Element} element The element whose box-sizing to get. * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if * box-sizing is not supported (IE7 and below). */ goog.style.getComputedBoxSizing = function(element) { return goog.style.getStyle_(element, 'boxSizing') || goog.style.getStyle_(element, 'MozBoxSizing') || goog.style.getStyle_(element, 'WebkitBoxSizing') || null; }; /** * Retrieves the computed value of the position CSS attribute. * @param {Element} element The element to get the position of. * @return {string} Position value. */ goog.style.getComputedPosition = function(element) { return goog.style.getStyle_(element, 'position'); }; /** * Retrieves the computed background color string for a given element. The * string returned is suitable for assigning to another element's * background-color, but is not guaranteed to be in any particular string * format. Accessing the color in a numeric form may not be possible in all * browsers or with all input. * * If the background color for the element is defined as a hexadecimal value, * the resulting string can be parsed by goog.color.parse in all supported * browsers. * * Whether named colors like "red" or "lightblue" get translated into a * format which can be parsed is browser dependent. Calling this function on * transparent elements will return "transparent" in most browsers or * "rgba(0, 0, 0, 0)" in WebKit. * @param {Element} element The element to get the background color of. * @return {string} The computed string value of the background color. */ goog.style.getBackgroundColor = function(element) { return goog.style.getStyle_(element, 'backgroundColor'); }; /** * Retrieves the computed value of the overflow-x CSS attribute. * @param {Element} element The element to get the overflow-x of. * @return {string} The computed string value of the overflow-x attribute. */ goog.style.getComputedOverflowX = function(element) { return goog.style.getStyle_(element, 'overflowX'); }; /** * Retrieves the computed value of the overflow-y CSS attribute. * @param {Element} element The element to get the overflow-y of. * @return {string} The computed string value of the overflow-y attribute. */ goog.style.getComputedOverflowY = function(element) { return goog.style.getStyle_(element, 'overflowY'); }; /** * Retrieves the computed value of the z-index CSS attribute. * @param {Element} element The element to get the z-index of. * @return {string|number} The computed value of the z-index attribute. */ goog.style.getComputedZIndex = function(element) { return goog.style.getStyle_(element, 'zIndex'); }; /** * Retrieves the computed value of the text-align CSS attribute. * @param {Element} element The element to get the text-align of. * @return {string} The computed string value of the text-align attribute. */ goog.style.getComputedTextAlign = function(element) { return goog.style.getStyle_(element, 'textAlign'); }; /** * Retrieves the computed value of the cursor CSS attribute. * @param {Element} element The element to get the cursor of. * @return {string} The computed string value of the cursor attribute. */ goog.style.getComputedCursor = function(element) { return goog.style.getStyle_(element, 'cursor'); }; /** * Retrieves the computed value of the CSS transform attribute. * @param {Element} element The element to get the transform of. * @return {string} The computed string representation of the transform matrix. */ goog.style.getComputedTransform = function(element) { var property = goog.style.getVendorStyleName_(element, 'transform'); return goog.style.getStyle_(element, property) || goog.style.getStyle_(element, 'transform'); }; /** * Sets the top/left values of an element. If no unit is specified in the * argument then it will add px. The second argument is required if the first * argument is a string or number and is ignored if the first argument * is a coordinate. * @param {Element} el Element to move. * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate. * @param {string|number=} opt_arg2 Top position. */ goog.style.setPosition = function(el, arg1, opt_arg2) { var x, y; if (arg1 instanceof goog.math.Coordinate) { x = arg1.x; y = arg1.y; } else { x = arg1; y = opt_arg2; } el.style.left = goog.style.getPixelStyleValue_( /** @type {number|string} */ (x), false); el.style.top = goog.style.getPixelStyleValue_( /** @type {number|string} */ (y), false); }; /** * Gets the offsetLeft and offsetTop properties of an element and returns them * in a Coordinate object * @param {Element} element Element. * @return {!goog.math.Coordinate} The position. */ goog.style.getPosition = function(element) { return new goog.math.Coordinate( /** @type {!HTMLElement} */ (element).offsetLeft, /** @type {!HTMLElement} */ (element).offsetTop); }; /** * Returns the viewport element for a particular document * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element * of. * @return {Element} document.documentElement or document.body. */ goog.style.getClientViewportElement = function(opt_node) { var doc; if (opt_node) { doc = goog.dom.getOwnerDocument(opt_node); } else { doc = goog.dom.getDocument(); } // In old IE versions the document.body represented the viewport if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && !goog.dom.getDomHelper(doc).isCss1CompatMode()) { return doc.body; } return doc.documentElement; }; /** * Calculates the viewport coordinates relative to the page/document * containing the node. The viewport may be the browser viewport for * non-iframe document, or the iframe container for iframe'd document. * @param {!Document} doc The document to use as the reference point. * @return {!goog.math.Coordinate} The page offset of the viewport. */ goog.style.getViewportPageOffset = function(doc) { var body = doc.body; var documentElement = doc.documentElement; var scrollLeft = body.scrollLeft || documentElement.scrollLeft; var scrollTop = body.scrollTop || documentElement.scrollTop; return new goog.math.Coordinate(scrollLeft, scrollTop); }; /** * Gets the client rectangle of the DOM element. * * getBoundingClientRect is part of a new CSS object model draft (with a * long-time presence in IE), replacing the error-prone parent offset * computation and the now-deprecated Gecko getBoxObjectFor. * * This utility patches common browser bugs in getBoundingClientRect. It * will fail if getBoundingClientRect is unsupported. * * If the element is not in the DOM, the result is undefined, and an error may * be thrown depending on user agent. * * @param {!Element} el The element whose bounding rectangle is being queried. * @return {Object} A native bounding rectangle with numerical left, top, * right, and bottom. Reported by Firefox to be of object type ClientRect. * @private */ goog.style.getBoundingClientRect_ = function(el) { var rect; try { rect = el.getBoundingClientRect(); } catch (e) { // In IE < 9, calling getBoundingClientRect on an orphan element raises an // "Unspecified Error". All other browsers return zeros. return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0}; } // Patch the result in IE only, so that this function can be inlined if // compiled for non-IE. if (goog.userAgent.IE && el.ownerDocument.body) { // In IE, most of the time, 2 extra pixels are added to the top and left // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and // IE6 standards mode, this border can be overridden by setting the // document element's border to zero -- thus, we cannot rely on the // offset always being 2 pixels. // In quirks mode, the offset can be determined by querying the body's // clientLeft/clientTop, but in standards mode, it is found by querying // the document element's clientLeft/clientTop. Since we already called // getBoundingClientRect we have already forced a reflow, so it is not // too expensive just to query them all. // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx var doc = el.ownerDocument; rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft; rect.top -= doc.documentElement.clientTop + doc.body.clientTop; } return rect; }; /** * Returns the first parent that could affect the position of a given element. * @param {Element} element The element to get the offset parent for. * @return {Element} The first offset parent or null if one cannot be found. */ goog.style.getOffsetParent = function(element) { // element.offsetParent does the right thing in IE7 and below. In other // browsers it only includes elements with position absolute, relative or // fixed, not elements with overflow set to auto or scroll. if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) { goog.asserts.assert(element && 'offsetParent' in element); return element.offsetParent; } var doc = goog.dom.getOwnerDocument(element); var positionStyle = goog.style.getStyle_(element, 'position'); var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute'; for (var parent = element.parentNode; parent && parent != doc; parent = parent.parentNode) { // Skip shadowDOM roots. if (parent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT && parent.host) { parent = parent.host; } positionStyle = goog.style.getStyle_(/** @type {!Element} */ (parent), 'position'); skipStatic = skipStatic && positionStyle == 'static' && parent != doc.documentElement && parent != doc.body; if (!skipStatic && (parent.scrollWidth > parent.clientWidth || parent.scrollHeight > parent.clientHeight || positionStyle == 'fixed' || positionStyle == 'absolute' || positionStyle == 'relative')) { return /** @type {!Element} */ (parent); } } return null; }; /** * Calculates and returns the visible rectangle for a given element. Returns a * box describing the visible portion of the nearest scrollable offset ancestor. * Coordinates are given relative to the document. * * @param {Element} element Element to get the visible rect for. * @return {goog.math.Box} Bounding elementBox describing the visible rect or * null if scrollable ancestor isn't inside the visible viewport. */ goog.style.getVisibleRectForElement = function(element) { var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0); var dom = goog.dom.getDomHelper(element); var body = dom.getDocument().body; var documentElement = dom.getDocument().documentElement; var scrollEl = dom.getDocumentScrollElement(); // Determine the size of the visible rect by climbing the dom accounting for // all scrollable containers. for (var el = element; el = goog.style.getOffsetParent(el);) { // clientWidth is zero for inline block elements in IE. // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0 if ((!goog.userAgent.IE || el.clientWidth != 0) && (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) && // body may have overflow set on it, yet we still get the entire // viewport. In some browsers, el.offsetParent may be // document.documentElement, so check for that too. (el != body && el != documentElement && goog.style.getStyle_(el, 'overflow') != 'visible')) { var pos = goog.style.getPageOffset(el); var client = goog.style.getClientLeftTop(el); pos.x += client.x; pos.y += client.y; visibleRect.top = Math.max(visibleRect.top, pos.y); visibleRect.right = Math.min(visibleRect.right, pos.x + el.clientWidth); visibleRect.bottom = Math.min(visibleRect.bottom, pos.y + el.clientHeight); visibleRect.left = Math.max(visibleRect.left, pos.x); } } // Clip by window's viewport. var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop; visibleRect.left = Math.max(visibleRect.left, scrollX); visibleRect.top = Math.max(visibleRect.top, scrollY); var winSize = dom.getViewportSize(); visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width); visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height); return visibleRect.top >= 0 && visibleRect.left >= 0 && visibleRect.bottom > visibleRect.top && visibleRect.right > visibleRect.left ? visibleRect : null; }; /** * Calculate the scroll position of {@code container} with the minimum amount so * that the content and the borders of the given {@code element} become visible. * If the element is bigger than the container, its top left corner will be * aligned as close to the container's top left corner as possible. * * @param {Element} element The element to make visible. * @param {Element=} opt_container The container to scroll. If not set, then the * document scroll element will be used. * @param {boolean=} opt_center Whether to center the element in the container. * Defaults to false. * @return {!goog.math.Coordinate} The new scroll position of the container, * in form of goog.math.Coordinate(scrollLeft, scrollTop). */ goog.style.getContainerOffsetToScrollInto = function( element, opt_container, opt_center) { var container = opt_container || goog.dom.getDocumentScrollElement(); // Absolute position of the element's border's top left corner. var elementPos = goog.style.getPageOffset(element); // Absolute position of the container's border's top left corner. var containerPos = goog.style.getPageOffset(container); var containerBorder = goog.style.getBorderBox(container); if (container == goog.dom.getDocumentScrollElement()) { // The element position is calculated based on the page offset, and the // document scroll element holds the scroll position within the page. We can // use the scroll position to calculate the relative position from the // element. var relX = elementPos.x - container.scrollLeft; var relY = elementPos.y - container.scrollTop; if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) { // In older versions of IE getPageOffset(element) does not include the // container border so it has to be added to accommodate. relX += containerBorder.left; relY += containerBorder.top; } } else { // Relative pos. of the element's border box to the container's content box. var relX = elementPos.x - containerPos.x - containerBorder.left; var relY = elementPos.y - containerPos.y - containerBorder.top; } // How much the element can move in the container, i.e. the difference between // the element's bottom-right-most and top-left-most position where it's // fully visible. var elementSize = goog.style.getSizeWithDisplay_(element); var spaceX = container.clientWidth - elementSize.width; var spaceY = container.clientHeight - elementSize.height; var scrollLeft = container.scrollLeft; var scrollTop = container.scrollTop; if (opt_center) { // All browsers round non-integer scroll positions down. scrollLeft += relX - spaceX / 2; scrollTop += relY - spaceY / 2; } else { // This formula was designed to give the correct scroll values in the // following cases: // - element is higher than container (spaceY < 0) => scroll down by relY // - element is not higher that container (spaceY >= 0): // - it is above container (relY < 0) => scroll up by abs(relY) // - it is below container (relY > spaceY) => scroll down by relY - spaceY // - it is in the container => don't scroll scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0)); scrollTop += Math.min(relY, Math.max(relY - spaceY, 0)); } return new goog.math.Coordinate(scrollLeft, scrollTop); }; /** * Changes the scroll position of {@code container} with the minimum amount so * that the content and the borders of the given {@code element} become visible. * If the element is bigger than the container, its top left corner will be * aligned as close to the container's top left corner as possible. * * @param {Element} element The element to make visible. * @param {Element=} opt_container The container to scroll. If not set, then the * document scroll element will be used. * @param {boolean=} opt_center Whether to center the element in the container. * Defaults to false. */ goog.style.scrollIntoContainerView = function( element, opt_container, opt_center) { var container = opt_container || goog.dom.getDocumentScrollElement(); var offset = goog.style.getContainerOffsetToScrollInto(element, container, opt_center); container.scrollLeft = offset.x; container.scrollTop = offset.y; }; /** * Returns clientLeft (width of the left border and, if the directionality is * right to left, the vertical scrollbar) and clientTop as a coordinate object. * * @param {Element} el Element to get clientLeft for. * @return {!goog.math.Coordinate} Client left and top. */ goog.style.getClientLeftTop = function(el) { return new goog.math.Coordinate(el.clientLeft, el.clientTop); }; /** * Returns a Coordinate object relative to the top-left of the HTML document. * Implemented as a single function to save having to do two recursive loops in * opera and safari just to get both coordinates. If you just want one value do * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but * note if you call both those methods the tree will be analysed twice. * * @param {Element} el Element to get the page offset for. * @return {!goog.math.Coordinate} The page offset. */ goog.style.getPageOffset = function(el) { var doc = goog.dom.getOwnerDocument(el); // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe. goog.asserts.assertObject(el, 'Parameter is required'); // NOTE(arv): If element is hidden (display none or disconnected or any the // ancestors are hidden) we get (0,0) by default but we still do the // accumulation of scroll position. // TODO(arv): Should we check if the node is disconnected and in that case // return (0,0)? var pos = new goog.math.Coordinate(0, 0); var viewportElement = goog.style.getClientViewportElement(doc); if (el == viewportElement) { // viewport is always at 0,0 as that defined the coordinate system for this // function - this avoids special case checks in the code below return pos; } var box = goog.style.getBoundingClientRect_(el); // Must add the scroll coordinates in to get the absolute page offset // of element since getBoundingClientRect returns relative coordinates to // the viewport. var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll(); pos.x = box.left + scrollCoord.x; pos.y = box.top + scrollCoord.y; return pos; }; /** * Returns the left coordinate of an element relative to the HTML document * @param {Element} el Elements. * @return {number} The left coordinate. */ goog.style.getPageOffsetLeft = function(el) { return goog.style.getPageOffset(el).x; }; /** * Returns the top coordinate of an element relative to the HTML document * @param {Element} el Elements. * @return {number} The top coordinate. */ goog.style.getPageOffsetTop = function(el) { return goog.style.getPageOffset(el).y; }; /** * Returns a Coordinate object relative to the top-left of an HTML document * in an ancestor frame of this element. Used for measuring the position of * an element inside a frame relative to a containing frame. * * @param {Element} el Element to get the page offset for. * @param {Window} relativeWin The window to measure relative to. If relativeWin * is not in the ancestor frame chain of the element, we measure relative to * the top-most window. * @return {!goog.math.Coordinate} The page offset. */ goog.style.getFramedPageOffset = function(el, relativeWin) { var position = new goog.math.Coordinate(0, 0); // Iterate up the ancestor frame chain, keeping track of the current window // and the current element in that window. var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el)); // MS Edge throws when accessing "parent" if el's containing iframe has been // deleted. if (!goog.reflect.canAccessProperty(currentWin, 'parent')) { return position; } var currentEl = el; do { // if we're at the top window, we want to get the page offset. // if we're at an inner frame, we only want to get the window position // so that we can determine the actual page offset in the context of // the outer window. var offset = currentWin == relativeWin ? goog.style.getPageOffset(currentEl) : goog.style.getClientPositionForElement_(goog.asserts.assert(currentEl)); position.x += offset.x; position.y += offset.y; } while (currentWin && currentWin != relativeWin && currentWin != currentWin.parent && (currentEl = currentWin.frameElement) && (currentWin = currentWin.parent)); return position; }; /** * Translates the specified rect relative to origBase page, for newBase page. * If origBase and newBase are the same, this function does nothing. * * @param {goog.math.Rect} rect The source rectangle relative to origBase page, * and it will have the translated result. * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle. * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant * coordinate. This must be a DOM for an ancestor frame of origBase * or the same as origBase. */ goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) { if (origBase.getDocument() != newBase.getDocument()) { var body = origBase.getDocument().body; var pos = goog.style.getFramedPageOffset(body, newBase.getWindow()); // Adjust Body's margin. pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body)); if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && !origBase.isCss1CompatMode()) { pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll()); } rect.left += pos.x; rect.top += pos.y; } }; /** * Returns the position of an element relative to another element in the * document. A relative to B * @param {Element|Event|goog.events.Event} a Element or mouse event whose * position we're calculating. * @param {Element|Event|goog.events.Event} b Element or mouse event position * is relative to. * @return {!goog.math.Coordinate} The relative position. */ goog.style.getRelativePosition = function(a, b) { var ap = goog.style.getClientPosition(a); var bp = goog.style.getClientPosition(b); return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y); }; /** * Returns the position of the event or the element's border box relative to * the client viewport. * @param {!Element} el Element whose position to get. * @return {!goog.math.Coordinate} The position. * @private */ goog.style.getClientPositionForElement_ = function(el) { var box = goog.style.getBoundingClientRect_(el); return new goog.math.Coordinate(box.left, box.top); }; /** * Returns the position of the event or the element's border box relative to * the client viewport. If an event is passed, and if this event is a "touch" * event, then the position of the first changedTouches will be returned. * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event. * @return {!goog.math.Coordinate} The position. */ goog.style.getClientPosition = function(el) { goog.asserts.assert(el); if (el.nodeType == goog.dom.NodeType.ELEMENT) { return goog.style.getClientPositionForElement_( /** @type {!Element} */ (el)); } else { var targetEvent = el.changedTouches ? el.changedTouches[0] : el; return new goog.math.Coordinate(targetEvent.clientX, targetEvent.clientY); } }; /** * Moves an element to the given coordinates relative to the client viewport. * @param {Element} el Absolutely positioned element to set page offset for. * It must be in the document. * @param {number|goog.math.Coordinate} x Left position of the element's margin * box or a coordinate object. * @param {number=} opt_y Top position of the element's margin box. */ goog.style.setPageOffset = function(el, x, opt_y) { // Get current pageoffset var cur = goog.style.getPageOffset(el); if (x instanceof goog.math.Coordinate) { opt_y = x.y; x = x.x; } // NOTE(arv): We cannot allow strings for x and y. We could but that would // require us to manually transform between different units // Work out deltas var dx = goog.asserts.assertNumber(x) - cur.x; var dy = Number(opt_y) - cur.y; // Set position to current left/top + delta goog.style.setPosition( el, /** @type {!HTMLElement} */ (el).offsetLeft + dx, /** @type {!HTMLElement} */ (el).offsetTop + dy); }; /** * Sets the width/height values of an element. If an argument is numeric, * or a goog.math.Size is passed, it is assumed to be pixels and will add * 'px' after converting it to an integer in string form. (This just sets the * CSS width and height properties so it might set content-box or border-box * size depending on the box model the browser is using.) * * @param {Element} element Element to set the size of. * @param {string|number|goog.math.Size} w Width of the element, or a * size object. * @param {string|number=} opt_h Height of the element. Required if w is not a * size object. */ goog.style.setSize = function(element, w, opt_h) { var h; if (w instanceof goog.math.Size) { h = w.height; w = w.width; } else { if (opt_h == undefined) { throw Error('missing height argument'); } h = opt_h; } goog.style.setWidth(element, /** @type {string|number} */ (w)); goog.style.setHeight(element, h); }; /** * Helper function to create a string to be set into a pixel-value style * property of an element. Can round to the nearest integer value. * * @param {string|number} value The style value to be used. If a number, * 'px' will be appended, otherwise the value will be applied directly. * @param {boolean} round Whether to round the nearest integer (if property * is a number). * @return {string} The string value for the property. * @private */ goog.style.getPixelStyleValue_ = function(value, round) { if (typeof value == 'number') { value = (round ? Math.round(value) : value) + 'px'; } return value; }; /** * Set the height of an element. Sets the element's style property. * @param {Element} element Element to set the height of. * @param {string|number} height The height value to set. If a number, 'px' * will be appended, otherwise the value will be applied directly. */ goog.style.setHeight = function(element, height) { element.style.height = goog.style.getPixelStyleValue_(height, true); }; /** * Set the width of an element. Sets the element's style property. * @param {Element} element Element to set the width of. * @param {string|number} width The width value to set. If a number, 'px' * will be appended, otherwise the value will be applied directly. */ goog.style.setWidth = function(element, width) { element.style.width = goog.style.getPixelStyleValue_(width, true); }; /** * Gets the height and width of an element, even if its display is none. * * Specifically, this returns the height and width of the border box, * irrespective of the box model in effect. * * Note that this function does not take CSS transforms into account. Please see * {@code goog.style.getTransformedSize}. * @param {Element} element Element to get size of. * @return {!goog.math.Size} Object with width/height properties. */ goog.style.getSize = function(element) { return goog.style.evaluateWithTemporaryDisplay_( goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element)); }; /** * Call {@code fn} on {@code element} such that {@code element}'s dimensions are * accurate when it's passed to {@code fn}. * @param {function(!Element): T} fn Function to call with {@code element} as * an argument after temporarily changing {@code element}'s display such * that its dimensions are accurate. * @param {!Element} element Element (which may have display none) to use as * argument to {@code fn}. * @return {T} Value returned by calling {@code fn} with {@code element}. * @template T * @private */ goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) { if (goog.style.getStyle_(element, 'display') != 'none') { return fn(element); } var style = element.style; var originalDisplay = style.display; var originalVisibility = style.visibility; var originalPosition = style.position; style.visibility = 'hidden'; style.position = 'absolute'; style.display = 'inline'; var retVal = fn(element); style.display = originalDisplay; style.position = originalPosition; style.visibility = originalVisibility; return retVal; }; /** * Gets the height and width of an element when the display is not none. * @param {Element} element Element to get size of. * @return {!goog.math.Size} Object with width/height properties. * @private */ goog.style.getSizeWithDisplay_ = function(element) { var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth; var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight; var webkitOffsetsZero = goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight; if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) && element.getBoundingClientRect) { // Fall back to calling getBoundingClientRect when offsetWidth or // offsetHeight are not defined, or when they are zero in WebKit browsers. // This makes sure that we return for the correct size for SVG elements, but // will still return 0 on Webkit prior to 534.8, see // http://trac.webkit.org/changeset/67252. var clientRect = goog.style.getBoundingClientRect_(element); return new goog.math.Size( clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); } return new goog.math.Size(offsetWidth, offsetHeight); }; /** * Gets the height and width of an element, post transform, even if its display * is none. * * This is like {@code goog.style.getSize}, except: * <ol> * <li>Takes webkitTransforms such as rotate and scale into account. * <li>Will return null if {@code element} doesn't respond to * {@code getBoundingClientRect}. * <li>Currently doesn't make sense on non-WebKit browsers which don't support * webkitTransforms. * </ol> * @param {!Element} element Element to get size of. * @return {goog.math.Size} Object with width/height properties. */ goog.style.getTransformedSize = function(element) { if (!element.getBoundingClientRect) { return null; } var clientRect = goog.style.evaluateWithTemporaryDisplay_( goog.style.getBoundingClientRect_, element); return new goog.math.Size( clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); }; /** * Returns a bounding rectangle for a given element in page space. * @param {Element} element Element to get bounds of. Must not be display none. * @return {!goog.math.Rect} Bounding rectangle for the element. */ goog.style.getBounds = function(element) { var o = goog.style.getPageOffset(element); var s = goog.style.getSize(element); return new goog.math.Rect(o.x, o.y, s.width, s.height); }; /** * Converts a CSS selector in the form style-property to styleProperty. * @param {*} selector CSS Selector. * @return {string} Camel case selector. * @deprecated Use goog.string.toCamelCase instead. */ goog.style.toCamelCase = function(selector) { return goog.string.toCamelCase(String(selector)); }; /** * Converts a CSS selector in the form styleProperty to style-property. * @param {string} selector Camel case selector. * @return {string} Selector cased. * @deprecated Use goog.string.toSelectorCase instead. */ goog.style.toSelectorCase = function(selector) { return goog.string.toSelectorCase(selector); }; /** * Gets the opacity of a node (x-browser). This gets the inline style opacity * of the node, and does not take into account the cascaded or the computed * style for this node. * @param {Element} el Element whose opacity has to be found. * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''} * if the opacity is not set. */ goog.style.getOpacity = function(el) { goog.asserts.assert(el); var style = el.style; var result = ''; if ('opacity' in style) { result = style.opacity; } else if ('MozOpacity' in style) { result = style.MozOpacity; } else if ('filter' in style) { var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/); if (match) { result = String(match[1] / 100); } } return result == '' ? result : Number(result); }; /** * Sets the opacity of a node (x-browser). * @param {Element} el Elements whose opacity has to be set. * @param {number|string} alpha Opacity between 0 and 1 or an empty string * {@code ''} to clear the opacity. */ goog.style.setOpacity = function(el, alpha) { goog.asserts.assert(el); var style = el.style; if ('opacity' in style) { style.opacity = alpha; } else if ('MozOpacity' in style) { style.MozOpacity = alpha; } else if ('filter' in style) { // TODO(arv): Overwriting the filter might have undesired side effects. if (alpha === '') { style.filter = ''; } else { style.filter = 'alpha(opacity=' + (Number(alpha) * 100) + ')'; } } }; /** * Sets the background of an element to a transparent image in a browser- * independent manner. * * This function does not support repeating backgrounds or alternate background * positions to match the behavior of Internet Explorer. It also does not * support sizingMethods other than crop since they cannot be replicated in * browsers other than Internet Explorer. * * @param {Element} el The element to set background on. * @param {string} src The image source URL. */ goog.style.setTransparentBackgroundImage = function(el, src) { var style = el.style; // It is safe to use the style.filter in IE only. In Safari 'filter' is in // style object but access to style.filter causes it to throw an exception. // Note: IE8 supports images with an alpha channel. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { // See TODO in setOpacity. style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' + 'src="' + src + '", sizingMethod="crop")'; } else { // Set style properties individually instead of using background shorthand // to prevent overwriting a pre-existing background color. style.backgroundImage = 'url(' + src + ')'; style.backgroundPosition = 'top left'; style.backgroundRepeat = 'no-repeat'; } }; /** * Clears the background image of an element in a browser independent manner. * @param {Element} el The element to clear background image for. */ goog.style.clearTransparentBackgroundImage = function(el) { var style = el.style; if ('filter' in style) { // See TODO in setOpacity. style.filter = ''; } else { // Set style properties individually instead of using background shorthand // to prevent overwriting a pre-existing background color. style.backgroundImage = 'none'; } }; /** * Shows or hides an element from the page. Hiding the element is done by * setting the display property to "none", removing the element from the * rendering hierarchy so it takes up no space. To show the element, the default * inherited display property is restored (defined either in stylesheets or by * the browser's default style rules.) * * Caveat 1: if the inherited display property for the element is set to "none" * by the stylesheets, that is the property that will be restored by a call to * showElement(), effectively toggling the display between "none" and "none". * * Caveat 2: if the element display style is set inline (by setting either * element.style.display or a style attribute in the HTML), a call to * showElement will clear that setting and defer to the inherited style in the * stylesheet. * @param {Element} el Element to show or hide. * @param {*} display True to render the element in its default style, * false to disable rendering the element. * @deprecated Use goog.style.setElementShown instead. */ goog.style.showElement = function(el, display) { goog.style.setElementShown(el, display); }; /** * Shows or hides an element from the page. Hiding the element is done by * setting the display property to "none", removing the element from the * rendering hierarchy so it takes up no space. To show the element, the default * inherited display property is restored (defined either in stylesheets or by * the browser's default style rules). * * Caveat 1: if the inherited display property for the element is set to "none" * by the stylesheets, that is the property that will be restored by a call to * setElementShown(), effectively toggling the display between "none" and * "none". * * Caveat 2: if the element display style is set inline (by setting either * element.style.display or a style attribute in the HTML), a call to * setElementShown will clear that setting and defer to the inherited style in * the stylesheet. * @param {Element} el Element to show or hide. * @param {*} isShown True to render the element in its default style, * false to disable rendering the element. */ goog.style.setElementShown = function(el, isShown) { el.style.display = isShown ? '' : 'none'; }; /** * Test whether the given element has been shown or hidden via a call to * {@link #setElementShown}. * * Note this is strictly a companion method for a call * to {@link #setElementShown} and the same caveats apply; in particular, this * method does not guarantee that the return value will be consistent with * whether or not the element is actually visible. * * @param {Element} el The element to test. * @return {boolean} Whether the element has been shown. * @see #setElementShown */ goog.style.isElementShown = function(el) { return el.style.display != 'none'; }; /** * Installs the styles string into the window that contains opt_node. If * opt_node is null, the main window is used. * @param {string} stylesString The style string to install. * @param {Node=} opt_node Node whose parent document should have the * styles installed. * @return {!Element|!StyleSheet} The style element created. * @deprecated Use {@link #installSafeStyleSheet} instead. */ goog.style.installStyles = function(stylesString, opt_node) { return goog.style.installSafeStyleSheet( goog.html.legacyconversions.safeStyleSheetFromString(stylesString), opt_node); }; /** * Installs the style sheet into the window that contains opt_node. If * opt_node is null, the main window is used. * @param {!goog.html.SafeStyleSheet} safeStyleSheet The style sheet to install. * @param {?Node=} opt_node Node whose parent document should have the * styles installed. * @return {!Element|!StyleSheet} The style element created. */ goog.style.installSafeStyleSheet = function(safeStyleSheet, opt_node) { var dh = goog.dom.getDomHelper(opt_node); var styleSheet = null; // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be // undefined as of IE 11. var doc = dh.getDocument(); if (goog.userAgent.IE && doc.createStyleSheet) { styleSheet = doc.createStyleSheet(); goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet); } else { var head = dh.getElementsByTagNameAndClass(goog.dom.TagName.HEAD)[0]; // In opera documents are not guaranteed to have a head element, thus we // have to make sure one exists before using it. if (!head) { var body = dh.getElementsByTagNameAndClass(goog.dom.TagName.BODY)[0]; head = dh.createDom(goog.dom.TagName.HEAD); body.parentNode.insertBefore(head, body); } styleSheet = dh.createDom(goog.dom.TagName.STYLE); // NOTE(user): Setting styles after the style element has been appended // to the head results in a nasty Webkit bug in certain scenarios. Please // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional // details. goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet); dh.appendChild(head, styleSheet); } return styleSheet; }; /** * Removes the styles added by {@link #installStyles}. * @param {Element|StyleSheet} styleSheet The value returned by * {@link #installStyles}. */ goog.style.uninstallStyles = function(styleSheet) { var node = styleSheet.ownerNode || styleSheet.owningElement || /** @type {Element} */ (styleSheet); goog.dom.removeNode(node); }; /** * Sets the content of a style element. The style element can be any valid * style element. This element will have its content completely replaced by * the stylesString. * @param {Element|StyleSheet} element A stylesheet element as returned by * installStyles. * @param {string} stylesString The new content of the stylesheet. * @deprecated Use {@link #setSafe