Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
michael@0 | 1 | /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | const Cu = Components.utils; |
michael@0 | 8 | const Ci = Components.interfaces; |
michael@0 | 9 | const Cr = Components.results; |
michael@0 | 10 | |
michael@0 | 11 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 12 | |
michael@0 | 13 | XPCOMUtils.defineLazyModuleGetter(this, "Services", |
michael@0 | 14 | "resource://gre/modules/Services.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | this.EXPORTED_SYMBOLS = ["LayoutHelpers"]; |
michael@0 | 17 | |
michael@0 | 18 | this.LayoutHelpers = LayoutHelpers = function(aTopLevelWindow) { |
michael@0 | 19 | this._topDocShell = aTopLevelWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 20 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 21 | .QueryInterface(Ci.nsIDocShell); |
michael@0 | 22 | }; |
michael@0 | 23 | |
michael@0 | 24 | LayoutHelpers.prototype = { |
michael@0 | 25 | |
michael@0 | 26 | /** |
michael@0 | 27 | * Get box quads adjusted for iframes and zoom level. |
michael@0 | 28 | * |
michael@0 | 29 | * @param {DOMNode} node |
michael@0 | 30 | * The node for which we are to get the box model region quads |
michael@0 | 31 | * @param {String} region |
michael@0 | 32 | * The box model region to return: |
michael@0 | 33 | * "content", "padding", "border" or "margin" |
michael@0 | 34 | */ |
michael@0 | 35 | getAdjustedQuads: function(node, region) { |
michael@0 | 36 | if (!node) { |
michael@0 | 37 | return; |
michael@0 | 38 | } |
michael@0 | 39 | |
michael@0 | 40 | let [quads] = node.getBoxQuads({ |
michael@0 | 41 | box: region |
michael@0 | 42 | }); |
michael@0 | 43 | |
michael@0 | 44 | if (!quads) { |
michael@0 | 45 | return; |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | let [xOffset, yOffset] = this._getNodeOffsets(node); |
michael@0 | 49 | let scale = this.calculateScale(node); |
michael@0 | 50 | |
michael@0 | 51 | return { |
michael@0 | 52 | p1: { |
michael@0 | 53 | w: quads.p1.w * scale, |
michael@0 | 54 | x: quads.p1.x * scale + xOffset, |
michael@0 | 55 | y: quads.p1.y * scale + yOffset, |
michael@0 | 56 | z: quads.p1.z * scale |
michael@0 | 57 | }, |
michael@0 | 58 | p2: { |
michael@0 | 59 | w: quads.p2.w * scale, |
michael@0 | 60 | x: quads.p2.x * scale + xOffset, |
michael@0 | 61 | y: quads.p2.y * scale + yOffset, |
michael@0 | 62 | z: quads.p2.z * scale |
michael@0 | 63 | }, |
michael@0 | 64 | p3: { |
michael@0 | 65 | w: quads.p3.w * scale, |
michael@0 | 66 | x: quads.p3.x * scale + xOffset, |
michael@0 | 67 | y: quads.p3.y * scale + yOffset, |
michael@0 | 68 | z: quads.p3.z * scale |
michael@0 | 69 | }, |
michael@0 | 70 | p4: { |
michael@0 | 71 | w: quads.p4.w * scale, |
michael@0 | 72 | x: quads.p4.x * scale + xOffset, |
michael@0 | 73 | y: quads.p4.y * scale + yOffset, |
michael@0 | 74 | z: quads.p4.z * scale |
michael@0 | 75 | }, |
michael@0 | 76 | bounds: { |
michael@0 | 77 | bottom: quads.bounds.bottom * scale + yOffset, |
michael@0 | 78 | height: quads.bounds.height * scale, |
michael@0 | 79 | left: quads.bounds.left * scale + xOffset, |
michael@0 | 80 | right: quads.bounds.right * scale + xOffset, |
michael@0 | 81 | top: quads.bounds.top * scale + yOffset, |
michael@0 | 82 | width: quads.bounds.width * scale, |
michael@0 | 83 | x: quads.bounds.x * scale + xOffset, |
michael@0 | 84 | y: quads.bounds.y * scale + yOffset |
michael@0 | 85 | } |
michael@0 | 86 | }; |
michael@0 | 87 | }, |
michael@0 | 88 | |
michael@0 | 89 | calculateScale: function(node) { |
michael@0 | 90 | let win = node.ownerDocument.defaultView; |
michael@0 | 91 | let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 92 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 93 | return winUtils.fullZoom; |
michael@0 | 94 | }, |
michael@0 | 95 | |
michael@0 | 96 | /** |
michael@0 | 97 | * Compute the absolute position and the dimensions of a node, relativalely |
michael@0 | 98 | * to the root window. |
michael@0 | 99 | * |
michael@0 | 100 | * @param nsIDOMNode aNode |
michael@0 | 101 | * a DOM element to get the bounds for |
michael@0 | 102 | * @param nsIWindow aContentWindow |
michael@0 | 103 | * the content window holding the node |
michael@0 | 104 | */ |
michael@0 | 105 | getRect: function LH_getRect(aNode, aContentWindow) { |
michael@0 | 106 | let frameWin = aNode.ownerDocument.defaultView; |
michael@0 | 107 | let clientRect = aNode.getBoundingClientRect(); |
michael@0 | 108 | |
michael@0 | 109 | // Go up in the tree of frames to determine the correct rectangle. |
michael@0 | 110 | // clientRect is read-only, we need to be able to change properties. |
michael@0 | 111 | let rect = {top: clientRect.top + aContentWindow.pageYOffset, |
michael@0 | 112 | left: clientRect.left + aContentWindow.pageXOffset, |
michael@0 | 113 | width: clientRect.width, |
michael@0 | 114 | height: clientRect.height}; |
michael@0 | 115 | |
michael@0 | 116 | // We iterate through all the parent windows. |
michael@0 | 117 | while (true) { |
michael@0 | 118 | |
michael@0 | 119 | // Are we in the top-level window? |
michael@0 | 120 | if (this.isTopLevelWindow(frameWin)) { |
michael@0 | 121 | break; |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | let frameElement = this.getFrameElement(frameWin); |
michael@0 | 125 | if (!frameElement) { |
michael@0 | 126 | break; |
michael@0 | 127 | } |
michael@0 | 128 | |
michael@0 | 129 | // We are in an iframe. |
michael@0 | 130 | // We take into account the parent iframe position and its |
michael@0 | 131 | // offset (borders and padding). |
michael@0 | 132 | let frameRect = frameElement.getBoundingClientRect(); |
michael@0 | 133 | |
michael@0 | 134 | let [offsetTop, offsetLeft] = |
michael@0 | 135 | this.getIframeContentOffset(frameElement); |
michael@0 | 136 | |
michael@0 | 137 | rect.top += frameRect.top + offsetTop; |
michael@0 | 138 | rect.left += frameRect.left + offsetLeft; |
michael@0 | 139 | |
michael@0 | 140 | frameWin = this.getParentWindow(frameWin); |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | return rect; |
michael@0 | 144 | }, |
michael@0 | 145 | |
michael@0 | 146 | /** |
michael@0 | 147 | * Returns iframe content offset (iframe border + padding). |
michael@0 | 148 | * Note: this function shouldn't need to exist, had the platform provided a |
michael@0 | 149 | * suitable API for determining the offset between the iframe's content and |
michael@0 | 150 | * its bounding client rect. Bug 626359 should provide us with such an API. |
michael@0 | 151 | * |
michael@0 | 152 | * @param aIframe |
michael@0 | 153 | * The iframe. |
michael@0 | 154 | * @returns array [offsetTop, offsetLeft] |
michael@0 | 155 | * offsetTop is the distance from the top of the iframe and the |
michael@0 | 156 | * top of the content document. |
michael@0 | 157 | * offsetLeft is the distance from the left of the iframe and the |
michael@0 | 158 | * left of the content document. |
michael@0 | 159 | */ |
michael@0 | 160 | getIframeContentOffset: function LH_getIframeContentOffset(aIframe) { |
michael@0 | 161 | let style = aIframe.contentWindow.getComputedStyle(aIframe, null); |
michael@0 | 162 | |
michael@0 | 163 | // In some cases, the computed style is null |
michael@0 | 164 | if (!style) { |
michael@0 | 165 | return [0, 0]; |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | let paddingTop = parseInt(style.getPropertyValue("padding-top")); |
michael@0 | 169 | let paddingLeft = parseInt(style.getPropertyValue("padding-left")); |
michael@0 | 170 | |
michael@0 | 171 | let borderTop = parseInt(style.getPropertyValue("border-top-width")); |
michael@0 | 172 | let borderLeft = parseInt(style.getPropertyValue("border-left-width")); |
michael@0 | 173 | |
michael@0 | 174 | return [borderTop + paddingTop, borderLeft + paddingLeft]; |
michael@0 | 175 | }, |
michael@0 | 176 | |
michael@0 | 177 | /** |
michael@0 | 178 | * Find an element from the given coordinates. This method descends through |
michael@0 | 179 | * frames to find the element the user clicked inside frames. |
michael@0 | 180 | * |
michael@0 | 181 | * @param DOMDocument aDocument the document to look into. |
michael@0 | 182 | * @param integer aX |
michael@0 | 183 | * @param integer aY |
michael@0 | 184 | * @returns Node|null the element node found at the given coordinates. |
michael@0 | 185 | */ |
michael@0 | 186 | getElementFromPoint: function LH_elementFromPoint(aDocument, aX, aY) { |
michael@0 | 187 | let node = aDocument.elementFromPoint(aX, aY); |
michael@0 | 188 | if (node && node.contentDocument) { |
michael@0 | 189 | if (node instanceof Ci.nsIDOMHTMLIFrameElement) { |
michael@0 | 190 | let rect = node.getBoundingClientRect(); |
michael@0 | 191 | |
michael@0 | 192 | // Gap between the iframe and its content window. |
michael@0 | 193 | let [offsetTop, offsetLeft] = this.getIframeContentOffset(node); |
michael@0 | 194 | |
michael@0 | 195 | aX -= rect.left + offsetLeft; |
michael@0 | 196 | aY -= rect.top + offsetTop; |
michael@0 | 197 | |
michael@0 | 198 | if (aX < 0 || aY < 0) { |
michael@0 | 199 | // Didn't reach the content document, still over the iframe. |
michael@0 | 200 | return node; |
michael@0 | 201 | } |
michael@0 | 202 | } |
michael@0 | 203 | if (node instanceof Ci.nsIDOMHTMLIFrameElement || |
michael@0 | 204 | node instanceof Ci.nsIDOMHTMLFrameElement) { |
michael@0 | 205 | let subnode = this.getElementFromPoint(node.contentDocument, aX, aY); |
michael@0 | 206 | if (subnode) { |
michael@0 | 207 | node = subnode; |
michael@0 | 208 | } |
michael@0 | 209 | } |
michael@0 | 210 | } |
michael@0 | 211 | return node; |
michael@0 | 212 | }, |
michael@0 | 213 | |
michael@0 | 214 | /** |
michael@0 | 215 | * Scroll the document so that the element "elem" appears in the viewport. |
michael@0 | 216 | * |
michael@0 | 217 | * @param Element elem the element that needs to appear in the viewport. |
michael@0 | 218 | * @param bool centered true if you want it centered, false if you want it to |
michael@0 | 219 | * appear on the top of the viewport. It is true by default, and that is |
michael@0 | 220 | * usually what you want. |
michael@0 | 221 | */ |
michael@0 | 222 | scrollIntoViewIfNeeded: function(elem, centered) { |
michael@0 | 223 | // We want to default to centering the element in the page, |
michael@0 | 224 | // so as to keep the context of the element. |
michael@0 | 225 | centered = centered === undefined? true: !!centered; |
michael@0 | 226 | |
michael@0 | 227 | let win = elem.ownerDocument.defaultView; |
michael@0 | 228 | let clientRect = elem.getBoundingClientRect(); |
michael@0 | 229 | |
michael@0 | 230 | // The following are always from the {top, bottom, left, right} |
michael@0 | 231 | // of the viewport, to the {top, …} of the box. |
michael@0 | 232 | // Think of them as geometrical vectors, it helps. |
michael@0 | 233 | // The origin is at the top left. |
michael@0 | 234 | |
michael@0 | 235 | let topToBottom = clientRect.bottom; |
michael@0 | 236 | let bottomToTop = clientRect.top - win.innerHeight; |
michael@0 | 237 | let leftToRight = clientRect.right; |
michael@0 | 238 | let rightToLeft = clientRect.left - win.innerWidth; |
michael@0 | 239 | let xAllowed = true; // We allow one translation on the x axis, |
michael@0 | 240 | let yAllowed = true; // and one on the y axis. |
michael@0 | 241 | |
michael@0 | 242 | // Whatever `centered` is, the behavior is the same if the box is |
michael@0 | 243 | // (even partially) visible. |
michael@0 | 244 | |
michael@0 | 245 | if ((topToBottom > 0 || !centered) && topToBottom <= elem.offsetHeight) { |
michael@0 | 246 | win.scrollBy(0, topToBottom - elem.offsetHeight); |
michael@0 | 247 | yAllowed = false; |
michael@0 | 248 | } else |
michael@0 | 249 | if ((bottomToTop < 0 || !centered) && bottomToTop >= -elem.offsetHeight) { |
michael@0 | 250 | win.scrollBy(0, bottomToTop + elem.offsetHeight); |
michael@0 | 251 | yAllowed = false; |
michael@0 | 252 | } |
michael@0 | 253 | |
michael@0 | 254 | if ((leftToRight > 0 || !centered) && leftToRight <= elem.offsetWidth) { |
michael@0 | 255 | if (xAllowed) { |
michael@0 | 256 | win.scrollBy(leftToRight - elem.offsetWidth, 0); |
michael@0 | 257 | xAllowed = false; |
michael@0 | 258 | } |
michael@0 | 259 | } else |
michael@0 | 260 | if ((rightToLeft < 0 || !centered) && rightToLeft >= -elem.offsetWidth) { |
michael@0 | 261 | if (xAllowed) { |
michael@0 | 262 | win.scrollBy(rightToLeft + elem.offsetWidth, 0); |
michael@0 | 263 | xAllowed = false; |
michael@0 | 264 | } |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | // If we want it centered, and the box is completely hidden, |
michael@0 | 268 | // then we center it explicitly. |
michael@0 | 269 | |
michael@0 | 270 | if (centered) { |
michael@0 | 271 | |
michael@0 | 272 | if (yAllowed && (topToBottom <= 0 || bottomToTop >= 0)) { |
michael@0 | 273 | win.scroll(win.scrollX, |
michael@0 | 274 | win.scrollY + clientRect.top |
michael@0 | 275 | - (win.innerHeight - elem.offsetHeight) / 2); |
michael@0 | 276 | } |
michael@0 | 277 | |
michael@0 | 278 | if (xAllowed && (leftToRight <= 0 || rightToLeft <= 0)) { |
michael@0 | 279 | win.scroll(win.scrollX + clientRect.left |
michael@0 | 280 | - (win.innerWidth - elem.offsetWidth) / 2, |
michael@0 | 281 | win.scrollY); |
michael@0 | 282 | } |
michael@0 | 283 | } |
michael@0 | 284 | |
michael@0 | 285 | if (!this.isTopLevelWindow(win)) { |
michael@0 | 286 | // We are inside an iframe. |
michael@0 | 287 | let frameElement = this.getFrameElement(win); |
michael@0 | 288 | this.scrollIntoViewIfNeeded(frameElement, centered); |
michael@0 | 289 | } |
michael@0 | 290 | }, |
michael@0 | 291 | |
michael@0 | 292 | /** |
michael@0 | 293 | * Check if a node and its document are still alive |
michael@0 | 294 | * and attached to the window. |
michael@0 | 295 | * |
michael@0 | 296 | * @param aNode |
michael@0 | 297 | */ |
michael@0 | 298 | isNodeConnected: function LH_isNodeConnected(aNode) |
michael@0 | 299 | { |
michael@0 | 300 | try { |
michael@0 | 301 | let connected = (aNode.ownerDocument && aNode.ownerDocument.defaultView && |
michael@0 | 302 | !(aNode.compareDocumentPosition(aNode.ownerDocument.documentElement) & |
michael@0 | 303 | aNode.DOCUMENT_POSITION_DISCONNECTED)); |
michael@0 | 304 | return connected; |
michael@0 | 305 | } catch (e) { |
michael@0 | 306 | // "can't access dead object" error |
michael@0 | 307 | return false; |
michael@0 | 308 | } |
michael@0 | 309 | }, |
michael@0 | 310 | |
michael@0 | 311 | /** |
michael@0 | 312 | * like win.parent === win, but goes through mozbrowsers and mozapps iframes. |
michael@0 | 313 | */ |
michael@0 | 314 | isTopLevelWindow: function LH_isTopLevelWindow(win) { |
michael@0 | 315 | let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 316 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 317 | .QueryInterface(Ci.nsIDocShell); |
michael@0 | 318 | |
michael@0 | 319 | return docShell === this._topDocShell; |
michael@0 | 320 | }, |
michael@0 | 321 | |
michael@0 | 322 | /** |
michael@0 | 323 | * Check a window is part of the top level window. |
michael@0 | 324 | */ |
michael@0 | 325 | isIncludedInTopLevelWindow: function LH_isIncludedInTopLevelWindow(win) { |
michael@0 | 326 | if (this.isTopLevelWindow(win)) { |
michael@0 | 327 | return true; |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | let parent = this.getParentWindow(win); |
michael@0 | 331 | if (!parent || parent === win) { |
michael@0 | 332 | return false; |
michael@0 | 333 | } |
michael@0 | 334 | |
michael@0 | 335 | return this.isIncludedInTopLevelWindow(parent); |
michael@0 | 336 | }, |
michael@0 | 337 | |
michael@0 | 338 | /** |
michael@0 | 339 | * like win.parent, but goes through mozbrowsers and mozapps iframes. |
michael@0 | 340 | */ |
michael@0 | 341 | getParentWindow: function LH_getParentWindow(win) { |
michael@0 | 342 | if (this.isTopLevelWindow(win)) { |
michael@0 | 343 | return null; |
michael@0 | 344 | } |
michael@0 | 345 | |
michael@0 | 346 | let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 347 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 348 | .QueryInterface(Ci.nsIDocShell); |
michael@0 | 349 | |
michael@0 | 350 | if (docShell.isBrowserOrApp) { |
michael@0 | 351 | let parentDocShell = docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries(); |
michael@0 | 352 | return parentDocShell ? parentDocShell.contentViewer.DOMDocument.defaultView : null; |
michael@0 | 353 | } else { |
michael@0 | 354 | return win.parent; |
michael@0 | 355 | } |
michael@0 | 356 | }, |
michael@0 | 357 | |
michael@0 | 358 | /** |
michael@0 | 359 | * like win.frameElement, but goes through mozbrowsers and mozapps iframes. |
michael@0 | 360 | * |
michael@0 | 361 | * @param DOMWindow win The window to get the frame for |
michael@0 | 362 | * @return DOMElement The element in which the window is embedded. |
michael@0 | 363 | */ |
michael@0 | 364 | getFrameElement: function LH_getFrameElement(win) { |
michael@0 | 365 | if (this.isTopLevelWindow(win)) { |
michael@0 | 366 | return null; |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | let winUtils = win. |
michael@0 | 370 | QueryInterface(Components.interfaces.nsIInterfaceRequestor). |
michael@0 | 371 | getInterface(Components.interfaces.nsIDOMWindowUtils); |
michael@0 | 372 | |
michael@0 | 373 | return winUtils.containerElement; |
michael@0 | 374 | }, |
michael@0 | 375 | |
michael@0 | 376 | /** |
michael@0 | 377 | * Get the x and y offsets for a node taking iframes into account. |
michael@0 | 378 | * |
michael@0 | 379 | * @param {DOMNode} node |
michael@0 | 380 | * The node for which we are to get the offset |
michael@0 | 381 | */ |
michael@0 | 382 | _getNodeOffsets: function(node) { |
michael@0 | 383 | let xOffset = 0; |
michael@0 | 384 | let yOffset = 0; |
michael@0 | 385 | let frameWin = node.ownerDocument.defaultView; |
michael@0 | 386 | let scale = this.calculateScale(node); |
michael@0 | 387 | |
michael@0 | 388 | while (true) { |
michael@0 | 389 | // Are we in the top-level window? |
michael@0 | 390 | if (this.isTopLevelWindow(frameWin)) { |
michael@0 | 391 | break; |
michael@0 | 392 | } |
michael@0 | 393 | |
michael@0 | 394 | let frameElement = this.getFrameElement(frameWin); |
michael@0 | 395 | if (!frameElement) { |
michael@0 | 396 | break; |
michael@0 | 397 | } |
michael@0 | 398 | |
michael@0 | 399 | // We are in an iframe. |
michael@0 | 400 | // We take into account the parent iframe position and its |
michael@0 | 401 | // offset (borders and padding). |
michael@0 | 402 | let frameRect = frameElement.getBoundingClientRect(); |
michael@0 | 403 | |
michael@0 | 404 | let [offsetTop, offsetLeft] = |
michael@0 | 405 | this.getIframeContentOffset(frameElement); |
michael@0 | 406 | |
michael@0 | 407 | xOffset += frameRect.left + offsetLeft; |
michael@0 | 408 | yOffset += frameRect.top + offsetTop; |
michael@0 | 409 | |
michael@0 | 410 | frameWin = this.getParentWindow(frameWin); |
michael@0 | 411 | } |
michael@0 | 412 | |
michael@0 | 413 | return [xOffset * scale, yOffset * scale]; |
michael@0 | 414 | }, |
michael@0 | 415 | |
michael@0 | 416 | |
michael@0 | 417 | |
michael@0 | 418 | /******************************************************************** |
michael@0 | 419 | * GetBoxQuads POLYFILL START TODO: Remove this when bug 917755 is fixed. |
michael@0 | 420 | ********************************************************************/ |
michael@0 | 421 | _getBoxQuadsFromRect: function(rect, node) { |
michael@0 | 422 | let scale = this.calculateScale(node); |
michael@0 | 423 | let [xOffset, yOffset] = this._getNodeOffsets(node); |
michael@0 | 424 | |
michael@0 | 425 | let out = { |
michael@0 | 426 | p1: { |
michael@0 | 427 | x: rect.left * scale + xOffset, |
michael@0 | 428 | y: rect.top * scale + yOffset |
michael@0 | 429 | }, |
michael@0 | 430 | p2: { |
michael@0 | 431 | x: (rect.left + rect.width) * scale + xOffset, |
michael@0 | 432 | y: rect.top * scale + yOffset |
michael@0 | 433 | }, |
michael@0 | 434 | p3: { |
michael@0 | 435 | x: (rect.left + rect.width) * scale + xOffset, |
michael@0 | 436 | y: (rect.top + rect.height) * scale + yOffset |
michael@0 | 437 | }, |
michael@0 | 438 | p4: { |
michael@0 | 439 | x: rect.left * scale + xOffset, |
michael@0 | 440 | y: (rect.top + rect.height) * scale + yOffset |
michael@0 | 441 | } |
michael@0 | 442 | }; |
michael@0 | 443 | |
michael@0 | 444 | out.bounds = { |
michael@0 | 445 | bottom: out.p4.y, |
michael@0 | 446 | height: out.p4.y - out.p1.y, |
michael@0 | 447 | left: out.p1.x, |
michael@0 | 448 | right: out.p2.x, |
michael@0 | 449 | top: out.p1.y, |
michael@0 | 450 | width: out.p2.x - out.p1.x, |
michael@0 | 451 | x: out.p1.x, |
michael@0 | 452 | y: out.p1.y |
michael@0 | 453 | }; |
michael@0 | 454 | |
michael@0 | 455 | return out; |
michael@0 | 456 | }, |
michael@0 | 457 | |
michael@0 | 458 | _parseNb: function(distance) { |
michael@0 | 459 | let nb = parseFloat(distance, 10); |
michael@0 | 460 | return isNaN(nb) ? 0 : nb; |
michael@0 | 461 | }, |
michael@0 | 462 | |
michael@0 | 463 | getAdjustedQuadsPolyfill: function(node, region) { |
michael@0 | 464 | // Get the border-box rect |
michael@0 | 465 | // Note that this is relative to the node's viewport, so before we can use |
michael@0 | 466 | // it, will need to go back up the frames like getRect |
michael@0 | 467 | let borderRect = node.getBoundingClientRect(); |
michael@0 | 468 | |
michael@0 | 469 | // If the boxType is border, no need to go any further, we're done |
michael@0 | 470 | if (region === "border") { |
michael@0 | 471 | return this._getBoxQuadsFromRect(borderRect, node); |
michael@0 | 472 | } |
michael@0 | 473 | |
michael@0 | 474 | // Else, need to get margin/padding/border distances |
michael@0 | 475 | let style = node.ownerDocument.defaultView.getComputedStyle(node); |
michael@0 | 476 | let camel = s => s.substring(0, 1).toUpperCase() + s.substring(1); |
michael@0 | 477 | let distances = {border:{}, padding:{}, margin: {}}; |
michael@0 | 478 | |
michael@0 | 479 | for (let side of ["top", "right", "bottom", "left"]) { |
michael@0 | 480 | distances.border[side] = this._parseNb(style["border" + camel(side) + "Width"]); |
michael@0 | 481 | distances.padding[side] = this._parseNb(style["padding" + camel(side)]); |
michael@0 | 482 | distances.margin[side] = this._parseNb(style["margin" + camel(side)]); |
michael@0 | 483 | } |
michael@0 | 484 | |
michael@0 | 485 | // From the border-box rect, calculate the content-box, padding-box and |
michael@0 | 486 | // margin-box rects |
michael@0 | 487 | function offsetRect(rect, offsetType, dir=1) { |
michael@0 | 488 | return { |
michael@0 | 489 | top: rect.top + (dir * distances[offsetType].top), |
michael@0 | 490 | left: rect.left + (dir * distances[offsetType].left), |
michael@0 | 491 | width: rect.width - (dir * (distances[offsetType].left + distances[offsetType].right)), |
michael@0 | 492 | height: rect.height - (dir * (distances[offsetType].top + distances[offsetType].bottom)) |
michael@0 | 493 | }; |
michael@0 | 494 | } |
michael@0 | 495 | |
michael@0 | 496 | if (region === "margin") { |
michael@0 | 497 | return this._getBoxQuadsFromRect(offsetRect(borderRect, "margin", -1), node); |
michael@0 | 498 | } else if (region === "padding") { |
michael@0 | 499 | return this._getBoxQuadsFromRect(offsetRect(borderRect, "border"), node); |
michael@0 | 500 | } else if (region === "content") { |
michael@0 | 501 | let paddingRect = offsetRect(borderRect, "border"); |
michael@0 | 502 | return this._getBoxQuadsFromRect(offsetRect(paddingRect, "padding"), node); |
michael@0 | 503 | } |
michael@0 | 504 | }, |
michael@0 | 505 | |
michael@0 | 506 | /******************************************************************** |
michael@0 | 507 | * GetBoxQuads POLYFILL END |
michael@0 | 508 | ********************************************************************/ |
michael@0 | 509 | }; |