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

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

mercurial