blob: 2818adaa9c8e81526c99d604b61be8d3920b4fa4 [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* 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.
*
* Author: jmarantz@google.com (Joshua Marantz)
*/
goog.provide('mob.layoutUtil');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
goog.require('goog.string');
goog.require('mob.layoutConstants');
goog.require('mob.util');
goog.require('mob.util.ElementClass');
/**
* Default maximum width in CSS pixels, used if it cannot be calculated from
* the system.
* @private @const {number}
*/
mob.layoutUtil.DEFAULT_MAX_WIDTH_ = 400;
/**
* Maximum allowed margin in pixels.
* @private @const {number}
*/
mob.layoutUtil.CLAMP_STYLE_LIMIT_PX_ = 4;
/**
* Maximum allowed negative margin. Margins less than this value
* are interpreted as an attempt by the site to shift things completely
* off the screen, and we don't want to subvert that.
*
* @private @const {number}
*/
mob.layoutUtil.MAX_ALLOWED_NEGATIVE_MARGIN_PX_ = -30;
/**
* Marker for elements with negative bottom margin.
* @const {string}
*/
mob.layoutUtil.NEGATIVE_BOTTOM_MARGIN_ATTR =
'data-pagespeed-negative-bottom-margin';
/**
* Returns an integer pixel dimension or null. Note that a null return
* might mean the computed dimension is 'auto' or something. This function
* strips the literal "px" from the return value before parsing as an int.
*
* @param {?CSSStyleDeclaration} computedStyle The window.getComputedStyle of
* an element.
* @param {string} name The name of a CSS dimension.
* @return {?number} the dimension value in pixels, or null if failure.
*/
mob.layoutUtil.computedDimension = function(computedStyle, name) {
var value = null;
if (computedStyle) {
value = mob.util.pixelValue(computedStyle.getPropertyValue(name));
}
return value;
};
/**
* Calculates the maximum width we want for elements on the page.
*
* @return {number}
*/
mob.layoutUtil.computeMaxWidth = function() {
var width = window.document.documentElement.clientWidth;
if (!width) {
return mob.layoutUtil.DEFAULT_MAX_WIDTH_;
}
// If there is a body, then subtract off any body padding.
var body = window.document.body;
if (body) {
var bodyStyle = window.getComputedStyle(body);
goog.array.forEach(
mob.layoutConstants.HORIZONTAL_PADDING_PROPERTIES, function(property) {
var value = mob.layoutUtil.computedDimension(bodyStyle, property);
if (value) {
width -= value;
}
});
}
return width;
};
/**
* Determines whether the computedStyle looks like it might be a sprite.
*
* @param {!CSSStyleDeclaration} computedStyle
* @return {boolean}
*/
mob.layoutUtil.isProbablyASprite = function(computedStyle) {
var size = computedStyle.getPropertyValue('background-size');
if (size == 'auto') {
return false;
}
var pos = computedStyle.getPropertyValue('background-position');
if (pos == 'none') {
return false;
}
// A precisely positioned pixel-position probably indicates a sprite.
var pieces = pos.split(' ');
return !!((pieces.length == 2) && (mob.util.pixelValue(pieces[0]) != null) &&
(mob.util.pixelValue(pieces[1]) != null));
};
/**
* Sets a property in the element's style with a new value. The new value
* is written as '!important'.
*
* @param {!Element} element
* @param {string} name
* @param {string} value
*/
mob.layoutUtil.setPropertyImportant = function(element, name, value) {
element.style.setProperty(name, value, 'important');
};
/**
* Makes an element be horizontally scrollable.
*
* @param {!Element} element
*/
mob.layoutUtil.makeHorizontallyScrollable = function(element) {
mob.layoutUtil.setPropertyImportant(element, 'overflow-x', 'auto');
mob.layoutUtil.setPropertyImportant(element, 'width', 'auto');
mob.layoutUtil.setPropertyImportant(element, 'display', 'block');
};
/**
* Counts the number of container-like objects. This is used for a heuristic
* to differentiate data-tables from layout-tables.
*
* @param {!Element} element
* @return {number}
*/
mob.layoutUtil.countContainers = function(element) {
var result = 0;
var tagName = element.tagName.toUpperCase();
if ((tagName == goog.dom.TagName.DIV) ||
(tagName == goog.dom.TagName.TABLE) || (tagName == goog.dom.TagName.UL)) {
++result;
}
for (var child = element.firstElementChild; child;
child = child.nextElementSibling) {
result += mob.layoutUtil.countContainers(child);
}
return result;
};
/**
* Determines whether an element looks like it might be a slide-show.
*
* @param {!Element} element
* @return {boolean}
*/
mob.layoutUtil.isPossiblyASlideShow = function(element) {
return goog.dom.classlist.contains(element, 'nivoSlider');
};
/**
* Returns the background images for an element as URL strings. If there
* are no images, an empty array is returned. If there was a parsing error,
* null is returned.
*
* Note: this ignores all other attributes of the background image. See
* https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Using_multiple_backgrounds
* for the full details of what these can be.
*
* TODO(jmarantz): move this to util.js and replace
* mob.util.findBackgroundImage_ there.
*
* @param {!Element} element
* @return {?Array.<string>}
*/
mob.layoutUtil.findBackgroundImages = function(element) {
var images = [];
var nodeName = element.tagName.toUpperCase();
if ((nodeName == goog.dom.TagName.SCRIPT) ||
(nodeName == goog.dom.TagName.STYLE) ||
!element.style) {
return images;
}
var computedStyle = window.getComputedStyle(element);
if (!computedStyle) {
return images;
}
var imagesString = computedStyle.getPropertyValue('background-image');
if (!imagesString || (imagesString == 'none')) {
return images;
}
// See https://developer.mozilla.org/en-US/docs/Web/CSS/background-image
// Simple Example: background-image: "url(a.png), url(b.png)";
// Ugly example: background-image: "url(a,b.png), url(c(d).png)"
//
// When you pull out the string out of Chrome dev tools the ugly example
// will look like this:
// window.getComputedStyle(document.getElementById('foo')).backgroundImage
// "url(a,b.png), url('c(d).png')"
//
// Thus we cannot fully parse the background image by just splitting on ','
// and stripping the 'url(' and ')'. We must lex the damn thing to do
// it right. But let's punt on that for now and try to use a faster path.
//
// First, we eliminate the case where the URL has embedded parens by
// returning null if we have any quotes.
if (goog.string.contains(imagesString, '\'') ||
goog.string.contains(imagesString, '"')) {
// TODO(jmarantz): Handle quoted URLs.
return null;
}
// Now we can split on comma, but note that url(a,b.png) will be unquoted.
// We'll know that failed because we will not find the closing paren in
// the first token.
var tokens = imagesString.split(',');
for (var i = 0; i < tokens.length; ++i) {
var token = goog.string.trim(tokens[i]);
// Only look at tokens starting with 'url('. This mechanism of
// using split(',') is not sufficient for full parsing of background
// properties. For example, if you have
// "linear-gradient(135deg, white, black)" it will split that
// into 3 tokens: ['linear-gradient(135deg', 'white', 'black)'],
// but if we are only looking at the tokens beginning with "url(" then
// it is sufficient for our needs. Otherwise we need to write a more
// complete lexer that tracks paren-depth and quoting state.
if (goog.string.startsWith(token, 'url(')) {
if (token.charAt(token.length - 1) != ')') {
// TODO(jmarantz): Handle commas in the middle of a URL.
return null; // Must have been a comma in a URL. Punt.
}
images.push(token.substring(4, token.length - 1));
}
}
return images;
};
/**
* Removes width constraints from a layout column. This is used for cleaning
* up desktop multi-column layouts, where the desktop column layout may not
* match the physical size of a phone.
*
* @param {!Element} element
* @param {!CSSStyleDeclaration} computedStyle
*/
mob.layoutUtil.removeWidthConstraint = function(element, computedStyle) {
// Input fields are sometimes reasonably sized, and shouldn't
// be auto-width.
var tagName = element.tagName.toUpperCase();
if ((tagName != goog.dom.TagName.INPUT) &&
(tagName != goog.dom.TagName.SELECT)) {
// Determine whether this element has a width constraint.
if ((!element.style.backgroundSize) &&
(computedStyle.width != 'auto')) {
mob.layoutUtil.setPropertyImportant(element, 'width', 'auto');
}
if (tagName != goog.dom.TagName.IMG) {
// Various table elements with explicit widths can be cleaned up
// to let the browser decide.
element.removeAttribute('width');
}
mob.layoutUtil.removeProperties_(
element, mob.layoutConstants.PROPERTIES_TO_REMOVE_FOR_SINGLE_COLUMN);
element.className += element.className ? ' ' : '';
element.classname += mob.util.ElementClass.SINGLE_COLUMN;
}
};
/**
* Removes a property from an HTML element.
* @param {!Element} element The HTML DOM element.
* @param {string} property The property to remove.
*/
mob.layoutUtil.removeProperty = function(element, property) {
if (element.style) {
element.style.removeProperty(property);
}
element.removeAttribute(property);
};
/**
* Removes the specified list of proeprties from element.
*
* @param {!Element} element
* @param {!Array.<string>} properties
* @private
*/
mob.layoutUtil.removeProperties_ = function(element, properties) {
for (var i = 1; i < arguments.length; ++i) {
mob.layoutUtil.removeProperty(element, arguments[i]);
}
};
/**
* Determines whether a table has only data in it (text and images),
* not more complex HTML structure. The presence of a non-empty
* thead or tfoot is also a strong indicator of tabular dat.
*
* @param {!Element} table
* @return {boolean}
*/
mob.layoutUtil.isDataTable = function(table) {
var numDataNodes = 0;
// Tables have this hierarchy:
// <table>
// <thead> <tbody> <tfoot> (index i)
// <tr> (index j)
// <td> (index k)
// content (index m) -- we don't use 'l' as an var.
// Some tables are used for layout. Some are used for showing tabular
// data. If therd is a non-empty thead then we'll assume it's tabular.
// If there is more than one row and more than one column, we'll assume
// it's tabular as well (might be wrong about this. We'll return 'false'
// from this routine if it looks tabular.
//
// Note: getElementsByTagName('td').length would not be correct here
// for counting data nodes, as that would count nodes in nested tables.
for (var tchild = table.firstElementChild; tchild;
tchild = tchild.nextElementSibling) {
for (var tr = tchild.firstElementChild; tr; tr = tr.nextElementSibling) {
var tagName = tchild.tagName.toUpperCase();
if ((tagName == goog.dom.TagName.THEAD) ||
(tagName == goog.dom.TagName.TFOOT)) {
// The presence of a non-empty thead or tfoot is a strong signal
// that the structure matters.
return true;
}
for (var td = tr.firstElementChild; td; td = td.nextElementSibling) {
if (td.tagName.toUpperCase() == goog.dom.TagName.TH) {
return true;
}
++numDataNodes;
}
}
}
// On some sites it looks much better to atomize the table, despite the fact
// that the container count (23) is not too high -- the data-node count (40).
//
// In other sites, the numbers are much lower; the critical table has only a
// couple of containers. For now, many sites are happy with 3*containers as
// the threshold, but I suspect we have not seen the last of this
// heuristic.
var numContainers = mob.layoutUtil.countContainers(table);
return ((3 * numContainers) <= numDataNodes);
};
/**
* Climbs up parent-nodes to find a 'td' and set the width of all the td in the
* 'tr' to 100/X % where X is the number of td. This works for some sites
* on Chrome. Note that we don't get such great results in Firefox
* responsive-design mode with a narrow screen. Instead, the aspect ratio and
* size of the picture is maintained, and the whole table becomes too wide.
*
* @param {!Element} element
*/
mob.layoutUtil.reallocateWidthToTableData = function(element) {
var tdParent = element;
while (tdParent && (tdParent.tagName.toUpperCase() != goog.dom.TagName.TD)) {
tdParent = tdParent.parentNode;
}
if (tdParent) {
var tr = tdParent.parentNode;
if (tr) {
var td, numTds = 0;
for (td = tr.firstElementChild; td; td = td.nextElementSibling) {
if (td.tagName.toUpperCase() == goog.dom.TagName.TD) {
++numTds;
}
}
if (numTds > 1) {
var style = 'width:' + Math.round(100 / numTds) + '%;';
for (td = tr.firstElementChild; td; td = td.nextElementSibling) {
if (td.tagName.toUpperCase() == goog.dom.TagName.TD) {
mob.util.addStyles(td, style);
}
}
}
}
}
};
/**
* @return {boolean}
*/
mob.layoutUtil.possiblyInQuirksMode = function() {
// http://stackoverflow.com/questions/627097/how-to-tell-if-a-browser-is-in-quirks-mode
return mob.util.getWindow().document.compatMode !== 'CSS1Compat';
};
/**
* Resizes a table to meet a width constraint.
*
* @param {!Element} element
* @param {number} maxWidth
*/
mob.layoutUtil.resizeWideTable = function(element, maxWidth) {
if (mob.layoutUtil.isDataTable(element)) {
mob.layoutUtil.makeHorizontallyScrollable(element);
} else if (mob.layoutUtil.possiblyInQuirksMode()) {
mob.layoutUtil.reorganizeTableQuirksMode(element, maxWidth);
} else {
mob.layoutUtil.reorganizeTableNoQuirksMode(element, maxWidth);
}
};
/**
* Re-arranges a table so that it can possibly be resized to the
* specified dimensions. For now, just strip out all the content
* and make them all separate divs.
*
* @param {!Element} table
* @param {number} maxWidth
*/
mob.layoutUtil.reorganizeTableNoQuirksMode = function(table, maxWidth) {
var tchild, row, data, div;
// Tables have this hierarchy:
// <table>
// <thead> <tbody> <tfoot> (tchild)
// <tr> (row)
// <td> (data)
// content
//
// For now we treat rows in the head and body the same, but we
// most certainly should not. Probably if a table has rows in the
// head then the structure should be changed to something else that
// retains the visual organization of header columns to body columns.
// E.g. one idea is turn a table with N body rows and M columns into
// a table with 1+X columns (X small, 1-3 depending on widths), M rows,
// and some kind of navigational element to choose which X of the original
// rows data should be displayed.
var fullWidth = '100%'; //'' + this.maxWidth_ + 'px';
mob.layoutUtil.removeProperty(table, 'width');
mob.layoutUtil.setPropertyImportant(table, 'max-width', fullWidth);
for (tchild = table.firstElementChild; tchild;
tchild = tchild.nextElementSibling) {
mob.layoutUtil.removeProperty(tchild, 'width');
mob.layoutUtil.setPropertyImportant(tchild, 'max-width', fullWidth);
for (row = tchild.firstElementChild; row; row = row.nextElementSibling) {
if (row.tagName.toUpperCase() == goog.dom.TagName.TR) {
mob.layoutUtil.removeProperty(row, 'width');
mob.layoutUtil.setPropertyImportant(row, 'max-width', fullWidth);
for (data = row.firstElementChild; data;
data = data.nextElementSibling) {
if (data.tagName.toUpperCase() == goog.dom.TagName.TD) {
mob.layoutUtil.setPropertyImportant(data, 'max-width', fullWidth);
mob.layoutUtil.setPropertyImportant(data, 'display',
'inline-block');
}
}
}
}
}
};
/**
* Re-arranges a table so that it can possibly be resized to the
* specified dimensions. In quirks mode, you can't make a TD
* behave nicely when narrowing a table, due to this code in
* blink/webkit:
* https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/css/resolver/StyleAdjuster.cpp&rcl=1413930987&l=310
* See also https://bugs.webkit.org/show_bug.cgi?id=38527
*
* Thus we have to rip out the table and put in divs. Note that this
* will erase the contents of iframes anywhere in the subtrees of the table,
* which will have to be reloaded. This can break some iframes, and thus
* it is preferable to use reorganizeTableNoQuirksMode, which just sets
* attributes on the table elements without changing the structure.
*
* @param {!Element} table
* @param {number} maxWidth
*/
mob.layoutUtil.reorganizeTableQuirksMode = function(table, maxWidth) {
var i, j, k, m, element, data, div, new_element;
// mob.util.createXPathFromNode(table));
// Tables have this hierarchy:
// <table>
// <thead> <tbody> <tfoot> (index i)
// <tr> (index j)
// <td> (index k)
// content (index m) -- we don't use 'l' as an var.
//
// For now we treat rows in the head and body the same, but we
// most certainly should not. Probably if a table has rows in the
// head then the structure should be changed to something else that
// retains the visual organization of header columns to body columns.
// E.g. one idea is turn a table with N body rows and M columns into
// a table with 1+X columns (X small, 1-3 depending on widths), M rows,
// and some kind of navigational element to choose which X of the original
// rows data should be displayed.
var replacement = document.createElement(goog.dom.TagName.DIV);
replacement.style.display = 'inline-block';
var tableChildren = goog.dom.getChildren(table);
for (i = 0; i < tableChildren.length; ++i) {
var bodyChildren = goog.dom.getChildren(tableChildren[i]);
for (j = 0; j < bodyChildren.length; ++j) {
var rowChildren = goog.dom.getChildren(bodyChildren[j]);
for (k = 0; k < rowChildren.length; ++k) {
data = rowChildren[k];
// If there is more than one elment in the <td>, then
// make a div for the elements, otherwise just
// move the element.
if (data.childNodes.length == 1) {
element = data.childNodes[0];
data.removeChild(element);
replacement.appendChild(element);
} else if (data.childNodes.length > 1) {
div = document.createElement(goog.dom.TagName.DIV);
div.style.display = 'inline-block';
var dataChildren = goog.dom.getChildren(data);
for (m = 0; m < dataChildren.length; ++m) {
element = dataChildren[m];
data.removeChild(element);
div.appendChild(element);
}
replacement.appendChild(div);
}
}
}
}
var parent = table.parentNode;
parent.replaceChild(replacement, table);
};
/**
* Determines whether two nonzero numbers are with 5% of one another.
*
* @param {number} x
* @param {number} y
* @return {boolean}
*/
mob.layoutUtil.aboutEqual = function(x, y) {
var ratio = (x > y) ? (y / x) : (x / y);
return (ratio > 0.95);
};
/**
* Finds the dimension as requested directly on the object or its
* immediate style. Does not find dimensions on CSS classes, or
* dimensions specified in 'em', percentages, or other units.
*
* @param {!Element} element The HTML DOM element.
* @param {string} name The name of the dimension.
* @return {?number} The pixel value as an integer, or null.
*/
mob.layoutUtil.findRequestedDimension = function(element, name) {
// See if the value is specified in the style attribute.
var value = null;
if (element.style) {
value = mob.util.pixelValue(element.style.getPropertyValue(name));
}
if (value == null) {
// See if the width is specified directly on the element.
value = mob.util.pixelValue(element.getAttribute(name));
}
return value;
};
/**
* @param {!Node} img
* @return {boolean}
*/
mob.layoutUtil.isSinglePixel = function(img) {
return img.naturalHeight == 1 && img.naturalWidth == 1;
};
/**
* Repairs the aspect-ratio damage done by the broser layout engine
* due to our max-width:100% CSS directive.
*
* @param {!Element} element
*/
mob.layoutUtil.repairDistortedImages = function(element) {
var computedStyle = window.getComputedStyle(element);
var requestedWidth = mob.layoutUtil.findRequestedDimension(element, 'width');
var requestedHeight =
mob.layoutUtil.findRequestedDimension(element, 'height');
if (requestedWidth && requestedHeight && computedStyle) {
var width = mob.layoutUtil.computedDimension(computedStyle, 'width');
var height = mob.layoutUtil.computedDimension(computedStyle, 'height');
if (width && height) {
var widthShrinkage = width / requestedWidth;
var heightShrinkage = height / requestedHeight;
if (!mob.layoutUtil.aboutEqual(widthShrinkage, heightShrinkage)) {
mob.util.consoleLog('aspect ratio problem for ' +
element.getAttribute('src'));
if (mob.layoutUtil.isSinglePixel(element)) {
var shrinkage = Math.min(widthShrinkage, heightShrinkage);
mob.layoutUtil.removeProperties_(element, ['width', 'height']);
element.style.width = requestedWidth * shrinkage;
element.style.height = requestedHeight * shrinkage;
} else if (widthShrinkage > heightShrinkage) {
mob.layoutUtil.removeProperty(element, 'height');
} else {
// If we let the width go free but set the height, the aspect ratio
// might not be maintained. A few ideas on how to fix are here
// http://stackoverflow.com/questions/21176336/css-image-to-have-fixed-height-max-width-and-maintain-aspect-ratio
// Let's try changing the height attribute to max-height.
mob.layoutUtil.removeProperties_(element, ['width', 'height']);
element.style.maxHeight = requestedHeight;
}
}
if (widthShrinkage < 0.25) {
mob.util.consoleLog('overshrinkage for ' + element.getAttribute('src'));
mob.layoutUtil.reallocateWidthToTableData(element);
}
}
}
};
/**
* Finds the top and bottom position of an element, in CSS pixels.
*
* @param {!Element} element
* @param {number} parentTop
* @return {!Array.<number>} top and bottom positions.
*/
mob.layoutUtil.findTopAndBottom = function(element, parentTop) {
var top;
var bottom;
var boundingBox = mob.util.boundingRect(element);
if (boundingBox) {
top = boundingBox.top;
bottom = boundingBox.bottom;
} else {
top = parentTop;
if (element.offsetParent == element.parentNode) {
top += element.offsetTop;
}
bottom = top + element.offsetHeight - 1;
}
return [top, bottom];
};
/**
* Resizes an image tag so it's no wider than the specified width,
* maintaining aspect ratio.
*
* @param {!Element} element
* @param {number} maxWidth
*/
mob.layoutUtil.resizeForegroundImage = function(element, maxWidth) {
var width = element.offsetWidth;
var height = element.offsetHeight;
var shrinkage = width / maxWidth;
if (shrinkage > 1) {
var newHeight = height / shrinkage;
mob.layoutUtil.setPropertyImportant(element, 'width', '' + maxWidth + 'px');
mob.layoutUtil.setPropertyImportant(element, 'height',
'' + newHeight + 'px');
}
};
/**
* Resizes an element's background image so it's no wider than the
* specified width, maintaining aspect ratio. Because you can't
* directly get the natural size of a background image, that data
* must be supplied by the caller.
*
* @param {!Element} element
* @param {!mob.util.Dimensions} imageSize
* @param {!CSSStyleDeclaration} computedStyle
* @param {number} maxWidth
*/
mob.layoutUtil.resizeBackgroundImage = function(element, imageSize,
computedStyle, maxWidth) {
var width = imageSize.width;
var height = imageSize.height;
if (width > maxWidth) {
var shrinkage = maxWidth / width;
height = Math.round(height * shrinkage);
var styles = 'background-size:' + maxWidth + 'px ' +
height + 'px;background-repeat:no-repeat;';
// If the element was previously sized exactly to the div, then resize
// the height of the div to match the new height of the background.
var elementHeight =
mob.layoutUtil.computedDimension(computedStyle, 'height');
if (height == elementHeight) {
styles += 'height:' + height + 'px;';
}
mob.util.addStyles(element, styles);
}
// Whether or not we are not width-constraining the background image, we
// give it a height constraint for the benefit of auto-sizing parent
// nodes. Note that we look specifically for 'min-height' in
// resizeVerticallyAndReturnBottom_, so this is both a signal to the
// browser and to a later pass.
mob.layoutUtil.setPropertyImportant(element, 'min-height',
'' + height + 'px');
};
/**
* Sets up text so that it will wrap on word boundaries.
*
* @param {!Element} element
*/
mob.layoutUtil.wrapTextOnWhitespace = function(element) {
// Fixes the top bar of sites that have white-space:nowrap so that all
// elements on the original line are visible when the width is constrained.
// Do this before recursing into children as this property inherits, and
// we'll need less override markup if we do it at the top level.
var computedStyle = window.getComputedStyle(element);
if (computedStyle.getPropertyValue('white-space') == 'nowrap') {
mob.layoutUtil.setPropertyImportant(element, 'white-space', 'normal');
}
};
/**
* Strips an element of its dimensions specified as a percentage.
*
* @param {!Element} element
* @param {!CSSStyleDeclaration} computedStyle
*/
mob.layoutUtil.stripPercentDimensions = function(element, computedStyle) {
for (var i = 0; i < mob.layoutConstants.NO_PERCENT.length; ++i) {
var name = mob.layoutConstants.NO_PERCENT[i];
var value = computedStyle.getPropertyValue(name);
if (value && (value != '100%') && (value != 'auto') &&
goog.string.endsWith(value, '%')) {
mob.layoutUtil.setPropertyImportant(element, name, 'auto');
}
}
};
/**
* Trims excess padding on elements.
*
* @param {!Element} element
* @param {!CSSStyleDeclaration} computedStyle
*/
mob.layoutUtil.trimPaddingAndMargins = function(element, computedStyle) {
// Don't remove the left-padding from lists; that makes the bullets
// disappear at the bottom of some sites. See
// http://www.w3schools.com/cssref/pr_list-style-position.asp
//
// Don't remove padding from body.
var tagName = element.tagName.toUpperCase();
var isList =
(tagName == goog.dom.TagName.UL) || (tagName == goog.dom.TagName.OL);
var isBody = (tagName == goog.dom.TagName.BODY);
var clampToZero = false;
// Reduce excess padding on margins. We don't want to eliminate
// all padding as that looks terrible on many sites.
var style = '';
for (var i = 0; i < mob.layoutConstants.CLAMPED_STYLES.length; ++i) {
var name = mob.layoutConstants.CLAMPED_STYLES[i];
if ((!isList || !goog.string.endsWith(name, '-left')) &&
(!isBody || !goog.string.startsWith(name, 'margin-'))) {
var value = mob.layoutUtil.computedDimension(computedStyle, name);
if (value == null) {
continue;
}
if (value > mob.layoutUtil.CLAMP_STYLE_LIMIT_PX_) {
// Without the 'important', juniper's 'register now' field
// has uneven input fields.
style += name + ':' + mob.layoutUtil.CLAMP_STYLE_LIMIT_PX_ +
'px !important;';
} else if (value < 0) {
clampToZero = true;
if (name == 'margin-bottom') {
// This *might* be a slide-show implemented with a negative
// margin-bottom based on the element height. However, it
// also might just be a small correction. Heuristically
// try to distinguish them.
// TODO(jmarantz): A better heuristic is to make the determination
// of whether the original margin-bottom matches the element height
// before applying a viewport and max-width:100%.
clampToZero =
(value > mob.layoutUtil.MAX_ALLOWED_NEGATIVE_MARGIN_PX_);
}
if (clampToZero) {
style += name + ':0px !important;';
} else {
// It's likely that our usage of max-width:100% and viewports has
// caused some heights to change (without any explicit JS
// overrides. We then may make further adjustments to the element
// height in expandColumns or elsewhere. So at this
// phase we don't adjust the margin-bottom, but just mark the
// element with an attribute we can easily find later.
// See http://goo.gl/gzWY6I [smashingmagazine.com]
element.setAttribute(mob.layoutUtil.NEGATIVE_BOTTOM_MARGIN_ATTR, '1');
// TODO(jmarantz): do this for margin-right as well.
}
}
}
}
mob.util.addStyles(element, style);
};