Wed, 31 Dec 2014 06:55:50 +0100
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;