Wed, 31 Dec 2014 06:55:46 +0100
Added tag TORBROWSER_REPLICA for changeset 6474c204b198
1 # -*- Mode: javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 // Services = object with smart getters for common XPCOM services
7 Components.utils.import("resource://gre/modules/Services.jsm");
8 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
9 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
10 Components.utils.import("resource:///modules/RecentWindow.jsm");
12 XPCOMUtils.defineLazyGetter(this, "BROWSER_NEW_TAB_URL", function () {
13 const PREF = "browser.newtab.url";
15 function getNewTabPageURL() {
16 if (!Services.prefs.prefHasUserValue(PREF)) {
17 if (PrivateBrowsingUtils.isWindowPrivate(window) &&
18 !PrivateBrowsingUtils.permanentPrivateBrowsing)
19 return "about:privatebrowsing";
20 }
21 let url = Services.prefs.getComplexValue(PREF, Ci.nsISupportsString).data;
22 return url || "about:blank";
23 }
25 function update() {
26 BROWSER_NEW_TAB_URL = getNewTabPageURL();
27 }
29 Services.prefs.addObserver(PREF, update, false);
31 addEventListener("unload", function onUnload() {
32 removeEventListener("unload", onUnload);
33 Services.prefs.removeObserver(PREF, update);
34 });
36 return getNewTabPageURL();
37 });
39 var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
41 var gBidiUI = false;
43 /**
44 * Determines whether the given url is considered a special URL for new tabs.
45 */
46 function isBlankPageURL(aURL) {
47 return aURL == "about:blank" || aURL == BROWSER_NEW_TAB_URL;
48 }
50 function getBrowserURL()
51 {
52 return "chrome://browser/content/browser.xul";
53 }
55 function getTopWin(skipPopups) {
56 // If this is called in a browser window, use that window regardless of
57 // whether it's the frontmost window, since commands can be executed in
58 // background windows (bug 626148).
59 if (top.document.documentElement.getAttribute("windowtype") == "navigator:browser" &&
60 (!skipPopups || top.toolbar.visible))
61 return top;
63 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
64 return RecentWindow.getMostRecentBrowserWindow({private: isPrivate,
65 allowPopups: !skipPopups});
66 }
68 function openTopWin(url) {
69 /* deprecated */
70 openUILinkIn(url, "current");
71 }
73 function getBoolPref(prefname, def)
74 {
75 try {
76 return Services.prefs.getBoolPref(prefname);
77 }
78 catch(er) {
79 return def;
80 }
81 }
83 /* openUILink handles clicks on UI elements that cause URLs to load.
84 *
85 * As the third argument, you may pass an object with the same properties as
86 * accepted by openUILinkIn, plus "ignoreButton" and "ignoreAlt".
87 */
88 function openUILink(url, event, aIgnoreButton, aIgnoreAlt, aAllowThirdPartyFixup,
89 aPostData, aReferrerURI) {
90 let params;
92 if (aIgnoreButton && typeof aIgnoreButton == "object") {
93 params = aIgnoreButton;
95 // don't forward "ignoreButton" and "ignoreAlt" to openUILinkIn
96 aIgnoreButton = params.ignoreButton;
97 aIgnoreAlt = params.ignoreAlt;
98 delete params.ignoreButton;
99 delete params.ignoreAlt;
100 } else {
101 params = {
102 allowThirdPartyFixup: aAllowThirdPartyFixup,
103 postData: aPostData,
104 referrerURI: aReferrerURI,
105 initiatingDoc: event ? event.target.ownerDocument : null
106 };
107 }
109 let where = whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
110 openUILinkIn(url, where, params);
111 }
114 /* whereToOpenLink() looks at an event to decide where to open a link.
115 *
116 * The event may be a mouse event (click, double-click, middle-click) or keypress event (enter).
117 *
118 * On Windows, the modifiers are:
119 * Ctrl new tab, selected
120 * Shift new window
121 * Ctrl+Shift new tab, in background
122 * Alt save
123 *
124 * Middle-clicking is the same as Ctrl+clicking (it opens a new tab).
125 *
126 * Exceptions:
127 * - Alt is ignored for menu items selected using the keyboard so you don't accidentally save stuff.
128 * (Currently, the Alt isn't sent here at all for menu items, but that will change in bug 126189.)
129 * - Alt is hard to use in context menus, because pressing Alt closes the menu.
130 * - Alt can't be used on the bookmarks toolbar because Alt is used for "treat this as something draggable".
131 * - The button is ignored for the middle-click-paste-URL feature, since it's always a middle-click.
132 */
133 function whereToOpenLink( e, ignoreButton, ignoreAlt )
134 {
135 // This method must treat a null event like a left click without modifier keys (i.e.
136 // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 })
137 // for compatibility purposes.
138 if (!e)
139 return "current";
141 var shift = e.shiftKey;
142 var ctrl = e.ctrlKey;
143 var meta = e.metaKey;
144 var alt = e.altKey && !ignoreAlt;
146 // ignoreButton allows "middle-click paste" to use function without always opening in a new window.
147 var middle = !ignoreButton && e.button == 1;
148 var middleUsesTabs = getBoolPref("browser.tabs.opentabfor.middleclick", true);
150 // Don't do anything special with right-mouse clicks. They're probably clicks on context menu items.
152 #ifdef XP_MACOSX
153 if (meta || (middle && middleUsesTabs))
154 #else
155 if (ctrl || (middle && middleUsesTabs))
156 #endif
157 return shift ? "tabshifted" : "tab";
159 if (alt && getBoolPref("browser.altClickSave", false))
160 return "save";
162 if (shift || (middle && !middleUsesTabs))
163 return "window";
165 return "current";
166 }
168 /* openUILinkIn opens a URL in a place specified by the parameter |where|.
169 *
170 * |where| can be:
171 * "current" current tab (if there aren't any browser windows, then in a new window instead)
172 * "tab" new tab (if there aren't any browser windows, then in a new window instead)
173 * "tabshifted" same as "tab" but in background if default is to select new tabs, and vice versa
174 * "window" new window
175 * "save" save to disk (with no filename hint!)
176 *
177 * aAllowThirdPartyFixup controls whether third party services such as Google's
178 * I Feel Lucky are allowed to interpret this URL. This parameter may be
179 * undefined, which is treated as false.
180 *
181 * Instead of aAllowThirdPartyFixup, you may also pass an object with any of
182 * these properties:
183 * allowThirdPartyFixup (boolean)
184 * postData (nsIInputStream)
185 * referrerURI (nsIURI)
186 * relatedToCurrent (boolean)
187 * skipTabAnimation (boolean)
188 */
189 function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) {
190 var params;
192 if (arguments.length == 3 && typeof arguments[2] == "object") {
193 params = aAllowThirdPartyFixup;
194 } else {
195 params = {
196 allowThirdPartyFixup: aAllowThirdPartyFixup,
197 postData: aPostData,
198 referrerURI: aReferrerURI
199 };
200 }
202 params.fromChrome = true;
204 openLinkIn(url, where, params);
205 }
207 function openLinkIn(url, where, params) {
208 if (!where || !url)
209 return;
211 var aFromChrome = params.fromChrome;
212 var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
213 var aPostData = params.postData;
214 var aCharset = params.charset;
215 var aReferrerURI = params.referrerURI;
216 var aRelatedToCurrent = params.relatedToCurrent;
217 var aDisableMCB = params.disableMCB;
218 var aInBackground = params.inBackground;
219 var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
220 var aInitiatingDoc = params.initiatingDoc;
221 var aIsPrivate = params.private;
222 var aSkipTabAnimation = params.skipTabAnimation;
224 if (where == "save") {
225 if (!aInitiatingDoc) {
226 Components.utils.reportError("openUILink/openLinkIn was called with " +
227 "where == 'save' but without initiatingDoc. See bug 814264.");
228 return;
229 }
230 saveURL(url, null, null, true, null, aReferrerURI, aInitiatingDoc);
231 return;
232 }
233 const Cc = Components.classes;
234 const Ci = Components.interfaces;
236 var w = getTopWin();
237 if ((where == "tab" || where == "tabshifted") &&
238 w && !w.toolbar.visible) {
239 w = getTopWin(true);
240 aRelatedToCurrent = false;
241 }
243 if (!w || where == "window") {
244 var sa = Cc["@mozilla.org/supports-array;1"].
245 createInstance(Ci.nsISupportsArray);
247 var wuri = Cc["@mozilla.org/supports-string;1"].
248 createInstance(Ci.nsISupportsString);
249 wuri.data = url;
251 let charset = null;
252 if (aCharset) {
253 charset = Cc["@mozilla.org/supports-string;1"]
254 .createInstance(Ci.nsISupportsString);
255 charset.data = "charset=" + aCharset;
256 }
258 var allowThirdPartyFixupSupports = Cc["@mozilla.org/supports-PRBool;1"].
259 createInstance(Ci.nsISupportsPRBool);
260 allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
262 sa.AppendElement(wuri);
263 sa.AppendElement(charset);
264 sa.AppendElement(aReferrerURI);
265 sa.AppendElement(aPostData);
266 sa.AppendElement(allowThirdPartyFixupSupports);
268 let features = "chrome,dialog=no,all";
269 if (aIsPrivate) {
270 features += ",private";
271 }
273 Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
274 return;
275 }
277 let loadInBackground = where == "current" ? false : aInBackground;
278 if (loadInBackground == null) {
279 loadInBackground = aFromChrome ?
280 false :
281 getBoolPref("browser.tabs.loadInBackground");
282 }
284 if (where == "current" && w.gBrowser.selectedTab.pinned) {
285 try {
286 let uriObj = Services.io.newURI(url, null, null);
287 if (!uriObj.schemeIs("javascript") &&
288 w.gBrowser.currentURI.host != uriObj.host) {
289 where = "tab";
290 loadInBackground = false;
291 }
292 } catch (err) {
293 where = "tab";
294 loadInBackground = false;
295 }
296 }
298 // Raise the target window before loading the URI, since loading it may
299 // result in a new frontmost window (e.g. "javascript:window.open('');").
300 w.focus();
302 switch (where) {
303 case "current":
304 let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
305 if (aAllowThirdPartyFixup) {
306 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
307 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
308 }
309 if (aDisallowInheritPrincipal)
310 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
311 w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData);
312 break;
313 case "tabshifted":
314 loadInBackground = !loadInBackground;
315 // fall through
316 case "tab":
317 let browser = w.gBrowser;
318 browser.loadOneTab(url, {
319 referrerURI: aReferrerURI,
320 charset: aCharset,
321 postData: aPostData,
322 inBackground: loadInBackground,
323 allowThirdPartyFixup: aAllowThirdPartyFixup,
324 relatedToCurrent: aRelatedToCurrent,
325 skipAnimation: aSkipTabAnimation,
326 disableMCB: aDisableMCB});
327 break;
328 }
330 w.gBrowser.selectedBrowser.focus();
332 if (!loadInBackground && w.isBlankPageURL(url))
333 w.focusAndSelectUrlBar();
334 }
336 // Used as an onclick handler for UI elements with link-like behavior.
337 // e.g. onclick="checkForMiddleClick(this, event);"
338 function checkForMiddleClick(node, event) {
339 // We should be using the disabled property here instead of the attribute,
340 // but some elements that this function is used with don't support it (e.g.
341 // menuitem).
342 if (node.getAttribute("disabled") == "true")
343 return; // Do nothing
345 if (event.button == 1) {
346 /* Execute the node's oncommand or command.
347 *
348 * XXX: we should use node.oncommand(event) once bug 246720 is fixed.
349 */
350 var target = node.hasAttribute("oncommand") ? node :
351 node.ownerDocument.getElementById(node.getAttribute("command"));
352 var fn = new Function("event", target.getAttribute("oncommand"));
353 fn.call(target, event);
355 // If the middle-click was on part of a menu, close the menu.
356 // (Menus close automatically with left-click but not with middle-click.)
357 closeMenus(event.target);
358 }
359 }
361 // Closes all popups that are ancestors of the node.
362 function closeMenus(node)
363 {
364 if ("tagName" in node) {
365 if (node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
366 && (node.tagName == "menupopup" || node.tagName == "popup"))
367 node.hidePopup();
369 closeMenus(node.parentNode);
370 }
371 }
373 // Gather all descendent text under given document node.
374 function gatherTextUnder ( root )
375 {
376 var text = "";
377 var node = root.firstChild;
378 var depth = 1;
379 while ( node && depth > 0 ) {
380 // See if this node is text.
381 if ( node.nodeType == Node.TEXT_NODE ) {
382 // Add this text to our collection.
383 text += " " + node.data;
384 } else if ( node instanceof HTMLImageElement) {
385 // If it has an "alt" attribute, add that.
386 var altText = node.getAttribute( "alt" );
387 if ( altText && altText != "" ) {
388 text += " " + altText;
389 }
390 }
391 // Find next node to test.
392 // First, see if this node has children.
393 if ( node.hasChildNodes() ) {
394 // Go to first child.
395 node = node.firstChild;
396 depth++;
397 } else {
398 // No children, try next sibling (or parent next sibling).
399 while ( depth > 0 && !node.nextSibling ) {
400 node = node.parentNode;
401 depth--;
402 }
403 if ( node.nextSibling ) {
404 node = node.nextSibling;
405 }
406 }
407 }
408 // Strip leading and tailing whitespace.
409 text = text.trim();
410 // Compress remaining whitespace.
411 text = text.replace( /\s+/g, " " );
412 return text;
413 }
415 function getShellService()
416 {
417 var shell = null;
418 try {
419 shell = Components.classes["@mozilla.org/browser/shell-service;1"]
420 .getService(Components.interfaces.nsIShellService);
421 } catch (e) {
422 }
423 return shell;
424 }
426 function isBidiEnabled() {
427 // first check the pref.
428 if (getBoolPref("bidi.browser.ui", false))
429 return true;
431 // then check intl.uidirection.<locale>
432 var chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"].
433 getService(Components.interfaces.nsIXULChromeRegistry);
434 if (chromeReg.isLocaleRTL("global"))
435 return true;
437 // now see if the system locale is an RTL one.
438 var rv = false;
440 try {
441 var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
442 .getService(Components.interfaces.nsILocaleService);
443 var systemLocale = localeService.getSystemLocale().getCategory("NSILOCALE_CTYPE").substr(0,3);
445 switch (systemLocale) {
446 case "ar-":
447 case "he-":
448 case "fa-":
449 case "ug-":
450 case "ur-":
451 case "syr":
452 rv = true;
453 Services.prefs.setBoolPref("bidi.browser.ui", true);
454 }
455 } catch (e) {}
457 return rv;
458 }
460 function openAboutDialog() {
461 var enumerator = Services.wm.getEnumerator("Browser:About");
462 while (enumerator.hasMoreElements()) {
463 // Only open one about window (Bug 599573)
464 let win = enumerator.getNext();
465 if (win.closed) {
466 continue;
467 }
468 win.focus();
469 return;
470 }
472 #ifdef XP_WIN
473 var features = "chrome,centerscreen,dependent";
474 #elifdef XP_MACOSX
475 var features = "chrome,resizable=no,minimizable=no";
476 #else
477 var features = "chrome,centerscreen,dependent,dialog=no";
478 #endif
479 window.openDialog("chrome://browser/content/aboutDialog.xul", "", features);
480 }
482 function openPreferences(paneID, extraArgs)
483 {
484 function switchToAdvancedSubPane(doc) {
485 if (extraArgs && extraArgs["advancedTab"]) {
486 let advancedPaneTabs = doc.getElementById("advancedPrefs");
487 advancedPaneTabs.selectedTab = doc.getElementById(extraArgs["advancedTab"]);
488 }
489 }
491 if (getBoolPref("browser.preferences.inContent")) {
492 let win = Services.wm.getMostRecentWindow("navigator:browser");
493 if (!win) {
494 return;
495 }
497 let newLoad = !win.switchToTabHavingURI("about:preferences", true);
498 let browser = win.gBrowser.selectedBrowser;
500 function switchToPane() {
501 if (paneID) {
502 browser.contentWindow.selectCategory(paneID);
503 }
504 switchToAdvancedSubPane(browser.contentDocument);
505 }
507 if (newLoad) {
508 browser.addEventListener("load", function onload() {
509 browser.removeEventListener("load", onload, true);
510 switchToPane();
511 }, true);
512 } else {
513 switchToPane();
514 }
515 } else {
516 var instantApply = getBoolPref("browser.preferences.instantApply", false);
517 var features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal");
519 var win = Services.wm.getMostRecentWindow("Browser:Preferences");
520 if (win) {
521 win.focus();
522 if (paneID) {
523 var pane = win.document.getElementById(paneID);
524 win.document.documentElement.showPane(pane);
525 }
527 switchToAdvancedSubPane(win.document);
528 } else {
529 openDialog("chrome://browser/content/preferences/preferences.xul",
530 "Preferences", features, paneID, extraArgs);
531 }
532 }
533 }
535 function openAdvancedPreferences(tabID)
536 {
537 openPreferences("paneAdvanced", { "advancedTab" : tabID });
538 }
540 /**
541 * Opens the troubleshooting information (about:support) page for this version
542 * of the application.
543 */
544 function openTroubleshootingPage()
545 {
546 openUILinkIn("about:support", "tab");
547 }
549 #ifdef MOZ_SERVICES_HEALTHREPORT
550 /**
551 * Opens the troubleshooting information (about:support) page for this version
552 * of the application.
553 */
554 function openHealthReport()
555 {
556 openUILinkIn("about:healthreport", "tab");
557 }
558 #endif
560 /**
561 * Opens the feedback page for this version of the application.
562 */
563 function openFeedbackPage()
564 {
565 var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
566 .getService(Components.interfaces.nsIURLFormatter)
567 .formatURLPref("app.feedback.baseURL");
568 openUILinkIn(url, "tab");
569 }
571 function openTourPage()
572 {
573 let scope = {}
574 Components.utils.import("resource:///modules/UITour.jsm", scope);
575 openUILinkIn(scope.UITour.url, "tab");
576 }
578 function buildHelpMenu()
579 {
580 // Enable/disable the "Report Web Forgery" menu item.
581 if (typeof gSafeBrowsing != "undefined")
582 gSafeBrowsing.setReportPhishingMenu();
583 }
585 function isElementVisible(aElement)
586 {
587 if (!aElement)
588 return false;
590 // If aElement or a direct or indirect parent is hidden or collapsed,
591 // height, width or both will be 0.
592 var bo = aElement.boxObject;
593 return (bo.height > 0 && bo.width > 0);
594 }
596 function makeURLAbsolute(aBase, aUrl)
597 {
598 // Note: makeURI() will throw if aUri is not a valid URI
599 return makeURI(aUrl, null, makeURI(aBase)).spec;
600 }
602 /**
603 * openNewTabWith: opens a new tab with the given URL.
604 *
605 * @param aURL
606 * The URL to open (as a string).
607 * @param aDocument
608 * Note this parameter is now ignored. There is no security check & no
609 * referrer header derived from aDocument (null case).
610 * @param aPostData
611 * Form POST data, or null.
612 * @param aEvent
613 * The triggering event (for the purpose of determining whether to open
614 * in the background), or null.
615 * @param aAllowThirdPartyFixup
616 * If true, then we allow the URL text to be sent to third party services
617 * (e.g., Google's I Feel Lucky) for interpretation. This parameter may
618 * be undefined in which case it is treated as false.
619 * @param [optional] aReferrer
620 * This will be used as the referrer. There will be no security check.
621 */
622 function openNewTabWith(aURL, aDocument, aPostData, aEvent,
623 aAllowThirdPartyFixup, aReferrer) {
625 // As in openNewWindowWith(), we want to pass the charset of the
626 // current document over to a new tab.
627 let originCharset = null;
628 if (document.documentElement.getAttribute("windowtype") == "navigator:browser")
629 originCharset = gBrowser.selectedBrowser.characterSet;
631 openLinkIn(aURL, aEvent && aEvent.shiftKey ? "tabshifted" : "tab",
632 { charset: originCharset,
633 postData: aPostData,
634 allowThirdPartyFixup: aAllowThirdPartyFixup,
635 referrerURI: aReferrer });
636 }
638 /**
639 * @param aDocument
640 * Note this parameter is ignored. See openNewTabWith()
641 */
642 function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer) {
643 // Extract the current charset menu setting from the current document and
644 // use it to initialize the new browser window...
645 let originCharset = null;
646 if (document.documentElement.getAttribute("windowtype") == "navigator:browser")
647 originCharset = gBrowser.selectedBrowser.characterSet;
649 openLinkIn(aURL, "window",
650 { charset: originCharset,
651 postData: aPostData,
652 allowThirdPartyFixup: aAllowThirdPartyFixup,
653 referrerURI: aReferrer });
654 }
656 // aCalledFromModal is optional
657 function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
658 var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
659 .getService(Components.interfaces.nsIURLFormatter)
660 .formatURLPref("app.support.baseURL");
661 url += aHelpTopic;
663 var where = aWhere;
664 if (!aWhere)
665 where = aCalledFromModal ? "window" : "tab";
667 openUILinkIn(url, where);
668 }
670 function openPrefsHelp() {
671 // non-instant apply prefwindows are usually modal, so we can't open in the topmost window,
672 // since its probably behind the window.
673 var instantApply = getBoolPref("browser.preferences.instantApply");
675 var helpTopic = document.getElementsByTagName("prefwindow")[0].currentPane.helpTopic;
676 openHelpLink(helpTopic, !instantApply);
677 }
679 function trimURL(aURL) {
680 // This function must not modify the given URL such that calling
681 // nsIURIFixup::createFixupURI with the result will produce a different URI.
682 return aURL /* remove single trailing slash for http/https/ftp URLs */
683 .replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
684 /* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
685 .replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
686 }