mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
431 lines
13 KiB
JavaScript
431 lines
13 KiB
JavaScript
/* 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;
|