browser/metro/base/content/Util.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/metro/base/content/Util.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,430 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +let Cc = Components.classes;
     1.9 +let Ci = Components.interfaces;
    1.10 +
    1.11 +Components.utils.import("resource:///modules/ContentUtil.jsm");
    1.12 +
    1.13 +let Util = {
    1.14 +  /*
    1.15 +   * General purpose utilities
    1.16 +   */
    1.17 +
    1.18 +  getWindowUtils: function getWindowUtils(aWindow) {
    1.19 +    return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
    1.20 +  },
    1.21 +
    1.22 +  // Put the Mozilla networking code into a state that will kick the
    1.23 +  // auto-connection process.
    1.24 +  forceOnline: function forceOnline() {
    1.25 +    Services.io.offline = false;
    1.26 +  },
    1.27 +
    1.28 +  /*
    1.29 +   * Timing utilties
    1.30 +   */
    1.31 +
    1.32 +  // Executes aFunc after other events have been processed.
    1.33 +  executeSoon: function executeSoon(aFunc) {
    1.34 +    Services.tm.mainThread.dispatch({
    1.35 +      run: function() {
    1.36 +        aFunc();
    1.37 +      }
    1.38 +    }, Ci.nsIThread.DISPATCH_NORMAL);
    1.39 +  },
    1.40 +
    1.41 +  /*
    1.42 +   * Console printing utilities
    1.43 +   */
    1.44 +
    1.45 +  // Like dump, but each arg is handled and there's an automatic newline
    1.46 +  dumpLn: function dumpLn() {
    1.47 +    for (let i = 0; i < arguments.length; i++)
    1.48 +      dump(arguments[i] + " ");
    1.49 +    dump("\n");
    1.50 +  },
    1.51 +
    1.52 +  /*
    1.53 +   * Element utilities
    1.54 +   */
    1.55 +
    1.56 +  transitionElementVisibility: function(aNodes, aVisible) {
    1.57 +    // accept single node or a collection of nodes
    1.58 +    aNodes = aNodes.length ? aNodes : [aNodes];
    1.59 +    let defd = Promise.defer();
    1.60 +    let pending = 0;
    1.61 +    Array.forEach(aNodes, function(aNode) {
    1.62 +      if (aVisible) {
    1.63 +        aNode.hidden = false;
    1.64 +        aNode.removeAttribute("fade"); // trigger transition to full opacity
    1.65 +      } else {
    1.66 +        aNode.setAttribute("fade", true); // trigger transition to 0 opacity
    1.67 +      }
    1.68 +      aNode.addEventListener("transitionend", function onTransitionEnd(aEvent){
    1.69 +        aNode.removeEventListener("transitionend", onTransitionEnd);
    1.70 +        if (!aVisible) {
    1.71 +          aNode.hidden = true;
    1.72 +        }
    1.73 +        pending--;
    1.74 +        if (!pending){
    1.75 +          defd.resolve(true);
    1.76 +        }
    1.77 +      }, false);
    1.78 +      pending++;
    1.79 +    });
    1.80 +    return defd.promise;
    1.81 +  },
    1.82 +
    1.83 +  isTextInput: function isTextInput(aElement) {
    1.84 +    return ((aElement instanceof Ci.nsIDOMHTMLInputElement &&
    1.85 +             aElement.mozIsTextField(false)) ||
    1.86 +            aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
    1.87 +  },
    1.88 +
    1.89 +  /**
    1.90 +   * Checks whether aElement's content can be edited either if it(or any of its
    1.91 +   * parents) has "contenteditable" attribute set to "true" or aElement's
    1.92 +   * ownerDocument is in design mode.
    1.93 +   */
    1.94 +  isEditableContent: function isEditableContent(aElement) {
    1.95 +    return !!aElement && (aElement.isContentEditable ||
    1.96 +                          this.isOwnerDocumentInDesignMode(aElement));
    1.97 +
    1.98 +  },
    1.99 +
   1.100 +  isEditable: function isEditable(aElement) {
   1.101 +    if (!aElement) {
   1.102 +      return false;
   1.103 +    }
   1.104 +
   1.105 +    if (this.isTextInput(aElement) || this.isEditableContent(aElement)) {
   1.106 +      return true;
   1.107 +    }
   1.108 +
   1.109 +    // If a body element is editable and the body is the child of an
   1.110 +    // iframe or div we can assume this is an advanced HTML editor
   1.111 +    if ((aElement instanceof Ci.nsIDOMHTMLIFrameElement ||
   1.112 +         aElement instanceof Ci.nsIDOMHTMLDivElement) &&
   1.113 +        aElement.contentDocument &&
   1.114 +        this.isEditableContent(aElement.contentDocument.body)) {
   1.115 +      return true;
   1.116 +    }
   1.117 +
   1.118 +    return false;
   1.119 +  },
   1.120 +
   1.121 +  /**
   1.122 +   * Checks whether aElement's owner document has design mode turned on.
   1.123 +   */
   1.124 +  isOwnerDocumentInDesignMode: function(aElement) {
   1.125 +    return !!aElement && !!aElement.ownerDocument &&
   1.126 +           aElement.ownerDocument.designMode == "on";
   1.127 +  },
   1.128 +
   1.129 +  isMultilineInput: function isMultilineInput(aElement) {
   1.130 +    return (aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
   1.131 +  },
   1.132 +
   1.133 +  isLink: function isLink(aElement) {
   1.134 +    return ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) ||
   1.135 +            (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href) ||
   1.136 +            aElement instanceof Ci.nsIDOMHTMLLinkElement ||
   1.137 +            aElement.getAttributeNS(kXLinkNamespace, "type") == "simple");
   1.138 +  },
   1.139 +
   1.140 +  isText: function isText(aElement) {
   1.141 +    return (aElement instanceof Ci.nsIDOMHTMLParagraphElement ||
   1.142 +            aElement instanceof Ci.nsIDOMHTMLDivElement ||
   1.143 +            aElement instanceof Ci.nsIDOMHTMLLIElement ||
   1.144 +            aElement instanceof Ci.nsIDOMHTMLPreElement ||
   1.145 +            aElement instanceof Ci.nsIDOMHTMLHeadingElement ||
   1.146 +            aElement instanceof Ci.nsIDOMHTMLTableCellElement ||
   1.147 +            aElement instanceof Ci.nsIDOMHTMLBodyElement);
   1.148 +  },
   1.149 +
   1.150 +  /*
   1.151 +   * Rect and nsIDOMRect utilities
   1.152 +   */
   1.153 +
   1.154 +  getCleanRect: function getCleanRect() {
   1.155 +    return {
   1.156 +      left: 0, top: 0, right: 0, bottom: 0
   1.157 +    };
   1.158 +  },
   1.159 +
   1.160 +  pointWithinRect: function pointWithinRect(aX, aY, aRect) {
   1.161 +    return (aRect.left < aX && aRect.top < aY &&
   1.162 +            aRect.right > aX && aRect.bottom > aY);
   1.163 +  },
   1.164 +
   1.165 +  pointWithinDOMRect: function pointWithinDOMRect(aX, aY, aRect) {
   1.166 +    if (!aRect.width || !aRect.height)
   1.167 +      return false;
   1.168 +    return this.pointWithinRect(aX, aY, aRect);
   1.169 +  },
   1.170 +
   1.171 +  isEmptyDOMRect: function isEmptyDOMRect(aRect) {
   1.172 +    if ((aRect.bottom - aRect.top) <= 0 &&
   1.173 +        (aRect.right - aRect.left) <= 0)
   1.174 +      return true;
   1.175 +    return false;
   1.176 +  },
   1.177 +
   1.178 +  // Dumps the details of a dom rect to the console
   1.179 +  dumpDOMRect: function dumpDOMRect(aMsg, aRect) {
   1.180 +    try {
   1.181 +      Util.dumpLn(aMsg,
   1.182 +                  "left:" + Math.round(aRect.left) + ",",
   1.183 +                  "top:" + Math.round(aRect.top) + ",",
   1.184 +                  "right:" + Math.round(aRect.right) + ",",
   1.185 +                  "bottom:" + Math.round(aRect.bottom) + ",",
   1.186 +                  "width:" + Math.round(aRect.right - aRect.left) + ",",
   1.187 +                  "height:" + Math.round(aRect.bottom - aRect.top) );
   1.188 +    } catch (ex) {
   1.189 +      Util.dumpLn("dumpDOMRect:", ex.message);
   1.190 +    }
   1.191 +  },
   1.192 +
   1.193 +  /*
   1.194 +   * DownloadUtils.convertByteUnits returns [size, localized-unit-string]
   1.195 +   * so they are joined for a single download size string.
   1.196 +   */
   1.197 +  getDownloadSize: function dv__getDownloadSize (aSize) {
   1.198 +    let [size, units] = DownloadUtils.convertByteUnits(aSize);
   1.199 +    if (aSize > 0)
   1.200 +      return size + units;
   1.201 +    else
   1.202 +      return Strings.browser.GetStringFromName("downloadsUnknownSize");
   1.203 +  },
   1.204 +
   1.205 +  /*
   1.206 +   * URIs and schemes
   1.207 +   */
   1.208 +
   1.209 +  makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
   1.210 +    return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
   1.211 +  },
   1.212 +
   1.213 +  makeURLAbsolute: function makeURLAbsolute(base, url) {
   1.214 +    // Note:  makeURI() will throw if url is not a valid URI
   1.215 +    return this.makeURI(url, null, this.makeURI(base)).spec;
   1.216 +  },
   1.217 +
   1.218 +  isLocalScheme: function isLocalScheme(aURL) {
   1.219 +    return ((aURL.indexOf("about:") == 0 &&
   1.220 +             aURL != "about:blank" &&
   1.221 +             aURL != "about:empty" &&
   1.222 +             aURL != "about:start") ||
   1.223 +            aURL.indexOf("chrome:") == 0);
   1.224 +  },
   1.225 +
   1.226 +  // Don't display anything in the urlbar for these special URIs.
   1.227 +  isURLEmpty: function isURLEmpty(aURL) {
   1.228 +    return (!aURL ||
   1.229 +            aURL == "about:blank" ||
   1.230 +            aURL == "about:empty" ||
   1.231 +            aURL == "about:home" ||
   1.232 +            aURL == "about:newtab" ||
   1.233 +            aURL.startsWith("about:newtab"));
   1.234 +  },
   1.235 +
   1.236 +  // Title to use for emptyURL tabs.
   1.237 +  getEmptyURLTabTitle: function getEmptyURLTabTitle() {
   1.238 +    let browserStrings = Services.strings.createBundle("chrome://browser/locale/browser.properties");
   1.239 +
   1.240 +    return browserStrings.GetStringFromName("tabs.emptyTabTitle");
   1.241 +  },
   1.242 +
   1.243 +  // Don't remember these pages in the session store.
   1.244 +  isURLMemorable: function isURLMemorable(aURL) {
   1.245 +    return !(aURL == "about:blank" ||
   1.246 +             aURL == "about:empty" ||
   1.247 +             aURL == "about:start");
   1.248 +  },
   1.249 +
   1.250 +  /*
   1.251 +   * Math utilities
   1.252 +   */
   1.253 +
   1.254 +  clamp: function(num, min, max) {
   1.255 +    return Math.max(min, Math.min(max, num));
   1.256 +  },
   1.257 +
   1.258 +  /*
   1.259 +   * Screen and layout utilities
   1.260 +   */
   1.261 +
   1.262 +   /*
   1.263 +    * translateToTopLevelWindow - Given an element potentially within
   1.264 +    * a subframe, calculate the offsets up to the top level browser.
   1.265 +    */
   1.266 +  translateToTopLevelWindow: function translateToTopLevelWindow(aElement) {
   1.267 +    let offsetX = 0;
   1.268 +    let offsetY = 0;
   1.269 +    let element = aElement;
   1.270 +    while (element &&
   1.271 +           element.ownerDocument &&
   1.272 +           element.ownerDocument.defaultView != content) {
   1.273 +      element = element.ownerDocument.defaultView.frameElement;
   1.274 +      let rect = element.getBoundingClientRect();
   1.275 +      offsetX += rect.left;
   1.276 +      offsetY += rect.top;
   1.277 +    }
   1.278 +    let win = null;
   1.279 +    if (element == aElement)
   1.280 +      win = content;
   1.281 +    else
   1.282 +      win = element.contentDocument.defaultView;
   1.283 +    return { targetWindow: win, offsetX: offsetX, offsetY: offsetY };
   1.284 +  },
   1.285 +
   1.286 +  get displayDPI() {
   1.287 +    delete this.displayDPI;
   1.288 +    return this.displayDPI = this.getWindowUtils(window).displayDPI;
   1.289 +  },
   1.290 +
   1.291 +  /*
   1.292 +   * aViewHeight - the height of the viewable area in the browser
   1.293 +   * aRect - a bounding rectangle of a selection or element.
   1.294 +   *
   1.295 +   * return - number of pixels for the browser to be shifted up by such
   1.296 +   * that aRect is centered vertically within aViewHeight.
   1.297 +   */
   1.298 +  centerElementInView: function centerElementInView(aViewHeight, aRect) {
   1.299 +    // If the bottom of the target bounds is higher than the new height,
   1.300 +    // there's no need to adjust. It will be above the keyboard.
   1.301 +    if (aRect.bottom <= aViewHeight) {
   1.302 +      return 0;
   1.303 +    }
   1.304 +
   1.305 +    // height of the target element
   1.306 +    let targetHeight = aRect.bottom - aRect.top;
   1.307 +    // height of the browser view.
   1.308 +    let viewBottom = content.innerHeight;
   1.309 +
   1.310 +    // If the target is shorter than the new content height, we can go ahead
   1.311 +    // and center it.
   1.312 +    if (targetHeight <= aViewHeight) {
   1.313 +      // Try to center the element vertically in the new content area, but
   1.314 +      // don't position such that the bottom of the browser view moves above
   1.315 +      // the top of the chrome. We purposely do not resize the browser window
   1.316 +      // by making it taller when trying to center elements that are near the
   1.317 +      // lower bounds. This would trigger reflow which can cause content to
   1.318 +      // shift around.
   1.319 +      let splitMargin = Math.round((aViewHeight - targetHeight) * .5);
   1.320 +      let distanceToPageBounds = viewBottom - aRect.bottom;
   1.321 +      let distanceFromChromeTop = aRect.bottom - aViewHeight;
   1.322 +      let distanceToCenter =
   1.323 +        distanceFromChromeTop + Math.min(distanceToPageBounds, splitMargin);
   1.324 +      return distanceToCenter;
   1.325 +    }
   1.326 +  },
   1.327 +
   1.328 +  /*
   1.329 +   * Local system utilities
   1.330 +   */
   1.331 +
   1.332 +  copyImageToClipboard: function Util_copyImageToClipboard(aImageLoadingContent) {
   1.333 +    let image = aImageLoadingContent.QueryInterface(Ci.nsIImageLoadingContent);
   1.334 +    if (!image) {
   1.335 +      Util.dumpLn("copyImageToClipboard error: image is not an nsIImageLoadingContent");
   1.336 +      return;
   1.337 +    }
   1.338 +    try {
   1.339 +      let xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
   1.340 +      xferable.init(null);
   1.341 +      let imgRequest = aImageLoadingContent.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
   1.342 +      let mimeType = imgRequest.mimeType;
   1.343 +      let imgContainer = imgRequest.image;
   1.344 +      let imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"].createInstance(Ci.nsISupportsInterfacePointer);
   1.345 +      imgPtr.data = imgContainer;
   1.346 +      xferable.setTransferData(mimeType, imgPtr, null);
   1.347 +      let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
   1.348 +      clip.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
   1.349 +    } catch (e) {
   1.350 +      Util.dumpLn(e.message);
   1.351 +    }
   1.352 +  },
   1.353 +};
   1.354 +
   1.355 +
   1.356 +/*
   1.357 + * Timeout
   1.358 + *
   1.359 + * Helper class to nsITimer that adds a little more pizazz.  Callback can be an
   1.360 + * object with a notify method or a function.
   1.361 + */
   1.362 +Util.Timeout = function(aCallback) {
   1.363 +  this._callback = aCallback;
   1.364 +  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.365 +  this._type = null;
   1.366 +};
   1.367 +
   1.368 +Util.Timeout.prototype = {
   1.369 +  // Timer callback. Don't call this manually.
   1.370 +  notify: function notify() {
   1.371 +    if (this._type == this._timer.TYPE_ONE_SHOT)
   1.372 +      this._type = null;
   1.373 +
   1.374 +    if (this._callback.notify)
   1.375 +      this._callback.notify();
   1.376 +    else
   1.377 +      this._callback.apply(null);
   1.378 +  },
   1.379 +
   1.380 +  // Helper function for once and interval.
   1.381 +  _start: function _start(aDelay, aType, aCallback) {
   1.382 +    if (aCallback)
   1.383 +      this._callback = aCallback;
   1.384 +    this.clear();
   1.385 +    this._timer.initWithCallback(this, aDelay, aType);
   1.386 +    this._type = aType;
   1.387 +    return this;
   1.388 +  },
   1.389 +
   1.390 +  // Do the callback once.  Cancels other timeouts on this object.
   1.391 +  once: function once(aDelay, aCallback) {
   1.392 +    return this._start(aDelay, this._timer.TYPE_ONE_SHOT, aCallback);
   1.393 +  },
   1.394 +
   1.395 +  // Do the callback every aDelay msecs. Cancels other timeouts on this object.
   1.396 +  interval: function interval(aDelay, aCallback) {
   1.397 +    return this._start(aDelay, this._timer.TYPE_REPEATING_SLACK, aCallback);
   1.398 +  },
   1.399 +
   1.400 +  // Clear any pending timeouts.
   1.401 +  clear: function clear() {
   1.402 +    if (this.isPending()) {
   1.403 +      this._timer.cancel();
   1.404 +      this._type = null;
   1.405 +    }
   1.406 +    return this;
   1.407 +  },
   1.408 +
   1.409 +  // If there is a pending timeout, call it and cancel the timeout.
   1.410 +  flush: function flush() {
   1.411 +    if (this.isPending()) {
   1.412 +      this.notify();
   1.413 +      this.clear();
   1.414 +    }
   1.415 +    return this;
   1.416 +  },
   1.417 +
   1.418 +  // Return true if we are waiting for a callback.
   1.419 +  isPending: function isPending() {
   1.420 +    return this._type !== null;
   1.421 +  }
   1.422 +};
   1.423 +
   1.424 +// Mixin the ContentUtil module exports
   1.425 +{
   1.426 +  for (let name in ContentUtil) {
   1.427 +    let copy = ContentUtil[name];
   1.428 +    if (copy !== undefined)
   1.429 +      Util[name] = copy;
   1.430 +  }
   1.431 +}
   1.432 +
   1.433 +this.Util = Util;

mercurial