summaryrefslogtreecommitdiff
path: root/browser/base/content/utilityOverlay.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/utilityOverlay.js')
-rw-r--r--browser/base/content/utilityOverlay.js924
1 files changed, 924 insertions, 0 deletions
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
new file mode 100644
index 0000000000..7da54e064d
--- /dev/null
+++ b/browser/base/content/utilityOverlay.js
@@ -0,0 +1,924 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * 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/. */
+
+// Services = object with smart getters for common XPCOM services
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Components.utils.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
+ "resource:///modules/ShellService.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+this.__defineGetter__("BROWSER_NEW_TAB_URL", () => {
+ if (PrivateBrowsingUtils.isWindowPrivate(window) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing &&
+ !aboutNewTabService.overridden) {
+ return "about:privatebrowsing";
+ }
+ return aboutNewTabService.newTabURL;
+});
+
+var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
+
+var gBidiUI = false;
+
+/**
+ * Determines whether the given url is considered a special URL for new tabs.
+ */
+function isBlankPageURL(aURL) {
+ return aURL == "about:blank" || aURL == BROWSER_NEW_TAB_URL;
+}
+
+function getBrowserURL()
+{
+ return "chrome://browser/content/browser.xul";
+}
+
+function getTopWin(skipPopups) {
+ // If this is called in a browser window, use that window regardless of
+ // whether it's the frontmost window, since commands can be executed in
+ // background windows (bug 626148).
+ if (top.document.documentElement.getAttribute("windowtype") == "navigator:browser" &&
+ (!skipPopups || top.toolbar.visible))
+ return top;
+
+ let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ return RecentWindow.getMostRecentBrowserWindow({private: isPrivate,
+ allowPopups: !skipPopups});
+}
+
+function openTopWin(url) {
+ /* deprecated */
+ openUILinkIn(url, "current");
+}
+
+function getBoolPref(prefname, def)
+{
+ try {
+ return Services.prefs.getBoolPref(prefname);
+ }
+ catch (er) {
+ return def;
+ }
+}
+
+/* openUILink handles clicks on UI elements that cause URLs to load.
+ *
+ * As the third argument, you may pass an object with the same properties as
+ * accepted by openUILinkIn, plus "ignoreButton" and "ignoreAlt".
+ */
+function openUILink(url, event, aIgnoreButton, aIgnoreAlt, aAllowThirdPartyFixup,
+ aPostData, aReferrerURI) {
+ let params;
+
+ if (aIgnoreButton && typeof aIgnoreButton == "object") {
+ params = aIgnoreButton;
+
+ // don't forward "ignoreButton" and "ignoreAlt" to openUILinkIn
+ aIgnoreButton = params.ignoreButton;
+ aIgnoreAlt = params.ignoreAlt;
+ delete params.ignoreButton;
+ delete params.ignoreAlt;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerURI: aReferrerURI,
+ referrerPolicy: Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ initiatingDoc: event ? event.target.ownerDocument : null,
+ };
+ }
+
+ let where = whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
+ openUILinkIn(url, where, params);
+}
+
+
+/* whereToOpenLink() looks at an event to decide where to open a link.
+ *
+ * The event may be a mouse event (click, double-click, middle-click) or keypress event (enter).
+ *
+ * On Windows, the modifiers are:
+ * Ctrl new tab, selected
+ * Shift new window
+ * Ctrl+Shift new tab, in background
+ * Alt save
+ *
+ * Middle-clicking is the same as Ctrl+clicking (it opens a new tab).
+ *
+ * Exceptions:
+ * - Alt is ignored for menu items selected using the keyboard so you don't accidentally save stuff.
+ * (Currently, the Alt isn't sent here at all for menu items, but that will change in bug 126189.)
+ * - Alt is hard to use in context menus, because pressing Alt closes the menu.
+ * - Alt can't be used on the bookmarks toolbar because Alt is used for "treat this as something draggable".
+ * - The button is ignored for the middle-click-paste-URL feature, since it's always a middle-click.
+ */
+function whereToOpenLink( e, ignoreButton, ignoreAlt )
+{
+ // This method must treat a null event like a left click without modifier keys (i.e.
+ // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 })
+ // for compatibility purposes.
+ if (!e)
+ return "current";
+
+ var shift = e.shiftKey;
+ var ctrl = e.ctrlKey;
+ var meta = e.metaKey;
+ var alt = e.altKey && !ignoreAlt;
+
+ // ignoreButton allows "middle-click paste" to use function without always opening in a new window.
+ var middle = !ignoreButton && e.button == 1;
+ var middleUsesTabs = getBoolPref("browser.tabs.opentabfor.middleclick", true);
+
+ // Don't do anything special with right-mouse clicks. They're probably clicks on context menu items.
+
+ var metaKey = AppConstants.platform == "macosx" ? meta : ctrl;
+ if (metaKey || (middle && middleUsesTabs))
+ return shift ? "tabshifted" : "tab";
+
+ if (alt && getBoolPref("browser.altClickSave", false))
+ return "save";
+
+ if (shift || (middle && !middleUsesTabs))
+ return "window";
+
+ return "current";
+}
+
+/* openUILinkIn opens a URL in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "current" current tab (if there aren't any browser windows, then in a new window instead)
+ * "tab" new tab (if there aren't any browser windows, then in a new window instead)
+ * "tabshifted" same as "tab" but in background if default is to select new tabs, and vice versa
+ * "window" new window
+ * "save" save to disk (with no filename hint!)
+ *
+ * aAllowThirdPartyFixup controls whether third party services such as Google's
+ * I Feel Lucky are allowed to interpret this URL. This parameter may be
+ * undefined, which is treated as false.
+ *
+ * Instead of aAllowThirdPartyFixup, you may also pass an object with any of
+ * these properties:
+ * allowThirdPartyFixup (boolean)
+ * postData (nsIInputStream)
+ * referrerURI (nsIURI)
+ * relatedToCurrent (boolean)
+ * skipTabAnimation (boolean)
+ * allowPinnedTabHostChange (boolean)
+ * allowPopups (boolean)
+ * userContextId (unsigned int)
+ */
+function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) {
+ var params;
+
+ if (arguments.length == 3 && typeof arguments[2] == "object") {
+ params = aAllowThirdPartyFixup;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerURI: aReferrerURI,
+ referrerPolicy: Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ };
+ }
+
+ params.fromChrome = true;
+
+ openLinkIn(url, where, params);
+}
+
+function openLinkIn(url, where, params) {
+ if (!where || !url)
+ return;
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ var aFromChrome = params.fromChrome;
+ var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ var aPostData = params.postData;
+ var aCharset = params.charset;
+ var aReferrerURI = params.referrerURI;
+ var aReferrerPolicy = ('referrerPolicy' in params ?
+ params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ var aRelatedToCurrent = params.relatedToCurrent;
+ var aAllowMixedContent = params.allowMixedContent;
+ var aInBackground = params.inBackground;
+ var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
+ var aInitiatingDoc = params.initiatingDoc;
+ var aIsPrivate = params.private;
+ var aSkipTabAnimation = params.skipTabAnimation;
+ var aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange;
+ var aNoReferrer = params.noReferrer;
+ var aAllowPopups = !!params.allowPopups;
+ var aUserContextId = params.userContextId;
+ var aIndicateErrorPageLoad = params.indicateErrorPageLoad;
+ var aPrincipal = params.originPrincipal;
+ var aForceAboutBlankViewerInCurrent =
+ params.forceAboutBlankViewerInCurrent;
+
+ // Establish a window in which we're running this code.
+ var w = getTopWin();
+
+ if ((where == "tab" || where == "tabshifted") &&
+ w && !w.toolbar.visible) {
+ w = getTopWin(true);
+ aRelatedToCurrent = false;
+ }
+
+ // Can only do this after we're sure of what |w| will be the rest of this function.
+ // Note that if |w| is null we might have no current browser (we'll open a new window).
+ var aCurrentBrowser = params.currentBrowser || (w && w.gBrowser.selectedBrowser);
+
+ if (where == "save") {
+ // TODO(1073187): propagate referrerPolicy.
+
+ // ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc
+ if ("isContentWindowPrivate" in params) {
+ saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, null, params.isContentWindowPrivate);
+ }
+ else {
+ if (!aInitiatingDoc) {
+ Components.utils.reportError("openUILink/openLinkIn was called with " +
+ "where == 'save' but without initiatingDoc. See bug 814264.");
+ return;
+ }
+ saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, aInitiatingDoc);
+ }
+ return;
+ }
+
+ if (!w || where == "window") {
+ // This propagates to window.arguments.
+ var sa = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+
+ var wuri = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ wuri.data = url;
+
+ let charset = null;
+ if (aCharset) {
+ charset = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ charset.data = "charset=" + aCharset;
+ }
+
+ var allowThirdPartyFixupSupports = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
+
+ var referrerURISupports = null;
+ if (aReferrerURI && !aNoReferrer) {
+ referrerURISupports = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ referrerURISupports.data = aReferrerURI.spec;
+ }
+
+ var referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"].
+ createInstance(Ci.nsISupportsPRUint32);
+ referrerPolicySupports.data = aReferrerPolicy;
+
+ var userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"].
+ createInstance(Ci.nsISupportsPRUint32);
+ userContextIdSupports.data = aUserContextId;
+
+ sa.appendElement(wuri, /* weak =*/ false);
+ sa.appendElement(charset, /* weak =*/ false);
+ sa.appendElement(referrerURISupports, /* weak =*/ false);
+ sa.appendElement(aPostData, /* weak =*/ false);
+ sa.appendElement(allowThirdPartyFixupSupports, /* weak =*/ false);
+ sa.appendElement(referrerPolicySupports, /* weak =*/ false);
+ sa.appendElement(userContextIdSupports, /* weak =*/ false);
+ sa.appendElement(aPrincipal, /* weak =*/ false);
+
+ let features = "chrome,dialog=no,all";
+ if (aIsPrivate) {
+ features += ",private";
+ }
+
+ Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
+ return;
+ }
+
+ let loadInBackground = where == "current" ? false : aInBackground;
+ if (loadInBackground == null) {
+ loadInBackground = aFromChrome ?
+ false :
+ getBoolPref("browser.tabs.loadInBackground");
+ }
+
+ let uriObj;
+ if (where == "current") {
+ try {
+ uriObj = Services.io.newURI(url, null, null);
+ } catch (e) {}
+ }
+
+ // We avoid using |w| here because in the 'popup window' case,
+ // if we pass a currentBrowser param |w.gBrowser| might not be the
+ // tabbrowser that contains |aCurrentBrowser|. We really only care
+ // about the tab linked to |aCurrentBrowser|.
+ let tab = aCurrentBrowser.getTabBrowser().getTabForBrowser(aCurrentBrowser);
+ if (where == "current" && tab.pinned &&
+ !aAllowPinnedTabHostChange) {
+ try {
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+ if (!uriObj || (!uriObj.schemeIs("javascript") &&
+ aCurrentBrowser.currentURI.host != uriObj.host)) {
+ where = "tab";
+ loadInBackground = false;
+ }
+ } catch (err) {
+ where = "tab";
+ loadInBackground = false;
+ }
+ }
+
+ // Raise the target window before loading the URI, since loading it may
+ // result in a new frontmost window (e.g. "javascript:window.open('');").
+ w.focus();
+
+ let browserUsedForLoad = null;
+ switch (where) {
+ case "current":
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+
+ // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL isn't supported for javascript URIs,
+ // i.e. it causes them not to load at all. Callers should strip
+ // "javascript:" from pasted strings to protect users from malicious URIs
+ // (see stripUnsafeProtocolOnPaste).
+ if (aDisallowInheritPrincipal && !(uriObj && uriObj.schemeIs("javascript"))) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
+ }
+
+ if (aAllowPopups) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
+ }
+ if (aIndicateErrorPageLoad) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
+ }
+
+ let {URI_INHERITS_SECURITY_CONTEXT} = Ci.nsIProtocolHandler;
+ if (aForceAboutBlankViewerInCurrent &&
+ (!uriObj ||
+ (Services.io.getProtocolFlags(uriObj.scheme) & URI_INHERITS_SECURITY_CONTEXT))) {
+ // Unless we know for sure we're not inheriting principals,
+ // force the about:blank viewer to have the right principal:
+ aCurrentBrowser.createAboutBlankContentViewer(aPrincipal);
+ }
+
+ aCurrentBrowser.loadURIWithFlags(url, {
+ flags: flags,
+ referrerURI: aNoReferrer ? null : aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ postData: aPostData,
+ userContextId: aUserContextId
+ });
+ browserUsedForLoad = aCurrentBrowser;
+ break;
+ case "tabshifted":
+ loadInBackground = !loadInBackground;
+ // fall through
+ case "tab":
+ let tabUsedForLoad = w.gBrowser.loadOneTab(url, {
+ referrerURI: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: loadInBackground,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ relatedToCurrent: aRelatedToCurrent,
+ skipAnimation: aSkipTabAnimation,
+ allowMixedContent: aAllowMixedContent,
+ noReferrer: aNoReferrer,
+ userContextId: aUserContextId,
+ originPrincipal: aPrincipal,
+ });
+ browserUsedForLoad = tabUsedForLoad.linkedBrowser;
+ break;
+ }
+
+ // Focus the content, but only if the browser used for the load is selected.
+ if (browserUsedForLoad &&
+ browserUsedForLoad == browserUsedForLoad.getTabBrowser().selectedBrowser) {
+ browserUsedForLoad.focus();
+ }
+
+ if (!loadInBackground && w.isBlankPageURL(url)) {
+ w.focusAndSelectUrlBar();
+ }
+}
+
+// Used as an onclick handler for UI elements with link-like behavior.
+// e.g. onclick="checkForMiddleClick(this, event);"
+function checkForMiddleClick(node, event) {
+ // We should be using the disabled property here instead of the attribute,
+ // but some elements that this function is used with don't support it (e.g.
+ // menuitem).
+ if (node.getAttribute("disabled") == "true")
+ return; // Do nothing
+
+ if (event.button == 1) {
+ /* Execute the node's oncommand or command.
+ *
+ * XXX: we should use node.oncommand(event) once bug 246720 is fixed.
+ */
+ var target = node.hasAttribute("oncommand") ? node :
+ node.ownerDocument.getElementById(node.getAttribute("command"));
+ var fn = new Function("event", target.getAttribute("oncommand"));
+ fn.call(target, event);
+
+ // If the middle-click was on part of a menu, close the menu.
+ // (Menus close automatically with left-click but not with middle-click.)
+ closeMenus(event.target);
+ }
+}
+
+// Populate a menu with user-context menu items. This method should be called
+// by onpopupshowing passing the event as first argument.
+function createUserContextMenu(event, isContextMenu = false, excludeUserContextId = 0) {
+ while (event.target.hasChildNodes()) {
+ event.target.removeChild(event.target.firstChild);
+ }
+
+ let bundle = document.getElementById("bundle_browser");
+ let docfrag = document.createDocumentFragment();
+
+ // If we are excluding a userContextId, we want to add a 'no-container' item.
+ if (excludeUserContextId) {
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("data-usercontextid", "0");
+ menuitem.setAttribute("label", bundle.getString("userContextNone.label"));
+ menuitem.setAttribute("accesskey", bundle.getString("userContextNone.accesskey"));
+
+ // We don't set an oncommand/command attribute because if we have
+ // to exclude a userContextId we are generating the contextMenu and
+ // isContextMenu will be true.
+
+ docfrag.appendChild(menuitem);
+
+ let menuseparator = document.createElement("menuseparator");
+ docfrag.appendChild(menuseparator);
+ }
+
+ ContextualIdentityService.getIdentities().forEach(identity => {
+ if (identity.userContextId == excludeUserContextId) {
+ return;
+ }
+
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("data-usercontextid", identity.userContextId);
+ menuitem.setAttribute("label", ContextualIdentityService.getUserContextLabel(identity.userContextId));
+
+ if (identity.accessKey) {
+ menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
+ }
+
+ menuitem.classList.add("menuitem-iconic");
+ menuitem.setAttribute("data-identity-color", identity.color);
+
+ if (!isContextMenu) {
+ menuitem.setAttribute("command", "Browser:NewUserContextTab");
+ }
+
+ menuitem.setAttribute("data-identity-icon", identity.icon);
+
+ docfrag.appendChild(menuitem);
+ });
+
+ if (!isContextMenu) {
+ docfrag.appendChild(document.createElement("menuseparator"));
+
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label",
+ bundle.getString("userContext.aboutPage.label"));
+ menuitem.setAttribute("accesskey",
+ bundle.getString("userContext.aboutPage.accesskey"));
+ menuitem.setAttribute("command", "Browser:OpenAboutContainers");
+ docfrag.appendChild(menuitem);
+ }
+
+ event.target.appendChild(docfrag);
+ return true;
+}
+
+// Closes all popups that are ancestors of the node.
+function closeMenus(node)
+{
+ if ("tagName" in node) {
+ if (node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ && (node.tagName == "menupopup" || node.tagName == "popup"))
+ node.hidePopup();
+
+ closeMenus(node.parentNode);
+ }
+}
+
+/** This function takes in a key element and compares it to the keys pressed during an event.
+ *
+ * @param aEvent
+ * The KeyboardEvent event you want to compare against your key.
+ *
+ * @param aKey
+ * The <key> element checked to see if it was called in aEvent.
+ * For example, aKey can be a variable set to document.getElementById("key_close")
+ * to check if the close command key was pressed in aEvent.
+*/
+function eventMatchesKey(aEvent, aKey)
+{
+ let keyPressed = aKey.getAttribute("key").toLowerCase();
+ let keyModifiers = aKey.getAttribute("modifiers");
+ let modifiers = ["Alt", "Control", "Meta", "Shift"];
+
+ if (aEvent.key != keyPressed) {
+ return false;
+ }
+ let eventModifiers = modifiers.filter(modifier => aEvent.getModifierState(modifier));
+ // Check if aEvent has a modifier and aKey doesn't
+ if (eventModifiers.length > 0 && keyModifiers.length == 0) {
+ return false;
+ }
+ // Check whether aKey's modifiers match aEvent's modifiers
+ if (keyModifiers) {
+ keyModifiers = keyModifiers.split(/[\s,]+/);
+ // Capitalize first letter of aKey's modifers to compare to aEvent's modifier
+ keyModifiers.forEach(function(modifier, index) {
+ if (modifier == "accel") {
+ keyModifiers[index] = AppConstants.platform == "macosx" ? "Meta" : "Control";
+ } else {
+ keyModifiers[index] = modifier[0].toUpperCase() + modifier.slice(1);
+ }
+ });
+ return modifiers.every(modifier => keyModifiers.includes(modifier) == aEvent.getModifierState(modifier));
+ }
+ return true;
+}
+
+// Gather all descendent text under given document node.
+function gatherTextUnder ( root )
+{
+ var text = "";
+ var node = root.firstChild;
+ var depth = 1;
+ while ( node && depth > 0 ) {
+ // See if this node is text.
+ if ( node.nodeType == Node.TEXT_NODE ) {
+ // Add this text to our collection.
+ text += " " + node.data;
+ } else if ( node instanceof HTMLImageElement) {
+ // If it has an "alt" attribute, add that.
+ var altText = node.getAttribute( "alt" );
+ if ( altText && altText != "" ) {
+ text += " " + altText;
+ }
+ }
+ // Find next node to test.
+ // First, see if this node has children.
+ if ( node.hasChildNodes() ) {
+ // Go to first child.
+ node = node.firstChild;
+ depth++;
+ } else {
+ // No children, try next sibling (or parent next sibling).
+ while ( depth > 0 && !node.nextSibling ) {
+ node = node.parentNode;
+ depth--;
+ }
+ if ( node.nextSibling ) {
+ node = node.nextSibling;
+ }
+ }
+ }
+ // Strip leading and tailing whitespace.
+ text = text.trim();
+ // Compress remaining whitespace.
+ text = text.replace( /\s+/g, " " );
+ return text;
+}
+
+// This function exists for legacy reasons.
+function getShellService()
+{
+ return ShellService;
+}
+
+function isBidiEnabled() {
+ // first check the pref.
+ if (getBoolPref("bidi.browser.ui", false))
+ return true;
+
+ // then check intl.uidirection.<locale>
+ var chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Components.interfaces.nsIXULChromeRegistry);
+ if (chromeReg.isLocaleRTL("global"))
+ return true;
+
+ // now see if the system locale is an RTL one.
+ var rv = false;
+
+ try {
+ var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
+ .getService(Components.interfaces.nsILocaleService);
+ var systemLocale = localeService.getSystemLocale().getCategory("NSILOCALE_CTYPE").substr(0, 3);
+
+ switch (systemLocale) {
+ case "ar-":
+ case "he-":
+ case "fa-":
+ case "ug-":
+ case "ur-":
+ case "syr":
+ rv = true;
+ Services.prefs.setBoolPref("bidi.browser.ui", true);
+ }
+ } catch (e) {}
+
+ return rv;
+}
+
+function openAboutDialog() {
+ var enumerator = Services.wm.getEnumerator("Browser:About");
+ while (enumerator.hasMoreElements()) {
+ // Only open one about window (Bug 599573)
+ let win = enumerator.getNext();
+ if (win.closed) {
+ continue;
+ }
+ win.focus();
+ return;
+ }
+
+ var features = "chrome,";
+ if (AppConstants.platform == "win") {
+ features += "centerscreen,dependent";
+ } else if (AppConstants.platform == "macosx") {
+ features += "resizable=no,minimizable=no";
+ } else {
+ features += "centerscreen,dependent,dialog=no";
+ }
+
+ window.openDialog("chrome://browser/content/aboutDialog.xul", "", features);
+}
+
+function openPreferences(paneID, extraArgs)
+{
+ function switchToAdvancedSubPane(doc) {
+ if (extraArgs && extraArgs["advancedTab"]) {
+ let advancedPaneTabs = doc.getElementById("advancedPrefs");
+ advancedPaneTabs.selectedTab = doc.getElementById(extraArgs["advancedTab"]);
+ }
+ }
+
+ // This function is duplicated from preferences.js.
+ function internalPrefCategoryNameToFriendlyName(aName) {
+ return (aName || "").replace(/^pane./, function(toReplace) { return toReplace[4].toLowerCase(); });
+ }
+
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let friendlyCategoryName = internalPrefCategoryNameToFriendlyName(paneID);
+ let params;
+ if (extraArgs && extraArgs["urlParams"]) {
+ params = new URLSearchParams();
+ let urlParams = extraArgs["urlParams"];
+ for (let name in urlParams) {
+ if (urlParams[name] !== undefined) {
+ params.set(name, urlParams[name]);
+ }
+ }
+ }
+ let preferencesURL = "about:preferences" + (params ? "?" + params : "") +
+ (friendlyCategoryName ? "#" + friendlyCategoryName : "");
+ let newLoad = true;
+ let browser = null;
+ if (!win) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ let windowArguments = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ let supportsStringPrefURL = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ supportsStringPrefURL.data = preferencesURL;
+ windowArguments.appendElement(supportsStringPrefURL, /* weak =*/ false);
+
+ win = Services.ww.openWindow(null, Services.prefs.getCharPref("browser.chromeURL"),
+ "_blank", "chrome,dialog=no,all", windowArguments);
+ } else {
+ let shouldReplaceFragment = friendlyCategoryName ? "whenComparingAndReplace" : "whenComparing";
+ newLoad = !win.switchToTabHavingURI(preferencesURL, true, { ignoreFragment: shouldReplaceFragment, replaceQueryString: true });
+ browser = win.gBrowser.selectedBrowser;
+ }
+
+ if (newLoad) {
+ Services.obs.addObserver(function advancedPaneLoadedObs(prefWin, topic, data) {
+ if (!browser) {
+ browser = win.gBrowser.selectedBrowser;
+ }
+ if (prefWin != browser.contentWindow) {
+ return;
+ }
+ Services.obs.removeObserver(advancedPaneLoadedObs, "advanced-pane-loaded");
+ switchToAdvancedSubPane(browser.contentDocument);
+ }, "advanced-pane-loaded", false);
+ } else {
+ if (paneID) {
+ browser.contentWindow.gotoPref(paneID);
+ }
+ switchToAdvancedSubPane(browser.contentDocument);
+ }
+}
+
+function openAdvancedPreferences(tabID)
+{
+ openPreferences("paneAdvanced", { "advancedTab" : tabID });
+}
+
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openTroubleshootingPage()
+{
+ openUILinkIn("about:support", "tab");
+}
+
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openHealthReport()
+{
+ openUILinkIn("about:healthreport", "tab");
+}
+
+/**
+ * Opens the feedback page for this version of the application.
+ */
+function openFeedbackPage()
+{
+ var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter)
+ .formatURLPref("app.feedback.baseURL");
+ openUILinkIn(url, "tab");
+}
+
+function openTourPage()
+{
+ let scope = {}
+ Components.utils.import("resource:///modules/UITour.jsm", scope);
+ openUILinkIn(scope.UITour.url, "tab");
+}
+
+function buildHelpMenu()
+{
+ // Enable/disable the "Report Web Forgery" menu item.
+ if (typeof gSafeBrowsing != "undefined") {
+ gSafeBrowsing.setReportPhishingMenu();
+ }
+}
+
+function isElementVisible(aElement)
+{
+ if (!aElement)
+ return false;
+
+ // If aElement or a direct or indirect parent is hidden or collapsed,
+ // height, width or both will be 0.
+ var bo = aElement.boxObject;
+ return (bo.height > 0 && bo.width > 0);
+}
+
+function makeURLAbsolute(aBase, aUrl)
+{
+ // Note: makeURI() will throw if aUri is not a valid URI
+ return makeURI(aUrl, null, makeURI(aBase)).spec;
+}
+
+/**
+ * openNewTabWith: opens a new tab with the given URL.
+ *
+ * @param aURL
+ * The URL to open (as a string).
+ * @param aDocument
+ * Note this parameter is now ignored. There is no security check & no
+ * referrer header derived from aDocument (null case).
+ * @param aPostData
+ * Form POST data, or null.
+ * @param aEvent
+ * The triggering event (for the purpose of determining whether to open
+ * in the background), or null.
+ * @param aAllowThirdPartyFixup
+ * If true, then we allow the URL text to be sent to third party services
+ * (e.g., Google's I Feel Lucky) for interpretation. This parameter may
+ * be undefined in which case it is treated as false.
+ * @param [optional] aReferrer
+ * This will be used as the referrer. There will be no security check.
+ * @param [optional] aReferrerPolicy
+ * Referrer policy - Ci.nsIHttpChannel.REFERRER_POLICY_*.
+ */
+function openNewTabWith(aURL, aDocument, aPostData, aEvent,
+ aAllowThirdPartyFixup, aReferrer, aReferrerPolicy) {
+
+ // As in openNewWindowWith(), we want to pass the charset of the
+ // current document over to a new tab.
+ let originCharset = null;
+ if (document.documentElement.getAttribute("windowtype") == "navigator:browser")
+ originCharset = gBrowser.selectedBrowser.characterSet;
+
+ openLinkIn(aURL, aEvent && aEvent.shiftKey ? "tabshifted" : "tab",
+ { charset: originCharset,
+ postData: aPostData,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ referrerURI: aReferrer,
+ referrerPolicy: aReferrerPolicy,
+ });
+}
+
+/**
+ * @param aDocument
+ * Note this parameter is ignored. See openNewTabWith()
+ */
+function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup,
+ aReferrer, aReferrerPolicy) {
+ // Extract the current charset menu setting from the current document and
+ // use it to initialize the new browser window...
+ let originCharset = null;
+ if (document.documentElement.getAttribute("windowtype") == "navigator:browser")
+ originCharset = gBrowser.selectedBrowser.characterSet;
+
+ openLinkIn(aURL, "window",
+ { charset: originCharset,
+ postData: aPostData,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ referrerURI: aReferrer,
+ referrerPolicy: aReferrerPolicy,
+ });
+}
+
+function getHelpLinkURL(aHelpTopic) {
+ var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter)
+ .formatURLPref("app.support.baseURL");
+ return url + aHelpTopic;
+}
+
+// aCalledFromModal is optional
+function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
+ var url = getHelpLinkURL(aHelpTopic);
+ var where = aWhere;
+ if (!aWhere)
+ where = aCalledFromModal ? "window" : "tab";
+
+ openUILinkIn(url, where);
+}
+
+function openPrefsHelp() {
+ // non-instant apply prefwindows are usually modal, so we can't open in the topmost window,
+ // since its probably behind the window.
+ var instantApply = getBoolPref("browser.preferences.instantApply");
+
+ var helpTopic = document.getElementsByTagName("prefwindow")[0].currentPane.helpTopic;
+ openHelpLink(helpTopic, !instantApply);
+}
+
+function trimURL(aURL) {
+ // This function must not modify the given URL such that calling
+ // nsIURIFixup::createFixupURI with the result will produce a different URI.
+
+ // remove single trailing slash for http/https/ftp URLs
+ let url = aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1");
+
+ // remove http://
+ if (!url.startsWith("http://")) {
+ return url;
+ }
+ let urlWithoutProtocol = url.substring(7);
+
+ let flags = Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP |
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ let fixedUpURL, expectedURLSpec;
+ try {
+ fixedUpURL = Services.uriFixup.createFixupURI(urlWithoutProtocol, flags);
+ expectedURLSpec = makeURI(aURL).spec;
+ } catch (ex) {
+ return url;
+ }
+ if (fixedUpURL.spec == expectedURLSpec) {
+ return urlWithoutProtocol;
+ }
+ return url;
+}