1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
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 var { AppConstants } = ChromeUtils.importESModule(
8 "resource://gre/modules/AppConstants.sys.mjs"
10 var { XPCOMUtils } = ChromeUtils.importESModule(
11 "resource://gre/modules/XPCOMUtils.sys.mjs"
14 ChromeUtils.defineESModuleGetters(this, {
15 AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
16 BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
17 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
18 ContextualIdentityService:
19 "resource://gre/modules/ContextualIdentityService.sys.mjs",
20 ExtensionSettingsStore:
21 "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
22 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
23 ReportBrokenSite: "resource:///modules/ReportBrokenSite.sys.mjs",
24 ShellService: "resource:///modules/ShellService.sys.mjs",
25 URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
28 ChromeUtils.defineLazyGetter(this, "ReferrerInfo", () =>
29 Components.Constructor(
30 "@mozilla.org/referrer-info;1",
36 Object.defineProperty(this, "BROWSER_NEW_TAB_URL", {
39 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
41 !PrivateBrowsingUtils.permanentPrivateBrowsing &&
42 !AboutNewTab.newTabURLOverridden
44 return "about:privatebrowsing";
46 // If an extension controls the setting and does not have private
47 // browsing permission, use the default setting.
48 let extensionControlled = Services.prefs.getBoolPref(
49 "browser.newtab.extensionControlled",
52 let privateAllowed = Services.prefs.getBoolPref(
53 "browser.newtab.privateAllowed",
56 // There is a potential on upgrade that the prefs are not set yet, so we double check
60 (extensionControlled ||
61 AboutNewTab.newTabURL.startsWith("moz-extension://"))
63 return "about:privatebrowsing";
66 return AboutNewTab.newTabURL;
70 var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
75 * Determines whether the given url is considered a special URL for new tabs.
77 function isBlankPageURL(aURL) {
79 aURL == "about:blank" ||
80 aURL == "about:home" ||
81 aURL == BROWSER_NEW_TAB_URL ||
82 aURL == "chrome://browser/content/blanktab.html"
86 function doGetProtocolFlags(aURI) {
87 return Services.io.getDynamicProtocolFlags(aURI);
95 aAllowThirdPartyFixup,
99 return URILoadingHelper.openUILink(
105 aAllowThirdPartyFixup,
111 // This is here for historical reasons. bug 1742889 covers cleaning this up.
112 function getRootEvent(aEvent) {
113 return BrowserUtils.getRootEvent(aEvent);
116 // This is here for historical reasons. bug 1742889 covers cleaning this up.
117 function whereToOpenLink(e, ignoreButton, ignoreAlt) {
118 return BrowserUtils.whereToOpenLink(e, ignoreButton, ignoreAlt);
121 function openTrustedLinkIn(url, where, params) {
122 URILoadingHelper.openTrustedLinkIn(window, url, where, params);
125 function openWebLinkIn(url, where, params) {
126 URILoadingHelper.openWebLinkIn(window, url, where, params);
129 function openLinkIn(url, where, params) {
130 return URILoadingHelper.openLinkIn(window, url, where, params);
133 // Used as an onclick handler for UI elements with link-like behavior.
134 // e.g. onclick="checkForMiddleClick(this, event);"
135 // Not needed for menuitems because those fire command events even on middle clicks.
136 function checkForMiddleClick(node, event) {
137 // We should be using the disabled property here instead of the attribute,
138 // but some elements that this function is used with don't support it (e.g.
140 if (node.getAttribute("disabled") == "true") {
144 if (event.target.tagName == "menuitem") {
145 // Menu items fire command on middle-click by themselves.
149 if (event.button == 1) {
150 /* Execute the node's oncommand or command.
153 let cmdEvent = document.createEvent("xulcommandevent");
154 cmdEvent.initCommandEvent(
168 node.dispatchEvent(cmdEvent);
170 // Stop the propagation of the click event, to prevent the event from being
171 // handled more than once.
172 // E.g. see https://bugzilla.mozilla.org/show_bug.cgi?id=1657992#c4
173 event.stopPropagation();
174 event.preventDefault();
176 // If the middle-click was on part of a menu, close the menu.
177 // (Menus close automatically with left-click but not with middle-click.)
178 closeMenus(event.target);
182 // Populate a menu with user-context menu items. This method should be called
183 // by onpopupshowing passing the event as first argument.
184 function createUserContextMenu(
187 isContextMenu = false,
188 excludeUserContextId = 0,
189 showDefaultTab = false,
190 useAccessKeys = true,
193 while (event.target.hasChildNodes()) {
194 event.target.firstChild.remove();
197 MozXULElement.insertFTLIfNeeded("toolkit/global/contextual-identity.ftl");
198 let docfrag = document.createDocumentFragment();
200 // If we are excluding a userContextId, we want to add a 'no-container' item.
201 if (excludeUserContextId || showDefaultTab) {
202 let menuitem = document.createXULElement("menuitem");
204 document.l10n.setAttributes(menuitem, "user-context-none");
207 ContextualIdentityService.formatContextLabel("user-context-none");
208 menuitem.setAttribute("label", label);
210 menuitem.setAttribute("data-usercontextid", "0");
211 if (!isContextMenu) {
212 menuitem.setAttribute("command", "Browser:NewUserContextTab");
215 docfrag.appendChild(menuitem);
217 let menuseparator = document.createXULElement("menuseparator");
218 docfrag.appendChild(menuseparator);
221 ContextualIdentityService.getPublicIdentities().forEach(identity => {
222 if (identity.userContextId == excludeUserContextId) {
226 let menuitem = document.createXULElement("menuitem");
227 menuitem.setAttribute("data-usercontextid", identity.userContextId);
229 menuitem.setAttribute("label", identity.name);
230 } else if (useAccessKeys) {
231 document.l10n.setAttributes(menuitem, identity.l10nId);
233 const label = ContextualIdentityService.formatContextLabel(
236 menuitem.setAttribute("label", label);
239 menuitem.classList.add("menuitem-iconic");
240 menuitem.classList.add("identity-color-" + identity.color);
242 if (!isContextMenu) {
243 menuitem.setAttribute("command", "Browser:NewUserContextTab");
246 menuitem.classList.add("identity-icon-" + identity.icon);
248 docfrag.appendChild(menuitem);
251 if (!isContextMenu) {
252 docfrag.appendChild(document.createXULElement("menuseparator"));
254 let menuitem = document.createXULElement("menuitem");
256 document.l10n.setAttributes(menuitem, "user-context-manage-containers");
258 const label = ContextualIdentityService.formatContextLabel(
259 "user-context-manage-containers"
261 menuitem.setAttribute("label", label);
263 menuitem.setAttribute("command", "Browser:OpenAboutContainers");
264 docfrag.appendChild(menuitem);
267 event.target.appendChild(docfrag);
271 // Closes all popups that are ancestors of the node.
272 function closeMenus(node) {
273 if ("tagName" in node) {
276 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" &&
277 (node.tagName == "menupopup" || node.tagName == "popup")
282 closeMenus(node.parentNode);
286 /** This function takes in a key element and compares it to the keys pressed during an event.
289 * The KeyboardEvent event you want to compare against your key.
292 * The <key> element checked to see if it was called in aEvent.
293 * For example, aKey can be a variable set to document.getElementById("key_close")
294 * to check if the close command key was pressed in aEvent.
296 function eventMatchesKey(aEvent, aKey) {
297 let keyPressed = (aKey.getAttribute("key") || "").toLowerCase();
298 let keyModifiers = aKey.getAttribute("modifiers");
299 let modifiers = ["Alt", "Control", "Meta", "Shift"];
301 if (aEvent.key != keyPressed) {
304 let eventModifiers = modifiers.filter(modifier =>
305 aEvent.getModifierState(modifier)
307 // Check if aEvent has a modifier and aKey doesn't
308 if (eventModifiers.length && !keyModifiers.length) {
311 // Check whether aKey's modifiers match aEvent's modifiers
313 keyModifiers = keyModifiers.split(/[\s,]+/);
314 // Capitalize first letter of aKey's modifers to compare to aEvent's modifier
315 keyModifiers.forEach(function (modifier, index) {
316 if (modifier == "accel") {
317 keyModifiers[index] =
318 AppConstants.platform == "macosx" ? "Meta" : "Control";
320 keyModifiers[index] = modifier[0].toUpperCase() + modifier.slice(1);
323 return modifiers.every(
325 keyModifiers.includes(modifier) == aEvent.getModifierState(modifier)
331 // Gather all descendent text under given document node.
332 function gatherTextUnder(root) {
334 var node = root.firstChild;
336 while (node && depth > 0) {
337 // See if this node is text.
338 if (node.nodeType == Node.TEXT_NODE) {
339 // Add this text to our collection.
340 text += " " + node.data;
341 } else if (HTMLImageElement.isInstance(node)) {
342 // If it has an "alt" attribute, add that.
343 var altText = node.getAttribute("alt");
345 text += " " + altText;
348 // Find next node to test.
349 // First, see if this node has children.
350 if (node.hasChildNodes()) {
351 // Go to first child.
352 node = node.firstChild;
355 // No children, try next sibling (or parent next sibling).
356 while (depth > 0 && !node.nextSibling) {
357 node = node.parentNode;
360 if (node.nextSibling) {
361 node = node.nextSibling;
365 // Strip leading and tailing whitespace.
367 // Compress remaining whitespace.
368 text = text.replace(/\s+/g, " ");
372 // This function exists for legacy reasons.
373 function getShellService() {
377 function isBidiEnabled() {
378 // first check the pref.
379 if (Services.prefs.getBoolPref("bidi.browser.ui", false)) {
383 // now see if the app locale is an RTL one.
384 const isRTL = Services.locale.isAppLocaleRTL;
387 Services.prefs.setBoolPref("bidi.browser.ui", true);
392 function openAboutDialog() {
393 for (let win of Services.wm.getEnumerator("Browser:About")) {
394 // Only open one about window (Bug 599573)
402 var features = "chrome,";
403 if (AppConstants.platform == "win") {
404 features += "centerscreen,dependent";
405 } else if (AppConstants.platform == "macosx") {
406 features += "centerscreen,resizable=no,minimizable=no";
408 features += "centerscreen,dependent,dialog=no";
411 window.openDialog("chrome://browser/content/aboutDialog.xhtml", "", features);
414 async function openPreferences(paneID, extraArgs) {
415 // This function is duplicated from preferences.js.
416 function internalPrefCategoryNameToFriendlyName(aName) {
417 return (aName || "").replace(/^pane./, function (toReplace) {
418 return toReplace[4].toLowerCase();
422 let win = Services.wm.getMostRecentWindow("navigator:browser");
423 let friendlyCategoryName = internalPrefCategoryNameToFriendlyName(paneID);
425 if (extraArgs && extraArgs.urlParams) {
426 params = new URLSearchParams();
427 let urlParams = extraArgs.urlParams;
428 for (let name in urlParams) {
429 if (urlParams[name] !== undefined) {
430 params.set(name, urlParams[name]);
434 let preferencesURLSuffix =
435 (params ? "?" + params : "") +
436 (friendlyCategoryName ? "#" + friendlyCategoryName : "");
440 let windowArguments = Cc["@mozilla.org/array;1"].createInstance(
443 let supportsStringPrefURL = Cc[
444 "@mozilla.org/supports-string;1"
445 ].createInstance(Ci.nsISupportsString);
446 supportsStringPrefURL.data = "about:preferences" + preferencesURLSuffix;
447 windowArguments.appendElement(supportsStringPrefURL);
449 win = Services.ww.openWindow(
451 AppConstants.BROWSER_CHROME_URL,
453 "chrome,dialog=no,all",
457 let shouldReplaceFragment = friendlyCategoryName
458 ? "whenComparingAndReplace"
460 newLoad = !win.switchToTabHavingURI(
461 "about:settings" + preferencesURLSuffix,
464 ignoreFragment: shouldReplaceFragment,
465 replaceQueryString: true,
467 Services.scriptSecurityManager.getSystemPrincipal(),
471 newLoad = !win.switchToTabHavingURI(
472 "about:preferences" + preferencesURLSuffix,
475 ignoreFragment: shouldReplaceFragment,
476 replaceQueryString: true,
478 Services.scriptSecurityManager.getSystemPrincipal(),
482 browser = win.gBrowser.selectedBrowser;
485 if (!newLoad && paneID) {
486 if (browser.contentDocument?.readyState != "complete") {
487 await new Promise(resolve => {
488 browser.addEventListener("load", resolve, {
494 browser.contentWindow.gotoPref(paneID);
499 * Opens the troubleshooting information (about:support) page for this version
500 * of the application.
502 function openTroubleshootingPage() {
503 openTrustedLinkIn("about:support", "tab");
507 * Opens the feedback page for this version of the application.
509 function openFeedbackPage() {
510 var url = Services.urlFormatter.formatURLPref("app.feedback.baseURL");
511 openTrustedLinkIn(url, "tab");
515 * Appends UTM parameters to then opens the SUMO URL for device migration.
517 function openSwitchingDevicesPage() {
518 let url = getHelpLinkURL("switching-devices");
519 let parsedUrl = new URL(url);
520 parsedUrl.searchParams.set("utm_content", "switch-to-new-device");
521 parsedUrl.searchParams.set("utm_source", "help-menu");
522 parsedUrl.searchParams.set("utm_medium", "firefox-desktop");
523 parsedUrl.searchParams.set("utm_campaign", "migration");
524 openTrustedLinkIn(parsedUrl.href, "tab");
527 function buildHelpMenu() {
528 document.getElementById("feedbackPage").disabled =
529 !Services.policies.isAllowed("feedbackCommands");
531 document.getElementById("helpSafeMode").disabled =
532 !Services.policies.isAllowed("safeMode");
534 document.getElementById("troubleShooting").disabled =
535 !Services.policies.isAllowed("aboutSupport");
537 let supportMenu = Services.policies.getSupportMenu();
539 let menuitem = document.getElementById("helpPolicySupport");
540 menuitem.hidden = false;
541 menuitem.setAttribute("label", supportMenu.Title);
542 if ("AccessKey" in supportMenu) {
543 menuitem.setAttribute("accesskey", supportMenu.AccessKey);
545 document.getElementById("helpPolicySeparator").hidden = false;
548 // Enable/disable the "Report Web Forgery" menu item.
549 if (typeof gSafeBrowsing != "undefined") {
550 gSafeBrowsing.setReportPhishingMenu();
554 function isElementVisible(aElement) {
559 // If aElement or a direct or indirect parent is hidden or collapsed,
560 // height, width or both will be 0.
561 var rect = aElement.getBoundingClientRect();
562 return rect.height > 0 && rect.width > 0;
565 function makeURLAbsolute(aBase, aUrl) {
566 // Note: makeURI() will throw if aUri is not a valid URI
567 return makeURI(aUrl, null, makeURI(aBase)).spec;
570 function getHelpLinkURL(aHelpTopic) {
571 var url = Services.urlFormatter.formatURLPref("app.support.baseURL");
572 return url + aHelpTopic;
575 // aCalledFromModal is optional
576 function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
577 var url = getHelpLinkURL(aHelpTopic);
580 where = aCalledFromModal ? "window" : "tab";
583 openTrustedLinkIn(url, where);
586 function openPrefsHelp(aEvent) {
587 let helpTopic = aEvent.target.getAttribute("helpTopic");
588 openHelpLink(helpTopic);