browser/metro/base/content/Util.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 let Cc = Components.classes;
michael@0 6 let Ci = Components.interfaces;
michael@0 7
michael@0 8 Components.utils.import("resource:///modules/ContentUtil.jsm");
michael@0 9
michael@0 10 let Util = {
michael@0 11 /*
michael@0 12 * General purpose utilities
michael@0 13 */
michael@0 14
michael@0 15 getWindowUtils: function getWindowUtils(aWindow) {
michael@0 16 return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
michael@0 17 },
michael@0 18
michael@0 19 // Put the Mozilla networking code into a state that will kick the
michael@0 20 // auto-connection process.
michael@0 21 forceOnline: function forceOnline() {
michael@0 22 Services.io.offline = false;
michael@0 23 },
michael@0 24
michael@0 25 /*
michael@0 26 * Timing utilties
michael@0 27 */
michael@0 28
michael@0 29 // Executes aFunc after other events have been processed.
michael@0 30 executeSoon: function executeSoon(aFunc) {
michael@0 31 Services.tm.mainThread.dispatch({
michael@0 32 run: function() {
michael@0 33 aFunc();
michael@0 34 }
michael@0 35 }, Ci.nsIThread.DISPATCH_NORMAL);
michael@0 36 },
michael@0 37
michael@0 38 /*
michael@0 39 * Console printing utilities
michael@0 40 */
michael@0 41
michael@0 42 // Like dump, but each arg is handled and there's an automatic newline
michael@0 43 dumpLn: function dumpLn() {
michael@0 44 for (let i = 0; i < arguments.length; i++)
michael@0 45 dump(arguments[i] + " ");
michael@0 46 dump("\n");
michael@0 47 },
michael@0 48
michael@0 49 /*
michael@0 50 * Element utilities
michael@0 51 */
michael@0 52
michael@0 53 transitionElementVisibility: function(aNodes, aVisible) {
michael@0 54 // accept single node or a collection of nodes
michael@0 55 aNodes = aNodes.length ? aNodes : [aNodes];
michael@0 56 let defd = Promise.defer();
michael@0 57 let pending = 0;
michael@0 58 Array.forEach(aNodes, function(aNode) {
michael@0 59 if (aVisible) {
michael@0 60 aNode.hidden = false;
michael@0 61 aNode.removeAttribute("fade"); // trigger transition to full opacity
michael@0 62 } else {
michael@0 63 aNode.setAttribute("fade", true); // trigger transition to 0 opacity
michael@0 64 }
michael@0 65 aNode.addEventListener("transitionend", function onTransitionEnd(aEvent){
michael@0 66 aNode.removeEventListener("transitionend", onTransitionEnd);
michael@0 67 if (!aVisible) {
michael@0 68 aNode.hidden = true;
michael@0 69 }
michael@0 70 pending--;
michael@0 71 if (!pending){
michael@0 72 defd.resolve(true);
michael@0 73 }
michael@0 74 }, false);
michael@0 75 pending++;
michael@0 76 });
michael@0 77 return defd.promise;
michael@0 78 },
michael@0 79
michael@0 80 isTextInput: function isTextInput(aElement) {
michael@0 81 return ((aElement instanceof Ci.nsIDOMHTMLInputElement &&
michael@0 82 aElement.mozIsTextField(false)) ||
michael@0 83 aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
michael@0 84 },
michael@0 85
michael@0 86 /**
michael@0 87 * Checks whether aElement's content can be edited either if it(or any of its
michael@0 88 * parents) has "contenteditable" attribute set to "true" or aElement's
michael@0 89 * ownerDocument is in design mode.
michael@0 90 */
michael@0 91 isEditableContent: function isEditableContent(aElement) {
michael@0 92 return !!aElement && (aElement.isContentEditable ||
michael@0 93 this.isOwnerDocumentInDesignMode(aElement));
michael@0 94
michael@0 95 },
michael@0 96
michael@0 97 isEditable: function isEditable(aElement) {
michael@0 98 if (!aElement) {
michael@0 99 return false;
michael@0 100 }
michael@0 101
michael@0 102 if (this.isTextInput(aElement) || this.isEditableContent(aElement)) {
michael@0 103 return true;
michael@0 104 }
michael@0 105
michael@0 106 // If a body element is editable and the body is the child of an
michael@0 107 // iframe or div we can assume this is an advanced HTML editor
michael@0 108 if ((aElement instanceof Ci.nsIDOMHTMLIFrameElement ||
michael@0 109 aElement instanceof Ci.nsIDOMHTMLDivElement) &&
michael@0 110 aElement.contentDocument &&
michael@0 111 this.isEditableContent(aElement.contentDocument.body)) {
michael@0 112 return true;
michael@0 113 }
michael@0 114
michael@0 115 return false;
michael@0 116 },
michael@0 117
michael@0 118 /**
michael@0 119 * Checks whether aElement's owner document has design mode turned on.
michael@0 120 */
michael@0 121 isOwnerDocumentInDesignMode: function(aElement) {
michael@0 122 return !!aElement && !!aElement.ownerDocument &&
michael@0 123 aElement.ownerDocument.designMode == "on";
michael@0 124 },
michael@0 125
michael@0 126 isMultilineInput: function isMultilineInput(aElement) {
michael@0 127 return (aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
michael@0 128 },
michael@0 129
michael@0 130 isLink: function isLink(aElement) {
michael@0 131 return ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) ||
michael@0 132 (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href) ||
michael@0 133 aElement instanceof Ci.nsIDOMHTMLLinkElement ||
michael@0 134 aElement.getAttributeNS(kXLinkNamespace, "type") == "simple");
michael@0 135 },
michael@0 136
michael@0 137 isText: function isText(aElement) {
michael@0 138 return (aElement instanceof Ci.nsIDOMHTMLParagraphElement ||
michael@0 139 aElement instanceof Ci.nsIDOMHTMLDivElement ||
michael@0 140 aElement instanceof Ci.nsIDOMHTMLLIElement ||
michael@0 141 aElement instanceof Ci.nsIDOMHTMLPreElement ||
michael@0 142 aElement instanceof Ci.nsIDOMHTMLHeadingElement ||
michael@0 143 aElement instanceof Ci.nsIDOMHTMLTableCellElement ||
michael@0 144 aElement instanceof Ci.nsIDOMHTMLBodyElement);
michael@0 145 },
michael@0 146
michael@0 147 /*
michael@0 148 * Rect and nsIDOMRect utilities
michael@0 149 */
michael@0 150
michael@0 151 getCleanRect: function getCleanRect() {
michael@0 152 return {
michael@0 153 left: 0, top: 0, right: 0, bottom: 0
michael@0 154 };
michael@0 155 },
michael@0 156
michael@0 157 pointWithinRect: function pointWithinRect(aX, aY, aRect) {
michael@0 158 return (aRect.left < aX && aRect.top < aY &&
michael@0 159 aRect.right > aX && aRect.bottom > aY);
michael@0 160 },
michael@0 161
michael@0 162 pointWithinDOMRect: function pointWithinDOMRect(aX, aY, aRect) {
michael@0 163 if (!aRect.width || !aRect.height)
michael@0 164 return false;
michael@0 165 return this.pointWithinRect(aX, aY, aRect);
michael@0 166 },
michael@0 167
michael@0 168 isEmptyDOMRect: function isEmptyDOMRect(aRect) {
michael@0 169 if ((aRect.bottom - aRect.top) <= 0 &&
michael@0 170 (aRect.right - aRect.left) <= 0)
michael@0 171 return true;
michael@0 172 return false;
michael@0 173 },
michael@0 174
michael@0 175 // Dumps the details of a dom rect to the console
michael@0 176 dumpDOMRect: function dumpDOMRect(aMsg, aRect) {
michael@0 177 try {
michael@0 178 Util.dumpLn(aMsg,
michael@0 179 "left:" + Math.round(aRect.left) + ",",
michael@0 180 "top:" + Math.round(aRect.top) + ",",
michael@0 181 "right:" + Math.round(aRect.right) + ",",
michael@0 182 "bottom:" + Math.round(aRect.bottom) + ",",
michael@0 183 "width:" + Math.round(aRect.right - aRect.left) + ",",
michael@0 184 "height:" + Math.round(aRect.bottom - aRect.top) );
michael@0 185 } catch (ex) {
michael@0 186 Util.dumpLn("dumpDOMRect:", ex.message);
michael@0 187 }
michael@0 188 },
michael@0 189
michael@0 190 /*
michael@0 191 * DownloadUtils.convertByteUnits returns [size, localized-unit-string]
michael@0 192 * so they are joined for a single download size string.
michael@0 193 */
michael@0 194 getDownloadSize: function dv__getDownloadSize (aSize) {
michael@0 195 let [size, units] = DownloadUtils.convertByteUnits(aSize);
michael@0 196 if (aSize > 0)
michael@0 197 return size + units;
michael@0 198 else
michael@0 199 return Strings.browser.GetStringFromName("downloadsUnknownSize");
michael@0 200 },
michael@0 201
michael@0 202 /*
michael@0 203 * URIs and schemes
michael@0 204 */
michael@0 205
michael@0 206 makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
michael@0 207 return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
michael@0 208 },
michael@0 209
michael@0 210 makeURLAbsolute: function makeURLAbsolute(base, url) {
michael@0 211 // Note: makeURI() will throw if url is not a valid URI
michael@0 212 return this.makeURI(url, null, this.makeURI(base)).spec;
michael@0 213 },
michael@0 214
michael@0 215 isLocalScheme: function isLocalScheme(aURL) {
michael@0 216 return ((aURL.indexOf("about:") == 0 &&
michael@0 217 aURL != "about:blank" &&
michael@0 218 aURL != "about:empty" &&
michael@0 219 aURL != "about:start") ||
michael@0 220 aURL.indexOf("chrome:") == 0);
michael@0 221 },
michael@0 222
michael@0 223 // Don't display anything in the urlbar for these special URIs.
michael@0 224 isURLEmpty: function isURLEmpty(aURL) {
michael@0 225 return (!aURL ||
michael@0 226 aURL == "about:blank" ||
michael@0 227 aURL == "about:empty" ||
michael@0 228 aURL == "about:home" ||
michael@0 229 aURL == "about:newtab" ||
michael@0 230 aURL.startsWith("about:newtab"));
michael@0 231 },
michael@0 232
michael@0 233 // Title to use for emptyURL tabs.
michael@0 234 getEmptyURLTabTitle: function getEmptyURLTabTitle() {
michael@0 235 let browserStrings = Services.strings.createBundle("chrome://browser/locale/browser.properties");
michael@0 236
michael@0 237 return browserStrings.GetStringFromName("tabs.emptyTabTitle");
michael@0 238 },
michael@0 239
michael@0 240 // Don't remember these pages in the session store.
michael@0 241 isURLMemorable: function isURLMemorable(aURL) {
michael@0 242 return !(aURL == "about:blank" ||
michael@0 243 aURL == "about:empty" ||
michael@0 244 aURL == "about:start");
michael@0 245 },
michael@0 246
michael@0 247 /*
michael@0 248 * Math utilities
michael@0 249 */
michael@0 250
michael@0 251 clamp: function(num, min, max) {
michael@0 252 return Math.max(min, Math.min(max, num));
michael@0 253 },
michael@0 254
michael@0 255 /*
michael@0 256 * Screen and layout utilities
michael@0 257 */
michael@0 258
michael@0 259 /*
michael@0 260 * translateToTopLevelWindow - Given an element potentially within
michael@0 261 * a subframe, calculate the offsets up to the top level browser.
michael@0 262 */
michael@0 263 translateToTopLevelWindow: function translateToTopLevelWindow(aElement) {
michael@0 264 let offsetX = 0;
michael@0 265 let offsetY = 0;
michael@0 266 let element = aElement;
michael@0 267 while (element &&
michael@0 268 element.ownerDocument &&
michael@0 269 element.ownerDocument.defaultView != content) {
michael@0 270 element = element.ownerDocument.defaultView.frameElement;
michael@0 271 let rect = element.getBoundingClientRect();
michael@0 272 offsetX += rect.left;
michael@0 273 offsetY += rect.top;
michael@0 274 }
michael@0 275 let win = null;
michael@0 276 if (element == aElement)
michael@0 277 win = content;
michael@0 278 else
michael@0 279 win = element.contentDocument.defaultView;
michael@0 280 return { targetWindow: win, offsetX: offsetX, offsetY: offsetY };
michael@0 281 },
michael@0 282
michael@0 283 get displayDPI() {
michael@0 284 delete this.displayDPI;
michael@0 285 return this.displayDPI = this.getWindowUtils(window).displayDPI;
michael@0 286 },
michael@0 287
michael@0 288 /*
michael@0 289 * aViewHeight - the height of the viewable area in the browser
michael@0 290 * aRect - a bounding rectangle of a selection or element.
michael@0 291 *
michael@0 292 * return - number of pixels for the browser to be shifted up by such
michael@0 293 * that aRect is centered vertically within aViewHeight.
michael@0 294 */
michael@0 295 centerElementInView: function centerElementInView(aViewHeight, aRect) {
michael@0 296 // If the bottom of the target bounds is higher than the new height,
michael@0 297 // there's no need to adjust. It will be above the keyboard.
michael@0 298 if (aRect.bottom <= aViewHeight) {
michael@0 299 return 0;
michael@0 300 }
michael@0 301
michael@0 302 // height of the target element
michael@0 303 let targetHeight = aRect.bottom - aRect.top;
michael@0 304 // height of the browser view.
michael@0 305 let viewBottom = content.innerHeight;
michael@0 306
michael@0 307 // If the target is shorter than the new content height, we can go ahead
michael@0 308 // and center it.
michael@0 309 if (targetHeight <= aViewHeight) {
michael@0 310 // Try to center the element vertically in the new content area, but
michael@0 311 // don't position such that the bottom of the browser view moves above
michael@0 312 // the top of the chrome. We purposely do not resize the browser window
michael@0 313 // by making it taller when trying to center elements that are near the
michael@0 314 // lower bounds. This would trigger reflow which can cause content to
michael@0 315 // shift around.
michael@0 316 let splitMargin = Math.round((aViewHeight - targetHeight) * .5);
michael@0 317 let distanceToPageBounds = viewBottom - aRect.bottom;
michael@0 318 let distanceFromChromeTop = aRect.bottom - aViewHeight;
michael@0 319 let distanceToCenter =
michael@0 320 distanceFromChromeTop + Math.min(distanceToPageBounds, splitMargin);
michael@0 321 return distanceToCenter;
michael@0 322 }
michael@0 323 },
michael@0 324
michael@0 325 /*
michael@0 326 * Local system utilities
michael@0 327 */
michael@0 328
michael@0 329 copyImageToClipboard: function Util_copyImageToClipboard(aImageLoadingContent) {
michael@0 330 let image = aImageLoadingContent.QueryInterface(Ci.nsIImageLoadingContent);
michael@0 331 if (!image) {
michael@0 332 Util.dumpLn("copyImageToClipboard error: image is not an nsIImageLoadingContent");
michael@0 333 return;
michael@0 334 }
michael@0 335 try {
michael@0 336 let xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
michael@0 337 xferable.init(null);
michael@0 338 let imgRequest = aImageLoadingContent.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
michael@0 339 let mimeType = imgRequest.mimeType;
michael@0 340 let imgContainer = imgRequest.image;
michael@0 341 let imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"].createInstance(Ci.nsISupportsInterfacePointer);
michael@0 342 imgPtr.data = imgContainer;
michael@0 343 xferable.setTransferData(mimeType, imgPtr, null);
michael@0 344 let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
michael@0 345 clip.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
michael@0 346 } catch (e) {
michael@0 347 Util.dumpLn(e.message);
michael@0 348 }
michael@0 349 },
michael@0 350 };
michael@0 351
michael@0 352
michael@0 353 /*
michael@0 354 * Timeout
michael@0 355 *
michael@0 356 * Helper class to nsITimer that adds a little more pizazz. Callback can be an
michael@0 357 * object with a notify method or a function.
michael@0 358 */
michael@0 359 Util.Timeout = function(aCallback) {
michael@0 360 this._callback = aCallback;
michael@0 361 this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 362 this._type = null;
michael@0 363 };
michael@0 364
michael@0 365 Util.Timeout.prototype = {
michael@0 366 // Timer callback. Don't call this manually.
michael@0 367 notify: function notify() {
michael@0 368 if (this._type == this._timer.TYPE_ONE_SHOT)
michael@0 369 this._type = null;
michael@0 370
michael@0 371 if (this._callback.notify)
michael@0 372 this._callback.notify();
michael@0 373 else
michael@0 374 this._callback.apply(null);
michael@0 375 },
michael@0 376
michael@0 377 // Helper function for once and interval.
michael@0 378 _start: function _start(aDelay, aType, aCallback) {
michael@0 379 if (aCallback)
michael@0 380 this._callback = aCallback;
michael@0 381 this.clear();
michael@0 382 this._timer.initWithCallback(this, aDelay, aType);
michael@0 383 this._type = aType;
michael@0 384 return this;
michael@0 385 },
michael@0 386
michael@0 387 // Do the callback once. Cancels other timeouts on this object.
michael@0 388 once: function once(aDelay, aCallback) {
michael@0 389 return this._start(aDelay, this._timer.TYPE_ONE_SHOT, aCallback);
michael@0 390 },
michael@0 391
michael@0 392 // Do the callback every aDelay msecs. Cancels other timeouts on this object.
michael@0 393 interval: function interval(aDelay, aCallback) {
michael@0 394 return this._start(aDelay, this._timer.TYPE_REPEATING_SLACK, aCallback);
michael@0 395 },
michael@0 396
michael@0 397 // Clear any pending timeouts.
michael@0 398 clear: function clear() {
michael@0 399 if (this.isPending()) {
michael@0 400 this._timer.cancel();
michael@0 401 this._type = null;
michael@0 402 }
michael@0 403 return this;
michael@0 404 },
michael@0 405
michael@0 406 // If there is a pending timeout, call it and cancel the timeout.
michael@0 407 flush: function flush() {
michael@0 408 if (this.isPending()) {
michael@0 409 this.notify();
michael@0 410 this.clear();
michael@0 411 }
michael@0 412 return this;
michael@0 413 },
michael@0 414
michael@0 415 // Return true if we are waiting for a callback.
michael@0 416 isPending: function isPending() {
michael@0 417 return this._type !== null;
michael@0 418 }
michael@0 419 };
michael@0 420
michael@0 421 // Mixin the ContentUtil module exports
michael@0 422 {
michael@0 423 for (let name in ContentUtil) {
michael@0 424 let copy = ContentUtil[name];
michael@0 425 if (copy !== undefined)
michael@0 426 Util[name] = copy;
michael@0 427 }
michael@0 428 }
michael@0 429
michael@0 430 this.Util = Util;

mercurial