diff -r 000000000000 -r 6474c204b198 browser/metro/base/content/Util.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/metro/base/content/Util.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,430 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let Cc = Components.classes; +let Ci = Components.interfaces; + +Components.utils.import("resource:///modules/ContentUtil.jsm"); + +let Util = { + /* + * General purpose utilities + */ + + getWindowUtils: function getWindowUtils(aWindow) { + return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + }, + + // Put the Mozilla networking code into a state that will kick the + // auto-connection process. + forceOnline: function forceOnline() { + Services.io.offline = false; + }, + + /* + * Timing utilties + */ + + // Executes aFunc after other events have been processed. + executeSoon: function executeSoon(aFunc) { + Services.tm.mainThread.dispatch({ + run: function() { + aFunc(); + } + }, Ci.nsIThread.DISPATCH_NORMAL); + }, + + /* + * Console printing utilities + */ + + // Like dump, but each arg is handled and there's an automatic newline + dumpLn: function dumpLn() { + for (let i = 0; i < arguments.length; i++) + dump(arguments[i] + " "); + dump("\n"); + }, + + /* + * Element utilities + */ + + transitionElementVisibility: function(aNodes, aVisible) { + // accept single node or a collection of nodes + aNodes = aNodes.length ? aNodes : [aNodes]; + let defd = Promise.defer(); + let pending = 0; + Array.forEach(aNodes, function(aNode) { + if (aVisible) { + aNode.hidden = false; + aNode.removeAttribute("fade"); // trigger transition to full opacity + } else { + aNode.setAttribute("fade", true); // trigger transition to 0 opacity + } + aNode.addEventListener("transitionend", function onTransitionEnd(aEvent){ + aNode.removeEventListener("transitionend", onTransitionEnd); + if (!aVisible) { + aNode.hidden = true; + } + pending--; + if (!pending){ + defd.resolve(true); + } + }, false); + pending++; + }); + return defd.promise; + }, + + isTextInput: function isTextInput(aElement) { + return ((aElement instanceof Ci.nsIDOMHTMLInputElement && + aElement.mozIsTextField(false)) || + aElement instanceof Ci.nsIDOMHTMLTextAreaElement); + }, + + /** + * Checks whether aElement's content can be edited either if it(or any of its + * parents) has "contenteditable" attribute set to "true" or aElement's + * ownerDocument is in design mode. + */ + isEditableContent: function isEditableContent(aElement) { + return !!aElement && (aElement.isContentEditable || + this.isOwnerDocumentInDesignMode(aElement)); + + }, + + isEditable: function isEditable(aElement) { + if (!aElement) { + return false; + } + + if (this.isTextInput(aElement) || this.isEditableContent(aElement)) { + return true; + } + + // If a body element is editable and the body is the child of an + // iframe or div we can assume this is an advanced HTML editor + if ((aElement instanceof Ci.nsIDOMHTMLIFrameElement || + aElement instanceof Ci.nsIDOMHTMLDivElement) && + aElement.contentDocument && + this.isEditableContent(aElement.contentDocument.body)) { + return true; + } + + return false; + }, + + /** + * Checks whether aElement's owner document has design mode turned on. + */ + isOwnerDocumentInDesignMode: function(aElement) { + return !!aElement && !!aElement.ownerDocument && + aElement.ownerDocument.designMode == "on"; + }, + + isMultilineInput: function isMultilineInput(aElement) { + return (aElement instanceof Ci.nsIDOMHTMLTextAreaElement); + }, + + isLink: function isLink(aElement) { + return ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) || + (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href) || + aElement instanceof Ci.nsIDOMHTMLLinkElement || + aElement.getAttributeNS(kXLinkNamespace, "type") == "simple"); + }, + + isText: function isText(aElement) { + return (aElement instanceof Ci.nsIDOMHTMLParagraphElement || + aElement instanceof Ci.nsIDOMHTMLDivElement || + aElement instanceof Ci.nsIDOMHTMLLIElement || + aElement instanceof Ci.nsIDOMHTMLPreElement || + aElement instanceof Ci.nsIDOMHTMLHeadingElement || + aElement instanceof Ci.nsIDOMHTMLTableCellElement || + aElement instanceof Ci.nsIDOMHTMLBodyElement); + }, + + /* + * Rect and nsIDOMRect utilities + */ + + getCleanRect: function getCleanRect() { + return { + left: 0, top: 0, right: 0, bottom: 0 + }; + }, + + pointWithinRect: function pointWithinRect(aX, aY, aRect) { + return (aRect.left < aX && aRect.top < aY && + aRect.right > aX && aRect.bottom > aY); + }, + + pointWithinDOMRect: function pointWithinDOMRect(aX, aY, aRect) { + if (!aRect.width || !aRect.height) + return false; + return this.pointWithinRect(aX, aY, aRect); + }, + + isEmptyDOMRect: function isEmptyDOMRect(aRect) { + if ((aRect.bottom - aRect.top) <= 0 && + (aRect.right - aRect.left) <= 0) + return true; + return false; + }, + + // Dumps the details of a dom rect to the console + dumpDOMRect: function dumpDOMRect(aMsg, aRect) { + try { + Util.dumpLn(aMsg, + "left:" + Math.round(aRect.left) + ",", + "top:" + Math.round(aRect.top) + ",", + "right:" + Math.round(aRect.right) + ",", + "bottom:" + Math.round(aRect.bottom) + ",", + "width:" + Math.round(aRect.right - aRect.left) + ",", + "height:" + Math.round(aRect.bottom - aRect.top) ); + } catch (ex) { + Util.dumpLn("dumpDOMRect:", ex.message); + } + }, + + /* + * DownloadUtils.convertByteUnits returns [size, localized-unit-string] + * so they are joined for a single download size string. + */ + getDownloadSize: function dv__getDownloadSize (aSize) { + let [size, units] = DownloadUtils.convertByteUnits(aSize); + if (aSize > 0) + return size + units; + else + return Strings.browser.GetStringFromName("downloadsUnknownSize"); + }, + + /* + * URIs and schemes + */ + + makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) { + return Services.io.newURI(aURL, aOriginCharset, aBaseURI); + }, + + makeURLAbsolute: function makeURLAbsolute(base, url) { + // Note: makeURI() will throw if url is not a valid URI + return this.makeURI(url, null, this.makeURI(base)).spec; + }, + + isLocalScheme: function isLocalScheme(aURL) { + return ((aURL.indexOf("about:") == 0 && + aURL != "about:blank" && + aURL != "about:empty" && + aURL != "about:start") || + aURL.indexOf("chrome:") == 0); + }, + + // Don't display anything in the urlbar for these special URIs. + isURLEmpty: function isURLEmpty(aURL) { + return (!aURL || + aURL == "about:blank" || + aURL == "about:empty" || + aURL == "about:home" || + aURL == "about:newtab" || + aURL.startsWith("about:newtab")); + }, + + // Title to use for emptyURL tabs. + getEmptyURLTabTitle: function getEmptyURLTabTitle() { + let browserStrings = Services.strings.createBundle("chrome://browser/locale/browser.properties"); + + return browserStrings.GetStringFromName("tabs.emptyTabTitle"); + }, + + // Don't remember these pages in the session store. + isURLMemorable: function isURLMemorable(aURL) { + return !(aURL == "about:blank" || + aURL == "about:empty" || + aURL == "about:start"); + }, + + /* + * Math utilities + */ + + clamp: function(num, min, max) { + return Math.max(min, Math.min(max, num)); + }, + + /* + * Screen and layout utilities + */ + + /* + * translateToTopLevelWindow - Given an element potentially within + * a subframe, calculate the offsets up to the top level browser. + */ + translateToTopLevelWindow: function translateToTopLevelWindow(aElement) { + let offsetX = 0; + let offsetY = 0; + let element = aElement; + while (element && + element.ownerDocument && + element.ownerDocument.defaultView != content) { + element = element.ownerDocument.defaultView.frameElement; + let rect = element.getBoundingClientRect(); + offsetX += rect.left; + offsetY += rect.top; + } + let win = null; + if (element == aElement) + win = content; + else + win = element.contentDocument.defaultView; + return { targetWindow: win, offsetX: offsetX, offsetY: offsetY }; + }, + + get displayDPI() { + delete this.displayDPI; + return this.displayDPI = this.getWindowUtils(window).displayDPI; + }, + + /* + * aViewHeight - the height of the viewable area in the browser + * aRect - a bounding rectangle of a selection or element. + * + * return - number of pixels for the browser to be shifted up by such + * that aRect is centered vertically within aViewHeight. + */ + centerElementInView: function centerElementInView(aViewHeight, aRect) { + // If the bottom of the target bounds is higher than the new height, + // there's no need to adjust. It will be above the keyboard. + if (aRect.bottom <= aViewHeight) { + return 0; + } + + // height of the target element + let targetHeight = aRect.bottom - aRect.top; + // height of the browser view. + let viewBottom = content.innerHeight; + + // If the target is shorter than the new content height, we can go ahead + // and center it. + if (targetHeight <= aViewHeight) { + // Try to center the element vertically in the new content area, but + // don't position such that the bottom of the browser view moves above + // the top of the chrome. We purposely do not resize the browser window + // by making it taller when trying to center elements that are near the + // lower bounds. This would trigger reflow which can cause content to + // shift around. + let splitMargin = Math.round((aViewHeight - targetHeight) * .5); + let distanceToPageBounds = viewBottom - aRect.bottom; + let distanceFromChromeTop = aRect.bottom - aViewHeight; + let distanceToCenter = + distanceFromChromeTop + Math.min(distanceToPageBounds, splitMargin); + return distanceToCenter; + } + }, + + /* + * Local system utilities + */ + + copyImageToClipboard: function Util_copyImageToClipboard(aImageLoadingContent) { + let image = aImageLoadingContent.QueryInterface(Ci.nsIImageLoadingContent); + if (!image) { + Util.dumpLn("copyImageToClipboard error: image is not an nsIImageLoadingContent"); + return; + } + try { + let xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable); + xferable.init(null); + let imgRequest = aImageLoadingContent.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); + let mimeType = imgRequest.mimeType; + let imgContainer = imgRequest.image; + let imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"].createInstance(Ci.nsISupportsInterfacePointer); + imgPtr.data = imgContainer; + xferable.setTransferData(mimeType, imgPtr, null); + let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard); + clip.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard); + } catch (e) { + Util.dumpLn(e.message); + } + }, +}; + + +/* + * Timeout + * + * Helper class to nsITimer that adds a little more pizazz. Callback can be an + * object with a notify method or a function. + */ +Util.Timeout = function(aCallback) { + this._callback = aCallback; + this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this._type = null; +}; + +Util.Timeout.prototype = { + // Timer callback. Don't call this manually. + notify: function notify() { + if (this._type == this._timer.TYPE_ONE_SHOT) + this._type = null; + + if (this._callback.notify) + this._callback.notify(); + else + this._callback.apply(null); + }, + + // Helper function for once and interval. + _start: function _start(aDelay, aType, aCallback) { + if (aCallback) + this._callback = aCallback; + this.clear(); + this._timer.initWithCallback(this, aDelay, aType); + this._type = aType; + return this; + }, + + // Do the callback once. Cancels other timeouts on this object. + once: function once(aDelay, aCallback) { + return this._start(aDelay, this._timer.TYPE_ONE_SHOT, aCallback); + }, + + // Do the callback every aDelay msecs. Cancels other timeouts on this object. + interval: function interval(aDelay, aCallback) { + return this._start(aDelay, this._timer.TYPE_REPEATING_SLACK, aCallback); + }, + + // Clear any pending timeouts. + clear: function clear() { + if (this.isPending()) { + this._timer.cancel(); + this._type = null; + } + return this; + }, + + // If there is a pending timeout, call it and cancel the timeout. + flush: function flush() { + if (this.isPending()) { + this.notify(); + this.clear(); + } + return this; + }, + + // Return true if we are waiting for a callback. + isPending: function isPending() { + return this._type !== null; + } +}; + +// Mixin the ContentUtil module exports +{ + for (let name in ContentUtil) { + let copy = ContentUtil[name]; + if (copy !== undefined) + Util[name] = copy; + } +} + +this.Util = Util;