style.js 74 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Utilities for element styles.
  16. *
  17. * @author arv@google.com (Erik Arvidsson)
  18. * @author eae@google.com (Emil A Eklund)
  19. * @see ../demos/inline_block_quirks.html
  20. * @see ../demos/inline_block_standards.html
  21. * @see ../demos/style_viewport.html
  22. */
  23. goog.provide('goog.style');
  24. goog.require('goog.array');
  25. goog.require('goog.asserts');
  26. goog.require('goog.dom');
  27. goog.require('goog.dom.NodeType');
  28. goog.require('goog.dom.TagName');
  29. goog.require('goog.dom.vendor');
  30. goog.require('goog.html.SafeStyleSheet');
  31. goog.require('goog.html.legacyconversions');
  32. goog.require('goog.math.Box');
  33. goog.require('goog.math.Coordinate');
  34. goog.require('goog.math.Rect');
  35. goog.require('goog.math.Size');
  36. goog.require('goog.object');
  37. goog.require('goog.reflect');
  38. goog.require('goog.string');
  39. goog.require('goog.userAgent');
  40. goog.forwardDeclare('goog.events.Event');
  41. /**
  42. * Sets a style value on an element.
  43. *
  44. * This function is not indended to patch issues in the browser's style
  45. * handling, but to allow easy programmatic access to setting dash-separated
  46. * style properties. An example is setting a batch of properties from a data
  47. * object without overwriting old styles. When possible, use native APIs:
  48. * elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
  49. * elem.style.cssText = 'property1: value1; property2: value2'.
  50. *
  51. * @param {Element} element The element to change.
  52. * @param {string|Object} style If a string, a style name. If an object, a hash
  53. * of style names to style values.
  54. * @param {string|number|boolean=} opt_value If style was a string, then this
  55. * should be the value.
  56. */
  57. goog.style.setStyle = function(element, style, opt_value) {
  58. if (goog.isString(style)) {
  59. goog.style.setStyle_(element, opt_value, style);
  60. } else {
  61. for (var key in style) {
  62. goog.style.setStyle_(element, style[key], key);
  63. }
  64. }
  65. };
  66. /**
  67. * Sets a style value on an element, with parameters swapped to work with
  68. * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
  69. * necessary.
  70. * @param {Element} element The element to change.
  71. * @param {string|number|boolean|undefined} value Style value.
  72. * @param {string} style Style name.
  73. * @private
  74. */
  75. goog.style.setStyle_ = function(element, value, style) {
  76. var propertyName = goog.style.getVendorJsStyleName_(element, style);
  77. if (propertyName) {
  78. // TODO(johnlenz): coerce to string?
  79. element.style[propertyName] = /** @type {?} */ (value);
  80. }
  81. };
  82. /**
  83. * Style name cache that stores previous property name lookups.
  84. *
  85. * This is used by setStyle to speed up property lookups, entries look like:
  86. * { StyleName: ActualPropertyName }
  87. *
  88. * @private {!Object<string, string>}
  89. */
  90. goog.style.styleNameCache_ = {};
  91. /**
  92. * Returns the style property name in camel-case. If it does not exist and a
  93. * vendor-specific version of the property does exist, then return the vendor-
  94. * specific property name instead.
  95. * @param {Element} element The element to change.
  96. * @param {string} style Style name.
  97. * @return {string} Vendor-specific style.
  98. * @private
  99. */
  100. goog.style.getVendorJsStyleName_ = function(element, style) {
  101. var propertyName = goog.style.styleNameCache_[style];
  102. if (!propertyName) {
  103. var camelStyle = goog.string.toCamelCase(style);
  104. propertyName = camelStyle;
  105. if (element.style[camelStyle] === undefined) {
  106. var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
  107. goog.string.toTitleCase(camelStyle);
  108. if (element.style[prefixedStyle] !== undefined) {
  109. propertyName = prefixedStyle;
  110. }
  111. }
  112. goog.style.styleNameCache_[style] = propertyName;
  113. }
  114. return propertyName;
  115. };
  116. /**
  117. * Returns the style property name in CSS notation. If it does not exist and a
  118. * vendor-specific version of the property does exist, then return the vendor-
  119. * specific property name instead.
  120. * @param {Element} element The element to change.
  121. * @param {string} style Style name.
  122. * @return {string} Vendor-specific style.
  123. * @private
  124. */
  125. goog.style.getVendorStyleName_ = function(element, style) {
  126. var camelStyle = goog.string.toCamelCase(style);
  127. if (element.style[camelStyle] === undefined) {
  128. var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
  129. goog.string.toTitleCase(camelStyle);
  130. if (element.style[prefixedStyle] !== undefined) {
  131. return goog.dom.vendor.getVendorPrefix() + '-' + style;
  132. }
  133. }
  134. return style;
  135. };
  136. /**
  137. * Retrieves an explicitly-set style value of a node. This returns '' if there
  138. * isn't a style attribute on the element or if this style property has not been
  139. * explicitly set in script.
  140. *
  141. * @param {Element} element Element to get style of.
  142. * @param {string} property Property to get, css-style (if you have a camel-case
  143. * property, use element.style[style]).
  144. * @return {string} Style value.
  145. */
  146. goog.style.getStyle = function(element, property) {
  147. // element.style is '' for well-known properties which are unset.
  148. // For for browser specific styles as 'filter' is undefined
  149. // so we need to return '' explicitly to make it consistent across
  150. // browsers.
  151. var styleValue = element.style[goog.string.toCamelCase(property)];
  152. // Using typeof here because of a bug in Safari 5.1, where this value
  153. // was undefined, but === undefined returned false.
  154. if (typeof(styleValue) !== 'undefined') {
  155. return styleValue;
  156. }
  157. return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
  158. '';
  159. };
  160. /**
  161. * Retrieves a computed style value of a node. It returns empty string if the
  162. * value cannot be computed (which will be the case in Internet Explorer) or
  163. * "none" if the property requested is an SVG one and it has not been
  164. * explicitly set (firefox and webkit).
  165. *
  166. * @param {Element} element Element to get style of.
  167. * @param {string} property Property to get (camel-case).
  168. * @return {string} Style value.
  169. */
  170. goog.style.getComputedStyle = function(element, property) {
  171. var doc = goog.dom.getOwnerDocument(element);
  172. if (doc.defaultView && doc.defaultView.getComputedStyle) {
  173. var styles = doc.defaultView.getComputedStyle(element, null);
  174. if (styles) {
  175. // element.style[..] is undefined for browser specific styles
  176. // as 'filter'.
  177. return styles[property] || styles.getPropertyValue(property) || '';
  178. }
  179. }
  180. return '';
  181. };
  182. /**
  183. * Gets the cascaded style value of a node, or null if the value cannot be
  184. * computed (only Internet Explorer can do this).
  185. *
  186. * @param {Element} element Element to get style of.
  187. * @param {string} style Property to get (camel-case).
  188. * @return {string} Style value.
  189. */
  190. goog.style.getCascadedStyle = function(element, style) {
  191. // TODO(nicksantos): This should be documented to return null. #fixTypes
  192. return /** @type {string} */ (
  193. element.currentStyle ? element.currentStyle[style] : null);
  194. };
  195. /**
  196. * Cross-browser pseudo get computed style. It returns the computed style where
  197. * available. If not available it tries the cascaded style value (IE
  198. * currentStyle) and in worst case the inline style value. It shouldn't be
  199. * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
  200. * discussion.
  201. *
  202. * @param {Element} element Element to get style of.
  203. * @param {string} style Property to get (must be camelCase, not css-style.).
  204. * @return {string} Style value.
  205. * @private
  206. */
  207. goog.style.getStyle_ = function(element, style) {
  208. return goog.style.getComputedStyle(element, style) ||
  209. goog.style.getCascadedStyle(element, style) ||
  210. (element.style && element.style[style]);
  211. };
  212. /**
  213. * Retrieves the computed value of the box-sizing CSS attribute.
  214. * Browser support: http://caniuse.com/css3-boxsizing.
  215. * @param {!Element} element The element whose box-sizing to get.
  216. * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if
  217. * box-sizing is not supported (IE7 and below).
  218. */
  219. goog.style.getComputedBoxSizing = function(element) {
  220. return goog.style.getStyle_(element, 'boxSizing') ||
  221. goog.style.getStyle_(element, 'MozBoxSizing') ||
  222. goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
  223. };
  224. /**
  225. * Retrieves the computed value of the position CSS attribute.
  226. * @param {Element} element The element to get the position of.
  227. * @return {string} Position value.
  228. */
  229. goog.style.getComputedPosition = function(element) {
  230. return goog.style.getStyle_(element, 'position');
  231. };
  232. /**
  233. * Retrieves the computed background color string for a given element. The
  234. * string returned is suitable for assigning to another element's
  235. * background-color, but is not guaranteed to be in any particular string
  236. * format. Accessing the color in a numeric form may not be possible in all
  237. * browsers or with all input.
  238. *
  239. * If the background color for the element is defined as a hexadecimal value,
  240. * the resulting string can be parsed by goog.color.parse in all supported
  241. * browsers.
  242. *
  243. * Whether named colors like "red" or "lightblue" get translated into a
  244. * format which can be parsed is browser dependent. Calling this function on
  245. * transparent elements will return "transparent" in most browsers or
  246. * "rgba(0, 0, 0, 0)" in WebKit.
  247. * @param {Element} element The element to get the background color of.
  248. * @return {string} The computed string value of the background color.
  249. */
  250. goog.style.getBackgroundColor = function(element) {
  251. return goog.style.getStyle_(element, 'backgroundColor');
  252. };
  253. /**
  254. * Retrieves the computed value of the overflow-x CSS attribute.
  255. * @param {Element} element The element to get the overflow-x of.
  256. * @return {string} The computed string value of the overflow-x attribute.
  257. */
  258. goog.style.getComputedOverflowX = function(element) {
  259. return goog.style.getStyle_(element, 'overflowX');
  260. };
  261. /**
  262. * Retrieves the computed value of the overflow-y CSS attribute.
  263. * @param {Element} element The element to get the overflow-y of.
  264. * @return {string} The computed string value of the overflow-y attribute.
  265. */
  266. goog.style.getComputedOverflowY = function(element) {
  267. return goog.style.getStyle_(element, 'overflowY');
  268. };
  269. /**
  270. * Retrieves the computed value of the z-index CSS attribute.
  271. * @param {Element} element The element to get the z-index of.
  272. * @return {string|number} The computed value of the z-index attribute.
  273. */
  274. goog.style.getComputedZIndex = function(element) {
  275. return goog.style.getStyle_(element, 'zIndex');
  276. };
  277. /**
  278. * Retrieves the computed value of the text-align CSS attribute.
  279. * @param {Element} element The element to get the text-align of.
  280. * @return {string} The computed string value of the text-align attribute.
  281. */
  282. goog.style.getComputedTextAlign = function(element) {
  283. return goog.style.getStyle_(element, 'textAlign');
  284. };
  285. /**
  286. * Retrieves the computed value of the cursor CSS attribute.
  287. * @param {Element} element The element to get the cursor of.
  288. * @return {string} The computed string value of the cursor attribute.
  289. */
  290. goog.style.getComputedCursor = function(element) {
  291. return goog.style.getStyle_(element, 'cursor');
  292. };
  293. /**
  294. * Retrieves the computed value of the CSS transform attribute.
  295. * @param {Element} element The element to get the transform of.
  296. * @return {string} The computed string representation of the transform matrix.
  297. */
  298. goog.style.getComputedTransform = function(element) {
  299. var property = goog.style.getVendorStyleName_(element, 'transform');
  300. return goog.style.getStyle_(element, property) ||
  301. goog.style.getStyle_(element, 'transform');
  302. };
  303. /**
  304. * Sets the top/left values of an element. If no unit is specified in the
  305. * argument then it will add px. The second argument is required if the first
  306. * argument is a string or number and is ignored if the first argument
  307. * is a coordinate.
  308. * @param {Element} el Element to move.
  309. * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
  310. * @param {string|number=} opt_arg2 Top position.
  311. */
  312. goog.style.setPosition = function(el, arg1, opt_arg2) {
  313. var x, y;
  314. if (arg1 instanceof goog.math.Coordinate) {
  315. x = arg1.x;
  316. y = arg1.y;
  317. } else {
  318. x = arg1;
  319. y = opt_arg2;
  320. }
  321. el.style.left = goog.style.getPixelStyleValue_(
  322. /** @type {number|string} */ (x), false);
  323. el.style.top = goog.style.getPixelStyleValue_(
  324. /** @type {number|string} */ (y), false);
  325. };
  326. /**
  327. * Gets the offsetLeft and offsetTop properties of an element and returns them
  328. * in a Coordinate object
  329. * @param {Element} element Element.
  330. * @return {!goog.math.Coordinate} The position.
  331. */
  332. goog.style.getPosition = function(element) {
  333. return new goog.math.Coordinate(
  334. /** @type {!HTMLElement} */ (element).offsetLeft,
  335. /** @type {!HTMLElement} */ (element).offsetTop);
  336. };
  337. /**
  338. * Returns the viewport element for a particular document
  339. * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
  340. * of.
  341. * @return {Element} document.documentElement or document.body.
  342. */
  343. goog.style.getClientViewportElement = function(opt_node) {
  344. var doc;
  345. if (opt_node) {
  346. doc = goog.dom.getOwnerDocument(opt_node);
  347. } else {
  348. doc = goog.dom.getDocument();
  349. }
  350. // In old IE versions the document.body represented the viewport
  351. if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
  352. !goog.dom.getDomHelper(doc).isCss1CompatMode()) {
  353. return doc.body;
  354. }
  355. return doc.documentElement;
  356. };
  357. /**
  358. * Calculates the viewport coordinates relative to the page/document
  359. * containing the node. The viewport may be the browser viewport for
  360. * non-iframe document, or the iframe container for iframe'd document.
  361. * @param {!Document} doc The document to use as the reference point.
  362. * @return {!goog.math.Coordinate} The page offset of the viewport.
  363. */
  364. goog.style.getViewportPageOffset = function(doc) {
  365. var body = doc.body;
  366. var documentElement = doc.documentElement;
  367. var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
  368. var scrollTop = body.scrollTop || documentElement.scrollTop;
  369. return new goog.math.Coordinate(scrollLeft, scrollTop);
  370. };
  371. /**
  372. * Gets the client rectangle of the DOM element.
  373. *
  374. * getBoundingClientRect is part of a new CSS object model draft (with a
  375. * long-time presence in IE), replacing the error-prone parent offset
  376. * computation and the now-deprecated Gecko getBoxObjectFor.
  377. *
  378. * This utility patches common browser bugs in getBoundingClientRect. It
  379. * will fail if getBoundingClientRect is unsupported.
  380. *
  381. * If the element is not in the DOM, the result is undefined, and an error may
  382. * be thrown depending on user agent.
  383. *
  384. * @param {!Element} el The element whose bounding rectangle is being queried.
  385. * @return {Object} A native bounding rectangle with numerical left, top,
  386. * right, and bottom. Reported by Firefox to be of object type ClientRect.
  387. * @private
  388. */
  389. goog.style.getBoundingClientRect_ = function(el) {
  390. var rect;
  391. try {
  392. rect = el.getBoundingClientRect();
  393. } catch (e) {
  394. // In IE < 9, calling getBoundingClientRect on an orphan element raises an
  395. // "Unspecified Error". All other browsers return zeros.
  396. return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0};
  397. }
  398. // Patch the result in IE only, so that this function can be inlined if
  399. // compiled for non-IE.
  400. if (goog.userAgent.IE && el.ownerDocument.body) {
  401. // In IE, most of the time, 2 extra pixels are added to the top and left
  402. // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and
  403. // IE6 standards mode, this border can be overridden by setting the
  404. // document element's border to zero -- thus, we cannot rely on the
  405. // offset always being 2 pixels.
  406. // In quirks mode, the offset can be determined by querying the body's
  407. // clientLeft/clientTop, but in standards mode, it is found by querying
  408. // the document element's clientLeft/clientTop. Since we already called
  409. // getBoundingClientRect we have already forced a reflow, so it is not
  410. // too expensive just to query them all.
  411. // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
  412. var doc = el.ownerDocument;
  413. rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
  414. rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
  415. }
  416. return rect;
  417. };
  418. /**
  419. * Returns the first parent that could affect the position of a given element.
  420. * @param {Element} element The element to get the offset parent for.
  421. * @return {Element} The first offset parent or null if one cannot be found.
  422. */
  423. goog.style.getOffsetParent = function(element) {
  424. // element.offsetParent does the right thing in IE7 and below. In other
  425. // browsers it only includes elements with position absolute, relative or
  426. // fixed, not elements with overflow set to auto or scroll.
  427. if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
  428. goog.asserts.assert(element && 'offsetParent' in element);
  429. return element.offsetParent;
  430. }
  431. var doc = goog.dom.getOwnerDocument(element);
  432. var positionStyle = goog.style.getStyle_(element, 'position');
  433. var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
  434. for (var parent = element.parentNode; parent && parent != doc;
  435. parent = parent.parentNode) {
  436. // Skip shadowDOM roots.
  437. if (parent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT && parent.host) {
  438. parent = parent.host;
  439. }
  440. positionStyle =
  441. goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
  442. skipStatic = skipStatic && positionStyle == 'static' &&
  443. parent != doc.documentElement && parent != doc.body;
  444. if (!skipStatic &&
  445. (parent.scrollWidth > parent.clientWidth ||
  446. parent.scrollHeight > parent.clientHeight ||
  447. positionStyle == 'fixed' || positionStyle == 'absolute' ||
  448. positionStyle == 'relative')) {
  449. return /** @type {!Element} */ (parent);
  450. }
  451. }
  452. return null;
  453. };
  454. /**
  455. * Calculates and returns the visible rectangle for a given element. Returns a
  456. * box describing the visible portion of the nearest scrollable offset ancestor.
  457. * Coordinates are given relative to the document.
  458. *
  459. * @param {Element} element Element to get the visible rect for.
  460. * @return {goog.math.Box} Bounding elementBox describing the visible rect or
  461. * null if scrollable ancestor isn't inside the visible viewport.
  462. */
  463. goog.style.getVisibleRectForElement = function(element) {
  464. var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
  465. var dom = goog.dom.getDomHelper(element);
  466. var body = dom.getDocument().body;
  467. var documentElement = dom.getDocument().documentElement;
  468. var scrollEl = dom.getDocumentScrollElement();
  469. // Determine the size of the visible rect by climbing the dom accounting for
  470. // all scrollable containers.
  471. for (var el = element; el = goog.style.getOffsetParent(el);) {
  472. // clientWidth is zero for inline block elements in IE.
  473. // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
  474. if ((!goog.userAgent.IE || el.clientWidth != 0) &&
  475. (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
  476. // body may have overflow set on it, yet we still get the entire
  477. // viewport. In some browsers, el.offsetParent may be
  478. // document.documentElement, so check for that too.
  479. (el != body && el != documentElement &&
  480. goog.style.getStyle_(el, 'overflow') != 'visible')) {
  481. var pos = goog.style.getPageOffset(el);
  482. var client = goog.style.getClientLeftTop(el);
  483. pos.x += client.x;
  484. pos.y += client.y;
  485. visibleRect.top = Math.max(visibleRect.top, pos.y);
  486. visibleRect.right = Math.min(visibleRect.right, pos.x + el.clientWidth);
  487. visibleRect.bottom =
  488. Math.min(visibleRect.bottom, pos.y + el.clientHeight);
  489. visibleRect.left = Math.max(visibleRect.left, pos.x);
  490. }
  491. }
  492. // Clip by window's viewport.
  493. var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
  494. visibleRect.left = Math.max(visibleRect.left, scrollX);
  495. visibleRect.top = Math.max(visibleRect.top, scrollY);
  496. var winSize = dom.getViewportSize();
  497. visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
  498. visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
  499. return visibleRect.top >= 0 && visibleRect.left >= 0 &&
  500. visibleRect.bottom > visibleRect.top &&
  501. visibleRect.right > visibleRect.left ?
  502. visibleRect :
  503. null;
  504. };
  505. /**
  506. * Calculate the scroll position of {@code container} with the minimum amount so
  507. * that the content and the borders of the given {@code element} become visible.
  508. * If the element is bigger than the container, its top left corner will be
  509. * aligned as close to the container's top left corner as possible.
  510. *
  511. * @param {Element} element The element to make visible.
  512. * @param {Element=} opt_container The container to scroll. If not set, then the
  513. * document scroll element will be used.
  514. * @param {boolean=} opt_center Whether to center the element in the container.
  515. * Defaults to false.
  516. * @return {!goog.math.Coordinate} The new scroll position of the container,
  517. * in form of goog.math.Coordinate(scrollLeft, scrollTop).
  518. */
  519. goog.style.getContainerOffsetToScrollInto = function(
  520. element, opt_container, opt_center) {
  521. var container = opt_container || goog.dom.getDocumentScrollElement();
  522. // Absolute position of the element's border's top left corner.
  523. var elementPos = goog.style.getPageOffset(element);
  524. // Absolute position of the container's border's top left corner.
  525. var containerPos = goog.style.getPageOffset(container);
  526. var containerBorder = goog.style.getBorderBox(container);
  527. if (container == goog.dom.getDocumentScrollElement()) {
  528. // The element position is calculated based on the page offset, and the
  529. // document scroll element holds the scroll position within the page. We can
  530. // use the scroll position to calculate the relative position from the
  531. // element.
  532. var relX = elementPos.x - container.scrollLeft;
  533. var relY = elementPos.y - container.scrollTop;
  534. if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) {
  535. // In older versions of IE getPageOffset(element) does not include the
  536. // container border so it has to be added to accommodate.
  537. relX += containerBorder.left;
  538. relY += containerBorder.top;
  539. }
  540. } else {
  541. // Relative pos. of the element's border box to the container's content box.
  542. var relX = elementPos.x - containerPos.x - containerBorder.left;
  543. var relY = elementPos.y - containerPos.y - containerBorder.top;
  544. }
  545. // How much the element can move in the container, i.e. the difference between
  546. // the element's bottom-right-most and top-left-most position where it's
  547. // fully visible.
  548. var elementSize = goog.style.getSizeWithDisplay_(element);
  549. var spaceX = container.clientWidth - elementSize.width;
  550. var spaceY = container.clientHeight - elementSize.height;
  551. var scrollLeft = container.scrollLeft;
  552. var scrollTop = container.scrollTop;
  553. if (opt_center) {
  554. // All browsers round non-integer scroll positions down.
  555. scrollLeft += relX - spaceX / 2;
  556. scrollTop += relY - spaceY / 2;
  557. } else {
  558. // This formula was designed to give the correct scroll values in the
  559. // following cases:
  560. // - element is higher than container (spaceY < 0) => scroll down by relY
  561. // - element is not higher that container (spaceY >= 0):
  562. // - it is above container (relY < 0) => scroll up by abs(relY)
  563. // - it is below container (relY > spaceY) => scroll down by relY - spaceY
  564. // - it is in the container => don't scroll
  565. scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
  566. scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
  567. }
  568. return new goog.math.Coordinate(scrollLeft, scrollTop);
  569. };
  570. /**
  571. * Changes the scroll position of {@code container} with the minimum amount so
  572. * that the content and the borders of the given {@code element} become visible.
  573. * If the element is bigger than the container, its top left corner will be
  574. * aligned as close to the container's top left corner as possible.
  575. *
  576. * @param {Element} element The element to make visible.
  577. * @param {Element=} opt_container The container to scroll. If not set, then the
  578. * document scroll element will be used.
  579. * @param {boolean=} opt_center Whether to center the element in the container.
  580. * Defaults to false.
  581. */
  582. goog.style.scrollIntoContainerView = function(
  583. element, opt_container, opt_center) {
  584. var container = opt_container || goog.dom.getDocumentScrollElement();
  585. var offset =
  586. goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
  587. container.scrollLeft = offset.x;
  588. container.scrollTop = offset.y;
  589. };
  590. /**
  591. * Returns clientLeft (width of the left border and, if the directionality is
  592. * right to left, the vertical scrollbar) and clientTop as a coordinate object.
  593. *
  594. * @param {Element} el Element to get clientLeft for.
  595. * @return {!goog.math.Coordinate} Client left and top.
  596. */
  597. goog.style.getClientLeftTop = function(el) {
  598. return new goog.math.Coordinate(el.clientLeft, el.clientTop);
  599. };
  600. /**
  601. * Returns a Coordinate object relative to the top-left of the HTML document.
  602. * Implemented as a single function to save having to do two recursive loops in
  603. * opera and safari just to get both coordinates. If you just want one value do
  604. * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
  605. * note if you call both those methods the tree will be analysed twice.
  606. *
  607. * @param {Element} el Element to get the page offset for.
  608. * @return {!goog.math.Coordinate} The page offset.
  609. */
  610. goog.style.getPageOffset = function(el) {
  611. var doc = goog.dom.getOwnerDocument(el);
  612. // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
  613. goog.asserts.assertObject(el, 'Parameter is required');
  614. // NOTE(arv): If element is hidden (display none or disconnected or any the
  615. // ancestors are hidden) we get (0,0) by default but we still do the
  616. // accumulation of scroll position.
  617. // TODO(arv): Should we check if the node is disconnected and in that case
  618. // return (0,0)?
  619. var pos = new goog.math.Coordinate(0, 0);
  620. var viewportElement = goog.style.getClientViewportElement(doc);
  621. if (el == viewportElement) {
  622. // viewport is always at 0,0 as that defined the coordinate system for this
  623. // function - this avoids special case checks in the code below
  624. return pos;
  625. }
  626. var box = goog.style.getBoundingClientRect_(el);
  627. // Must add the scroll coordinates in to get the absolute page offset
  628. // of element since getBoundingClientRect returns relative coordinates to
  629. // the viewport.
  630. var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
  631. pos.x = box.left + scrollCoord.x;
  632. pos.y = box.top + scrollCoord.y;
  633. return pos;
  634. };
  635. /**
  636. * Returns the left coordinate of an element relative to the HTML document
  637. * @param {Element} el Elements.
  638. * @return {number} The left coordinate.
  639. */
  640. goog.style.getPageOffsetLeft = function(el) {
  641. return goog.style.getPageOffset(el).x;
  642. };
  643. /**
  644. * Returns the top coordinate of an element relative to the HTML document
  645. * @param {Element} el Elements.
  646. * @return {number} The top coordinate.
  647. */
  648. goog.style.getPageOffsetTop = function(el) {
  649. return goog.style.getPageOffset(el).y;
  650. };
  651. /**
  652. * Returns a Coordinate object relative to the top-left of an HTML document
  653. * in an ancestor frame of this element. Used for measuring the position of
  654. * an element inside a frame relative to a containing frame.
  655. *
  656. * @param {Element} el Element to get the page offset for.
  657. * @param {Window} relativeWin The window to measure relative to. If relativeWin
  658. * is not in the ancestor frame chain of the element, we measure relative to
  659. * the top-most window.
  660. * @return {!goog.math.Coordinate} The page offset.
  661. */
  662. goog.style.getFramedPageOffset = function(el, relativeWin) {
  663. var position = new goog.math.Coordinate(0, 0);
  664. // Iterate up the ancestor frame chain, keeping track of the current window
  665. // and the current element in that window.
  666. var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
  667. // MS Edge throws when accessing "parent" if el's containing iframe has been
  668. // deleted.
  669. if (!goog.reflect.canAccessProperty(currentWin, 'parent')) {
  670. return position;
  671. }
  672. var currentEl = el;
  673. do {
  674. // if we're at the top window, we want to get the page offset.
  675. // if we're at an inner frame, we only want to get the window position
  676. // so that we can determine the actual page offset in the context of
  677. // the outer window.
  678. var offset = currentWin == relativeWin ?
  679. goog.style.getPageOffset(currentEl) :
  680. goog.style.getClientPositionForElement_(goog.asserts.assert(currentEl));
  681. position.x += offset.x;
  682. position.y += offset.y;
  683. } while (currentWin && currentWin != relativeWin &&
  684. currentWin != currentWin.parent &&
  685. (currentEl = currentWin.frameElement) &&
  686. (currentWin = currentWin.parent));
  687. return position;
  688. };
  689. /**
  690. * Translates the specified rect relative to origBase page, for newBase page.
  691. * If origBase and newBase are the same, this function does nothing.
  692. *
  693. * @param {goog.math.Rect} rect The source rectangle relative to origBase page,
  694. * and it will have the translated result.
  695. * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
  696. * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
  697. * coordinate. This must be a DOM for an ancestor frame of origBase
  698. * or the same as origBase.
  699. */
  700. goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
  701. if (origBase.getDocument() != newBase.getDocument()) {
  702. var body = origBase.getDocument().body;
  703. var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());
  704. // Adjust Body's margin.
  705. pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
  706. if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
  707. !origBase.isCss1CompatMode()) {
  708. pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
  709. }
  710. rect.left += pos.x;
  711. rect.top += pos.y;
  712. }
  713. };
  714. /**
  715. * Returns the position of an element relative to another element in the
  716. * document. A relative to B
  717. * @param {Element|Event|goog.events.Event} a Element or mouse event whose
  718. * position we're calculating.
  719. * @param {Element|Event|goog.events.Event} b Element or mouse event position
  720. * is relative to.
  721. * @return {!goog.math.Coordinate} The relative position.
  722. */
  723. goog.style.getRelativePosition = function(a, b) {
  724. var ap = goog.style.getClientPosition(a);
  725. var bp = goog.style.getClientPosition(b);
  726. return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
  727. };
  728. /**
  729. * Returns the position of the event or the element's border box relative to
  730. * the client viewport.
  731. * @param {!Element} el Element whose position to get.
  732. * @return {!goog.math.Coordinate} The position.
  733. * @private
  734. */
  735. goog.style.getClientPositionForElement_ = function(el) {
  736. var box = goog.style.getBoundingClientRect_(el);
  737. return new goog.math.Coordinate(box.left, box.top);
  738. };
  739. /**
  740. * Returns the position of the event or the element's border box relative to
  741. * the client viewport. If an event is passed, and if this event is a "touch"
  742. * event, then the position of the first changedTouches will be returned.
  743. * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
  744. * @return {!goog.math.Coordinate} The position.
  745. */
  746. goog.style.getClientPosition = function(el) {
  747. goog.asserts.assert(el);
  748. if (el.nodeType == goog.dom.NodeType.ELEMENT) {
  749. return goog.style.getClientPositionForElement_(
  750. /** @type {!Element} */ (el));
  751. } else {
  752. var targetEvent = el.changedTouches ? el.changedTouches[0] : el;
  753. return new goog.math.Coordinate(targetEvent.clientX, targetEvent.clientY);
  754. }
  755. };
  756. /**
  757. * Moves an element to the given coordinates relative to the client viewport.
  758. * @param {Element} el Absolutely positioned element to set page offset for.
  759. * It must be in the document.
  760. * @param {number|goog.math.Coordinate} x Left position of the element's margin
  761. * box or a coordinate object.
  762. * @param {number=} opt_y Top position of the element's margin box.
  763. */
  764. goog.style.setPageOffset = function(el, x, opt_y) {
  765. // Get current pageoffset
  766. var cur = goog.style.getPageOffset(el);
  767. if (x instanceof goog.math.Coordinate) {
  768. opt_y = x.y;
  769. x = x.x;
  770. }
  771. // NOTE(arv): We cannot allow strings for x and y. We could but that would
  772. // require us to manually transform between different units
  773. // Work out deltas
  774. var dx = goog.asserts.assertNumber(x) - cur.x;
  775. var dy = Number(opt_y) - cur.y;
  776. // Set position to current left/top + delta
  777. goog.style.setPosition(
  778. el, /** @type {!HTMLElement} */ (el).offsetLeft + dx,
  779. /** @type {!HTMLElement} */ (el).offsetTop + dy);
  780. };
  781. /**
  782. * Sets the width/height values of an element. If an argument is numeric,
  783. * or a goog.math.Size is passed, it is assumed to be pixels and will add
  784. * 'px' after converting it to an integer in string form. (This just sets the
  785. * CSS width and height properties so it might set content-box or border-box
  786. * size depending on the box model the browser is using.)
  787. *
  788. * @param {Element} element Element to set the size of.
  789. * @param {string|number|goog.math.Size} w Width of the element, or a
  790. * size object.
  791. * @param {string|number=} opt_h Height of the element. Required if w is not a
  792. * size object.
  793. */
  794. goog.style.setSize = function(element, w, opt_h) {
  795. var h;
  796. if (w instanceof goog.math.Size) {
  797. h = w.height;
  798. w = w.width;
  799. } else {
  800. if (opt_h == undefined) {
  801. throw Error('missing height argument');
  802. }
  803. h = opt_h;
  804. }
  805. goog.style.setWidth(element, /** @type {string|number} */ (w));
  806. goog.style.setHeight(element, h);
  807. };
  808. /**
  809. * Helper function to create a string to be set into a pixel-value style
  810. * property of an element. Can round to the nearest integer value.
  811. *
  812. * @param {string|number} value The style value to be used. If a number,
  813. * 'px' will be appended, otherwise the value will be applied directly.
  814. * @param {boolean} round Whether to round the nearest integer (if property
  815. * is a number).
  816. * @return {string} The string value for the property.
  817. * @private
  818. */
  819. goog.style.getPixelStyleValue_ = function(value, round) {
  820. if (typeof value == 'number') {
  821. value = (round ? Math.round(value) : value) + 'px';
  822. }
  823. return value;
  824. };
  825. /**
  826. * Set the height of an element. Sets the element's style property.
  827. * @param {Element} element Element to set the height of.
  828. * @param {string|number} height The height value to set. If a number, 'px'
  829. * will be appended, otherwise the value will be applied directly.
  830. */
  831. goog.style.setHeight = function(element, height) {
  832. element.style.height = goog.style.getPixelStyleValue_(height, true);
  833. };
  834. /**
  835. * Set the width of an element. Sets the element's style property.
  836. * @param {Element} element Element to set the width of.
  837. * @param {string|number} width The width value to set. If a number, 'px'
  838. * will be appended, otherwise the value will be applied directly.
  839. */
  840. goog.style.setWidth = function(element, width) {
  841. element.style.width = goog.style.getPixelStyleValue_(width, true);
  842. };
  843. /**
  844. * Gets the height and width of an element, even if its display is none.
  845. *
  846. * Specifically, this returns the height and width of the border box,
  847. * irrespective of the box model in effect.
  848. *
  849. * Note that this function does not take CSS transforms into account. Please see
  850. * {@code goog.style.getTransformedSize}.
  851. * @param {Element} element Element to get size of.
  852. * @return {!goog.math.Size} Object with width/height properties.
  853. */
  854. goog.style.getSize = function(element) {
  855. return goog.style.evaluateWithTemporaryDisplay_(
  856. goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
  857. };
  858. /**
  859. * Call {@code fn} on {@code element} such that {@code element}'s dimensions are
  860. * accurate when it's passed to {@code fn}.
  861. * @param {function(!Element): T} fn Function to call with {@code element} as
  862. * an argument after temporarily changing {@code element}'s display such
  863. * that its dimensions are accurate.
  864. * @param {!Element} element Element (which may have display none) to use as
  865. * argument to {@code fn}.
  866. * @return {T} Value returned by calling {@code fn} with {@code element}.
  867. * @template T
  868. * @private
  869. */
  870. goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
  871. if (goog.style.getStyle_(element, 'display') != 'none') {
  872. return fn(element);
  873. }
  874. var style = element.style;
  875. var originalDisplay = style.display;
  876. var originalVisibility = style.visibility;
  877. var originalPosition = style.position;
  878. style.visibility = 'hidden';
  879. style.position = 'absolute';
  880. style.display = 'inline';
  881. var retVal = fn(element);
  882. style.display = originalDisplay;
  883. style.position = originalPosition;
  884. style.visibility = originalVisibility;
  885. return retVal;
  886. };
  887. /**
  888. * Gets the height and width of an element when the display is not none.
  889. * @param {Element} element Element to get size of.
  890. * @return {!goog.math.Size} Object with width/height properties.
  891. * @private
  892. */
  893. goog.style.getSizeWithDisplay_ = function(element) {
  894. var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth;
  895. var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight;
  896. var webkitOffsetsZero =
  897. goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
  898. if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
  899. element.getBoundingClientRect) {
  900. // Fall back to calling getBoundingClientRect when offsetWidth or
  901. // offsetHeight are not defined, or when they are zero in WebKit browsers.
  902. // This makes sure that we return for the correct size for SVG elements, but
  903. // will still return 0 on Webkit prior to 534.8, see
  904. // http://trac.webkit.org/changeset/67252.
  905. var clientRect = goog.style.getBoundingClientRect_(element);
  906. return new goog.math.Size(
  907. clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
  908. }
  909. return new goog.math.Size(offsetWidth, offsetHeight);
  910. };
  911. /**
  912. * Gets the height and width of an element, post transform, even if its display
  913. * is none.
  914. *
  915. * This is like {@code goog.style.getSize}, except:
  916. * <ol>
  917. * <li>Takes webkitTransforms such as rotate and scale into account.
  918. * <li>Will return null if {@code element} doesn't respond to
  919. * {@code getBoundingClientRect}.
  920. * <li>Currently doesn't make sense on non-WebKit browsers which don't support
  921. * webkitTransforms.
  922. * </ol>
  923. * @param {!Element} element Element to get size of.
  924. * @return {goog.math.Size} Object with width/height properties.
  925. */
  926. goog.style.getTransformedSize = function(element) {
  927. if (!element.getBoundingClientRect) {
  928. return null;
  929. }
  930. var clientRect = goog.style.evaluateWithTemporaryDisplay_(
  931. goog.style.getBoundingClientRect_, element);
  932. return new goog.math.Size(
  933. clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
  934. };
  935. /**
  936. * Returns a bounding rectangle for a given element in page space.
  937. * @param {Element} element Element to get bounds of. Must not be display none.
  938. * @return {!goog.math.Rect} Bounding rectangle for the element.
  939. */
  940. goog.style.getBounds = function(element) {
  941. var o = goog.style.getPageOffset(element);
  942. var s = goog.style.getSize(element);
  943. return new goog.math.Rect(o.x, o.y, s.width, s.height);
  944. };
  945. /**
  946. * Converts a CSS selector in the form style-property to styleProperty.
  947. * @param {*} selector CSS Selector.
  948. * @return {string} Camel case selector.
  949. * @deprecated Use goog.string.toCamelCase instead.
  950. */
  951. goog.style.toCamelCase = function(selector) {
  952. return goog.string.toCamelCase(String(selector));
  953. };
  954. /**
  955. * Converts a CSS selector in the form styleProperty to style-property.
  956. * @param {string} selector Camel case selector.
  957. * @return {string} Selector cased.
  958. * @deprecated Use goog.string.toSelectorCase instead.
  959. */
  960. goog.style.toSelectorCase = function(selector) {
  961. return goog.string.toSelectorCase(selector);
  962. };
  963. /**
  964. * Gets the opacity of a node (x-browser). This gets the inline style opacity
  965. * of the node, and does not take into account the cascaded or the computed
  966. * style for this node.
  967. * @param {Element} el Element whose opacity has to be found.
  968. * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
  969. * if the opacity is not set.
  970. */
  971. goog.style.getOpacity = function(el) {
  972. goog.asserts.assert(el);
  973. var style = el.style;
  974. var result = '';
  975. if ('opacity' in style) {
  976. result = style.opacity;
  977. } else if ('MozOpacity' in style) {
  978. result = style.MozOpacity;
  979. } else if ('filter' in style) {
  980. var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
  981. if (match) {
  982. result = String(match[1] / 100);
  983. }
  984. }
  985. return result == '' ? result : Number(result);
  986. };
  987. /**
  988. * Sets the opacity of a node (x-browser).
  989. * @param {Element} el Elements whose opacity has to be set.
  990. * @param {number|string} alpha Opacity between 0 and 1 or an empty string
  991. * {@code ''} to clear the opacity.
  992. */
  993. goog.style.setOpacity = function(el, alpha) {
  994. goog.asserts.assert(el);
  995. var style = el.style;
  996. if ('opacity' in style) {
  997. style.opacity = alpha;
  998. } else if ('MozOpacity' in style) {
  999. style.MozOpacity = alpha;
  1000. } else if ('filter' in style) {
  1001. // TODO(arv): Overwriting the filter might have undesired side effects.
  1002. if (alpha === '') {
  1003. style.filter = '';
  1004. } else {
  1005. style.filter = 'alpha(opacity=' + (Number(alpha) * 100) + ')';
  1006. }
  1007. }
  1008. };
  1009. /**
  1010. * Sets the background of an element to a transparent image in a browser-
  1011. * independent manner.
  1012. *
  1013. * This function does not support repeating backgrounds or alternate background
  1014. * positions to match the behavior of Internet Explorer. It also does not
  1015. * support sizingMethods other than crop since they cannot be replicated in
  1016. * browsers other than Internet Explorer.
  1017. *
  1018. * @param {Element} el The element to set background on.
  1019. * @param {string} src The image source URL.
  1020. */
  1021. goog.style.setTransparentBackgroundImage = function(el, src) {
  1022. var style = el.style;
  1023. // It is safe to use the style.filter in IE only. In Safari 'filter' is in
  1024. // style object but access to style.filter causes it to throw an exception.
  1025. // Note: IE8 supports images with an alpha channel.
  1026. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
  1027. // See TODO in setOpacity.
  1028. style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
  1029. 'src="' + src + '", sizingMethod="crop")';
  1030. } else {
  1031. // Set style properties individually instead of using background shorthand
  1032. // to prevent overwriting a pre-existing background color.
  1033. style.backgroundImage = 'url(' + src + ')';
  1034. style.backgroundPosition = 'top left';
  1035. style.backgroundRepeat = 'no-repeat';
  1036. }
  1037. };
  1038. /**
  1039. * Clears the background image of an element in a browser independent manner.
  1040. * @param {Element} el The element to clear background image for.
  1041. */
  1042. goog.style.clearTransparentBackgroundImage = function(el) {
  1043. var style = el.style;
  1044. if ('filter' in style) {
  1045. // See TODO in setOpacity.
  1046. style.filter = '';
  1047. } else {
  1048. // Set style properties individually instead of using background shorthand
  1049. // to prevent overwriting a pre-existing background color.
  1050. style.backgroundImage = 'none';
  1051. }
  1052. };
  1053. /**
  1054. * Shows or hides an element from the page. Hiding the element is done by
  1055. * setting the display property to "none", removing the element from the
  1056. * rendering hierarchy so it takes up no space. To show the element, the default
  1057. * inherited display property is restored (defined either in stylesheets or by
  1058. * the browser's default style rules.)
  1059. *
  1060. * Caveat 1: if the inherited display property for the element is set to "none"
  1061. * by the stylesheets, that is the property that will be restored by a call to
  1062. * showElement(), effectively toggling the display between "none" and "none".
  1063. *
  1064. * Caveat 2: if the element display style is set inline (by setting either
  1065. * element.style.display or a style attribute in the HTML), a call to
  1066. * showElement will clear that setting and defer to the inherited style in the
  1067. * stylesheet.
  1068. * @param {Element} el Element to show or hide.
  1069. * @param {*} display True to render the element in its default style,
  1070. * false to disable rendering the element.
  1071. * @deprecated Use goog.style.setElementShown instead.
  1072. */
  1073. goog.style.showElement = function(el, display) {
  1074. goog.style.setElementShown(el, display);
  1075. };
  1076. /**
  1077. * Shows or hides an element from the page. Hiding the element is done by
  1078. * setting the display property to "none", removing the element from the
  1079. * rendering hierarchy so it takes up no space. To show the element, the default
  1080. * inherited display property is restored (defined either in stylesheets or by
  1081. * the browser's default style rules).
  1082. *
  1083. * Caveat 1: if the inherited display property for the element is set to "none"
  1084. * by the stylesheets, that is the property that will be restored by a call to
  1085. * setElementShown(), effectively toggling the display between "none" and
  1086. * "none".
  1087. *
  1088. * Caveat 2: if the element display style is set inline (by setting either
  1089. * element.style.display or a style attribute in the HTML), a call to
  1090. * setElementShown will clear that setting and defer to the inherited style in
  1091. * the stylesheet.
  1092. * @param {Element} el Element to show or hide.
  1093. * @param {*} isShown True to render the element in its default style,
  1094. * false to disable rendering the element.
  1095. */
  1096. goog.style.setElementShown = function(el, isShown) {
  1097. el.style.display = isShown ? '' : 'none';
  1098. };
  1099. /**
  1100. * Test whether the given element has been shown or hidden via a call to
  1101. * {@link #setElementShown}.
  1102. *
  1103. * Note this is strictly a companion method for a call
  1104. * to {@link #setElementShown} and the same caveats apply; in particular, this
  1105. * method does not guarantee that the return value will be consistent with
  1106. * whether or not the element is actually visible.
  1107. *
  1108. * @param {Element} el The element to test.
  1109. * @return {boolean} Whether the element has been shown.
  1110. * @see #setElementShown
  1111. */
  1112. goog.style.isElementShown = function(el) {
  1113. return el.style.display != 'none';
  1114. };
  1115. /**
  1116. * Installs the styles string into the window that contains opt_node. If
  1117. * opt_node is null, the main window is used.
  1118. * @param {string} stylesString The style string to install.
  1119. * @param {Node=} opt_node Node whose parent document should have the
  1120. * styles installed.
  1121. * @return {!Element|!StyleSheet} The style element created.
  1122. * @deprecated Use {@link #installSafeStyleSheet} instead.
  1123. */
  1124. goog.style.installStyles = function(stylesString, opt_node) {
  1125. return goog.style.installSafeStyleSheet(
  1126. goog.html.legacyconversions.safeStyleSheetFromString(stylesString),
  1127. opt_node);
  1128. };
  1129. /**
  1130. * Installs the style sheet into the window that contains opt_node. If
  1131. * opt_node is null, the main window is used.
  1132. * @param {!goog.html.SafeStyleSheet} safeStyleSheet The style sheet to install.
  1133. * @param {?Node=} opt_node Node whose parent document should have the
  1134. * styles installed.
  1135. * @return {!Element|!StyleSheet} The style element created.
  1136. */
  1137. goog.style.installSafeStyleSheet = function(safeStyleSheet, opt_node) {
  1138. var dh = goog.dom.getDomHelper(opt_node);
  1139. var styleSheet = null;
  1140. // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
  1141. // undefined as of IE 11.
  1142. var doc = dh.getDocument();
  1143. if (goog.userAgent.IE && doc.createStyleSheet) {
  1144. styleSheet = doc.createStyleSheet();
  1145. goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet);
  1146. } else {
  1147. var head = dh.getElementsByTagNameAndClass(goog.dom.TagName.HEAD)[0];
  1148. // In opera documents are not guaranteed to have a head element, thus we
  1149. // have to make sure one exists before using it.
  1150. if (!head) {
  1151. var body = dh.getElementsByTagNameAndClass(goog.dom.TagName.BODY)[0];
  1152. head = dh.createDom(goog.dom.TagName.HEAD);
  1153. body.parentNode.insertBefore(head, body);
  1154. }
  1155. styleSheet = dh.createDom(goog.dom.TagName.STYLE);
  1156. // NOTE(user): Setting styles after the style element has been appended
  1157. // to the head results in a nasty Webkit bug in certain scenarios. Please
  1158. // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
  1159. // details.
  1160. goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet);
  1161. dh.appendChild(head, styleSheet);
  1162. }
  1163. return styleSheet;
  1164. };
  1165. /**
  1166. * Removes the styles added by {@link #installStyles}.
  1167. * @param {Element|StyleSheet} styleSheet The value returned by
  1168. * {@link #installStyles}.
  1169. */
  1170. goog.style.uninstallStyles = function(styleSheet) {
  1171. var node = styleSheet.ownerNode || styleSheet.owningElement ||
  1172. /** @type {Element} */ (styleSheet);
  1173. goog.dom.removeNode(node);
  1174. };
  1175. /**
  1176. * Sets the content of a style element. The style element can be any valid
  1177. * style element. This element will have its content completely replaced by
  1178. * the stylesString.
  1179. * @param {Element|StyleSheet} element A stylesheet element as returned by
  1180. * installStyles.
  1181. * @param {string} stylesString The new content of the stylesheet.
  1182. * @deprecated Use {@link #setSafeStyleSheet} instead.
  1183. */
  1184. goog.style.setStyles = function(element, stylesString) {
  1185. goog.style.setSafeStyleSheet(/** @type {!Element|!StyleSheet} */ (element),
  1186. goog.html.legacyconversions.safeStyleSheetFromString(stylesString));
  1187. };
  1188. /**
  1189. * Sets the content of a style element. The style element can be any valid
  1190. * style element. This element will have its content completely replaced by
  1191. * the safeStyleSheet.
  1192. * @param {!Element|!StyleSheet} element A stylesheet element as returned by
  1193. * installStyles.
  1194. * @param {!goog.html.SafeStyleSheet} safeStyleSheet The new content of the
  1195. * stylesheet.
  1196. */
  1197. goog.style.setSafeStyleSheet = function(element, safeStyleSheet) {
  1198. var stylesString = goog.html.SafeStyleSheet.unwrap(safeStyleSheet);
  1199. if (goog.userAgent.IE && goog.isDef(element.cssText)) {
  1200. // Adding the selectors individually caused the browser to hang if the
  1201. // selector was invalid or there were CSS comments. Setting the cssText of
  1202. // the style node works fine and ignores CSS that IE doesn't understand.
  1203. // However IE >= 11 doesn't support cssText any more, so we make sure that
  1204. // cssText is a defined property and otherwise fall back to innerHTML.
  1205. element.cssText = stylesString;
  1206. } else {
  1207. // Setting textContent doesn't work in Safari, see b/29340337.
  1208. element.innerHTML = stylesString;
  1209. }
  1210. };
  1211. /**
  1212. * Sets 'white-space: pre-wrap' for a node (x-browser).
  1213. *
  1214. * There are as many ways of specifying pre-wrap as there are browsers.
  1215. *
  1216. * CSS3/IE8: white-space: pre-wrap;
  1217. * Mozilla: white-space: -moz-pre-wrap;
  1218. * Opera: white-space: -o-pre-wrap;
  1219. * IE6/7: white-space: pre; word-wrap: break-word;
  1220. *
  1221. * @param {Element} el Element to enable pre-wrap for.
  1222. */
  1223. goog.style.setPreWrap = function(el) {
  1224. var style = el.style;
  1225. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
  1226. style.whiteSpace = 'pre';
  1227. style.wordWrap = 'break-word';
  1228. } else if (goog.userAgent.GECKO) {
  1229. style.whiteSpace = '-moz-pre-wrap';
  1230. } else {
  1231. style.whiteSpace = 'pre-wrap';
  1232. }
  1233. };
  1234. /**
  1235. * Sets 'display: inline-block' for an element (cross-browser).
  1236. * @param {Element} el Element to which the inline-block display style is to be
  1237. * applied.
  1238. * @see ../demos/inline_block_quirks.html
  1239. * @see ../demos/inline_block_standards.html
  1240. */
  1241. goog.style.setInlineBlock = function(el) {
  1242. var style = el.style;
  1243. // Without position:relative, weirdness ensues. Just accept it and move on.
  1244. style.position = 'relative';
  1245. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
  1246. // IE8 supports inline-block so fall through to the else
  1247. // Zoom:1 forces hasLayout, display:inline gives inline behavior.
  1248. style.zoom = '1';
  1249. style.display = 'inline';
  1250. } else {
  1251. // Opera, Webkit, and Safari seem to do OK with the standard inline-block
  1252. // style.
  1253. style.display = 'inline-block';
  1254. }
  1255. };
  1256. /**
  1257. * Returns true if the element is using right to left (rtl) direction.
  1258. * @param {Element} el The element to test.
  1259. * @return {boolean} True for right to left, false for left to right.
  1260. */
  1261. goog.style.isRightToLeft = function(el) {
  1262. return 'rtl' == goog.style.getStyle_(el, 'direction');
  1263. };
  1264. /**
  1265. * The CSS style property corresponding to an element being
  1266. * unselectable on the current browser platform (null if none).
  1267. * Opera and IE instead use a DOM attribute 'unselectable'. MS Edge uses
  1268. * the Webkit prefix.
  1269. * @type {?string}
  1270. * @private
  1271. */
  1272. goog.style.unselectableStyle_ = goog.userAgent.GECKO ?
  1273. 'MozUserSelect' :
  1274. goog.userAgent.WEBKIT || goog.userAgent.EDGE ? 'WebkitUserSelect' : null;
  1275. /**
  1276. * Returns true if the element is set to be unselectable, false otherwise.
  1277. * Note that on some platforms (e.g. Mozilla), even if an element isn't set
  1278. * to be unselectable, it will behave as such if any of its ancestors is
  1279. * unselectable.
  1280. * @param {Element} el Element to check.
  1281. * @return {boolean} Whether the element is set to be unselectable.
  1282. */
  1283. goog.style.isUnselectable = function(el) {
  1284. if (goog.style.unselectableStyle_) {
  1285. return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none';
  1286. } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
  1287. return el.getAttribute('unselectable') == 'on';
  1288. }
  1289. return false;
  1290. };
  1291. /**
  1292. * Makes the element and its descendants selectable or unselectable. Note
  1293. * that on some platforms (e.g. Mozilla), even if an element isn't set to
  1294. * be unselectable, it will behave as such if any of its ancestors is
  1295. * unselectable.
  1296. * @param {Element} el The element to alter.
  1297. * @param {boolean} unselectable Whether the element and its descendants
  1298. * should be made unselectable.
  1299. * @param {boolean=} opt_noRecurse Whether to only alter the element's own
  1300. * selectable state, and leave its descendants alone; defaults to false.
  1301. */
  1302. goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) {
  1303. // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure?
  1304. var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null;
  1305. var name = goog.style.unselectableStyle_;
  1306. if (name) {
  1307. // Add/remove the appropriate CSS style to/from the element and its
  1308. // descendants.
  1309. var value = unselectable ? 'none' : '';
  1310. // MathML elements do not have a style property. Verify before setting.
  1311. if (el.style) {
  1312. el.style[name] = value;
  1313. }
  1314. if (descendants) {
  1315. for (var i = 0, descendant; descendant = descendants[i]; i++) {
  1316. if (descendant.style) {
  1317. descendant.style[name] = value;
  1318. }
  1319. }
  1320. }
  1321. } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
  1322. // Toggle the 'unselectable' attribute on the element and its descendants.
  1323. var value = unselectable ? 'on' : '';
  1324. el.setAttribute('unselectable', value);
  1325. if (descendants) {
  1326. for (var i = 0, descendant; descendant = descendants[i]; i++) {
  1327. descendant.setAttribute('unselectable', value);
  1328. }
  1329. }
  1330. }
  1331. };
  1332. /**
  1333. * Gets the border box size for an element.
  1334. * @param {Element} element The element to get the size for.
  1335. * @return {!goog.math.Size} The border box size.
  1336. */
  1337. goog.style.getBorderBoxSize = function(element) {
  1338. return new goog.math.Size(
  1339. /** @type {!HTMLElement} */ (element).offsetWidth,
  1340. /** @type {!HTMLElement} */ (element).offsetHeight);
  1341. };
  1342. /**
  1343. * Sets the border box size of an element. This is potentially expensive in IE
  1344. * if the document is CSS1Compat mode
  1345. * @param {Element} element The element to set the size on.
  1346. * @param {goog.math.Size} size The new size.
  1347. */
  1348. goog.style.setBorderBoxSize = function(element, size) {
  1349. var doc = goog.dom.getOwnerDocument(element);
  1350. var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
  1351. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10') &&
  1352. (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
  1353. var style = element.style;
  1354. if (isCss1CompatMode) {
  1355. var paddingBox = goog.style.getPaddingBox(element);
  1356. var borderBox = goog.style.getBorderBox(element);
  1357. style.pixelWidth = size.width - borderBox.left - paddingBox.left -
  1358. paddingBox.right - borderBox.right;
  1359. style.pixelHeight = size.height - borderBox.top - paddingBox.top -
  1360. paddingBox.bottom - borderBox.bottom;
  1361. } else {
  1362. style.pixelWidth = size.width;
  1363. style.pixelHeight = size.height;
  1364. }
  1365. } else {
  1366. goog.style.setBoxSizingSize_(element, size, 'border-box');
  1367. }
  1368. };
  1369. /**
  1370. * Gets the content box size for an element. This is potentially expensive in
  1371. * all browsers.
  1372. * @param {Element} element The element to get the size for.
  1373. * @return {!goog.math.Size} The content box size.
  1374. */
  1375. goog.style.getContentBoxSize = function(element) {
  1376. var doc = goog.dom.getOwnerDocument(element);
  1377. var ieCurrentStyle = goog.userAgent.IE && element.currentStyle;
  1378. if (ieCurrentStyle && goog.dom.getDomHelper(doc).isCss1CompatMode() &&
  1379. ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' &&
  1380. !ieCurrentStyle.boxSizing) {
  1381. // If IE in CSS1Compat mode than just use the width and height.
  1382. // If we have a boxSizing then fall back on measuring the borders etc.
  1383. var width = goog.style.getIePixelValue_(
  1384. element, /** @type {string} */ (ieCurrentStyle.width), 'width',
  1385. 'pixelWidth');
  1386. var height = goog.style.getIePixelValue_(
  1387. element, /** @type {string} */ (ieCurrentStyle.height), 'height',
  1388. 'pixelHeight');
  1389. return new goog.math.Size(width, height);
  1390. } else {
  1391. var borderBoxSize = goog.style.getBorderBoxSize(element);
  1392. var paddingBox = goog.style.getPaddingBox(element);
  1393. var borderBox = goog.style.getBorderBox(element);
  1394. return new goog.math.Size(
  1395. borderBoxSize.width - borderBox.left - paddingBox.left -
  1396. paddingBox.right - borderBox.right,
  1397. borderBoxSize.height - borderBox.top - paddingBox.top -
  1398. paddingBox.bottom - borderBox.bottom);
  1399. }
  1400. };
  1401. /**
  1402. * Sets the content box size of an element. This is potentially expensive in IE
  1403. * if the document is BackCompat mode.
  1404. * @param {Element} element The element to set the size on.
  1405. * @param {goog.math.Size} size The new size.
  1406. */
  1407. goog.style.setContentBoxSize = function(element, size) {
  1408. var doc = goog.dom.getOwnerDocument(element);
  1409. var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
  1410. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10') &&
  1411. (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
  1412. var style = element.style;
  1413. if (isCss1CompatMode) {
  1414. style.pixelWidth = size.width;
  1415. style.pixelHeight = size.height;
  1416. } else {
  1417. var paddingBox = goog.style.getPaddingBox(element);
  1418. var borderBox = goog.style.getBorderBox(element);
  1419. style.pixelWidth = size.width + borderBox.left + paddingBox.left +
  1420. paddingBox.right + borderBox.right;
  1421. style.pixelHeight = size.height + borderBox.top + paddingBox.top +
  1422. paddingBox.bottom + borderBox.bottom;
  1423. }
  1424. } else {
  1425. goog.style.setBoxSizingSize_(element, size, 'content-box');
  1426. }
  1427. };
  1428. /**
  1429. * Helper function that sets the box sizing as well as the width and height
  1430. * @param {Element} element The element to set the size on.
  1431. * @param {goog.math.Size} size The new size to set.
  1432. * @param {string} boxSizing The box-sizing value.
  1433. * @private
  1434. */
  1435. goog.style.setBoxSizingSize_ = function(element, size, boxSizing) {
  1436. var style = element.style;
  1437. if (goog.userAgent.GECKO) {
  1438. style.MozBoxSizing = boxSizing;
  1439. } else if (goog.userAgent.WEBKIT) {
  1440. style.WebkitBoxSizing = boxSizing;
  1441. } else {
  1442. // Includes IE8 and Opera 9.50+
  1443. style.boxSizing = boxSizing;
  1444. }
  1445. // Setting this to a negative value will throw an exception on IE
  1446. // (and doesn't do anything different than setting it to 0).
  1447. style.width = Math.max(size.width, 0) + 'px';
  1448. style.height = Math.max(size.height, 0) + 'px';
  1449. };
  1450. /**
  1451. * IE specific function that converts a non pixel unit to pixels.
  1452. * @param {Element} element The element to convert the value for.
  1453. * @param {string} value The current value as a string. The value must not be
  1454. * ''.
  1455. * @param {string} name The CSS property name to use for the converstion. This
  1456. * should be 'left', 'top', 'width' or 'height'.
  1457. * @param {string} pixelName The CSS pixel property name to use to get the
  1458. * value in pixels.
  1459. * @return {number} The value in pixels.
  1460. * @private
  1461. */
  1462. goog.style.getIePixelValue_ = function(element, value, name, pixelName) {
  1463. // Try if we already have a pixel value. IE does not do half pixels so we
  1464. // only check if it matches a number followed by 'px'.
  1465. if (/^\d+px?$/.test(value)) {
  1466. return parseInt(value, 10);
  1467. } else {
  1468. var oldStyleValue = element.style[name];
  1469. var oldRuntimeValue = element.runtimeStyle[name];
  1470. // set runtime style to prevent changes
  1471. element.runtimeStyle[name] = element.currentStyle[name];
  1472. element.style[name] = value;
  1473. var pixelValue = element.style[pixelName];
  1474. // restore
  1475. element.style[name] = oldStyleValue;
  1476. element.runtimeStyle[name] = oldRuntimeValue;
  1477. return +pixelValue;
  1478. }
  1479. };
  1480. /**
  1481. * Helper function for getting the pixel padding or margin for IE.
  1482. * @param {Element} element The element to get the padding for.
  1483. * @param {string} propName The property name.
  1484. * @return {number} The pixel padding.
  1485. * @private
  1486. */
  1487. goog.style.getIePixelDistance_ = function(element, propName) {
  1488. var value = goog.style.getCascadedStyle(element, propName);
  1489. return value ?
  1490. goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') :
  1491. 0;
  1492. };
  1493. /**
  1494. * Gets the computed paddings or margins (on all sides) in pixels.
  1495. * @param {Element} element The element to get the padding for.
  1496. * @param {string} stylePrefix Pass 'padding' to retrieve the padding box,
  1497. * or 'margin' to retrieve the margin box.
  1498. * @return {!goog.math.Box} The computed paddings or margins.
  1499. * @private
  1500. */
  1501. goog.style.getBox_ = function(element, stylePrefix) {
  1502. if (goog.userAgent.IE) {
  1503. var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left');
  1504. var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right');
  1505. var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top');
  1506. var bottom =
  1507. goog.style.getIePixelDistance_(element, stylePrefix + 'Bottom');
  1508. return new goog.math.Box(top, right, bottom, left);
  1509. } else {
  1510. // On non-IE browsers, getComputedStyle is always non-null.
  1511. var left = goog.style.getComputedStyle(element, stylePrefix + 'Left');
  1512. var right = goog.style.getComputedStyle(element, stylePrefix + 'Right');
  1513. var top = goog.style.getComputedStyle(element, stylePrefix + 'Top');
  1514. var bottom = goog.style.getComputedStyle(element, stylePrefix + 'Bottom');
  1515. // NOTE(arv): Gecko can return floating point numbers for the computed
  1516. // style values.
  1517. return new goog.math.Box(
  1518. parseFloat(top), parseFloat(right), parseFloat(bottom),
  1519. parseFloat(left));
  1520. }
  1521. };
  1522. /**
  1523. * Gets the computed paddings (on all sides) in pixels.
  1524. * @param {Element} element The element to get the padding for.
  1525. * @return {!goog.math.Box} The computed paddings.
  1526. */
  1527. goog.style.getPaddingBox = function(element) {
  1528. return goog.style.getBox_(element, 'padding');
  1529. };
  1530. /**
  1531. * Gets the computed margins (on all sides) in pixels.
  1532. * @param {Element} element The element to get the margins for.
  1533. * @return {!goog.math.Box} The computed margins.
  1534. */
  1535. goog.style.getMarginBox = function(element) {
  1536. return goog.style.getBox_(element, 'margin');
  1537. };
  1538. /**
  1539. * A map used to map the border width keywords to a pixel width.
  1540. * @type {!Object}
  1541. * @private
  1542. */
  1543. goog.style.ieBorderWidthKeywords_ = {
  1544. 'thin': 2,
  1545. 'medium': 4,
  1546. 'thick': 6
  1547. };
  1548. /**
  1549. * Helper function for IE to get the pixel border.
  1550. * @param {Element} element The element to get the pixel border for.
  1551. * @param {string} prop The part of the property name.
  1552. * @return {number} The value in pixels.
  1553. * @private
  1554. */
  1555. goog.style.getIePixelBorder_ = function(element, prop) {
  1556. if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') {
  1557. return 0;
  1558. }
  1559. var width = goog.style.getCascadedStyle(element, prop + 'Width');
  1560. if (width in goog.style.ieBorderWidthKeywords_) {
  1561. return goog.style.ieBorderWidthKeywords_[width];
  1562. }
  1563. return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
  1564. };
  1565. /**
  1566. * Gets the computed border widths (on all sides) in pixels
  1567. * @param {Element} element The element to get the border widths for.
  1568. * @return {!goog.math.Box} The computed border widths.
  1569. */
  1570. goog.style.getBorderBox = function(element) {
  1571. if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
  1572. var left = goog.style.getIePixelBorder_(element, 'borderLeft');
  1573. var right = goog.style.getIePixelBorder_(element, 'borderRight');
  1574. var top = goog.style.getIePixelBorder_(element, 'borderTop');
  1575. var bottom = goog.style.getIePixelBorder_(element, 'borderBottom');
  1576. return new goog.math.Box(top, right, bottom, left);
  1577. } else {
  1578. // On non-IE browsers, getComputedStyle is always non-null.
  1579. var left = goog.style.getComputedStyle(element, 'borderLeftWidth');
  1580. var right = goog.style.getComputedStyle(element, 'borderRightWidth');
  1581. var top = goog.style.getComputedStyle(element, 'borderTopWidth');
  1582. var bottom = goog.style.getComputedStyle(element, 'borderBottomWidth');
  1583. return new goog.math.Box(
  1584. parseFloat(top), parseFloat(right), parseFloat(bottom),
  1585. parseFloat(left));
  1586. }
  1587. };
  1588. /**
  1589. * Returns the font face applied to a given node. Opera and IE should return
  1590. * the font actually displayed. Firefox returns the author's most-preferred
  1591. * font (whether the browser is capable of displaying it or not.)
  1592. * @param {Element} el The element whose font family is returned.
  1593. * @return {string} The font family applied to el.
  1594. */
  1595. goog.style.getFontFamily = function(el) {
  1596. var doc = goog.dom.getOwnerDocument(el);
  1597. var font = '';
  1598. // The moveToElementText method from the TextRange only works if the element
  1599. // is attached to the owner document.
  1600. if (doc.body.createTextRange && goog.dom.contains(doc, el)) {
  1601. var range = doc.body.createTextRange();
  1602. range.moveToElementText(el);
  1603. try {
  1604. font = range.queryCommandValue('FontName');
  1605. } catch (e) {
  1606. // This is a workaround for a awkward exception.
  1607. // On some IE, there is an exception coming from it.
  1608. // The error description from this exception is:
  1609. // This window has already been registered as a drop target
  1610. // This is bogus description, likely due to a bug in ie.
  1611. font = '';
  1612. }
  1613. }
  1614. if (!font) {
  1615. // Note if for some reason IE can't derive FontName with a TextRange, we
  1616. // fallback to using currentStyle
  1617. font = goog.style.getStyle_(el, 'fontFamily');
  1618. }
  1619. // Firefox returns the applied font-family string (author's list of
  1620. // preferred fonts.) We want to return the most-preferred font, in lieu of
  1621. // the *actually* applied font.
  1622. var fontsArray = font.split(',');
  1623. if (fontsArray.length > 1) font = fontsArray[0];
  1624. // Sanitize for x-browser consistency:
  1625. // Strip quotes because browsers aren't consistent with how they're
  1626. // applied; Opera always encloses, Firefox sometimes, and IE never.
  1627. return goog.string.stripQuotes(font, '"\'');
  1628. };
  1629. /**
  1630. * Regular expression used for getLengthUnits.
  1631. * @type {RegExp}
  1632. * @private
  1633. */
  1634. goog.style.lengthUnitRegex_ = /[^\d]+$/;
  1635. /**
  1636. * Returns the units used for a CSS length measurement.
  1637. * @param {string} value A CSS length quantity.
  1638. * @return {?string} The units of measurement.
  1639. */
  1640. goog.style.getLengthUnits = function(value) {
  1641. var units = value.match(goog.style.lengthUnitRegex_);
  1642. return units && units[0] || null;
  1643. };
  1644. /**
  1645. * Map of absolute CSS length units
  1646. * @type {!Object}
  1647. * @private
  1648. */
  1649. goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
  1650. 'cm': 1,
  1651. 'in': 1,
  1652. 'mm': 1,
  1653. 'pc': 1,
  1654. 'pt': 1
  1655. };
  1656. /**
  1657. * Map of relative CSS length units that can be accurately converted to px
  1658. * font-size values using getIePixelValue_. Only units that are defined in
  1659. * relation to a font size are convertible (%, small, etc. are not).
  1660. * @type {!Object}
  1661. * @private
  1662. */
  1663. goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
  1664. 'em': 1,
  1665. 'ex': 1
  1666. };
  1667. /**
  1668. * Returns the font size, in pixels, of text in an element.
  1669. * @param {Element} el The element whose font size is returned.
  1670. * @return {number} The font size (in pixels).
  1671. */
  1672. goog.style.getFontSize = function(el) {
  1673. var fontSize = goog.style.getStyle_(el, 'fontSize');
  1674. var sizeUnits = goog.style.getLengthUnits(fontSize);
  1675. if (fontSize && 'px' == sizeUnits) {
  1676. // NOTE(user): This could be parseFloat instead, but IE doesn't return
  1677. // decimal fractions in getStyle_ and Firefox reports the fractions, but
  1678. // ignores them when rendering. Interestingly enough, when we force the
  1679. // issue and size something to e.g., 50% of 25px, the browsers round in
  1680. // opposite directions with Firefox reporting 12px and IE 13px. I punt.
  1681. return parseInt(fontSize, 10);
  1682. }
  1683. // In IE, we can convert absolute length units to a px value using
  1684. // goog.style.getIePixelValue_. Units defined in relation to a font size
  1685. // (em, ex) are applied relative to the element's parentNode and can also
  1686. // be converted.
  1687. if (goog.userAgent.IE) {
  1688. if (String(sizeUnits) in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) {
  1689. return goog.style.getIePixelValue_(el, fontSize, 'left', 'pixelLeft');
  1690. } else if (
  1691. el.parentNode && el.parentNode.nodeType == goog.dom.NodeType.ELEMENT &&
  1692. String(sizeUnits) in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) {
  1693. // Check the parent size - if it is the same it means the relative size
  1694. // value is inherited and we therefore don't want to count it twice. If
  1695. // it is different, this element either has explicit style or has a CSS
  1696. // rule applying to it.
  1697. var parentElement = /** @type {!Element} */ (el.parentNode);
  1698. var parentSize = goog.style.getStyle_(parentElement, 'fontSize');
  1699. return goog.style.getIePixelValue_(
  1700. parentElement, fontSize == parentSize ? '1em' : fontSize, 'left',
  1701. 'pixelLeft');
  1702. }
  1703. }
  1704. // Sometimes we can't cleanly find the font size (some units relative to a
  1705. // node's parent's font size are difficult: %, smaller et al), so we create
  1706. // an invisible, absolutely-positioned span sized to be the height of an 'M'
  1707. // rendered in its parent's (i.e., our target element's) font size. This is
  1708. // the definition of CSS's font size attribute.
  1709. var sizeElement = goog.dom.createDom(goog.dom.TagName.SPAN, {
  1710. 'style': 'visibility:hidden;position:absolute;' +
  1711. 'line-height:0;padding:0;margin:0;border:0;height:1em;'
  1712. });
  1713. goog.dom.appendChild(el, sizeElement);
  1714. fontSize = sizeElement.offsetHeight;
  1715. goog.dom.removeNode(sizeElement);
  1716. return fontSize;
  1717. };
  1718. /**
  1719. * Parses a style attribute value. Converts CSS property names to camel case.
  1720. * @param {string} value The style attribute value.
  1721. * @return {!Object} Map of CSS properties to string values.
  1722. */
  1723. goog.style.parseStyleAttribute = function(value) {
  1724. var result = {};
  1725. goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
  1726. var keyValue = pair.match(/\s*([\w-]+)\s*\:(.+)/);
  1727. if (keyValue) {
  1728. var styleName = keyValue[1];
  1729. var styleValue = goog.string.trim(keyValue[2]);
  1730. result[goog.string.toCamelCase(styleName.toLowerCase())] = styleValue;
  1731. }
  1732. });
  1733. return result;
  1734. };
  1735. /**
  1736. * Reverse of parseStyleAttribute; that is, takes a style object and returns the
  1737. * corresponding attribute value. Converts camel case property names to proper
  1738. * CSS selector names.
  1739. * @param {Object} obj Map of CSS properties to values.
  1740. * @return {string} The style attribute value.
  1741. */
  1742. goog.style.toStyleAttribute = function(obj) {
  1743. var buffer = [];
  1744. goog.object.forEach(obj, function(value, key) {
  1745. buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
  1746. });
  1747. return buffer.join('');
  1748. };
  1749. /**
  1750. * Sets CSS float property on an element.
  1751. * @param {Element} el The element to set float property on.
  1752. * @param {string} value The value of float CSS property to set on this element.
  1753. */
  1754. goog.style.setFloat = function(el, value) {
  1755. el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value;
  1756. };
  1757. /**
  1758. * Gets value of explicitly-set float CSS property on an element.
  1759. * @param {Element} el The element to get float property of.
  1760. * @return {string} The value of explicitly-set float CSS property on this
  1761. * element.
  1762. */
  1763. goog.style.getFloat = function(el) {
  1764. return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || '';
  1765. };
  1766. /**
  1767. * Returns the scroll bar width (represents the width of both horizontal
  1768. * and vertical scroll).
  1769. *
  1770. * @param {string=} opt_className An optional class name (or names) to apply
  1771. * to the invisible div created to measure the scrollbar. This is necessary
  1772. * if some scrollbars are styled differently than others.
  1773. * @return {number} The scroll bar width in px.
  1774. */
  1775. goog.style.getScrollbarWidth = function(opt_className) {
  1776. // Add two hidden divs. The child div is larger than the parent and
  1777. // forces scrollbars to appear on it.
  1778. // Using overflow:scroll does not work consistently with scrollbars that
  1779. // are styled with ::-webkit-scrollbar.
  1780. var outerDiv = goog.dom.createElement(goog.dom.TagName.DIV);
  1781. if (opt_className) {
  1782. outerDiv.className = opt_className;
  1783. }
  1784. outerDiv.style.cssText = 'overflow:auto;' +
  1785. 'position:absolute;top:0;width:100px;height:100px';
  1786. var innerDiv = goog.dom.createElement(goog.dom.TagName.DIV);
  1787. goog.style.setSize(innerDiv, '200px', '200px');
  1788. outerDiv.appendChild(innerDiv);
  1789. goog.dom.appendChild(goog.dom.getDocument().body, outerDiv);
  1790. var width = outerDiv.offsetWidth - outerDiv.clientWidth;
  1791. goog.dom.removeNode(outerDiv);
  1792. return width;
  1793. };
  1794. /**
  1795. * Regular expression to extract x and y translation components from a CSS
  1796. * transform Matrix representation.
  1797. *
  1798. * @type {!RegExp}
  1799. * @const
  1800. * @private
  1801. */
  1802. goog.style.MATRIX_TRANSLATION_REGEX_ = new RegExp(
  1803. 'matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' +
  1804. '[0-9\\.\\-]+, [0-9\\.\\-]+, ' +
  1805. '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)');
  1806. /**
  1807. * Returns the x,y translation component of any CSS transforms applied to the
  1808. * element, in pixels.
  1809. *
  1810. * @param {!Element} element The element to get the translation of.
  1811. * @return {!goog.math.Coordinate} The CSS translation of the element in px.
  1812. */
  1813. goog.style.getCssTranslation = function(element) {
  1814. var transform = goog.style.getComputedTransform(element);
  1815. if (!transform) {
  1816. return new goog.math.Coordinate(0, 0);
  1817. }
  1818. var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_);
  1819. if (!matches) {
  1820. return new goog.math.Coordinate(0, 0);
  1821. }
  1822. return new goog.math.Coordinate(
  1823. parseFloat(matches[1]), parseFloat(matches[2]));
  1824. };