# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. var Ci = Components.interfaces; var Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/RecentWindow.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", "resource:///modules/CharsetMenu.jsm"); const nsIWebNavigation = Ci.nsIWebNavigation; const gToolbarInfoSeparators = ["|", "-"]; var gLastBrowserCharset = null; var gPrevCharset = null; var gProxyFavIcon = null; var gLastValidURLStr = ""; var gInPrintPreviewMode = false; var gContextMenu = null; // nsContextMenu instance var gMultiProcessBrowser = false; #ifndef XP_MACOSX var gEditUIVisible = true; #endif [ ["gBrowser", "content"], ["gNavToolbox", "navigator-toolbox"], ["gURLBar", "urlbar"], ["gNavigatorBundle", "bundle_browser"] ].forEach(function(elementGlobal) { var [name, id] = elementGlobal; window.__defineGetter__(name, function() { var element = document.getElementById(id); if (!element) return null; delete window[name]; return window[name] = element; }); window.__defineSetter__(name, function(val) { delete window[name]; return window[name] = val; }); }); // Smart getter for the findbar. If you don't wish to force the creation of // the findbar, check gFindBarInitialized first. var gFindBarInitialized = false; XPCOMUtils.defineLazyGetter(window, "gFindBar", function() { let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; let findbar = document.createElementNS(XULNS, "findbar"); findbar.id = "FindToolbar"; let browserBottomBox = document.getElementById("browser-bottombox"); browserBottomBox.insertBefore(findbar, browserBottomBox.firstChild); // Force a style flush to ensure that our binding is attached. findbar.clientTop; findbar.browser = gBrowser.mCurrentBrowser; window.gFindBarInitialized = true; return findbar; }); XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"); XPCOMUtils.defineLazyGetter(this, "gPrefService", function() { return Services.prefs; }); this.__defineGetter__("AddonManager", function() { let tmp = {}; Cu.import("resource://gre/modules/AddonManager.jsm", tmp); return this.AddonManager = tmp.AddonManager; }); this.__defineSetter__("AddonManager", function(val) { delete this.AddonManager; return this.AddonManager = val; }); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", "resource:///modules/AboutHomeUtils.jsm"); #ifdef MOZ_SERVICES_SYNC XPCOMUtils.defineLazyModuleGetter(this, "Weave", "resource://services-sync/main.js"); #endif XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function() { let tmp = {}; Cu.import("resource:///modules/PopupNotifications.jsm", tmp); try { return new tmp.PopupNotifications(gBrowser, document.getElementById("notification-popup"), document.getElementById("notification-popup-box")); } catch(ex) { Cu.reportError(ex); return null; } }); XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs", "resource://gre/modules/PageThumbs.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader", "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader"); XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"); var gInitialPages = [ "about:blank", "about:newtab", "about:home", "about:privatebrowsing", "about:sessionrestore", "about:logopage" ]; #include browser-addons.js #include browser-feeds.js #include browser-fullScreen.js #include browser-fullZoom.js #include browser-places.js #include browser-plugins.js #include browser-tabPreviews.js #include browser-thumbnails.js #include browser-uacompat.js #ifdef MOZ_WEBRTC #include browser-webrtcUI.js #endif #include browser-gestureSupport.js #ifdef MOZ_SERVICES_SYNC #include browser-syncui.js #endif XPCOMUtils.defineLazyGetter(this, "Win7Features", function() { #ifdef XP_WIN const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1"; if (WINTASKBAR_CONTRACTID in Cc && Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) { let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek; return { onOpenWindow: function() { AeroPeek.onOpenWindow(window); }, onCloseWindow: function() { AeroPeek.onCloseWindow(window); } }; } #endif return null; }); XPCOMUtils.defineLazyGetter(this, "PageMenu", function() { let tmp = {}; Cu.import("resource:///modules/PageMenu.jsm", tmp); return new tmp.PageMenu(); }); /** * We can avoid adding multiple load event listeners and save some time by adding * one listener that calls all real handlers. */ function pageShowEventHandlers(persisted) { charsetLoadListener(); XULBrowserWindow.asyncUpdateUI(); // The PluginClickToPlay events are not fired when navigating using the // BF cache. |persisted| is true when the page is loaded from the // BF cache, so this code reshows the notification if necessary. if (persisted) { gPluginHandler.reshowClickToPlayNotification(); } } function UpdateBackForwardCommands(aWebNavigation) { var backBroadcaster = document.getElementById("Browser:Back"); var forwardBroadcaster = document.getElementById("Browser:Forward"); // Avoid setting attributes on broadcasters if the value hasn't changed! // Remember, guys, setting attributes on elements is expensive! They // get inherited into anonymous content, broadcast to other widgets, etc.! // Don't do it if the value hasn't changed! - dwh var backDisabled = backBroadcaster.hasAttribute("disabled"); var forwardDisabled = forwardBroadcaster.hasAttribute("disabled"); if (backDisabled == aWebNavigation.canGoBack) { if (backDisabled) { backBroadcaster.removeAttribute("disabled"); } else { backBroadcaster.setAttribute("disabled", true); } } if (forwardDisabled == aWebNavigation.canGoForward) { if (forwardDisabled) { forwardBroadcaster.removeAttribute("disabled"); } else { forwardBroadcaster.setAttribute("disabled", true); } } } /** * Click-and-Hold implementation for the Back and Forward buttons * XXXmano: should this live in toolbarbutton.xml? */ function SetClickAndHoldHandlers() { var timer; function openMenu(aButton) { cancelHold(aButton); aButton.firstChild.hidden = false; aButton.open = true; } function mousedownHandler(aEvent) { if (aEvent.button != 0 || aEvent.currentTarget.open || aEvent.currentTarget.disabled) return; // Prevent the menupopup from opening immediately aEvent.currentTarget.firstChild.hidden = true; aEvent.currentTarget.addEventListener("mouseout", mouseoutHandler, false); aEvent.currentTarget.addEventListener("mouseup", mouseupHandler, false); timer = setTimeout(openMenu, 500, aEvent.currentTarget); } function mouseoutHandler(aEvent) { let buttonRect = aEvent.currentTarget.getBoundingClientRect(); if (aEvent.clientX >= buttonRect.left && aEvent.clientX <= buttonRect.right && aEvent.clientY >= buttonRect.bottom) { openMenu(aEvent.currentTarget); } else { cancelHold(aEvent.currentTarget); } } function mouseupHandler(aEvent) { cancelHold(aEvent.currentTarget); } function cancelHold(aButton) { clearTimeout(timer); aButton.removeEventListener("mouseout", mouseoutHandler, false); aButton.removeEventListener("mouseup", mouseupHandler, false); } function clickHandler(aEvent) { if (aEvent.button == 0 && aEvent.target == aEvent.currentTarget && !aEvent.currentTarget.open && !aEvent.currentTarget.disabled) { let cmdEvent = document.createEvent("xulcommandevent"); cmdEvent.initCommandEvent("command", true, true, window, 0, aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey, aEvent.metaKey, null); aEvent.currentTarget.dispatchEvent(cmdEvent); } } function _addClickAndHoldListenersOnElement(aElm) { aElm.addEventListener("mousedown", mousedownHandler, true); aElm.addEventListener("click", clickHandler, true); } // Bug 414797: Clone unified-back-forward-button's context menu into both the // back and the forward buttons. var unifiedButton = document.getElementById("unified-back-forward-button"); if (unifiedButton && !unifiedButton._clickHandlersAttached) { unifiedButton._clickHandlersAttached = true; let popup = document.getElementById("backForwardMenu").cloneNode(true); popup.removeAttribute("id"); // Prevent the context attribute on unified-back-forward-button from being // inherited. popup.setAttribute("context", ""); let backButton = document.getElementById("back-button"); backButton.setAttribute("type", "menu"); backButton.appendChild(popup); _addClickAndHoldListenersOnElement(backButton); let forwardButton = document.getElementById("forward-button"); popup = popup.cloneNode(true); forwardButton.setAttribute("type", "menu"); forwardButton.appendChild(popup); _addClickAndHoldListenersOnElement(forwardButton); } } const gSessionHistoryObserver = { observe: function(subject, topic, data) { if (topic != "browser:purge-session-history") { return; } var backCommand = document.getElementById("Browser:Back"); backCommand.setAttribute("disabled", "true"); var fwdCommand = document.getElementById("Browser:Forward"); fwdCommand.setAttribute("disabled", "true"); // Hide session restore button on about:home window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton"); if (gURLBar) { // Clear undo history of the URL bar gURLBar.editor.transactionManager.clear() } } }; var gFindBarSettings = { messageName: "Findbar:Keypress", prefName: "accessibility.typeaheadfind", findAsYouType: null, init: function() { window.messageManager.addMessageListener(this.messageName, this); gPrefService.addObserver(this.prefName, this, false); this.writeFindAsYouType(); }, uninit: function() { window.messageManager.removeMessageListener(this.messageName, this); try { gPrefService.removeObserver(this.prefName, this); } catch(ex) { Cu.reportError(ex); } }, observe: function(aSubject, aTopic, aData) { if (aTopic != "nsPref:changed") { return; } this.writeFindAsYouType(); }, writeFindAsYouType: function() { this.findAsYouType = gPrefService.getBoolPref(this.prefName); }, receiveMessage: function(aMessage) { switch (aMessage.name) { case this.messageName: // If the find bar for chrome window's context is not yet alive, // only initialize it if there's a possibility FindAsYouType // will be used. // There's no point in doing it for most random keypresses. if (!gFindBarInitialized && aMessage.data.shouldFastFind) { let shouldFastFind = this.findAsYouType; if (!shouldFastFind) { // Please keep in sync with toolkit/content/widgets/findbar.xml const FAYT_LINKS_KEY = "'"; const FAYT_TEXT_KEY = "/"; let charCode = aMessage.data.fakeEvent.charCode; let key = charCode ? String.fromCharCode(charCode) : null; shouldFastFind = key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY; } if (shouldFastFind) { // Make sure we return the result. return gFindBar.receiveMessage(aMessage); } } break; } } }; var gURLBarSettings = { prefSuggest: "browser.urlbar.suggest.", // For searching in the source code: // browser.urlbar.suggest.bookmark // browser.urlbar.suggest.history // browser.urlbar.suggest.openpage prefSuggests: [ "bookmark", "history", "openpage" ], prefKeyword: "keyword.enabled", observe: function(aSubject, aTopic, aData) { if (aTopic != "nsPref:changed") { return; } this.writePlaceholder(); }, writePlaceholder: function() { if (!gURLBar) { return; } let attribute = "placeholder"; let prefs = this.prefSuggests.map(pref => { return this.prefSuggest + pref; }); prefs.push(this.prefKeyword); let placeholderDefault = prefs.some(pref => { return gPrefService.getBoolPref(pref); }); if (placeholderDefault) { gURLBar.setAttribute(attribute, gNavigatorBundle.getString("urlbar.placeholder")); } else { gURLBar.setAttribute(attribute, gNavigatorBundle.getString("urlbar.placeholderURLOnly")); } } }; /** * Given a starting docshell and a URI to look up, find the docshell the URI * is loaded in. * @param aDocument * A document to find instead of using just a URI - this is more specific. * @param aDocShell * The doc shell to start at * @param aSoughtURI * The URI that we're looking for * @returns The doc shell that the sought URI is loaded in. Can be in * subframes. */ function findChildShell(aDocument, aDocShell, aSoughtURI) { aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation); aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor); var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument); if ((aDocument && doc == aDocument) || (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec)) { return aDocShell; } var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem); for (var i = 0; i < node.childCount; ++i) { var docShell = node.getChildAt(i); docShell = findChildShell(aDocument, docShell, aSoughtURI); if (docShell) { return docShell; } } return null; } var gPopupBlockerObserver = { _reportButton: null, onReportButtonClick: function(aEvent) { if (aEvent.button != 0 || aEvent.target != this._reportButton) { return; } document.getElementById("blockedPopupOptions") .openPopup(this._reportButton, "after_end", 0, 2, false, false, aEvent); }, handleEvent: function(aEvent) { if (aEvent.originalTarget != gBrowser.selectedBrowser) { return; } if (!this._reportButton && gURLBar) { this._reportButton = document.getElementById("page-report-button"); } if (!gBrowser.selectedBrowser.blockedPopups || !gBrowser.selectedBrowser.blockedPopups.length) { // Hide the icon in the location bar (if the location bar exists) if (gURLBar) { this._reportButton.hidden = true; } return; } if (gURLBar) { this._reportButton.hidden = false; } // Only show the notification again if we've not already shown it. Since // notifications are per-browser, we don't need to worry about re-adding // it. if (!gBrowser.selectedBrowser.blockedPopups.reported) { if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) { var brandBundle = document.getElementById("bundle_brand"); var brandShortName = brandBundle.getString("brandShortName"); var popupCount = gBrowser.selectedBrowser.blockedPopups.length; var popupButtonText = gNavigatorBundle.getString("popupWarningButton"); var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey"); var messageBase = gNavigatorBundle.getString("popupWarning.message"); var message = PluralForm.get(popupCount, messageBase) .replace("#1", brandShortName) .replace("#2", popupCount); var notificationBox = gBrowser.getNotificationBox(); var notification = notificationBox.getNotificationWithValue("popup-blocked"); if (notification) { notification.label = message; } else { var buttons = [{ label: popupButtonText, accessKey: popupButtonAccesskey, popup: "blockedPopupOptions", callback: null }]; const priority = notificationBox.PRIORITY_WARNING_MEDIUM; notificationBox.appendNotification(message, "popup-blocked", "chrome://browser/skin/Info.png", priority, buttons); } } // Record the fact that we've reported this blocked popup, so we don't // show it again. gBrowser.selectedBrowser.blockedPopups.reported = true; } }, toggleAllowPopupsForSite: function(aEvent) { var pm = Services.perms; var shouldBlock = aEvent.target.getAttribute("block") == "true"; var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION; pm.add(gBrowser.currentURI, "popup", perm); gBrowser.getNotificationBox().removeCurrentNotification(); }, fillPopupList: function(aEvent) { // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites // we should really walk the blockedPopups and create a list of "allow for " // menuitems for the common subset of hosts present in the report, this will // make us frame-safe. // // XXXjst - Note that when this is fixed to work with multi-framed sites, // also back out the fix for bug 343772 where // nsGlobalWindow::CheckOpenAllow() was changed to also // check if the top window's location is whitelisted. let browser = gBrowser.selectedBrowser; var uri = browser.currentURI; var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite"); try { blockedPopupAllowSite.removeAttribute("hidden"); var pm = Services.perms; if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) { // Offer an item to block popups for this site, if a whitelist entry exists // already for it. let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host || uri.spec]); blockedPopupAllowSite.setAttribute("label", blockString); blockedPopupAllowSite.setAttribute("block", "true"); } else { // Offer an item to allow popups for this site let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host || uri.spec]); blockedPopupAllowSite.setAttribute("label", allowString); blockedPopupAllowSite.removeAttribute("block"); } } catch(e) { blockedPopupAllowSite.setAttribute("hidden", "true"); } if (PrivateBrowsingUtils.isWindowPrivate(window)) { blockedPopupAllowSite.setAttribute("disabled", "true"); } else { blockedPopupAllowSite.removeAttribute("disabled"); } let blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage"); let showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage"); blockedPopupDontShowMessage.setAttribute("checked", !showMessage); if (aEvent.target.anchorNode.id == "page-report-button") { aEvent.target.anchorNode.setAttribute("open", "true"); blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromLocationbar")); } else { blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage")); } let blockedPopupsSeparator = document.getElementById("blockedPopupsSeparator"); blockedPopupsSeparator.setAttribute("hidden", true); gBrowser.selectedBrowser.retrieveListOfBlockedPopups().then(blockedPopups => { let foundUsablePopupURI = false; if (blockedPopups) { for (let i = 0; i < blockedPopups.length; i++) { let blockedPopup = blockedPopups[i]; // popupWindowURI will be null if the file picker popup is blocked. // xxxdz this should make the option say "Show file picker" and do it (Bug 590306) if (!blockedPopup.popupWindowURIspec) { continue; } var popupURIspec = blockedPopup.popupWindowURIspec; // Sometimes the popup URI that we get back from the blockedPopup // isn't useful (for instance, netscape.com's popup URI ends up // being "http://www.netscape.com", which isn't really the URI of // the popup they're trying to show). This isn't going to be // useful to the user, so we won't create a menu item for it. if (popupURIspec == "" || popupURIspec == "about:blank" || popupURIspec == "" || popupURIspec == uri.spec) { continue; } // Because of the short-circuit above, we may end up in a situation // in which we don't have any usable popup addresses to show in // the menu, and therefore we shouldn't show the separator. However, // since we got past the short-circuit, we must've found at least // one usable popup URI and thus we'll turn on the separator later. foundUsablePopupURI = true; var menuitem = document.createElement("menuitem"); var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix", [popupURIspec]); menuitem.setAttribute("label", label); menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);"); menuitem.setAttribute("popupReportIndex", i); menuitem.popupReportBrowser = browser; aEvent.target.appendChild(menuitem); } } // Show the separator if we added any // showable popup addresses to the menu. if (foundUsablePopupURI) { blockedPopupsSeparator.removeAttribute("hidden"); } }, null); }, onPopupHiding: function(aEvent) { if (aEvent.target.anchorNode.id == "page-report-button") { aEvent.target.anchorNode.removeAttribute("open"); } let item = aEvent.target.lastChild; while (item && item.getAttribute("observes") != "blockedPopupsSeparator") { let next = item.previousSibling; item.parentNode.removeChild(item); item = next; } }, showBlockedPopup: function(aEvent) { var target = aEvent.target; var popupReportIndex = target.getAttribute("popupReportIndex"); let browser = target.popupReportBrowser; browser.unblockPopup(popupReportIndex); }, editPopupSettings: function() { var host = ""; try { host = gBrowser.currentURI.host; } catch(e) {} var bundlePreferences = document.getElementById("bundle_preferences"); var params = { blockVisible : false, sessionVisible : false, allowVisible : true, prefilledHost : host, permissionType : "popup", windowTitle : bundlePreferences.getString("popuppermissionstitle"), introText : bundlePreferences.getString("popuppermissionstext") }; var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions"); if (existingWindow) { existingWindow.initWithParams(params); existingWindow.focus(); } else { window.openDialog("chrome://browser/content/preferences/permissions.xul", "_blank", "resizable,dialog=no,centerscreen", params); } }, dontShowMessage: function() { var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage"); gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage); gBrowser.getNotificationBox().removeCurrentNotification(); } }; const gXSSObserver = { observe: function(aSubject, aTopic, aData) { // Don't do anything if the notification is disabled. if (!gPrefService.getBoolPref("security.xssfilter.displayWarning")) { return; } // Parse incoming XSS array aSubject.QueryInterface(Ci.nsIArray); var policy = aSubject.queryElementAt(0, Ci.nsISupportsString).data; var content = aSubject.queryElementAt(1, Ci.nsISupportsString).data; var domain = aSubject.queryElementAt(2, Ci.nsISupportsString).data; var url = aSubject.queryElementAt(3, Ci.nsISupportsCString).data; var blockMode = aSubject.queryElementAt(4, Ci.nsISupportsPRBool).data; // If it is a block mode event, do not display the infobar if (blockMode) { return; } var nb = gBrowser.getNotificationBox(); const priority = nb.PRIORITY_WARNING_MEDIUM; var buttons = [{ label: 'View Unsafe Content', accessKey: 'V', popup: null, callback: function() { alert(content); } }]; if (domain !== "") { buttons.push({ label: 'Add Domain Exception', accessKey: 'A', popup: null, callback: function() { let whitelist = gPrefService.getCharPref("security.xssfilter.whitelist"); if (whitelist != "") { whitelist = whitelist + "," + domain; } else { whitelist = domain; } // Write the updated whitelist. Since this is observed by the XSS filter, // it will automatically sync to the back-end and update immediately. gPrefService.setCharPref("security.xssfilter.whitelist", whitelist); // After setting this, we automatically reload the page. BrowserReloadSkipCache(); } }); } nb.appendNotification("The XSS Filter has detected a potential XSS attack. Type: " + policy, 'popup-blocked', 'chrome://browser/skin/Info.png', priority, buttons); } }; var gBrowserInit = { delayedStartupFinished: false, onLoad: function() { gMultiProcessBrowser = gPrefService.getBoolPref("browser.tabs.remote"); var mustLoadSidebar = false; Cc["@mozilla.org/eventlistenerservice;1"] .getService(Ci.nsIEventListenerService) .addSystemEventListener(gBrowser, "click", contentAreaClick, true); gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false); // Note that the XBL binding is untrusted gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true); gBrowser.addEventListener("PluginCrashed", gPluginHandler, true); gBrowser.addEventListener("PluginOutdated", gPluginHandler, true); gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true); gBrowser.addEventListener("PluginRemoved", gPluginHandler, true); Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false); window.addEventListener("AppCommand", HandleAppCommandEvent, true); // These routines add message listeners. They must run before // loading the frame script to ensure that we don't miss any // message sent between when the frame script is loaded and when // the listener is registered. #ifdef MOZ_DEVTOOLS DevToolsTheme.init(); #endif gFindBarSettings.init(); messageManager.loadFrameScript("chrome://browser/content/content.js", true); messageManager.loadFrameScript("chrome://browser/content/content-sessionStore.js", true); // initialize observers and listeners // and give C++ access to gBrowser XULBrowserWindow.init(); window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIXULWindow) .XULBrowserWindow = window.XULBrowserWindow; window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess(); // set default character set if provided // window.arguments[1]: character set (string) if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) { if (window.arguments[1].startsWith("charset=")) { var arrayArgComponents = window.arguments[1].split("="); if (arrayArgComponents) { //we should "inherit" the charset menu setting in a new window //TFE FIXME: this is now a wrappednative and can't be set this way. //getMarkupDocumentViewer().defaultCharacterSet = arrayArgComponents[1]; } } } // Manually hook up session and global history for the first browser // so that we don't have to load global history before bringing up a // window. // Wire up session and global history before any possible // progress notifications for back/forward button updating gBrowser.webNavigation.sessionHistory = Cc["@mozilla.org/browser/shistory;1"] .createInstance(Ci.nsISHistory); Services.obs.addObserver(gBrowser.browsers[0], "browser:purge-session-history", false); // remove the disablehistory attribute so the browser cleans up, as // though it had done this work itself gBrowser.browsers[0].removeAttribute("disablehistory"); // enable global history try { if (!gMultiProcessBrowser) { gBrowser.docShell.useGlobalHistory = true; } } catch(ex) { Cu.reportError("Places database may be locked: " + ex); } // hook up UI through progress listener gBrowser.addProgressListener(window.XULBrowserWindow); gBrowser.addTabsProgressListener(window.TabsProgressListener); // setup our common DOMLinkAdded listener gBrowser.addEventListener("DOMLinkAdded", DOMLinkHandler, false); // setup our MozApplicationManifest listener gBrowser.addEventListener("MozApplicationManifest", OfflineApps, false); // setup simple gestures support gGestureSupport.init(true); // setup history swipe animation gHistorySwipeAnimation.init(); if (window.opener && !window.opener.closed) { let openerSidebarBox = window.opener.document.getElementById("sidebar-box"); // If the opener had a sidebar, open the same sidebar in our window. // The opener can be the hidden window too, if we're coming from the state // where no windows are open, and the hidden window has no sidebar box. if (openerSidebarBox && !openerSidebarBox.hidden) { let sidebarCmd = openerSidebarBox.getAttribute("sidebarcommand"); let sidebarCmdElem = document.getElementById(sidebarCmd); // dynamically generated sidebars will fail this check. if (sidebarCmdElem) { let sidebarBox = document.getElementById("sidebar-box"); let sidebarTitle = document.getElementById("sidebar-title"); sidebarTitle.setAttribute( "value", window.opener.document.getElementById("sidebar-title").getAttribute("value")); sidebarBox.setAttribute("width", openerSidebarBox.boxObject.width); sidebarBox.setAttribute("sidebarcommand", sidebarCmd); // Note: we're setting 'src' on sidebarBox, which is a , not on // the . This lets us delay the actual load until // delayedStartup(). sidebarBox.setAttribute( "src", window.opener.document.getElementById("sidebar").getAttribute("src")); mustLoadSidebar = true; sidebarBox.hidden = false; document.getElementById("sidebar-splitter").hidden = false; sidebarCmdElem.setAttribute("checked", "true"); } } } else { let box = document.getElementById("sidebar-box"); if (box.hasAttribute("sidebarcommand")) { let commandID = box.getAttribute("sidebarcommand"); if (commandID) { let command = document.getElementById(commandID); if (command) { mustLoadSidebar = true; box.hidden = false; document.getElementById("sidebar-splitter").hidden = false; command.setAttribute("checked", "true"); } else { // Remove the |sidebarcommand| attribute, because the element it // refers to no longer exists, so we should assume this sidebar // panel has been uninstalled. (249883) box.removeAttribute("sidebarcommand"); } } } } // Certain kinds of automigration rely on this notification to complete their // tasks BEFORE the browser window is shown. Services.obs.notifyObservers(null, "browser-window-before-show", ""); // Set a sane starting width/height for all resolutions on new profiles. if (!document.documentElement.hasAttribute("width")) { let defaultWidth; let defaultHeight; // Very small: maximize the window // Portrait : use about full width and 3/4 height, to view entire pages // at once (without being obnoxiously tall) // Widescreen: use about half width, to suggest side-by-side page view // Otherwise : use 3/4 height and width if (screen.availHeight <= 600) { document.documentElement.setAttribute("sizemode", "maximized"); defaultWidth = 610; defaultHeight = 450; } else { if (screen.availWidth <= screen.availHeight) { defaultWidth = screen.availWidth * .9; defaultHeight = screen.availHeight * .75; } else if (screen.availWidth >= 2048) { defaultWidth = (screen.availWidth / 2) - 20; defaultHeight = screen.availHeight - 10; } else { defaultWidth = screen.availWidth * .75; defaultHeight = screen.availHeight * .75; } #ifdef MOZ_WIDGET_GTK2 // On X, we're not currently able to account for the size of the window // border. Use 28px as a guess (titlebar + bottom window border) defaultHeight -= 28; #endif } document.documentElement.setAttribute("width", defaultWidth); document.documentElement.setAttribute("height", defaultHeight); } if (!gShowPageResizers) { document.getElementById("status-bar").setAttribute("hideresizer", "true"); } if (!window.toolbar.visible) { // adjust browser UI for popups if (gURLBar) { gURLBar.setAttribute("readonly", "true"); gURLBar.setAttribute("enablehistory", "false"); } goSetCommandEnabled("cmd_newNavigatorTab", false); } #ifdef MENUBAR_CAN_AUTOHIDE updateAppButtonDisplay(); #endif // Misc. inits. CombinedStopReload.init(); allTabs.readPref(); TabsOnTop.init(); AudioIndicator.init(); gPrivateBrowsingUI.init(); TabsInTitlebar.init(); retrieveToolbarIconsizesFromTheme(); ToolbarIconColor.init(); UserAgentCompatibility.init(); #ifdef XP_WIN if (window.matchMedia("(-moz-os-version: windows-win8)").matches && window.matchMedia("(-moz-windows-default-theme)").matches) { let windows8WindowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}).Windows8WindowFrameColor; var windowFrameColor; windowFrameColor = windows8WindowFrameColor.get_win8(); // Formula from Microsoft's UWP guideline. let backgroundLuminance = (windowFrameColor[0] * 2 + windowFrameColor[1] * 5 + windowFrameColor[2]) / 8; if (backgroundLuminance <= 128) { document.documentElement.setAttribute("darkwindowframe", "true"); } } #endif // Wait until chrome is painted before executing code not critical to making the window visible this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar); window.addEventListener("MozAfterPaint", this._boundDelayedStartup); this._loadHandled = true; }, _cancelDelayedStartup: function() { window.removeEventListener("MozAfterPaint", this._boundDelayedStartup); this._boundDelayedStartup = null; }, _delayedStartup: function(mustLoadSidebar) { let tmp = {}; this._cancelDelayedStartup(); let uriToLoad = this._getUriToLoad(); var isLoadingBlank = isBlankPageURL(uriToLoad); // This pageshow listener needs to be registered before we may call // swapBrowsersAndCloseOther() to receive pageshow events fired by that. gBrowser.addEventListener("pageshow", function(event) { // Filter out events that are not about the document load we are interested in if (content && event.target == content.document) { setTimeout(pageShowEventHandlers, 0, event.persisted); } }, true); if (uriToLoad && uriToLoad != "about:blank") { if (uriToLoad instanceof Ci.nsISupportsArray) { let count = uriToLoad.Count(); let specs = []; for (let i = 0; i < count; i++) { let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString); specs.push(urisstring.data); } // This function throws for certain malformed URIs, so use exception handling // so that we don't disrupt startup try { gBrowser.loadTabs(specs, false, true); } catch(e) {} } else if (uriToLoad instanceof XULElement) { // swap the given tab with the default about:blank tab and then close // the original tab in the other window. // Stop the about:blank load gBrowser.stop(); // make sure it has a docshell gBrowser.docShell; gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad); } else if (window.arguments.length >= 3) { // window.arguments[2]: referrer (nsIURI | string) // [3]: postData (nsIInputStream) // [4]: allowThirdPartyFixup (bool) // [5]: referrerPolicy (int) // [6]: originPrincipal (nsIPrincipal) // [7]: triggeringPrincipal (nsIPrincipal) let referrerURI = window.arguments[2]; if (typeof(referrerURI) == "string") { try { referrerURI = makeURI(referrerURI); } catch(e) { referrerURI = null; } } let referrerPolicy = (window.arguments[5] != undefined ? window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT); loadURI(uriToLoad, referrerURI, window.arguments[3] || null, window.arguments[4] || false, referrerPolicy, // pass the origin principal (if any) and force its use to create // an initial about:blank viewer if present: window.arguments[6], !!window.arguments[6], window.arguments[7]); window.focus(); } else { // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3. // Such callers expect that window.arguments[0] is handled as a single URI. loadOneOrMoreURIs(uriToLoad); } } Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false); Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false); Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false); Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false); Services.obs.addObserver(gXPInstallObserver, "addon-install-origin-blocked", false); Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false); Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false); Services.obs.addObserver(gXSSObserver, "xss-on-violate-policy", false); gPrefService.addObserver(gURLBarSettings.prefSuggest, gURLBarSettings, false); gPrefService.addObserver(gURLBarSettings.prefKeyword, gURLBarSettings, false); gURLBarSettings.writePlaceholder(); BrowserOffline.init(); OfflineApps.init(); IndexedDBPromptHelper.init(); AddonManager.addAddonListener(AddonsMgrListener); #ifdef MOZ_WEBRTC WebrtcIndicator.init(); #endif // Ensure login manager is up and running. Services.logins; if (mustLoadSidebar) { let sidebar = document.getElementById("sidebar"); let sidebarBox = document.getElementById("sidebar-box"); sidebar.setAttribute("src", sidebarBox.getAttribute("src")); } UpdateUrlbarSearchSplitterState(); if (!isLoadingBlank || !focusAndSelectUrlBar()) { gBrowser.selectedBrowser.focus(); } gNavToolbox.customizeDone = BrowserToolboxCustomizeDone; gNavToolbox.customizeChange = BrowserToolboxCustomizeChange; // Set up Sanitize Item this._initializeSanitizer(); // Enable/Disable auto-hide tabbar gBrowser.tabContainer.updateVisibility(); gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false); var homeButton = document.getElementById("home-button"); gHomeButton.updateTooltip(homeButton); gHomeButton.updatePersonalToolbarStyle(homeButton); // BiDi UI gBidiUI = isBidiEnabled(); if (gBidiUI) { document.getElementById("documentDirection-separator").hidden = false; document.getElementById("documentDirection-swap").hidden = false; document.getElementById("textfieldDirection-separator").hidden = false; document.getElementById("textfieldDirection-swap").hidden = false; } // Setup click-and-hold gestures access to the session history // menus if global click-and-hold isn't turned on if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false)) { SetClickAndHoldHandlers(); } // Initialize the full zoom setting. // We do this before the session restore service gets initialized so we can // apply full zoom settings to tabs restored by the session restore service. FullZoom.init(); // Bug 666804 - NetworkPrioritizer support for e10s if (!gMultiProcessBrowser) { let NP = {}; Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP); NP.trackBrowserWindow(window); } // initialize the session-restore service (in case it's not already running) let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); let ssPromise = ss.init(window); PlacesToolbarHelper.init(); ctrlTab.readPref(); gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false); gPrefService.addObserver(allTabs.prefName, allTabs, false); // Initialize the download manager some time after the app starts so that // auto-resume downloads begin (such as after crashing or quitting with // active downloads) and speeds up the first-load of the download manager UI. // If the user manually opens the download manager before the timeout, the // downloads will start right away, and getting the service again won't hurt. setTimeout(function() { try { Cu.import("resource:///modules/DownloadsCommon.jsm", {}) .DownloadsCommon.initializeAllDataLinks(); Cu.import("resource:///modules/DownloadsTaskbar.jsm", {}) .DownloadsTaskbar.registerIndicator(window); } catch(ex) { Cu.reportError(ex); } }, 10000); // Load the Login Manager data from disk off the main thread, some time // after startup. If the data is required before the timeout, for example // because a restored page contains a password field, it will be loaded on // the main thread, and this initialization request will be ignored. setTimeout(function() { try { Services.logins; } catch(ex) { Cu.reportError(ex); } }, 3000); // The object handling the downloads indicator is also initialized here in the // delayed startup function, but the actual indicator element is not loaded // unless there are downloads to be displayed. DownloadsButton.initializeIndicator(); #ifndef XP_MACOSX updateEditUIVisibility(); let placesContext = document.getElementById("placesContext"); placesContext.addEventListener("popupshowing", updateEditUIVisibility, false); placesContext.addEventListener("popuphiding", updateEditUIVisibility, false); #endif #ifdef MOZ_PERSONAS gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true); gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true); gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true); #endif // Bug 666808 - AeroPeek support for e10s if (!gMultiProcessBrowser) { if (Win7Features) { Win7Features.onOpenWindow(); } } // called when we go into full screen, even if initiated by a web page script window.addEventListener("fullscreen", onFullScreen, true); // Called when we enter DOM full-screen mode. Note we can already be in browser // full-screen mode when we enter DOM full-screen mode. window.addEventListener("MozDOMFullscreen:NewOrigin", onMozEnteredDomFullscreen, true); if (window.fullScreen) { onFullScreen(); } if (document.mozFullScreen) { onMozEnteredDomFullscreen(); } #ifdef MOZ_SERVICES_SYNC // initialize the sync UI gSyncUI.init(); #endif gBrowserThumbnails.init(); setUrlAndSearchBarWidthForConditionalForwardButton(); window.addEventListener("resize", function resizeHandler(event) { if (event.target == window) { setUrlAndSearchBarWidthForConditionalForwardButton(); } }); // Enable Error Console? let consoleEnabled = gPrefService.getBoolPref("devtools.errorconsole.enabled"); if (consoleEnabled) { let cmd = document.getElementById("Tools:ErrorConsole"); cmd.removeAttribute("disabled"); cmd.removeAttribute("hidden"); } #ifdef MENUBAR_CAN_AUTOHIDE // If the user (or the locale) hasn't enabled the top-level "Character // Encoding" menu via the "browser.menu.showCharacterEncoding" preference, // hide it. if ("true" != gPrefService.getComplexValue("browser.menu.showCharacterEncoding", Ci.nsIPrefLocalizedString).data) { document.getElementById("appmenu_charsetMenu").hidden = true; } #endif let appMenuButton = document.getElementById("appmenu-button"); let appMenuPopup = document.getElementById("appmenu-popup"); if (appMenuButton && appMenuPopup) { let appMenuOpening = null; appMenuButton.addEventListener("mousedown", function(event) { if (event.button == 0) { appMenuOpening = new Date(); } }, false); appMenuPopup.addEventListener("popupshown", function(event) { if (event.target != appMenuPopup || !appMenuOpening) { return; } let duration = new Date() - appMenuOpening; appMenuOpening = null; }, false); } window.addEventListener("mousemove", MousePosTracker, false); window.addEventListener("dragover", MousePosTracker, false); // End startup crash tracking after a delay to catch crashes while restoring // tabs and to postpone saving the pref to disk. try { const startupCrashEndDelay = 30 * 1000; setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay); } catch(ex) { Cu.reportError("Could not end startup crash tracking: " + ex); } ssPromise.then(() => { // Bail out if the window has been closed in the meantime. if (window.closed) { return; } if ("TabView" in window) { TabView.init(); } // XXX: do we still need this?... setTimeout(function() { BrowserChromeTest.markAsReady(); }, 0); }); this.delayedStartupFinished = true; Services.obs.notifyObservers(window, "browser-delayed-startup-finished", ""); }, // Returns the URI(s) to load at startup. _getUriToLoad: function() { // window.arguments[0]: URI to load (string), or an nsISupportsArray of // nsISupportsStrings to load, or a xul:tab of // a tabbrowser, which will be replaced by this // window (for this case, all other arguments are // ignored). if (!window.arguments || !window.arguments[0]) { return null; } let uri = window.arguments[0]; let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"] .getService(Ci.nsISessionStartup); let defaultArgs = Cc["@mozilla.org/browser/clh;1"] .getService(Ci.nsIBrowserHandler) .defaultArgs; // If the given URI matches defaultArgs (the default homepage) we want // to block its load if we're going to restore a session anyway. if (uri == defaultArgs && sessionStartup.willOverrideHomepage) { return null; } return uri; }, onUnload: function() { // In certain scenarios it's possible for unload to be fired before onload, // (e.g. if the window is being closed after browser.js loads but before the // load completes). In that case, there's nothing to do here. if (!this._loadHandled) return; // First clean up services initialized in gBrowserInit.onLoad (or those whose // uninit methods don't depend on the services having been initialized). allTabs.uninit(); CombinedStopReload.uninit(); gGestureSupport.init(false); gHistorySwipeAnimation.uninit(); FullScreen.cleanup(); Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed"); try { gBrowser.removeProgressListener(window.XULBrowserWindow); gBrowser.removeTabsProgressListener(window.TabsProgressListener); } catch(ex) {} BookmarkingUI.uninit(); TabsOnTop.uninit(); AudioIndicator.uninit(); TabsInTitlebar.uninit(); ToolbarIconColor.uninit(); #ifdef MOZ_DEVTOOLS DevToolsTheme.uninit(); #endif gFindBarSettings.uninit(); UserAgentCompatibility.uninit(); var enumerator = Services.wm.getEnumerator(null); enumerator.getNext(); if (!enumerator.hasMoreElements()) { document.persist("sidebar-box", "sidebarcommand"); document.persist("sidebar-box", "width"); document.persist("sidebar-box", "src"); document.persist("sidebar-title", "value"); } // Now either cancel delayedStartup, or clean up the services initialized from // it. if (this._boundDelayedStartup) { this._cancelDelayedStartup(); } else { if (Win7Features) { Win7Features.onCloseWindow(); } gPrefService.removeObserver(ctrlTab.prefName, ctrlTab); gPrefService.removeObserver(allTabs.prefName, allTabs); ctrlTab.uninit(); if ("TabView" in window) { TabView.uninit(); } gBrowserThumbnails.uninit(); FullZoom.destroy(); Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history"); Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled"); Services.obs.removeObserver(gXPInstallObserver, "addon-install-started"); Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked"); Services.obs.removeObserver(gXPInstallObserver, "addon-install-origin-blocked"); Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed"); Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete"); Services.obs.removeObserver(gXSSObserver, "xss-on-violate-policy"); try { gPrefService.removeObserver(gURLBarSettings.prefSuggest, gURLBarSettings); gPrefService.removeObserver(gURLBarSettings.prefKeyword, gURLBarSettings); } catch(ex) { Cu.reportError(ex); } try { gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton); } catch(ex) { Cu.reportError(ex); } BrowserOffline.uninit(); OfflineApps.uninit(); IndexedDBPromptHelper.uninit(); AddonManager.removeAddonListener(AddonsMgrListener); } // Final window teardown, do this last. window.XULBrowserWindow.destroy(); window.XULBrowserWindow = null; window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIXULWindow) .XULBrowserWindow = null; window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null; }, #ifdef XP_MACOSX // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and // nonBrowserWindowShutdown() are used for non-browser windows in // macBrowserOverlay nonBrowserWindowStartup: function() { // Disable inappropriate commands / submenus var disabledItems = ['Browser:SavePage', 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain', 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload', 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen', 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs', 'View:PageInfo', 'Browser:ToggleAddonBar']; var element; for (let disabledItem of disabledItems) { element = document.getElementById(disabledItem); if (element) { element.setAttribute("disabled", "true"); } } // If no windows are active (i.e. we're the hidden window), disable the close, minimize // and zoom menu commands as well if (window.location.href == "chrome://browser/content/hiddenWindow.xul") { var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow']; for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) { element = document.getElementById(hiddenWindowDisabledItem); if (element) { element.setAttribute("disabled", "true"); } } // also hide the window-list separator element = document.getElementById("sep-window-list"); element.setAttribute("hidden", "true"); // Setup the dock menu. let dockMenuElement = document.getElementById("menu_mac_dockmenu"); if (dockMenuElement != null) { let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"] .createInstance(Ci.nsIStandaloneNativeMenu); try { nativeMenu.init(dockMenuElement); let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"] .getService(Ci.nsIMacDockSupport); dockSupport.dockMenu = nativeMenu; } catch(e) {} } } if (PrivateBrowsingUtils.permanentPrivateBrowsing) { document.getElementById("macDockMenuNewWindow").hidden = true; } this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0); }, nonBrowserWindowDelayedStartup: function() { this._delayedStartupTimeoutId = null; // initialise the offline listener BrowserOffline.init(); // Set up Sanitize Item this._initializeSanitizer(); // initialize the private browsing UI gPrivateBrowsingUI.init(); #ifdef MOZ_SERVICES_SYNC // initialize the sync UI gSyncUI.init(); #endif }, nonBrowserWindowShutdown: function() { // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do - // just cancel the pending timeout and return; if (this._delayedStartupTimeoutId) { clearTimeout(this._delayedStartupTimeoutId); return; } BrowserOffline.uninit(); }, #endif _initializeSanitizer: function() { const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize"; if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) { gPrefService.clearUserPref(kDidSanitizeDomain); // We need to persist this preference change, since we want to // check it at next app start even if the browser exits abruptly gPrefService.savePrefFile(null); } /** * Migrate Firefox 3.0 privacy.item prefs under one of these conditions: * * a) User has customized any privacy.item prefs * b) privacy.sanitize.sanitizeOnShutdown is set */ if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) { let itemBranch = gPrefService.getBranch("privacy.item."); let itemArray = itemBranch.getChildList(""); // See if any privacy.item prefs are set let doMigrate = itemArray.some(function(name) itemBranch.prefHasUserValue(name)); // Or if sanitizeOnShutdown is set if (!doMigrate) { doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown"); } if (doMigrate) { let cpdBranch = gPrefService.getBranch("privacy.cpd."); let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown."); for (let name of itemArray) { try { // don't migrate password or offlineApps clearing in the CRH dialog since // there's no UI for those anymore. They default to false. bug 497656 if (name != "passwords" && name != "offlineApps") cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name)); clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name)); } catch(e) { Cu.reportError("Exception thrown during privacy pref migration: " + e); } } } gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true); } }, } /* Legacy global init functions */ var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit); var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit); #ifdef XP_MACOSX var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit); var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit); var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit); #endif function HandleAppCommandEvent(evt) { switch (evt.command) { case "Back": BrowserBack(); break; case "Forward": BrowserForward(); break; case "Reload": BrowserReloadSkipCache(); break; case "Stop": if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") { BrowserStop(); } break; case "Search": BrowserSearch.webSearch(); break; case "Bookmarks": toggleSidebar('viewBookmarksSidebar'); break; case "Home": BrowserHome(); break; case "New": BrowserOpenTab(); break; case "Close": BrowserCloseTabOrWindow(); break; case "Find": gFindBar.onFindCommand(); break; case "Help": openHelpLink(''); break; case "Open": BrowserOpenFileWindow(); break; case "Print": PrintUtils.print(); break; case "Save": saveDocument(window.content.document); break; case "SendMail": MailIntegration.sendLinkForWindow(window.content); break; default: return; } evt.stopPropagation(); evt.preventDefault(); } function gotoHistoryIndex(aEvent) { let index = aEvent.target.getAttribute("index"); if (!index) { return false; } let where = whereToOpenLink(aEvent); if (where == "current") { // Normal click. Go there in the current tab and update session history. try { gBrowser.gotoIndex(index); } catch(ex) { return false; } return true; } // Modified click. Go there in a new tab/window. duplicateTabIn(gBrowser.selectedTab, where, index - gBrowser.sessionHistory.index); return true; } function BrowserForward(aEvent) { let where = whereToOpenLink(aEvent, false, true); if (where == "current") { try { gBrowser.goForward(); } catch(ex) {} } else { duplicateTabIn(gBrowser.selectedTab, where, 1); } } function BrowserBack(aEvent) { let where = whereToOpenLink(aEvent, false, true); if (where == "current") { try { gBrowser.goBack(); } catch(ex) {} } else { duplicateTabIn(gBrowser.selectedTab, where, -1); } } function BrowserHandleBackspace() { switch (gPrefService.getIntPref("browser.backspace_action")) { case 0: BrowserBack(); break; case 1: goDoCommand("cmd_scrollPageUp"); break; } } function BrowserHandleShiftBackspace() { switch (gPrefService.getIntPref("browser.backspace_action")) { case 0: BrowserForward(); break; case 1: goDoCommand("cmd_scrollPageDown"); break; } } function BrowserStop() { const stopFlags = nsIWebNavigation.STOP_ALL; gBrowser.webNavigation.stop(stopFlags); } function BrowserReloadOrDuplicate(aEvent) { var backgroundTabModifier = aEvent.button == 1 || #ifdef XP_MACOSX aEvent.metaKey; #else aEvent.ctrlKey; #endif if (aEvent.shiftKey && !backgroundTabModifier) { BrowserReloadSkipCache(); return; } let where = whereToOpenLink(aEvent, false, true); if (where == "current") { BrowserReload(); } else { duplicateTabIn(gBrowser.selectedTab, where); } } function BrowserReload() { const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE; BrowserReloadWithFlags(reloadFlags); } function BrowserReloadSkipCache() { // Bypass proxy and cache. const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; BrowserReloadWithFlags(reloadFlags); } var BrowserHome = BrowserGoHome; function BrowserGoHome(aEvent) { if (aEvent && "button" in aEvent && aEvent.button == 2) { // right-click: do nothing return; } var homePage = gHomeButton.getHomePage(); var where = whereToOpenLink(aEvent, false, true); var urls; // Home page should open in a new tab when current tab is an app tab if (where == "current" && gBrowser && gBrowser.selectedTab.pinned) { where = "tab"; } // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages switch (where) { case "current": loadOneOrMoreURIs(homePage); break; case "tabshifted": case "tab": urls = homePage.split("|"); var loadInBackground = Services.prefs.getBoolPref("browser.tabs.loadBookmarksInBackground", false); gBrowser.loadTabs(urls, loadInBackground); break; case "window": OpenBrowserWindow(); break; } } function loadOneOrMoreURIs(aURIString) { #ifdef XP_MACOSX // we're not a browser window, pass the URI string to a new browser window if (window.location.href != getBrowserURL()) { window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString); return; } #endif // This function throws for certain malformed URIs, so use exception handling // so that we don't disrupt startup try { gBrowser.loadTabs(aURIString.split("|"), false, true); } catch(e) {} } function focusAndSelectUrlBar() { if (gURLBar) { if (window.fullScreen) { FullScreen.showNavToolbox(); } gURLBar.select(); if (document.activeElement == gURLBar.inputField) { return true; } } return false; } function openLocation() { if (focusAndSelectUrlBar()) { return; } #ifdef XP_MACOSX if (window.location.href != getBrowserURL()) { var win = getTopWin(); if (win) { // If there's an open browser window, it should handle this command win.focus() win.openLocation(); } else { // If there are no open browser windows, open a new one win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no", BROWSER_NEW_TAB_URL); win.addEventListener("load", openLocationCallback, false); } return; } #endif openDialog("chrome://browser/content/openLocation.xul", "_blank", "chrome,modal,titlebar", window); } function openLocationCallback() { // make sure the DOM is ready setTimeout(function() { this.openLocation(); }, 0); } function BrowserOpenTab() { openUILinkIn(BROWSER_NEW_TAB_URL, "tab"); } /* Called from the openLocation dialog. This allows that dialog to instruct its opener to open a new window and then step completely out of the way. Anything less byzantine is causing horrible crashes, rather believably, though oddly only on Linux. */ function delayedOpenWindow(chrome, flags, href, postData) { // The other way to use setTimeout, // setTimeout(openDialog, 10, chrome, "_blank", flags, url), // doesn't work here. The extra "magic" extra argument setTimeout adds to // the callback function would confuse gBrowserInit.onLoad() by making // window.arguments[1] be an integer instead of null. setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10); } /* Required because the tab needs time to set up its content viewers and get the load of the URI kicked off before becoming the active content area. */ function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup) { gBrowser.loadOneTab(aUrl, { referrerURI: aReferrer, charset: aCharset, postData: aPostData, inBackground: false, allowThirdPartyFixup: aAllowThirdPartyFixup }); } var gLastOpenDirectory = { _lastDir: null, get path() { if (!this._lastDir || !this._lastDir.exists()) { try { this._lastDir = gPrefService.getComplexValue("browser.open.lastDir", Ci.nsILocalFile); if (!this._lastDir.exists()) this._lastDir = null; } catch(e) {} } return this._lastDir; }, set path(val) { try { if (!val || !val.isDirectory()) { return; } } catch(e) { return; } this._lastDir = val.clone(); // Don't save the last open directory pref inside the Private Browsing mode if (!PrivateBrowsingUtils.isWindowPrivate(window)) { gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile, this._lastDir); } }, reset: function() { this._lastDir = null; } }; function BrowserOpenFileWindow() { // Get filepicker component. try { const nsIFilePicker = Ci.nsIFilePicker; let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); let fpCallback = function fpCallback_done(aResult) { if (aResult == nsIFilePicker.returnOK) { try { if (fp.file) { gLastOpenDirectory.path = fp.file.parent.QueryInterface(Ci.nsILocalFile); } } catch(ex) {} openUILinkIn(fp.fileURL.spec, "current"); } }; fp.init(window, gNavigatorBundle.getString("openFile"), nsIFilePicker.modeOpen); fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText | nsIFilePicker.filterImages | nsIFilePicker.filterXML | nsIFilePicker.filterHTML); fp.displayDirectory = gLastOpenDirectory.path; fp.open(fpCallback); } catch(ex) {} } function BrowserCloseTabOrWindow() { #ifdef XP_MACOSX // If we're not a browser window, just close the window if (window.location.href != getBrowserURL()) { closeWindow(true); return; } #endif // If the current tab is the last one, this will close the window. gBrowser.removeCurrentTab({animate: true}); } function BrowserTryToCloseWindow() { if (WindowIsClosing()) { // WindowIsClosing does all the necessary checks window.close(); } } function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy, originPrincipal, forceAboutBlankViewerInCurrent, triggeringPrincipal) { if (postData === undefined) { postData = null; } var flags = nsIWebNavigation.LOAD_FLAGS_NONE; if (allowThirdPartyFixup) { flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; flags |= nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; } try { gBrowser.loadURIWithFlags(uri, { flags: flags, referrerURI: referrer, referrerPolicy: referrerPolicy, postData: postData, originPrincipal: originPrincipal, triggeringPrincipal: triggeringPrincipal, forceAboutBlankViewerInCurrent: forceAboutBlankViewerInCurrent }); } catch(e) {} } /** * Given a urlbar value, discerns between URIs, keywords and aliases. * * @param url * The urlbar value. * @param callback (optional, deprecated) * The callback function invoked when done. This parameter is * deprecated, please use the Promise that is returned. * * @return Promise<{ postData, url, mayInheritPrincipal }> */ function getShortcutOrURIAndPostData(url, callback = null) { if (callback) { Deprecated.warning("Please use the Promise returned by " + "getShortcutOrURIAndPostData() instead of passing a " + "callback", "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294"); } return Task.spawn(function* () { let mayInheritPrincipal = false; let postData = null; let shortcutURL = null; let keyword = url; let param = ""; let offset = url.indexOf(" "); if (offset > 0) { keyword = url.substr(0, offset); param = url.substr(offset + 1); } let engine = Services.search.getEngineByAlias(keyword); if (engine) { let submission = engine.getSubmission(param, null, "keyword"); postData = submission.postData; return { postData: submission.postData, url: submission.uri.spec, mayInheritPrincipal }; } let entry = yield PlacesUtils.keywords.fetch(keyword); if (entry) { shortcutURL = entry.url.href; postData = entry.postData; } if (!shortcutURL) { return { postData, url, mayInheritPrincipal }; } let escapedPostData = ""; if (postData) { escapedPostData = unescape(postData); } if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { let charset = ""; const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; let matches = shortcutURL.match(re); if (matches) { [, shortcutURL, charset] = matches; } else { let uri; try { // makeURI() throws if URI is invalid. uri = makeURI(shortcutURL); } catch(ex) {} if (uri) { // Try to get the saved character-set. // Will return an empty string if character-set is not found. charset = yield PlacesUtils.getCharsetForURI(uri); } } // encodeURIComponent produces UTF-8, and cannot be used for other charsets. // escape() works in those cases, but it doesn't uri-encode +, @, and /. // Therefore we need to manually replace these ASCII characters by their // encodeURIComponent result, to match the behavior of nsEscape() with // url_XPAlphas let encodedParam = ""; if (charset && charset != "UTF-8") { encodedParam = escape(convertFromUnicode(charset, param)). replace(/[+@\/]+/g, encodeURIComponent); } else { // Default charset is UTF-8 encodedParam = encodeURIComponent(param); } shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); if (/%s/i.test(escapedPostData)) { // POST keyword postData = getPostDataStream(escapedPostData, param, encodedParam, "application/x-www-form-urlencoded"); } // This URL came from a bookmark, so it's safe to let it inherit the current // document's principal. mayInheritPrincipal = true; return { postData, url: shortcutURL, mayInheritPrincipal }; } if (param) { // This keyword doesn't take a parameter, but one was provided. Just return // the original URL. postData = null; return { postData, url, mayInheritPrincipal }; } // This URL came from a bookmark, so it's safe to let it inherit the current // document's principal. mayInheritPrincipal = true; return { postData, url: shortcutURL, mayInheritPrincipal }; }).then(data => { if (callback) { callback(data); } return data; }); } function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) { var dataStream = Cc["@mozilla.org/io/string-input-stream;1"] .createInstance(Ci.nsIStringInputStream); aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword); dataStream.data = aStringData; var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"]. createInstance(Ci.nsIMIMEInputStream); mimeStream.addHeader("Content-Type", aType); mimeStream.addContentLength = true; mimeStream.setData(dataStream); return mimeStream.QueryInterface(Ci.nsIInputStream); } function getLoadContext() { return window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsILoadContext); } function readFromClipboard() { var url; try { // Create transferable that will transfer the text. var trans = Components.classes["@mozilla.org/widget/transferable;1"] .createInstance(Components.interfaces.nsITransferable); trans.init(getLoadContext()); trans.addDataFlavor("text/unicode"); // If available, use selection clipboard, otherwise global one if (Services.clipboard.supportsSelectionClipboard()) { Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard); } else { Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); } var data = {}; var dataLen = {}; trans.getTransferData("text/unicode", data, dataLen); if (data) { data = data.value.QueryInterface(Components.interfaces.nsISupportsString); url = data.data.substring(0, dataLen.value / 2); } } catch(ex) {} return url; } function BrowserViewSourceOfDocument(aArgsOrDocument) { let args; if (aArgsOrDocument instanceof Document) { let doc = aArgsOrDocument; let requestor = doc.defaultView .QueryInterface(Ci.nsIInterfaceRequestor); let browser = requestor.getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShell) .chromeEventHandler; let outerWindowID = requestor.getInterface(Ci.nsIDOMWindowUtils) .outerWindowID; let URL = browser.currentURI.spec; args = { browser, outerWindowID, URL }; } else { args = aArgsOrDocument; } let viewInternal = () => { top.gViewSourceUtils.viewSource(args); } // Check if external view source is enabled. If so, try it. If it fails, // fallback to internal view source. if (Services.prefs.getBoolPref("view_source.editor.external")) { top.gViewSourceUtils .openInExternalEditor(args, null, null, null, result => { if (!result) { viewInternal(); } }); } else { // Display using internal view source viewInternal(); } } // doc - document to use for source, or null for this window's document // initialTab - name of the initial tab to display, or null for the first tab // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted function BrowserPageInfo(doc, initialTab, imageElement) { var args = {doc: doc, initialTab: initialTab, imageElement: imageElement}; var windows = Services.wm.getEnumerator("Browser:page-info"); var documentURL = doc ? doc.location : window.content.document.location; // Check for windows matching the url while (windows.hasMoreElements()) { var currentWindow = windows.getNext(); if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) { currentWindow.focus(); currentWindow.resetPageInfo(args); return currentWindow; } } // We didn't find a matching window, so open a new one. return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "", "chrome,toolbar,dialog=no,resizable", args); } function URLBarSetURI(aURI) { var value = gBrowser.userTypedValue; var valid = false; if (value == null) { let uri = aURI || gBrowser.currentURI; // Strip off "wyciwyg://" and passwords for the location bar try { uri = Services.uriFixup.createExposableURI(uri); } catch(e) {} // Replace initial page URIs with an empty string // only if there's no opener (bug 370555). // Bug 863515 - Make content.opener checks work in electrolysis. if (gInitialPages.indexOf(uri.spec) != -1) { value = !gMultiProcessBrowser && content.opener ? uri.spec : ""; } else { value = losslessDecodeURI(uri); } valid = !isBlankPageURL(uri.spec); } let isDifferentValidValue = valid && value != gURLBar.value; gURLBar.value = value; gURLBar.valueIsTyped = !valid; if (isDifferentValidValue) { gURLBar.selectionStart = gURLBar.selectionEnd = 0; } SetPageProxyState(valid ? "valid" : "invalid"); } function losslessDecodeURI(aURI) { let scheme = aURI.scheme; let decodeASCIIOnly = !(/(https|http|file|ftp)/i.test(scheme)); var value = aURI.spec; // Try to decode as UTF-8 if there's no encoding sequence that we would break. if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value)) { if (decodeASCIIOnly) { // This only decodes ASCII characters (hex) 20-7e, except 25 (%). // This avoids both cases stipulated below (%-related issues, and \r, \n // and \t, which would be %0d, %0a and %09, respectively) as well as any // non-US-ascii characters. value = value.replace(/%(2[0-4]|2[6-9a-f]|[3-6][0-9a-f]|7[0-9a-e])/g, decodeURI); } else { try { value = decodeURI(value) // 1. decodeURI decodes %25 to %, which creates unintended // encoding sequences. Re-encode it, unless it's part of // a sequence that survived decodeURI, i.e. one for: // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#' // (RFC 3987 section 3.2) // 2. Re-encode whitespace so that it doesn't get eaten away // by the location bar (bug 410726). .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig, encodeURIComponent); } catch(e) {} } } // Encode invisible characters (C0/C1 control characters, U+007F [DEL], // U+00A0 [no-break space], line and paragraph separator, braille space, // object replacement character) (bug 452979, bug 909264) value = value.replace(/[\u0000-\u001f\u007f-\u00a0\u2028\u2029\u2800\ufffc]/g, encodeURIComponent); // Encode default ignorable characters (bug 546013) // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186). // This includes all bidirectional formatting characters. // (RFC 3987 sections 3.2 and 4.1 paragraph 6) value = value.replace(/[\u00ad\u034f\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b\u200e-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g, encodeURIComponent); return value; } function UpdateUrlbarSearchSplitterState() { var splitter = document.getElementById("urlbar-search-splitter"); var urlbar = document.getElementById("urlbar-container"); var searchbar = document.getElementById("search-container"); var stop = document.getElementById("stop-button"); var ibefore = null; if (urlbar && searchbar) { if (urlbar.nextSibling == searchbar || urlbar.getAttribute("combined") && stop && stop.nextSibling == searchbar) { ibefore = searchbar; } else if (searchbar.nextSibling == urlbar) { ibefore = urlbar; } } if (ibefore) { if (!splitter) { splitter = document.createElement("splitter"); splitter.id = "urlbar-search-splitter"; splitter.setAttribute("resizebefore", "flex"); splitter.setAttribute("resizeafter", "flex"); splitter.setAttribute("skipintoolbarset", "true"); splitter.className = "chromeclass-toolbar-additional"; } urlbar.parentNode.insertBefore(splitter, ibefore); } else if (splitter) { splitter.parentNode.removeChild(splitter); } } function setUrlAndSearchBarWidthForConditionalForwardButton() { // Workaround for bug 694084: Showing/hiding the conditional forward button resizes // the search bar when the url/search bar splitter hasn't been used. var urlbarContainer = document.getElementById("urlbar-container"); var searchbarContainer = document.getElementById("search-container"); if (!urlbarContainer || !searchbarContainer || urlbarContainer.hasAttribute("width") || searchbarContainer.hasAttribute("width") || urlbarContainer.parentNode != searchbarContainer.parentNode) { return; } urlbarContainer.style.width = searchbarContainer.style.width = ""; var urlbarWidth = urlbarContainer.clientWidth; var searchbarWidth = searchbarContainer.clientWidth; urlbarContainer.style.width = urlbarWidth + "px"; searchbarContainer.style.width = searchbarWidth + "px"; } function UpdatePageProxyState() { if (gURLBar && gURLBar.value != gLastValidURLStr) { SetPageProxyState("invalid"); } } function SetPageProxyState(aState) { BookmarkingUI.onPageProxyStateChanged(aState); if (!gURLBar) { return; } if (!gProxyFavIcon) { gProxyFavIcon = document.getElementById("page-proxy-favicon"); } gURLBar.setAttribute("pageproxystate", aState); gProxyFavIcon.setAttribute("pageproxystate", aState); // the page proxy state is set to valid via OnLocationChange, which // gets called when we switch tabs. if (aState == "valid") { gLastValidURLStr = gURLBar.value; gURLBar.addEventListener("input", UpdatePageProxyState, false); PageProxySetIcon(gBrowser.getIcon()); } else if (aState == "invalid") { gURLBar.removeEventListener("input", UpdatePageProxyState, false); PageProxyClearIcon(); } } function PageProxySetIcon (aURL) { if (!gProxyFavIcon) { return; } if (gBrowser.selectedBrowser.contentDocument instanceof ImageDocument) { // PageProxyClearIcon(); gProxyFavIcon.setAttribute("src", "chrome://browser/skin/imagedocument.png"); return; } if (!aURL) { PageProxyClearIcon(); } else if (gProxyFavIcon.getAttribute("src") != aURL) { gProxyFavIcon.setAttribute("src", aURL); } } function PageProxyClearIcon () { gProxyFavIcon.removeAttribute("src"); } function PageProxyClickHandler(aEvent) { if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste")) middleMousePaste(aEvent); } /** * Handle load of some pages (about:*) so that we can make modifications * to the DOM for unprivileged pages. */ function BrowserOnAboutPageLoad(doc) { /* === about:home === */ if (doc.documentURI.toLowerCase() == "about:home") { if (!PrivateBrowsingUtils.isWindowPrivate(window)) { let wrapper = {}; Cu.import("resource:///modules/sessionstore/SessionStore.jsm", wrapper); let ss = wrapper.SessionStore; ss.promiseInitialized.then(function() { if (ss.canRestoreLastSession) { doc.getElementById("launcher").setAttribute("session", "true"); } }).then(null, function onError(x) { Cu.reportError("Error in SessionStore init while processing 'about:home': " + x); }); } // Inject search engine and snippets URL. let docElt = doc.documentElement; if (AboutHomeUtils.showKnowYourRights) { docElt.setAttribute("showKnowYourRights", "true"); // Set pref to indicate we've shown the notification. let currentVersion = Services.prefs.getIntPref("browser.rights.version"); Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true); } function updateSearchEngine() { let engine = AboutHomeUtils.defaultSearchEngine; docElt.setAttribute("searchEngineName", engine.name); docElt.setAttribute("searchEnginePostData", engine.postDataString || ""); docElt.setAttribute("searchEngineURL", engine.searchURL); } Services.search.init(updateSearchEngine); // Listen for the event that's triggered when the user changes search engine. // At this point we simply reload about:home to reflect the change. Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false); // Remove the observer when the page is reloaded or closed. doc.defaultView.addEventListener("pagehide", function removeObserver() { doc.defaultView.removeEventListener("pagehide", removeObserver); Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified"); }, false); } /* === about:newtab === */ if (doc.documentURI.toLowerCase() == "about:newtab") { let docElt = doc.documentElement; function updateSearchEngine() { let engine = AboutHomeUtils.defaultSearchEngine; docElt.setAttribute("searchEngineName", engine.name); docElt.setAttribute("searchEnginePostData", engine.postDataString || ""); docElt.setAttribute("searchEngineURL", engine.searchURL); } Services.search.init(updateSearchEngine); // Listen for the event that's triggered when the user changes search engine. // At this point we simply reload about:newtab to reflect the change. Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false); // Remove the observer when the page is reloaded or closed. doc.defaultView.addEventListener("pagehide", function removeObserver() { doc.defaultView.removeEventListener("pagehide", removeObserver); Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified"); }, false); } } /** * Handle command events bubbling up from error page content */ var BrowserOnClick = { handleEvent: function(aEvent) { if (!aEvent.isTrusted || // Don't trust synthetic events aEvent.button == 2 || aEvent.target.localName != "button") { return; } let originalTarget = aEvent.originalTarget; let ownerDoc = originalTarget.ownerDocument; // If the event came from an ssl error page, it is probably either the "Add // Exception…" or "Get me out of here!" button if (ownerDoc.documentURI.startsWith("about:certerror")) { this.onAboutCertError(originalTarget, ownerDoc); } else if (ownerDoc.documentURI.startsWith("about:neterror")) { this.onAboutNetError(originalTarget, ownerDoc); } else if (ownerDoc.documentURI.toLowerCase() == "about:home") { this.onAboutHome(originalTarget, ownerDoc); } }, onAboutCertError: function(aTargetElm, aOwnerDoc) { let elmId = aTargetElm.getAttribute("id"); let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); switch (elmId) { case "exceptionDialogButton": let params = { exceptionAdded : false }; try { switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) { case 2 : // Pre-fetch & pre-populate params.prefetchCert = true; case 1 : // Pre-populate params.location = aOwnerDoc.location.href; } } catch(e) { Components.utils.reportError("Couldn't get ssl_override pref: " + e); } window.openDialog('chrome://pippki/content/exceptionDialog.xul', '','chrome,centerscreen,modal', params); // If the user added the exception cert, attempt to reload the page if (params.exceptionAdded) { aOwnerDoc.location.reload(); } break; case "getMeOutOfHereButton": getMeOutOfHere(); break; case "technicalContent": break; case "expertContent": break; } }, onAboutNetError: function(aTargetElm, aOwnerDoc) { let elmId = aTargetElm.getAttribute("id"); if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI)) { return; } Services.io.offline = false; }, onAboutHome: function(aTargetElm, aOwnerDoc) { let elmId = aTargetElm.getAttribute("id"); switch (elmId) { case "restorePreviousSession": let ss = Cc["@mozilla.org/browser/sessionstore;1"] .getService(Ci.nsISessionStore); if (ss.canRestoreLastSession) { ss.restoreLastSession(); } aOwnerDoc.getElementById("launcher").removeAttribute("session"); break; case "downloads": BrowserDownloadsUI(); break; case "bookmarks": PlacesCommandHook.showPlacesOrganizer("AllBookmarks"); break; case "history": PlacesCommandHook.showPlacesOrganizer("History"); break; case "addons": BrowserOpenAddonsMgr(); break; case "sync": openPreferences("paneSync"); break; case "settings": openPreferences(); break; } }, }; /** * Re-direct the browser to a known-safe page. This function is * used when, for example, the user browses to a known malware page * and is presented with about:blocked. The "Get me out of here!" * button should take the user to the default start page so that even * when their own homepage is infected, we can get them somewhere safe. */ function getMeOutOfHere() { try { let toBlank = Services.prefs.getBoolPref("browser.escape_to_blank"); if (toBlank) { content.location = "about:logopage"; return; } } catch(e) { Components.utils.reportError("Couldn't get escape pref: " + e); } // Get the start page from the *default* pref branch, not the user's var prefs = Services.prefs.getDefaultBranch(null); var url = BROWSER_NEW_TAB_URL; try { url = prefs.getComplexValue("browser.startup.homepage", Ci.nsIPrefLocalizedString).data; // If url is a pipe-delimited set of pages, just take the first one. if (url.includes("|")) url = url.split("|")[0]; } catch(e) { Components.utils.reportError("Couldn't get homepage pref: " + e); } content.location = url; } function BrowserFullScreen() { window.fullScreen = !window.fullScreen; } function onFullScreen() { FullScreen.toggle(); } function onMozEnteredDomFullscreen(event) { FullScreen.enterDomFullscreen(event); } function getWebNavigation() { return gBrowser.webNavigation; } function BrowserReloadWithFlags(reloadFlags) { // Reset DOS mitigation for auth prompts when user initiates a reload. let browser = gBrowser.selectedBrowser; delete browser.authPromptCounter; // First, we'll try to use the session history object to reload so // that framesets are handled properly. If we're in a special // window (such as view-source) that has no session history, fall // back on using the web navigation's reload method. var webNav = gBrowser.webNavigation; try { var sh = webNav.sessionHistory; if (sh) webNav = sh.QueryInterface(nsIWebNavigation); } catch(e) {} try { webNav.reload(reloadFlags); } catch(e) {} } var PrintPreviewListener = { _printPreviewTab: null, _tabBeforePrintPreview: null, getPrintPreviewBrowser: function() { if (!this._printPreviewTab) { this._tabBeforePrintPreview = gBrowser.selectedTab; this._printPreviewTab = gBrowser.loadOneTab("about:blank", { inBackground: false }); gBrowser.selectedTab = this._printPreviewTab; } return gBrowser.getBrowserForTab(this._printPreviewTab); }, getSourceBrowser: function() { return this._tabBeforePrintPreview ? this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser; }, getNavToolbox: function() { return gNavToolbox; }, onEnter: function() { // We might have accidentally switched tabs since the user invoked print // preview if (gBrowser.selectedTab != this._printPreviewTab) { gBrowser.selectedTab = this._printPreviewTab; } gInPrintPreviewMode = true; this._toggleAffectedChrome(); }, onExit: function() { gBrowser.selectedTab = this._tabBeforePrintPreview; this._tabBeforePrintPreview = null; gInPrintPreviewMode = false; this._toggleAffectedChrome(); gBrowser.removeTab(this._printPreviewTab); this._printPreviewTab = null; }, _toggleAffectedChrome: function() { gNavToolbox.collapsed = gInPrintPreviewMode; if (gInPrintPreviewMode) { this._hideChrome(); } else { this._showChrome(); } if (this._chromeState.sidebarOpen) { toggleSidebar(this._sidebarCommand); } #ifdef MENUBAR_CAN_AUTOHIDE updateAppButtonDisplay(); #endif }, _hideChrome: function() { this._chromeState = {}; var sidebar = document.getElementById("sidebar-box"); this._chromeState.sidebarOpen = !sidebar.hidden; this._sidebarCommand = sidebar.getAttribute("sidebarcommand"); var notificationBox = gBrowser.getNotificationBox(); this._chromeState.notificationsOpen = !notificationBox.notificationsHidden; notificationBox.notificationsHidden = true; document.getElementById("sidebar").setAttribute("src", "about:blank"); var addonBar = document.getElementById("addon-bar"); this._chromeState.addonBarOpen = !addonBar.collapsed; addonBar.collapsed = true; gBrowser.updateWindowResizers(); this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden; if (gFindBarInitialized) { gFindBar.close(); } var globalNotificationBox = document.getElementById("global-notificationbox"); this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden; globalNotificationBox.notificationsHidden = true; this._chromeState.syncNotificationsOpen = false; var syncNotifications = document.getElementById("sync-notifications"); if (syncNotifications) { this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden; syncNotifications.notificationsHidden = true; } }, _showChrome: function() { if (this._chromeState.notificationsOpen) { gBrowser.getNotificationBox().notificationsHidden = false; } if (this._chromeState.addonBarOpen) { document.getElementById("addon-bar").collapsed = false; gBrowser.updateWindowResizers(); } if (this._chromeState.findOpen) { gFindBar.open(); } if (this._chromeState.globalNotificationsOpen) { document.getElementById("global-notificationbox").notificationsHidden = false; } if (this._chromeState.syncNotificationsOpen) { document.getElementById("sync-notifications").notificationsHidden = false; } } } function getMarkupDocumentViewer() { return gBrowser.markupDocumentViewer; } // This function is obsolete. Newer code should use instead. function FillInHTMLTooltip(tipElement) { document.getElementById("aHTMLTooltip").fillInPageTooltip(tipElement); } var browserDragAndDrop = { canDropLink: function(aEvent) { return Services.droppedLinkHandler.canDropLink(aEvent, true) }, dragOver: function(aEvent) { if (this.canDropLink(aEvent)) { aEvent.preventDefault(); } }, dropLinks: function(aEvent, aDisallowInherit) { return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit); } }; var homeButtonObserver = { onDrop: function(aEvent) { // disallow setting home pages that inherit the principal let links = browserDragAndDrop.dropLinks(aEvent, true); if (links.length) { setTimeout(openHomeDialog, 0, links.map(link => link.url).join("|")); } }, onDragOver: function(aEvent) { browserDragAndDrop.dragOver(aEvent); aEvent.dropEffect = "link"; }, onDragExit: function(aEvent) { } } function openHomeDialog(aURL) { var promptTitle = gNavigatorBundle.getString("droponhometitle"); var promptMsg; if (aURL.includes("|")) { promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple"); } else { promptMsg = gNavigatorBundle.getString("droponhomemsg"); } var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg, Services.prompt.STD_YES_NO_BUTTONS, null, null, null, null, {value: 0}); if (pressedVal == 0) { try { var homepageStr = Components.classes["@mozilla.org/supports-string;1"] .createInstance(Components.interfaces.nsISupportsString); homepageStr.data = aURL; gPrefService.setComplexValue("browser.startup.homepage", Components.interfaces.nsISupportsString, homepageStr); } catch(ex) { dump("Failed to set the home page.\n"+ex+"\n"); } } } var bookmarksButtonObserver = { onDrop: function(aEvent) { let name = {}; let url = browserDragAndDrop.drop(aEvent, name); try { PlacesUIUtils.showBookmarkDialog({ action: "add", type: "bookmark", uri: makeURI(url), title: name, hiddenRows: [ "description", "location", "loadInSidebar", "keyword" ] }, window); } catch(ex) {} }, onDragOver: function(aEvent) { browserDragAndDrop.dragOver(aEvent); aEvent.dropEffect = "link"; }, onDragExit: function(aEvent) { } } var newTabButtonObserver = { onDragOver: function(aEvent) { browserDragAndDrop.dragOver(aEvent); }, onDragExit: function(aEvent) {}, onDrop: function(aEvent) { let links = browserDragAndDrop.dropLinks(aEvent); Task.spawn(function*() { for (let link of links) { let data = yield getShortcutOrURIAndPostData(link.url); if (data.url) { // allow third-party services to fixup this URL openNewTabWith(data.url, null, data.postData, aEvent, true); } } }); } } var newWindowButtonObserver = { onDragOver: function(aEvent) { browserDragAndDrop.dragOver(aEvent); }, onDragExit: function(aEvent) {}, onDrop: function(aEvent) { let links = browserDragAndDrop.dropLinks(aEvent); Task.spawn(function*() { for (let link of links) { let data = yield getShortcutOrURIAndPostData(link.url); if (data.url) { // allow third-party services to fixup this URL openNewWindowWith(data.url, null, data.postData, true); } } }); } } const DOMLinkHandler = { handleEvent: function(event) { switch (event.type) { case "DOMLinkAdded": this.onLinkAdded(event); break; } }, getLinkIconURI: function(aLink) { let targetDoc = aLink.ownerDocument; var uri = makeURI(aLink.href, targetDoc.characterSet); // Verify that the load of this icon is legal. // Some error or special pages can load their favicon. // To be on the safe side, only allow chrome:// favicons. var isAllowedPage = [ /^about:neterror\?/, /^about:blocked\?/, /^about:certerror\?/, /^about:home$/, ].some(function(re) re.test(targetDoc.documentURI)); if (!isAllowedPage || !uri.schemeIs("chrome")) { var ssm = Services.scriptSecurityManager; try { ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri, Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); } catch(e) { return null; } } try { var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]. getService(Ci.nsIContentPolicy); } catch(e) { // Refuse to load if we can't do a security check. return null; } // Security says okay, now ask content policy if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE, uri, targetDoc.documentURIObject, aLink, aLink.type, null) != Ci.nsIContentPolicy.ACCEPT) { return null; } try { uri.userPass = ""; } catch(e) { // some URIs are immutable } return uri; }, onLinkAdded: function(event) { var link = event.originalTarget; var rel = link.rel && link.rel.toLowerCase(); if (!link || !link.ownerDocument || !rel || !link.href) { return; } var feedAdded = false; var iconAdded = false; var searchAdded = false; var rels = {}; for (let relString of rel.split(/\s+/)) { rels[relString] = true; } for (let relVal in rels) { switch (relVal) { case "feed": case "alternate": if (!feedAdded) { if (!rels.feed && rels.alternate && rels.stylesheet) { break; } if (isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) { FeedHandler.addFeed(link, link.ownerDocument); feedAdded = true; } } break; case "icon": if (!iconAdded) { if (!gPrefService.getBoolPref("browser.chrome.site_icons")) { break; } var uri = this.getLinkIconURI(link); if (!uri) { break; } if (gBrowser.isFailedIcon(uri)) { break; } var browserIndex = gBrowser.getBrowserIndexForDocument(link.ownerDocument); if (browserIndex == -1) { // no browser? no favicon. break; } let tab = gBrowser.tabs[browserIndex]; gBrowser.setIcon(tab, uri.spec, link.ownerDocument.nodePrincipal); iconAdded = true; } break; case "search": if (!searchAdded) { var type = link.type && link.type.toLowerCase(); type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); if (type == "application/opensearchdescription+xml" && link.title && /^(?:https?|ftp):/i.test(link.href) && !PrivateBrowsingUtils.isWindowPrivate(window)) { var engine = { title: link.title, href: link.href }; Services.search.init(function() { BrowserSearch.addEngine(engine, link.ownerDocument); }); searchAdded = true; } } break; } } } } const BrowserSearch = { addEngine: function(engine, targetDoc) { if (!this.searchBar) { return; } var browser = gBrowser.getBrowserForDocument(targetDoc); // ignore search engines from subframes (see bug 479408) if (!browser) { return; } // Check to see whether we've already added an engine with this title if (browser.engines) { if (browser.engines.some(function(e) e.title == engine.title)) { return; } } // Append the URI and an appropriate title to the browser data. // Use documentURIObject in the check for shouldLoadFavIcon so that we // do the right thing with about:-style error pages. Bug 453442 var iconURL = null; if (gBrowser.shouldLoadFavIcon(targetDoc.documentURIObject)) { iconURL = targetDoc.documentURIObject.prePath + "/favicon.ico"; } var hidden = false; // If this engine (identified by title) is already in the list, add it // to the list of hidden engines rather than to the main list. // XXX This will need to be changed when engines are identified by URL; // see bug 335102. if (Services.search.getEngineByName(engine.title)) { hidden = true; } var engines = (hidden ? browser.hiddenEngines : browser.engines) || []; engines.push({ uri: engine.href, title: engine.title, icon: iconURL }); if (hidden) { browser.hiddenEngines = engines; } else { browser.engines = engines; } }, /** * Gives focus to the search bar, if it is present on the toolbar, or loads * the default engine's search form otherwise. For Mac, opens a new window * or focuses an existing window, if necessary. */ webSearch: function() { #ifdef XP_MACOSX if (window.location.href != getBrowserURL()) { var win = getTopWin(); if (win) { // If there's an open browser window, it should handle this command win.focus(); win.BrowserSearch.webSearch(); } else { // If there are no open browser windows, open a new one var observer = function observer(subject, topic, data) { if (subject == win) { BrowserSearch.webSearch(); Services.obs.removeObserver(observer, "browser-delayed-startup-finished"); } } win = window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", "about:blank"); Services.obs.addObserver(observer, "browser-delayed-startup-finished", false); } return; } #endif var searchBar = this.searchBar; if (searchBar && window.fullScreen) { FullScreen.showNavToolbox(); } if (searchBar) { searchBar.select(); } if (!searchBar || document.activeElement != searchBar.textbox.inputField) { openUILinkIn(Services.search.defaultEngine.searchForm, "current"); } }, /** * Loads a search results page, given a set of search terms. Uses the current * engine if the search bar is visible, or the default engine otherwise. * * @param searchText * The search terms to use for the search. * * @param useNewTab * Boolean indicating whether or not the search should load in a new * tab. * * @param purpose [optional] * A string meant to indicate the context of the search request. This * allows the search service to provide a different nsISearchSubmission * depending on e.g. where the search is triggered in the UI. * * @return string Name of the search engine used to perform a search or null * if a search was not performed. */ loadSearch: function(searchText, useNewTab, purpose) { var engine; // If the search bar is visible, use the current engine, otherwise, fall // back to the default engine. if (isElementVisible(this.searchBar)) { engine = Services.search.currentEngine; } else { engine = Services.search.defaultEngine; } var submission = engine.getSubmission(searchText, null, purpose); // HTML response // getSubmission can return null if the engine doesn't have a URL // with a text/html response type. This is unlikely (since // SearchService._addEngineToStore() should fail for such an engine), // but let's be on the safe side. if (!submission) { return null; } let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground"); openLinkIn(submission.uri.spec, useNewTab ? "tab" : "current", { postData: submission.postData, inBackground: inBackground, relatedToCurrent: true }); return engine.name; }, /** * Perform a search initiated from the context menu. * * This should only be called from the context menu. See * BrowserSearch.loadSearch for the preferred API. */ loadSearchFromContext: function(terms) { let engine = BrowserSearch.loadSearch(terms, true, "contextmenu"); }, /** * Returns the search bar element if it is present in the toolbar, null otherwise. */ get searchBar() { return document.getElementById("searchbar"); }, loadAddEngines: function() { var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow"); var where = newWindowPref == 3 ? "tab" : "window"; var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true); openUILinkIn(searchEnginesURL, where); }, }; XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch); function FillHistoryMenu(aParent) { // Lazily add the hover listeners on first showing and never remove them if (!aParent.hasStatusListener) { // Show history item's uri in the status bar when hovering, and clear on exit aParent.addEventListener("DOMMenuItemActive", function(aEvent) { // Only the current page should have the checked attribute, so skip it if (!aEvent.target.hasAttribute("checked")) XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri")); }, false); aParent.addEventListener("DOMMenuItemInactive", function() { XULBrowserWindow.setOverLink(""); }, false); aParent.hasStatusListener = true; } // Remove old entries if any var children = aParent.childNodes; for (var i = children.length - 1; i >= 0; --i) { if (children[i].hasAttribute("index")) { aParent.removeChild(children[i]); } } var webNav = gBrowser.webNavigation; var sessionHistory = webNav.sessionHistory; var count = sessionHistory.count; if (count <= 1) { // don't display the popup for a single item return false; } const MAX_HISTORY_MENU_ITEMS = 15; var index = sessionHistory.index; var half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2); var start = Math.max(index - half_length, 0); var end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count); if (end == count) { start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0); } var tooltipBack = gNavigatorBundle.getString("tabHistory.goBack"); var tooltipCurrent = gNavigatorBundle.getString("tabHistory.current"); var tooltipForward = gNavigatorBundle.getString("tabHistory.goForward"); for (var j = end - 1; j >= start; j--) { let item = document.createElement("menuitem"); let entry = sessionHistory.getEntryAtIndex(j, false); let uri = entry.URI.spec; item.setAttribute("uri", uri); item.setAttribute("label", entry.title || uri); item.setAttribute("index", j); if (j != index) { PlacesUtils.favicons.getFaviconURLForPage(entry.URI, function(aURI) { if (aURI) { let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec; item.style.listStyleImage = "url(" + iconURL + ")"; } }); } if (j < index) { item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon"; item.setAttribute("tooltiptext", tooltipBack); } else if (j == index) { item.setAttribute("type", "radio"); item.setAttribute("checked", "true"); item.className = "unified-nav-current"; item.setAttribute("tooltiptext", tooltipCurrent); } else { item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon"; item.setAttribute("tooltiptext", tooltipForward); } aParent.appendChild(item); } return true; } function addToUrlbarHistory(aUrlToAdd) { if (!PrivateBrowsingUtils.isWindowPrivate(window) && aUrlToAdd && !aUrlToAdd.includes(" ") && !/[\x00-\x1F]/.test(aUrlToAdd)) { PlacesUIUtils.markPageAsTyped(aUrlToAdd); } } function toJavaScriptConsole() { toOpenWindowByType("global:console", "chrome://global/content/console.xul"); } function BrowserDownloadsUI() { if (PrivateBrowsingUtils.isWindowPrivate(window)) { openUILinkIn("about:downloads", "tab"); } else { PlacesCommandHook.showPlacesOrganizer("Downloads"); } } function toOpenWindowByType(inType, uri, features) { var topWindow = Services.wm.getMostRecentWindow(inType); if (topWindow) { topWindow.focus(); } else if (features) { window.open(uri, "_blank", features); } else { window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"); } } function OpenBrowserWindow(options) { function newDocumentShown(doc, topic, data) { if (topic == "document-shown" && doc != document && doc.defaultView == win) { Services.obs.removeObserver(newDocumentShown, "document-shown"); } }; Services.obs.addObserver(newDocumentShown, "document-shown", false); var charsetArg = new String(); var handler = Components.classes["@mozilla.org/browser/clh;1"] .getService(Components.interfaces.nsIBrowserHandler); var defaultArgs = handler.defaultArgs; var wintype = document.documentElement.getAttribute('windowtype'); var extraFeatures = ""; if (options && options.private) { extraFeatures = ",private"; if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { // Force the new window to load about:privatebrowsing instead of the default home page defaultArgs = "about:privatebrowsing"; } } else { extraFeatures = ",non-private"; } // if and only if the current window is a browser window and it has a document with a character // set, then extract the current charset menu setting from the current document and use it to // initialize the new browser window... var win; if (window && (wintype == "navigator:browser") && window.content && window.content.document) { var DocCharset = window.content.document.characterSet; charsetArg = "charset="+DocCharset; //we should "inherit" the charset menu setting in a new window win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg); } else { // forget about the charset information. win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs); } return win; } var gCustomizeSheet = false; function BrowserCustomizeToolbar() { // Disable the toolbar context menu items var menubar = document.getElementById("main-menubar"); for (let childNode of menubar.childNodes) { childNode.setAttribute("disabled", true); } var cmd = document.getElementById("cmd_CustomizeToolbars"); cmd.setAttribute("disabled", "true"); var splitter = document.getElementById("urlbar-search-splitter"); if (splitter) { splitter.parentNode.removeChild(splitter); } CombinedStopReload.uninit(); PlacesToolbarHelper.customizeStart(); BookmarkingUI.customizeStart(); DownloadsButton.customizeStart(); TabsInTitlebar.allowedBy("customizing-toolbars", false); var customizeURL = "chrome://global/content/customizeToolbar.xul"; gCustomizeSheet = Services.prefs.getBoolPref("toolbar.customization.usesheet", false); if (gCustomizeSheet) { let sheetFrame = document.createElement("iframe"); let panel = document.getElementById("customizeToolbarSheetPopup"); sheetFrame.id = "customizeToolbarSheetIFrame"; sheetFrame.toolbox = gNavToolbox; sheetFrame.panel = panel; sheetFrame.setAttribute("style", panel.getAttribute("sheetstyle")); panel.appendChild(sheetFrame); // Open the panel, but make it invisible until the iframe has loaded so // that the user doesn't see a white flash. panel.style.visibility = "hidden"; gNavToolbox.addEventListener("beforecustomization", function onBeforeCustomization() { gNavToolbox.removeEventListener("beforecustomization", onBeforeCustomization, false); panel.style.removeProperty("visibility"); }, false); sheetFrame.setAttribute("src", customizeURL); panel.openPopup(gNavToolbox, "after_start", 0, 0); } else { window.openDialog(customizeURL, "CustomizeToolbar", "chrome,titlebar,toolbar,location,resizable,dependent", gNavToolbox); } } function BrowserToolboxCustomizeDone(aToolboxChanged) { if (gCustomizeSheet) { document.getElementById("customizeToolbarSheetPopup").hidePopup(); let iframe = document.getElementById("customizeToolbarSheetIFrame"); iframe.parentNode.removeChild(iframe); } // Update global UI elements that may have been added or removed if (aToolboxChanged) { gURLBar = document.getElementById("urlbar"); gProxyFavIcon = document.getElementById("page-proxy-favicon"); gHomeButton.updateTooltip(); gIdentityHandler._cacheElements(); window.XULBrowserWindow.init(); #ifndef XP_MACOSX updateEditUIVisibility(); #endif // Hacky: update the PopupNotifications' object's reference to the iconBox, // if it already exists, since it may have changed if the URL bar was // added/removed. if (!window.__lookupGetter__("PopupNotifications")) { PopupNotifications.iconBox = document.getElementById("notification-popup-box"); } } PlacesToolbarHelper.customizeDone(); BookmarkingUI.customizeDone(); DownloadsButton.customizeDone(); // The url bar splitter state is dependent on whether stop/reload // and the location bar are combined, so we need this ordering CombinedStopReload.init(); UpdateUrlbarSearchSplitterState(); setUrlAndSearchBarWidthForConditionalForwardButton(); // Update the urlbar if (gURLBar) { gURLBarSettings.writePlaceholder(); URLBarSetURI(); XULBrowserWindow.asyncUpdateUI(); BookmarkingUI.updateStarState(); } TabsInTitlebar.allowedBy("customizing-toolbars", true); // Re-enable parts of the UI we disabled during the dialog var menubar = document.getElementById("main-menubar"); for (let childNode of menubar.childNodes) { childNode.setAttribute("disabled", false); } var cmd = document.getElementById("cmd_CustomizeToolbars"); cmd.removeAttribute("disabled"); // make sure to re-enable click-and-hold if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false)) { SetClickAndHoldHandlers(); } gBrowser.selectedBrowser.focus(); } function BrowserToolboxCustomizeChange(aType) { switch (aType) { case "iconsize": case "mode": retrieveToolbarIconsizesFromTheme(); break; default: gHomeButton.updatePersonalToolbarStyle(); BookmarkingUI.customizeChange(); allTabs.readPref(); } } /** * Allows themes to override the "iconsize" attribute on toolbars. */ function retrieveToolbarIconsizesFromTheme() { function retrieveToolbarIconsize(aToolbar) { if (aToolbar.localName != "toolbar") { return; } // The theme indicates that it wants to override the "iconsize" attribute // by specifying a special value for the "counter-reset" property on the // toolbar. A custom property cannot be used because getComputedStyle can // only return the values of standard CSS properties. let counterReset = getComputedStyle(aToolbar).counterReset; if (counterReset == "smallicons 0") { aToolbar.setAttribute("iconsize", "small"); } else if (counterReset == "largeicons 0") { aToolbar.setAttribute("iconsize", "large"); } } Array.forEach(gNavToolbox.childNodes, retrieveToolbarIconsize); gNavToolbox.externalToolbars.forEach(retrieveToolbarIconsize); } /** * Update the global flag that tracks whether or not any edit UI (the Edit menu, * edit-related items in the context menu, and edit-related toolbar buttons * is visible, then update the edit commands' enabled state accordingly. We use * this flag to skip updating the edit commands on focus or selection changes * when no UI is visible to improve performance (including pageload performance, * since focus changes when you load a new page). * * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands' * enabled state so the UI will reflect it appropriately. * * If the UI isn't visible, we enable all edit commands so keyboard shortcuts * still work and just lazily disable them as needed when the user presses a * shortcut. * * This doesn't work on Mac, since Mac menus flash when users press their * keyboard shortcuts, so edit UI is essentially always visible on the Mac, * and we need to always update the edit commands. Thus on Mac this function * is a no op. */ function updateEditUIVisibility() { #ifndef XP_MACOSX let editMenuPopupState = document.getElementById("menu_EditPopup").state; let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state; let placesContextMenuPopupState = document.getElementById("placesContext").state; #ifdef MENUBAR_CAN_AUTOHIDE let appMenuPopupState = document.getElementById("appmenu-popup").state; #endif // The UI is visible if the Edit menu is opening or open, if the context menu // is open, or if the toolbar has been customized to include the Cut, Copy, // or Paste toolbar buttons. gEditUIVisible = editMenuPopupState == "showing" || editMenuPopupState == "open" || contextMenuPopupState == "showing" || contextMenuPopupState == "open" || placesContextMenuPopupState == "showing" || placesContextMenuPopupState == "open" || #ifdef MENUBAR_CAN_AUTOHIDE appMenuPopupState == "showing" || appMenuPopupState == "open" || #endif document.getElementById("cut-button") || document.getElementById("copy-button") || document.getElementById("paste-button") ? true : false; if (gEditUIVisible) { // If UI is visible, update the edit commands' enabled state to reflect // whether or not they are actually enabled for the current focus/selection. goUpdateGlobalEditMenuItems(); } else { // Otherwise, enable all commands, so that keyboard shortcuts still work, // then lazily determine their actual enabled state when the user presses // a keyboard shortcut. goSetCommandEnabled("cmd_undo", true); goSetCommandEnabled("cmd_redo", true); goSetCommandEnabled("cmd_cut", true); goSetCommandEnabled("cmd_copy", true); goSetCommandEnabled("cmd_paste", true); goSetCommandEnabled("cmd_selectAll", true); goSetCommandEnabled("cmd_delete", true); goSetCommandEnabled("cmd_switchTextDirection", true); } #endif } /** * Makes the Character Encoding menu enabled or disabled as appropriate. * To be called when the View menu or the app menu is opened. */ function updateCharacterEncodingMenuState() { let charsetMenu = document.getElementById("charsetMenu"); let appCharsetMenu = document.getElementById("appmenu_charsetMenu"); let appDevCharsetMenu = document.getElementById("appmenu_developer_charsetMenu"); // gBrowser is null on Mac when the menubar shows in the context of // non-browser windows. The above elements may be null depending on // what parts of the menubar are present. E.g. no app menu on Mac. if (gBrowser && gBrowser.docShell && gBrowser.docShell.mayEnableCharacterEncodingMenu) { if (charsetMenu) { charsetMenu.removeAttribute("disabled"); } if (appCharsetMenu) { appCharsetMenu.removeAttribute("disabled"); } if (appDevCharsetMenu) { appDevCharsetMenu.removeAttribute("disabled"); } } else { if (charsetMenu) { charsetMenu.setAttribute("disabled", "true"); } if (appCharsetMenu) { appCharsetMenu.setAttribute("disabled", "true"); } if (appDevCharsetMenu) { appDevCharsetMenu.setAttribute("disabled", "true"); } } } var XULBrowserWindow = { // Stored Status, Link and Loading values status: "", defaultStatus: "", overLink: "", startTime: 0, statusText: "", isBusy: false, // Don't hide navigation controls and toolbars for "special" pages. SBaD, M! // If necessary, can add pages that need this treatment like so: // inContentWhitelist: ["about:addons", "about:downloads", "about:permissions", "about:sync-progress"], inContentWhitelist: [], QueryInterface: function(aIID) { if (aIID.equals(Ci.nsIWebProgressListener) || aIID.equals(Ci.nsIWebProgressListener2) || aIID.equals(Ci.nsISupportsWeakReference) || aIID.equals(Ci.nsIXULBrowserWindow) || aIID.equals(Ci.nsISupports)) { return this; } throw Cr.NS_NOINTERFACE; }, get stopCommand () { delete this.stopCommand; return this.stopCommand = document.getElementById("Browser:Stop"); }, get reloadCommand () { delete this.reloadCommand; return this.reloadCommand = document.getElementById("Browser:Reload"); }, get statusTextField () { delete this.statusTextField; return this.statusTextField = document.getElementById("statusbar-display"); }, get isImage () { delete this.isImage; return this.isImage = document.getElementById("isImage"); }, init: function() { this.throbberElement = document.getElementById("navigator-throbber"); // Bug 666809 - SecurityUI support for e10s if (gMultiProcessBrowser) { return; } // Initialize the security button's state and tooltip text. Remember to reset // _hostChanged, otherwise onSecurityChange will short circuit. var securityUI = gBrowser.securityUI; this._hostChanged = true; this.onSecurityChange(null, null, securityUI.state); }, destroy: function() { // XXXjag to avoid leaks :-/, see bug 60729 delete this.throbberElement; delete this.stopCommand; delete this.reloadCommand; delete this.statusTextField; delete this.statusText; }, setJSStatus: function() { // unsupported }, setDefaultStatus: function(status) { this.defaultStatus = status; this.updateStatusField(); }, setOverLink: function(url, anchorElt) { // Encode bidirectional formatting characters. // (RFC 3987 sections 3.2 and 4.1 paragraph 6) url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, encodeURIComponent); if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */) { url = trimURL(url); } this.overLink = url; LinkTargetDisplay.update(); }, updateStatusField: function() { var text, type, types = ["overLink"]; if (this._busyUI) { types.push("status"); } types.push("defaultStatus"); for (type of types) { text = this[type]; if (text) { break; } } // check the current value so we don't trigger an attribute change // and cause needless (slow!) UI updates if (this.statusText != text) { let field = this.statusTextField; field.setAttribute("previoustype", field.getAttribute("type")); field.setAttribute("type", type); field.label = text; field.setAttribute("crop", type == "overLink" ? "center" : "end"); this.statusText = text; } }, // Called before links are navigated to to allow us to retarget them if needed. onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) { let target = this._onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab); return target; }, _onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) { // Don't modify non-default targets or targets that aren't in top-level app // tab docshells (isAppTab will be false for app tab subframes). if (originalTarget != "" || !isAppTab) { return originalTarget; } // External links from within app tabs should always open in new tabs // instead of replacing the app tab's page (Bug 575561) let linkHost; let docHost; try { linkHost = linkURI.host; docHost = linkNode.ownerDocument.documentURIObject.host; } catch(e) { // nsIURI.host can throw for non-nsStandardURL nsIURIs. // If we fail to get either host, just return originalTarget. return originalTarget; } if (docHost == linkHost) { return originalTarget; } // Special case: ignore "www" prefix if it is part of host string let [longHost, shortHost] = linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost]; if (longHost == "www." + shortHost) { return originalTarget; } return "_blank"; }, onLinkIconAvailable: function(aIconURL) { if (gProxyFavIcon && gBrowser.userTypedValue === null) { PageProxySetIcon(aIconURL); // update the favicon in the URL bar } }, onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { // Do nothing. }, onProgressChange64: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { return this.onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); }, // This function fires only for the currently selected tab. onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { const nsIWebProgressListener = Ci.nsIWebProgressListener; const nsIChannel = Ci.nsIChannel; if (aStateFlags & nsIWebProgressListener.STATE_START && aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { if (aRequest && aWebProgress.isTopLevel) { // clear out feed data gBrowser.selectedBrowser.feeds = null; // clear out search-engine data gBrowser.selectedBrowser.engines = null; } this.isBusy = true; if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { this._busyUI = true; // Turn the throbber on. if (this.throbberElement) { this.throbberElement.setAttribute("busy", "true"); } // XXX: This needs to be based on window activity... this.stopCommand.removeAttribute("disabled"); CombinedStopReload.switchToStop(); } } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { // This (thanks to the filter) is a network stop or the last // request stop outside of loading the document, stop throbbers // and progress bars and such if (aRequest) { let msg = ""; let location; // Get the URI either from a channel or a pseudo-object if (aRequest instanceof nsIChannel || "URI" in aRequest) { location = aRequest.URI; // For keyword URIs clear the user typed value since they will be changed into real URIs if (location.scheme == "keyword" && aWebProgress.isTopLevel) { gBrowser.userTypedValue = null; } if (location.spec != "about:blank") { switch (aStatus) { case Components.results.NS_ERROR_NET_TIMEOUT: msg = gNavigatorBundle.getString("nv_timeout"); break; } } } this.status = ""; this.setDefaultStatus(msg); // Disable menu entries for images, enable otherwise if (!gMultiProcessBrowser && content.document && BrowserUtils.mimeTypeIsTextBased(content.document.contentType)) { this.isImage.removeAttribute('disabled'); } else { this.isImage.setAttribute('disabled', 'true'); } } this.isBusy = false; if (this._busyUI) { this._busyUI = false; // Turn the throbber off. if (this.throbberElement) { this.throbberElement.removeAttribute("busy"); } this.stopCommand.setAttribute("disabled", "true"); CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest); } } }, onLocationChange: function(aWebProgress, aRequest, aLocationURI, aFlags) { var location = aLocationURI ? aLocationURI.spec : ""; this._hostChanged = true; // If displayed, hide the form validation popup. FormValidationHandler.hidePopup(); let pageTooltip = document.getElementById("aHTMLTooltip"); let tooltipNode = pageTooltip.triggerNode; if (tooltipNode) { // Optimise for the common case if (aWebProgress.isTopLevel) { pageTooltip.hidePopup(); } else { for (let tooltipWindow = tooltipNode.ownerDocument.defaultView; tooltipWindow != tooltipWindow.parent; tooltipWindow = tooltipWindow.parent) { if (tooltipWindow == aWebProgress.DOMWindow) { pageTooltip.hidePopup(); break; } } } } // Disable menu entries for images, enable otherwise if (!gMultiProcessBrowser && content.document && BrowserUtils.mimeTypeIsTextBased(content.document.contentType)) { this.isImage.removeAttribute('disabled'); } else { this.isImage.setAttribute('disabled', 'true'); } this.hideOverLinkImmediately = true; this.setOverLink("", null); this.hideOverLinkImmediately = false; // We should probably not do this if the value has changed since the user // searched. // Update urlbar only if a new page was loaded on the primary content area. // Do not update urlbar if there was a subframe navigation var browser = gBrowser.selectedBrowser; if (aWebProgress.isTopLevel) { if ((location == "about:blank" && (gMultiProcessBrowser || !content.opener)) || location == "") { // Second condition is for new tabs, otherwise // reload function is enabled until tab is refreshed. this.reloadCommand.setAttribute("disabled", "true"); } else { this.reloadCommand.removeAttribute("disabled"); } if (gURLBar) { URLBarSetURI(aLocationURI); // Update starring UI BookmarkingUI.updateStarState(); } // Show or hide browser chrome based on the whitelist if (this.hideChromeForLocation(location)) { document.documentElement.setAttribute("disablechrome", "true"); } else { let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); if (ss.getTabValue(gBrowser.selectedTab, "appOrigin")) { document.documentElement.setAttribute("disablechrome", "true"); } else { document.documentElement.removeAttribute("disablechrome"); } } // Utility functions for disabling find var shouldDisableFind = function(aDocument) { let docElt = aDocument.documentElement; return docElt && docElt.getAttribute("disablefastfind") == "true"; } var disableFindCommands = function(aDisable) { let findCommands = [document.getElementById("cmd_find"), document.getElementById("cmd_findAgain"), document.getElementById("cmd_findPrevious")]; for (let elt of findCommands) { if (aDisable) { elt.setAttribute("disabled", "true"); } else { elt.removeAttribute("disabled"); } } if (gFindBarInitialized) { if (!gFindBar.hidden && aDisable) { gFindBar.hidden = true; this._findbarTemporarilyHidden = true; } else if (this._findbarTemporarilyHidden && !aDisable) { gFindBar.hidden = false; this._findbarTemporarilyHidden = false; } } }.bind(this); var onContentRSChange = function onContentRSChange(e) { if (e.target.readyState != "interactive" && e.target.readyState != "complete") { return; } e.target.removeEventListener("readystatechange", onContentRSChange); disableFindCommands(shouldDisableFind(e.target)); } // Disable find commands in documents that ask for them to be disabled. if (!gMultiProcessBrowser && aLocationURI && (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) { // Don't need to re-enable/disable find commands for same-document location changes // (e.g. the replaceStates in about:addons) if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) { if (content.document.readyState == "interactive" || content.document.readyState == "complete") { disableFindCommands(shouldDisableFind(content.document)); } else { content.document.addEventListener("readystatechange", onContentRSChange); } } } else { disableFindCommands(false); } if (gFindBarInitialized) { if (gFindBar.findMode != gFindBar.FIND_NORMAL) { // Close the Find toolbar if we're in old-style TAF mode gFindBar.close(); } // XXX // See: https://github.com/MoonchildProductions/Pale-Moon/issues/364 // An actual preference: findbar.highlightAll /* if (!(gPrefService.getBoolPref("accessibility.typeaheadfind.highlightallremember") || gPrefService.getBoolPref("accessibility.typeaheadfind.highlightallbydefault"))) { // fix bug 253793 - turn off highlight when page changes gFindBar.getElement("highlight").checked = false; } */ } } UpdateBackForwardCommands(gBrowser.webNavigation); gGestureSupport.restoreRotationState(); // See bug 358202, when tabs are switched during a drag operation, // timers don't fire on windows (bug 203573) if (aRequest) { setTimeout(function() { XULBrowserWindow.asyncUpdateUI(); }, 0); } else { this.asyncUpdateUI(); } }, asyncUpdateUI: function() { FeedHandler.updateFeeds(); }, hideChromeForLocation: function(aLocation) { aLocation = aLocation.toLowerCase(); return this.inContentWhitelist.some(function(aSpec) { return aSpec == aLocation; }); }, onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) { this.status = aMessage; this.updateStatusField(); }, // Properties used to cache security state used to update the UI _state: null, _hostChanged: false, // onLocationChange will flip this bit onSecurityChange: function(aWebProgress, aRequest, aState) { // Don't need to do anything if the data we use to update the UI hasn't // changed if (this._state == aState && !this._hostChanged) { #ifdef DEBUG try { var contentHost = gBrowser.contentWindow.location.host; if (this._host !== undefined && this._host != contentHost) { Components.utils.reportError( "ASSERTION: browser.js host is inconsistent. Content window has " + "<" + contentHost + "> but cached host is <" + this._host + ">.\n" ); } } catch(ex) {} #endif return; } this._state = aState; #ifdef DEBUG try { this._host = gBrowser.contentWindow.location.host; } catch(ex) { this._host = null; } #endif this._hostChanged = false; // aState is defined as a bitmask that may be extended in the future. // We filter out any unknown bits before testing for known values. const wpl = Components.interfaces.nsIWebProgressListener; const wpl_security_bits = wpl.STATE_IS_SECURE | wpl.STATE_IS_BROKEN | wpl.STATE_IS_INSECURE; var level; switch (this._state & wpl_security_bits) { case wpl.STATE_IS_SECURE: level = "high"; break; case wpl.STATE_IS_BROKEN: level = "broken"; break; } if (level) { // We don't style the Location Bar based on the the 'level' attribute // anymore, but still set it for third-party themes. if (gURLBar) { gURLBar.setAttribute("level", level); } } else { if (gURLBar) { gURLBar.removeAttribute("level"); } } if (gMultiProcessBrowser) { return; } // Don't pass in the actual location object, since it can cause us to // hold on to the window object too long. Just pass in the fields we // care about. (bug 424829) var location = gBrowser.contentWindow.location; var locationObj = {}; try { // about:blank can be used by webpages so pretend it is http locationObj.protocol = location == "about:blank" ? "http:" : location.protocol; locationObj.host = location.host; locationObj.hostname = location.hostname; locationObj.port = location.port; } catch(ex) { // Can sometimes throw if the URL being visited has no host/hostname, // e.g. about:blank. The _state for these pages means we won't need these // properties anyways, though. } gIdentityHandler.checkIdentity(this._state, locationObj); }, // simulate all change notifications after switching tabs onUpdateCurrentBrowser: function(aStateFlags, aStatus, aMessage, aTotalProgress) { if (FullZoom.updateBackgroundTabs) { FullZoom.onLocationChange(gBrowser.currentURI, true); } var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP; // use a pseudo-object instead of a (potentially nonexistent) channel for getting // a correct error message - and make sure that the UI is always either in // loading (STATE_START) or done (STATE_STOP) mode this.onStateChange( gBrowser.webProgress, { URI: gBrowser.currentURI }, loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START, aStatus ); // status message and progress value are undefined if we're done with loading if (loadingDone) { return; } this.onStatusChange(gBrowser.webProgress, null, 0, aMessage); } }; var LinkTargetDisplay = { get DELAY_SHOW() { delete this.DELAY_SHOW; return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay"); }, DELAY_HIDE: 250, _timer: 0, get _isVisible () { return XULBrowserWindow.statusTextField.label != ""; }, update: function() { clearTimeout(this._timer); window.removeEventListener("mousemove", this, true); if (!XULBrowserWindow.overLink) { if (XULBrowserWindow.hideOverLinkImmediately) { this._hide(); } else { this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE); } return; } if (this._isVisible) { XULBrowserWindow.updateStatusField(); } else { // Let the display appear when the mouse doesn't move within the delay this._showDelayed(); window.addEventListener("mousemove", this, true); } }, handleEvent: function(event) { switch (event.type) { case "mousemove": // Restart the delay since the mouse was moved clearTimeout(this._timer); this._showDelayed(); break; } }, _showDelayed: function() { this._timer = setTimeout(function(self) { XULBrowserWindow.updateStatusField(); window.removeEventListener("mousemove", self, true); }, this.DELAY_SHOW, this); }, _hide: function() { clearTimeout(this._timer); XULBrowserWindow.updateStatusField(); } }; var CombinedStopReload = { init: function() { if (this._initialized) { return; } var urlbar = document.getElementById("urlbar-container"); var reload = document.getElementById("reload-button"); var stop = document.getElementById("stop-button"); if (urlbar) { if (urlbar.parentNode.getAttribute("mode") != "icons" || !reload || urlbar.nextSibling != reload || !stop || reload.nextSibling != stop) { urlbar.removeAttribute("combined"); } else { urlbar.setAttribute("combined", "true"); reload = document.getElementById("urlbar-reload-button"); stop = document.getElementById("urlbar-stop-button"); } } if (!stop || !reload || reload.nextSibling != stop) { return; } this._initialized = true; if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") { reload.setAttribute("displaystop", "true"); } stop.addEventListener("click", this, false); this.reload = reload; this.stop = stop; }, uninit: function() { if (!this._initialized) { return; } this._cancelTransition(); this._initialized = false; this.stop.removeEventListener("click", this, false); this.reload = null; this.stop = null; }, handleEvent: function(event) { // the only event we listen to is "click" on the stop button if (event.button == 0 && !this.stop.disabled) { this._stopClicked = true; } }, switchToStop: function() { if (!this._initialized) { return; } this._cancelTransition(); this.reload.setAttribute("displaystop", "true"); }, switchToReload: function(aDelay) { if (!this._initialized) { return; } this.reload.removeAttribute("displaystop"); if (!aDelay || this._stopClicked) { this._stopClicked = false; this._cancelTransition(); this.reload.disabled = XULBrowserWindow.reloadCommand .getAttribute("disabled") == "true"; return; } if (this._timer) { return; } // Temporarily disable the reload button to prevent the user from // accidentally reloading the page when intending to click the stop button this.reload.disabled = true; this._timer = setTimeout(function(self) { self._timer = 0; self.reload.disabled = XULBrowserWindow.reloadCommand .getAttribute("disabled") == "true"; }, 650, this); }, _cancelTransition: function() { if (this._timer) { clearTimeout(this._timer); this._timer = 0; } } }; var TabsProgressListener = { onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { // Attach a listener to watch for "click" events bubbling up from error // pages and other similar page. This lets us fix bugs like 401575 which // require error page UI to do privileged things, without letting error // pages have any privilege themselves. // We can't look for this during onLocationChange since at that point the // document URI is not yet the about:-uri of the error page. let doc = gMultiProcessBrowser ? null : aWebProgress.DOMWindow.document; if (!gMultiProcessBrowser && aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && Components.isSuccessCode(aStatus) && doc.documentURI.startsWith("about:") && !doc.documentURI.toLowerCase().startsWith("about:blank") && !doc.documentElement.hasAttribute("hasBrowserHandlers")) { // STATE_STOP may be received twice for documents, thus store an // attribute to ensure handling it just once. doc.documentElement.setAttribute("hasBrowserHandlers", "true"); aBrowser.addEventListener("click", BrowserOnClick, true); aBrowser.addEventListener("pagehide", function onPageHide(event) { if (event.target.defaultView.frameElement) { return; } aBrowser.removeEventListener("click", BrowserOnClick, true); aBrowser.removeEventListener("pagehide", onPageHide, true); if (event.target.documentElement) { event.target.documentElement.removeAttribute("hasBrowserHandlers"); } }, true); // We also want to make changes to page UI for unprivileged about pages. BrowserOnAboutPageLoad(doc); } }, onLocationChange: function(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) { // Filter out location changes caused by anchor navigation // or history.push/pop/replaceState. if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { return; } // Only need to call locationChange if the PopupNotifications object // for this window has already been initialized (i.e. its getter no // longer exists) if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) { PopupNotifications.locationChange(aBrowser); } gBrowser.getNotificationBox(aBrowser).removeTransientNotifications(); // Filter out location changes in sub documents. if (aWebProgress.isTopLevel) { // Initialize the click-to-play state. aBrowser._clickToPlayPluginsActivated = new Map(); aBrowser._clickToPlayAllPluginsActivated = false; aBrowser._pluginScriptedState = gPluginHandler.PLUGIN_SCRIPTED_STATE_NONE; FullZoom.onLocationChange(aLocationURI, false, aBrowser); } }, onRefreshAttempted: function(aBrowser, aWebProgress, aURI, aDelay, aSameURI) { if (gPrefService.getBoolPref("accessibility.blockautorefresh")) { let brandBundle = document.getElementById("bundle_brand"); let brandShortName = brandBundle.getString("brandShortName"); let refreshButtonText = gNavigatorBundle.getString("refreshBlocked.goButton"); let refreshButtonAccesskey = gNavigatorBundle.getString("refreshBlocked.goButton.accesskey"); let message = gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel" : "refreshBlocked.redirectLabel", [brandShortName]); let docShell = aWebProgress.DOMWindow .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShell); let notificationBox = gBrowser.getNotificationBox(aBrowser); let notification = notificationBox.getNotificationWithValue("refresh-blocked"); if (notification) { notification.label = message; notification.refreshURI = aURI; notification.delay = aDelay; notification.docShell = docShell; } else { let buttons = [{ label: refreshButtonText, accessKey: refreshButtonAccesskey, callback: function(aNotification, aButton) { var refreshURI = aNotification.docShell .QueryInterface(Ci.nsIRefreshURI); refreshURI.forceRefreshURI(aNotification.refreshURI, aNotification.delay, true); } }]; notification = notificationBox.appendNotification(message, "refresh-blocked", "chrome://browser/skin/Info.png", notificationBox.PRIORITY_INFO_MEDIUM, buttons); notification.refreshURI = aURI; notification.delay = aDelay; notification.docShell = docShell; } return false; } return true; } } function nsBrowserAccess() { } nsBrowserAccess.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]), openURI: function(aURI, aOpener, aWhere, aContext) { var newWindow = null; var isExternal = !!(aContext & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); if (aOpener && isExternal) { Cu.reportError("nsBrowserAccess.openURI did not expect an opener to be " + "passed if the context is OPEN_EXTERNAL."); throw Cr.NS_ERROR_FAILURE; } if (isExternal && aURI && aURI.schemeIs("chrome")) { dump("use -chrome command-line option to load external chrome urls\n"); return null; } if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { if (isExternal && gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external")) { aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external"); } else { aWhere = gPrefService.getIntPref("browser.link.open_newwindow"); } } let referrer = aOpener ? makeURI(aOpener.location.href) : null; let triggeringPrincipal = null; let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT; if (aOpener && aOpener.document) { referrerPolicy = aOpener.document.referrerPolicy; triggeringPrincipal = aOpener.document.nodePrincipal; } switch (aWhere) { case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW : // FIXME: Bug 408379. So how come this doesn't send the // referrer like the other loads do? var url = aURI ? aURI.spec : "about:blank"; // Pass all params to openDialog to ensure that "url" isn't passed through // loadOneOrMoreURIs, which splits based on "|" newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null); break; case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB : let win, needToFocusWin; // try the current window. if we're in a popup, fall back on the most recent browser window if (window.toolbar.visible) { win = window; } else { let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window); win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate}); needToFocusWin = true; } if (!win) { // we couldn't find a suitable window, a new one needs to be opened. return null; } if (isExternal && (!aURI || aURI.spec == "about:blank")) { win.BrowserOpenTab(); // this also focuses the location bar win.focus(); newWindow = win.content; break; } let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"); let openerWindow = (aContext & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener; let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", { triggeringPrincipal: triggeringPrincipal, referrerURI: referrer, referrerPolicy: referrerPolicy, fromExternal: isExternal, inBackground: loadInBackground, opener: openerWindow }); let browser = win.gBrowser.getBrowserForTab(tab); if (gPrefService.getBoolPref("browser.tabs.noWindowActivationOnExternal")) { isExternal = false; // this is a hack, but it works } newWindow = browser.contentWindow; if (needToFocusWin || (!loadInBackground && isExternal)) { newWindow.focus(); } break; default : // OPEN_CURRENTWINDOW or an illegal value newWindow = content; if (aURI) { let loadflags = isExternal ? Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL : Ci.nsIWebNavigation.LOAD_FLAGS_NONE; gBrowser.loadURIWithFlags(aURI.spec, { flags: loadflags, triggeringPrincipal: triggeringPrincipal, referrerURI: referrer, referrerPolicy: referrerPolicy }); } if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground")) { window.focus(); } } return newWindow; }, isTabContentWindow: function(aWindow) { return gBrowser.browsers.some(function(browser) browser.contentWindow == aWindow); } } function onViewToolbarsPopupShowing(aEvent, aInsertPoint) { var popup = aEvent.target; if (popup != aEvent.currentTarget) { return; } // Empty the menu for (var i = popup.childNodes.length-1; i >= 0; --i) { var deadItem = popup.childNodes[i]; if (deadItem.hasAttribute("toolbarId")) { popup.removeChild(deadItem); } } var firstMenuItem = aInsertPoint || popup.firstChild; let toolbarNodes = Array.slice(gNavToolbox.childNodes); toolbarNodes.push(document.getElementById("addon-bar")); for (let toolbar of toolbarNodes) { #ifdef MOZ_WIDGET_GTK if (toolbar.id == "toolbar-menubar" && document.documentElement.getAttribute("shellshowingmenubar") == "true") { continue; } #endif let toolbarName = toolbar.getAttribute("toolbarname"); if (toolbarName) { let menuItem = document.createElement("menuitem"); let hidingAttribute = toolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed"; menuItem.setAttribute("id", "toggle_" + toolbar.id); menuItem.setAttribute("toolbarId", toolbar.id); menuItem.setAttribute("type", "checkbox"); menuItem.setAttribute("label", toolbarName); menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true"); if (popup.id != "appmenu_customizeMenu") { menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey")); } if (popup.id != "toolbar-context-menu") { menuItem.setAttribute("key", toolbar.getAttribute("key")); } popup.insertBefore(menuItem, firstMenuItem); menuItem.addEventListener("command", onViewToolbarCommand, false); } } } function onViewToolbarCommand(aEvent) { var toolbarId = aEvent.originalTarget.getAttribute("toolbarId"); var toolbar = document.getElementById(toolbarId); var isVisible = aEvent.originalTarget.getAttribute("checked") == "true"; setToolbarVisibility(toolbar, isVisible); } function setToolbarVisibility(toolbar, isVisible) { var hidingAttribute = toolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed"; toolbar.setAttribute(hidingAttribute, !isVisible); document.persist(toolbar.id, hidingAttribute); // Customizable toolbars - persist the hiding attribute. if (toolbar.hasAttribute("customindex")) { var toolbox = toolbar.parentNode; var name = toolbar.getAttribute("toolbarname"); if (toolbox.toolbarset) { try { // Checking all attributes starting with "toolbar". Array.prototype.slice.call(toolbox.toolbarset.attributes, 0) .find(x => { if (x.name.startsWith("toolbar")) { var toolbarInfo = x.value; var infoSplit = toolbarInfo.split(gToolbarInfoSeparators[0]); if (infoSplit[0] == name) { infoSplit[1] = [ infoSplit[1].split(gToolbarInfoSeparators[1], 1), !isVisible ].join(gToolbarInfoSeparators[1]); toolbox.toolbarset.setAttribute( x.name, infoSplit.join(gToolbarInfoSeparators[0])); document.persist(toolbox.toolbarset.id, x.name); } } }); } catch(e) { Components.utils.reportError( "Customizable toolbars - persist the hiding attribute: " + e); } } } PlacesToolbarHelper.init(); BookmarkingUI.onToolbarVisibilityChange(); gBrowser.updateWindowResizers(); #ifdef MENUBAR_CAN_AUTOHIDE updateAppButtonDisplay(); #endif if (isVisible) { ToolbarIconColor.inferFromText(); } } var AudioIndicator = { init: function() { Services.prefs.addObserver(this._prefName, this, false); this.syncUI(); }, uninit: function() { Services.prefs.removeObserver(this._prefName, this); }, toggle: function() { this.enabled = !Services.prefs.getBoolPref(this._prefName); }, syncUI: function() { document.getElementById("context_toggleMuteTab").setAttribute("hidden", this.enabled); document.getElementById("key_toggleMute").setAttribute("disabled", this.enabled); }, get enabled () { return !Services.prefs.getBoolPref(this._prefName); }, set enabled (val) { Services.prefs.setBoolPref(this._prefName, !!val); return val; }, observe: function(subject, topic, data) { if (topic == "nsPref:changed") { this.syncUI(); } }, _prefName: "browser.tabs.showAudioPlayingIcon" } var TabsOnTop = { init: function() { Services.prefs.addObserver(this._prefName, this, false); }, uninit: function() { Services.prefs.removeObserver(this._prefName, this); }, toggle: function() { this.enabled = !Services.prefs.getBoolPref(this._prefName); }, syncUI: function() { let userEnabled = Services.prefs.getBoolPref(this._prefName); let enabled = userEnabled && gBrowser.tabContainer.visible; document.getElementById("cmd_ToggleTabsOnTop") .setAttribute("checked", userEnabled); document.documentElement.setAttribute("tabsontop", enabled); document.getElementById("navigator-toolbox").setAttribute("tabsontop", enabled); document.getElementById("TabsToolbar").setAttribute("tabsontop", enabled); document.getElementById("nav-bar").setAttribute("tabsontop", enabled); gBrowser.tabContainer.setAttribute("tabsontop", enabled); TabsInTitlebar.allowedBy("tabs-on-top", enabled); }, get enabled () { return gNavToolbox.getAttribute("tabsontop") == "true"; }, set enabled (val) { Services.prefs.setBoolPref(this._prefName, !!val); return val; }, observe: function(subject, topic, data) { if (topic == "nsPref:changed") { this.syncUI(); } }, _prefName: "browser.tabs.onTop" } var TabsInTitlebar = { init: function() { #ifdef CAN_DRAW_IN_TITLEBAR this._readPref(); Services.prefs.addObserver(this._prefName, this, false); // Don't trust the initial value of the sizemode attribute; wait for // the resize event (handled in tabbrowser.xml). this.allowedBy("sizemode", false); this._initialized = true; #endif }, allowedBy: function(condition, allow) { #ifdef CAN_DRAW_IN_TITLEBAR if (allow) { if (condition in this._disallowed) { delete this._disallowed[condition]; this._update(); } } else { if (!(condition in this._disallowed)) { this._disallowed[condition] = null; this._update(); } } #endif }, get enabled() { return document.documentElement.getAttribute("tabsintitlebar") == "true"; }, #ifdef CAN_DRAW_IN_TITLEBAR observe: function(subject, topic, data) { if (topic == "nsPref:changed") this._readPref(); }, _initialized: false, _disallowed: {}, _prefName: "browser.tabs.drawInTitlebar", _readPref: function() { this.allowedBy("pref", Services.prefs.getBoolPref(this._prefName)); }, _update: function() { function $(id) document.getElementById(id); function rect(ele) ele.getBoundingClientRect(); if (!this._initialized || window.fullScreen) { return; } let allowed = true; for (let something in this._disallowed) { allowed = false; break; } if (allowed == this.enabled) { return; } let titlebar = $("titlebar"); if (allowed) { let tabsToolbar = $("TabsToolbar"); #ifdef MENUBAR_CAN_AUTOHIDE let appmenuButtonBox = $("appmenu-button-container"); this._sizePlaceholder("appmenu-button", rect(appmenuButtonBox).width); #endif let captionButtonsBox = $("titlebar-buttonbox"); this._sizePlaceholder("caption-buttons", rect(captionButtonsBox).width); let tabsToolbarRect = rect(tabsToolbar); let titlebarTop = rect($("titlebar-content")).top; titlebar.style.marginBottom = - Math.min(tabsToolbarRect.top - titlebarTop, tabsToolbarRect.height) + "px"; document.documentElement.setAttribute("tabsintitlebar", "true"); if (!this._draghandle) { let tmp = {}; Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp); this._draghandle = new tmp.WindowDraggingElement(tabsToolbar); this._draghandle.mouseDownCheck = function() { return !this._dragBindingAlive && TabsInTitlebar.enabled; }; } } else { document.documentElement.removeAttribute("tabsintitlebar"); titlebar.style.marginBottom = ""; } ToolbarIconColor.inferFromText(); }, _sizePlaceholder: function(type, width) { Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"), function(node) { node.width = width; }); }, #endif uninit: function() { #ifdef CAN_DRAW_IN_TITLEBAR this._initialized = false; Services.prefs.removeObserver(this._prefName, this); #endif } }; #ifdef MENUBAR_CAN_AUTOHIDE function updateAppButtonDisplay() { var displayAppButton = !gInPrintPreviewMode && window.menubar.visible && document.getElementById("toolbar-menubar").getAttribute("autohide") == "true"; #ifdef CAN_DRAW_IN_TITLEBAR document.getElementById("titlebar").hidden = !displayAppButton; if (displayAppButton) { document.documentElement.setAttribute("chromemargin", "0,2,2,2"); } else { document.documentElement.removeAttribute("chromemargin"); } TabsInTitlebar.allowedBy("drawing-in-titlebar", displayAppButton); #else document.getElementById("appmenu-toolbar-button").hidden = !displayAppButton; #endif } #endif #ifdef CAN_DRAW_IN_TITLEBAR function onTitlebarMaxClick() { if (window.windowState == window.STATE_MAXIMIZED) { window.restore(); } else { window.maximize(); } } #endif function displaySecurityInfo() { BrowserPageInfo(null, "securityTab"); } /** * Opens or closes the sidebar identified by commandID. * * @param commandID a string identifying the sidebar to toggle; see the * note below. (Optional if a sidebar is already open.) * @param forceOpen boolean indicating whether the sidebar should be * opened regardless of its current state (optional). * @note * We expect to find a xul:broadcaster element with the specified ID. * The following attributes on that element may be used and/or modified: * - id (required) the string to match commandID. The convention * is to use this naming scheme: 'viewSidebar'. * - sidebarurl (required) specifies the URL to load in this sidebar. * - sidebartitle or label (in that order) specify the title to * display on the sidebar. * - checked indicates whether the sidebar is currently displayed. * Note that toggleSidebar updates this attribute when * it changes the sidebar's visibility. * - group this attribute must be set to "sidebar". */ function toggleSidebar(commandID, forceOpen) { var sidebarBox = document.getElementById("sidebar-box"); if (!commandID) { commandID = sidebarBox.getAttribute("sidebarcommand"); } var sidebarBroadcaster = document.getElementById(commandID); var sidebar = document.getElementById("sidebar"); // xul:browser var sidebarTitle = document.getElementById("sidebar-title"); var sidebarSplitter = document.getElementById("sidebar-splitter"); if (sidebarBroadcaster.getAttribute("checked") == "true") { if (!forceOpen) { // Replace the document currently displayed in the sidebar with about:blank // so that we can free memory by unloading the page. We need to explicitly // create a new content viewer because the old one doesn't get destroyed // until about:blank has loaded (which does not happen as long as the // element is hidden). sidebar.setAttribute("src", "about:blank"); sidebar.docShell.createAboutBlankContentViewer(null); sidebarBroadcaster.removeAttribute("checked"); sidebarBox.setAttribute("sidebarcommand", ""); sidebarTitle.value = ""; sidebarBox.hidden = true; sidebarSplitter.hidden = true; gBrowser.selectedBrowser.focus(); } else { fireSidebarFocusedEvent(); } return; } // now we need to show the specified sidebar // ..but first update the 'checked' state of all sidebar broadcasters var broadcasters = document.getElementsByAttribute("group", "sidebar"); for (let broadcaster of broadcasters) { // skip elements that observe sidebar broadcasters and random // other elements if (broadcaster.localName != "broadcaster") { continue; } if (broadcaster != sidebarBroadcaster) { broadcaster.removeAttribute("checked"); } else { sidebarBroadcaster.setAttribute("checked", "true"); } } sidebarBox.hidden = false; sidebarSplitter.hidden = false; var url = sidebarBroadcaster.getAttribute("sidebarurl"); var title = sidebarBroadcaster.getAttribute("sidebartitle"); if (!title) { title = sidebarBroadcaster.getAttribute("label"); } sidebar.setAttribute("src", url); // kick off async load sidebarBox.setAttribute("sidebarcommand", sidebarBroadcaster.id); sidebarTitle.value = title; // We set this attribute here in addition to setting it on the // element itself, because the code in gBrowserInit.onUnload persists this // attribute, not the "src" of the . The reason it // does that is that we want to delay sidebar load a bit when a browser // window opens. See delayedStartup(). sidebarBox.setAttribute("src", url); if (sidebar.contentDocument.location.href != url) { sidebar.addEventListener("load", sidebarOnLoad, true); } else { // older code handled this case, so we do it too fireSidebarFocusedEvent(); } } function sidebarOnLoad(event) { var sidebar = document.getElementById("sidebar"); sidebar.removeEventListener("load", sidebarOnLoad, true); // We're handling the 'load' event before it bubbles up to the usual // (non-capturing) event handlers. Let it bubble up before firing the // SidebarFocused event. setTimeout(fireSidebarFocusedEvent, 0); } /** * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar * a chance to adjust focus as needed. An additional event is needed, because * we don't want to focus the sidebar when it's opened on startup or in a new * window, only when the user opens the sidebar. */ function fireSidebarFocusedEvent() { var sidebar = document.getElementById("sidebar"); var event = document.createEvent("Events"); event.initEvent("SidebarFocused", true, false); sidebar.contentWindow.dispatchEvent(event); } var gHomeButton = { prefDomain: "browser.startup.homepage", observe: function(aSubject, aTopic, aPrefName) { if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain) { return; } this.updateTooltip(); }, updateTooltip: function(homeButton) { if (!homeButton) { homeButton = document.getElementById("home-button"); } if (homeButton) { var homePage = this.getHomePage(); homePage = homePage.replace(/\|/g,', '); if (homePage.toLowerCase() == "about:home") { homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip")); } else { homeButton.setAttribute("tooltiptext", homePage); } } }, getHomePage: function() { var url; try { url = gPrefService.getComplexValue(this.prefDomain, Components.interfaces.nsIPrefLocalizedString).data; } catch(e) {} // use this if we can't find the pref if (!url) { var configBundle = Services.strings .createBundle("chrome://branding/locale/browserconfig.properties"); url = configBundle.GetStringFromName(this.prefDomain); } return url; }, updatePersonalToolbarStyle: function(homeButton) { if (!homeButton) { homeButton = document.getElementById("home-button"); } if (homeButton) { homeButton.className = homeButton.parentNode.id == "PersonalToolbar" || homeButton.parentNode.parentNode.id == "PersonalToolbar" ? homeButton.className.replace("toolbarbutton-1", "bookmark-item") : homeButton.className.replace("bookmark-item", "toolbarbutton-1"); } } }; /** * Gets the selected text in the active browser. Leading and trailing * whitespace is removed, and consecutive whitespace is replaced by a single * space. A maximum of 150 characters will be returned, regardless of the value * of aCharLen. * * @param aCharLen * The maximum number of characters to return. */ function getBrowserSelection(aCharLen) { // selections of more than 150 characters aren't useful const kMaxSelectionLen = 150; const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen); let commandDispatcher = document.commandDispatcher; var focusedWindow = commandDispatcher.focusedWindow; var selection = focusedWindow.getSelection().toString(); // try getting a selected text in text input. if (!selection) { let element = commandDispatcher.focusedElement; var isOnTextInput = function isOnTextInput(elem) { // we avoid to return a value if a selection is in password field. // ref. bug 565717 return elem instanceof HTMLTextAreaElement || (elem instanceof HTMLInputElement && elem.mozIsTextField(true)); }; if (isOnTextInput(element)) { selection = element.QueryInterface(Ci.nsIDOMNSEditableElement) .editor.selection.toString(); } } if (selection) { if (selection.length > charLen) { // only use the first charLen important chars. see bug 221361 var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}"); pattern.test(selection); selection = RegExp.lastMatch; } selection = selection.trim().replace(/\s+/g, " "); if (selection.length > charLen) { selection = selection.substr(0, charLen); } } return selection; } var gWebPanelURI; function openWebPanel(aTitle, aURI) { // Ensure that the web panels sidebar is open. toggleSidebar('viewWebPanelsSidebar', true); // Set the title of the panel. document.getElementById("sidebar-title").value = aTitle; // Tell the Web Panels sidebar to load the bookmark. var sidebar = document.getElementById("sidebar"); if (sidebar.docShell && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) { sidebar.contentWindow.loadWebPanel(aURI); if (gWebPanelURI) { gWebPanelURI = ""; sidebar.removeEventListener("load", asyncOpenWebPanel, true); } } else { // The panel is still being constructed. Attach an onload handler. if (!gWebPanelURI) sidebar.addEventListener("load", asyncOpenWebPanel, true); gWebPanelURI = aURI; } } function asyncOpenWebPanel(event) { var sidebar = document.getElementById("sidebar"); if (gWebPanelURI && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) { sidebar.contentWindow.loadWebPanel(gWebPanelURI); } gWebPanelURI = ""; sidebar.removeEventListener("load", asyncOpenWebPanel, true); } /* * - [ Dependencies ] --------------------------------------------------------- * utilityOverlay.js: * - gatherTextUnder */ /** * Extracts linkNode and href for the current click target. * * @param event * The click event. * @return [href, linkNode]. * * @note linkNode will be null if the click wasn't on an anchor * element (or XLink). */ function hrefAndLinkNodeForClickEvent(event) { function isHTMLLink(aNode) { // Be consistent with what nsContextMenu.js does. return ((aNode instanceof HTMLAnchorElement && aNode.href) || (aNode instanceof HTMLAreaElement && aNode.href) || aNode instanceof HTMLLinkElement); } let node = event.target; while (node && !isHTMLLink(node)) { node = node.parentNode; } if (node) { return [node.href, node]; } // If there is no linkNode, try simple XLink. let href, baseURI; node = event.target; while (node && !href) { if (node.nodeType == Node.ELEMENT_NODE && (node.localName == "a" || node.namespaceURI == "http://www.w3.org/1998/Math/MathML")) { href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); if (href) { baseURI = node.baseURI; break; } } node = node.parentNode; } // In case of XLink, we don't return the node we got href from since // callers expect -like elements. return [href ? makeURLAbsolute(baseURI, href) : null, null]; } /** * Called whenever the user clicks in the content area. * * @param event * The click event. * @param isPanelClick * Whether the event comes from a web panel. * @note default event is prevented if the click is handled. */ function contentAreaClick(event, isPanelClick) { if (!event.isTrusted || event.defaultPrevented || event.button == 2) { return; } let [href, linkNode] = hrefAndLinkNodeForClickEvent(event); if (!href) { // Not a link, handle middle mouse navigation. if (event.button == 1 && gPrefService.getBoolPref("middlemouse.contentLoadURL") && !gPrefService.getBoolPref("general.autoScroll")) { middleMousePaste(event); event.preventDefault(); } return; } // This code only applies if we have a linkNode (i.e. clicks on real anchor // elements, as opposed to XLink). if (linkNode && event.button == 0 && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { // A Web panel's links should target the main content area. Do this // if no modifier keys are down and if there's no target or the target // equals _main (the IE convention) or _content (the Mozilla convention). let target = linkNode.target; let mainTarget = !target || target == "_content" || target == "_main"; if (isPanelClick && mainTarget) { // javascript and data links should be executed in the current browser. if (linkNode.getAttribute("onclick") || href.startsWith("javascript:") || href.startsWith("data:")) { return; } try { urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal); } catch(ex) { // Prevent loading unsecure destinations. event.preventDefault(); return; } loadURI(href, null, null, false); event.preventDefault(); return; } if (linkNode.getAttribute("rel") == "sidebar") { // This is the Opera convention for a special link that, when clicked, // allows to add a sidebar panel. The link's title attribute contains // the title that should be used for the sidebar panel. PlacesUIUtils.showBookmarkDialog({ action: "add", type: "bookmark", uri: makeURI(href), title: linkNode.getAttribute("title"), loadBookmarkInSidebar: true, hiddenRows: ["description", "location", "keyword"] }, window); event.preventDefault(); return; } } handleLinkClick(event, href, linkNode); // Mark the page as a user followed link. This is done so that history can // distinguish automatic embed visits from user activated ones. For example // pages loaded in frames are embed visits and lost with the session, while // visits across frames should be preserved. try { if (!PrivateBrowsingUtils.isWindowPrivate(window)) { PlacesUIUtils.markPageAsFollowedLink(href); } } catch(ex) { // Skip invalid URIs. } } /** * Handles clicks on links. * * @return true if the click event was handled, false otherwise. */ function handleLinkClick(event, href, linkNode) { if (event.button == 2) { // right click return false; } var where = whereToOpenLink(event); if (where == "current") { return false; } var doc = event.target.ownerDocument; if (where == "save") { saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true, true, doc.documentURIObject, doc); event.preventDefault(); return true; } urlSecurityCheck(href, doc.nodePrincipal); openLinkIn(href, where, { referrerURI: doc.documentURIObject, charset: doc.characterSet, referrerPolicy: doc.referrerPolicy, originPrincipal: doc.nodePrincipal, triggeringPrincipal: doc.nodePrincipal }); event.preventDefault(); return true; } function middleMousePaste(event) { let clipboard = readFromClipboard(); if (!clipboard) { return; } // Strip embedded newlines and surrounding whitespace, to match the URL // bar's behavior (stripsurroundingwhitespace) clipboard = clipboard.replace(/\s*\n\s*/g, ""); // if it's not the current tab, we don't need to do anything because the // browser doesn't exist. let where = whereToOpenLink(event, true, false); let lastLocationChange; if (where == "current") { lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; } getShortcutOrURIAndPostData(clipboard).then(data => { try { makeURI(data.url); } catch(ex) { // Not a valid URI. return; } try { addToUrlbarHistory(data.url); } catch(ex) { // Things may go wrong when adding url to session history, // but don't let that interfere with the loading of the url. Cu.reportError(ex); } if (where != "current" || lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { openUILink(data.url, event, { ignoreButton: true, disallowInheritPrincipal: !data.mayInheritPrincipal, initiatingDoc: event ? event.target.ownerDocument : null }); } }); event.stopPropagation(); } // handleDroppedLink has the following 2 overloads: // handleDroppedLink(event, url, name) // handleDroppedLink(event, links) function handleDroppedLink(event, urlOrLinks, name) { let links; if (Array.isArray(urlOrLinks)) { links = urlOrLinks; } else { links = [{ url: urlOrLinks, name, type: "" }]; } let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); if (event.shiftKey) { inBackground = !inBackground; } Task.spawn(function*() { let urls = []; let postDatas = []; for (let link of links) { let data = yield getShortcutOrURIAndPostData(link.url); urls.push(data.url); postDatas.push(data.postData); } if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { gBrowser.loadTabs(urls, { inBackground, replace: true, allowThirdPartyFixup: false, postDatas }); } }); // Keep the event from being handled by the dragDrop listeners // built-in to Goanna if they happen to be above us. event.preventDefault(); }; function MultiplexHandler(event) { try { var node = event.target; var name = node.getAttribute('name'); if (name == 'detectorGroup') { BrowserCharsetReload(); SelectDetector(event, false); } else if (name == 'charsetGroup') { var charset = node.getAttribute('id'); charset = charset.substring(charset.indexOf('charset.') + 'charset.'.length); BrowserSetForcedCharacterSet(charset); } else if (name == 'charsetCustomize') { //do nothing - please remove this else statement, once the charset prefs moves to the pref window } else { BrowserSetForcedCharacterSet(node.getAttribute('id')); } } catch(ex) { // XXX: Do we really want an alert here with just the error? alert(ex); } } function SelectDetector(event, doReload) { var uri = event.target.getAttribute("id"); var prefvalue = uri.substring(uri.indexOf('chardet.') + 'chardet.'.length); if ("off" == prefvalue) { // "off" is special value to turn off the detectors prefvalue = ""; } try { var str = Cc["@mozilla.org/supports-string;1"] .createInstance(Ci.nsISupportsString); str.data = prefvalue; gPrefService.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str); if (doReload) { window.content.location.reload(); } } catch(ex) { // XXX: Do we really want to use "dump" here? dump("Failed to set the intl.charset.detector preference.\n"); } } function BrowserSetForcedCharacterSet(aCharset) { gBrowser.docShell.charset = aCharset; // Save the forced character-set if (!PrivateBrowsingUtils.isWindowPrivate(window)) { PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset); } BrowserCharsetReload(); } function BrowserCharsetReload() { BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE); } function charsetMenuGetElement(parent, id) { return parent.getElementsByAttribute("id", id)[0]; } function UpdateCurrentCharset(target) { // extract the charset from DOM var wnd = document.commandDispatcher.focusedWindow; if ((window == wnd) || (wnd == null)) { wnd = window.content; } // Uncheck previous item if (gPrevCharset) { var pref_item = charsetMenuGetElement(target, "charset." + gPrevCharset); if (pref_item) { pref_item.setAttribute('checked', 'false'); } } var menuitem = charsetMenuGetElement(target, "charset." + wnd.document.characterSet); if (menuitem) { menuitem.setAttribute('checked', 'true'); } } function UpdateCharsetDetector(target) { var prefvalue; try { prefvalue = gPrefService.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString).data; } catch(ex) {} if (!prefvalue) { prefvalue = "off"; } var menuitem = charsetMenuGetElement(target, "chardet." + prefvalue); if (menuitem) { menuitem.setAttribute("checked", "true"); } } function UpdateMenus(event) { UpdateCurrentCharset(event.target); UpdateCharsetDetector(event.target); } function charsetLoadListener() { var charset = window.content.document.characterSet; if (charset.length > 0 && (charset != gLastBrowserCharset)) { gPrevCharset = gLastBrowserCharset; gLastBrowserCharset = charset; } } var gPageStyleMenu = { _getAllStyleSheets: function(frameset) { var styleSheetsArray = Array.slice(frameset.document.styleSheets); for (let i = 0; i < frameset.frames.length; i++) { let frameSheets = this._getAllStyleSheets(frameset.frames[i]); styleSheetsArray = styleSheetsArray.concat(frameSheets); } return styleSheetsArray; }, fillPopup: function(menuPopup) { var noStyle = menuPopup.firstChild; var persistentOnly = noStyle.nextSibling; var sep = persistentOnly.nextSibling; while (sep.nextSibling) { menuPopup.removeChild(sep.nextSibling); } var styleSheets = this._getAllStyleSheets(window.content); var currentStyleSheets = {}; var styleDisabled = getMarkupDocumentViewer().authorStyleDisabled; var haveAltSheets = false; var altStyleSelected = false; for (let currentStyleSheet of styleSheets) { if (!currentStyleSheet.title) { continue; } // Skip any stylesheets whose media attribute doesn't match. if (currentStyleSheet.media.length > 0) { let mediaQueryList = currentStyleSheet.media.mediaText; if (!window.content.matchMedia(mediaQueryList).matches) continue; } if (!currentStyleSheet.disabled) { altStyleSelected = true; } haveAltSheets = true; let lastWithSameTitle = null; if (currentStyleSheet.title in currentStyleSheets) { lastWithSameTitle = currentStyleSheets[currentStyleSheet.title]; } if (!lastWithSameTitle) { let menuItem = document.createElement("menuitem"); menuItem.setAttribute("type", "radio"); menuItem.setAttribute("label", currentStyleSheet.title); menuItem.setAttribute("data", currentStyleSheet.title); menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled); menuItem.setAttribute("oncommand", "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));"); menuPopup.appendChild(menuItem); currentStyleSheets[currentStyleSheet.title] = menuItem; } else if (currentStyleSheet.disabled) { lastWithSameTitle.removeAttribute("checked"); } } noStyle.setAttribute("checked", styleDisabled); persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled); persistentOnly.hidden = (window.content.document.preferredStyleSheetSet) ? haveAltSheets : false; sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets; }, _stylesheetInFrame: function(frame, title) { return Array.some(frame.document.styleSheets, function(stylesheet) { return stylesheet.title == title }); }, _stylesheetSwitchFrame: function(frame, title) { var docStyleSheets = frame.document.styleSheets; for (let i = 0; i < docStyleSheets.length; ++i) { let docStyleSheet = docStyleSheets[i]; if (docStyleSheet.title) { docStyleSheet.disabled = (docStyleSheet.title != title); } else if (docStyleSheet.disabled) { docStyleSheet.disabled = false; } } }, _stylesheetSwitchAll: function(frameset, title) { if (!title || this._stylesheetInFrame(frameset, title)) { this._stylesheetSwitchFrame(frameset, title); } for (let i = 0; i < frameset.frames.length; i++) { this._stylesheetSwitchAll(frameset.frames[i], title); } }, switchStyleSheet: function(title, contentWindow) { getMarkupDocumentViewer().authorStyleDisabled = false; this._stylesheetSwitchAll(contentWindow || content, title); }, disableStyle: function() { getMarkupDocumentViewer().authorStyleDisabled = true; }, }; /* Legacy global page-style functions */ var getAllStyleSheets = gPageStyleMenu._getAllStyleSheets.bind(gPageStyleMenu); var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu); function stylesheetSwitchAll(contentWindow, title) { gPageStyleMenu.switchStyleSheet(title, contentWindow); } function setStyleDisabled(disabled) { if (disabled) { gPageStyleMenu.disableStyle(); } } var BrowserOffline = { _inited: false, ///////////////////////////////////////////////////////////////////////////// // BrowserOffline Public Methods init: function() { if (!this._uiElement) { this._uiElement = document.getElementById("workOfflineMenuitemState"); } Services.obs.addObserver(this, "network:offline-status-changed", false); this._updateOfflineUI(Services.io.offline); this._inited = true; }, uninit: function() { if (this._inited) { Services.obs.removeObserver(this, "network:offline-status-changed"); } }, toggleOfflineStatus: function() { var ioService = Services.io; // Stop automatic management of the offline status try { ioService.manageOfflineStatus = false; } catch(ex) {} if (!ioService.offline && !this._canGoOffline()) { this._updateOfflineUI(false); return; } ioService.offline = !ioService.offline; }, ///////////////////////////////////////////////////////////////////////////// // nsIObserver observe: function(aSubject, aTopic, aState) { if (aTopic != "network:offline-status-changed") { return; } this._updateOfflineUI(aState == "offline"); }, ///////////////////////////////////////////////////////////////////////////// // BrowserOffline Implementation Methods _canGoOffline: function() { try { var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null); // Something aborted the quit process. if (cancelGoOffline.data) { return false; } } catch(ex) {} return true; }, _uiElement: null, _updateOfflineUI: function(aOffline) { var offlineLocked = gPrefService.prefIsLocked("network.online"); if (offlineLocked) { this._uiElement.setAttribute("disabled", "true"); } this._uiElement.setAttribute("checked", aOffline); } }; var OfflineApps = { ///////////////////////////////////////////////////////////////////////////// // OfflineApps Public Methods init: function() { Services.obs.addObserver(this, "offline-cache-update-completed", false); }, uninit: function() { Services.obs.removeObserver(this, "offline-cache-update-completed"); }, handleEvent: function(event) { if (event.type == "MozApplicationManifest") { this.offlineAppRequested(event.originalTarget.defaultView); } }, ///////////////////////////////////////////////////////////////////////////// // OfflineApps Implementation Methods // XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow // were taken from browser/components/feeds/src/WebContentConverter. _getBrowserWindowForContentWindow: function(aContentWindow) { return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem) .rootTreeItem .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow) .wrappedJSObject; }, _getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) { // This depends on pseudo APIs of browser.js and tabbrowser.xml aContentWindow = aContentWindow.top; var browsers = aBrowserWindow.gBrowser.browsers; for (let browser of browsers) { if (browser.contentWindow == aContentWindow) { return browser; } } // handle other browser/iframe elements that may need popupnotifications let browser = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShell) .chromeEventHandler; if (browser.getAttribute("popupnotificationanchor")) { return browser; } return null; }, _getManifestURI: function(aWindow) { if (!aWindow.document.documentElement) { return null; } var attr = aWindow.document.documentElement.getAttribute("manifest"); if (!attr) { return null; } try { var contentURI = makeURI(aWindow.location.href, null, null); return makeURI(attr, aWindow.document.characterSet, contentURI); } catch(e) { return null; } }, // A cache update isn't tied to a specific window. Try to find // the best browser in which to warn the user about space usage _getBrowserForCacheUpdate: function(aCacheUpdate) { // Prefer the current browser var uri = this._getManifestURI(content); if (uri && uri.equals(aCacheUpdate.manifestURI)) { return gBrowser.selectedBrowser; } var browsers = gBrowser.browsers; for (let browser of browsers) { uri = this._getManifestURI(browser.contentWindow); if (uri && uri.equals(aCacheUpdate.manifestURI)) { return browser; } } // is this from a non-tab browser/iframe? browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]"); for (let browser of browsers) { uri = this._getManifestURI(browser.contentWindow); if (uri && uri.equals(aCacheUpdate.manifestURI)) { return browser; } } return null; }, _warnUsage: function(aBrowser, aURI) { if (!aBrowser) { return; } let mainAction = { label: gNavigatorBundle.getString("offlineApps.manageUsage"), accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"), callback: OfflineApps.manage }; let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn"); let message = gNavigatorBundle.getFormattedString("offlineApps.usage", [ aURI.host, warnQuota / 1024 ]); let anchorID = "indexedDB-notification-icon"; PopupNotifications.show(aBrowser, "offline-app-usage", message, anchorID, mainAction); // Now that we've warned once, prevent the warning from showing up // again. Services.perms.add(aURI, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN); }, // XXX: duplicated in preferences/advanced.js _getOfflineAppUsage: function(host, groups) { var cacheService = Cc["@mozilla.org/network/application-cache-service;1"] .getService(Ci.nsIApplicationCacheService); if (!groups) { try { groups = cacheService.getGroups(); } catch(ex) { // Cache disabled. return 0; } } var usage = 0; for (let group of groups) { var uri = Services.io.newURI(group, null, null); if (uri.asciiHost == host) { var cache = cacheService.getActiveCache(group); usage += cache.usage; } } return usage; }, _checkUsage: function(aURI) { // if the user has already allowed excessive usage, don't bother checking if (Services.perms.testExactPermission(aURI, "offline-app") != Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) { var usage = this._getOfflineAppUsage(aURI.asciiHost); var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn"); if (usage >= warnQuota * 1024) { return true; } } return false; }, offlineAppRequested: function(aContentWindow) { if (!gPrefService.getBoolPref("browser.offline-apps.notify")) { return; } let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow); let browser = this._getBrowserForContentWindow(browserWindow, aContentWindow); let currentURI = aContentWindow.document.documentURIObject; // don't bother showing UI if the user has already made a decision if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION) { return; } try { if (gPrefService.getBoolPref("offline-apps.allow_by_default")) { // all pages can use offline capabilities, no need to ask the user return; } } catch(e) { // this pref isn't set by default, ignore failures } let host = currentURI.asciiHost; let notificationID = "offline-app-requested-" + host; let notification = PopupNotifications.getNotification(notificationID, browser); if (notification) { notification.options.documents.push(aContentWindow.document); } else { let mainAction = { label: gNavigatorBundle.getString("offlineApps.allow"), accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"), callback: function() { for (let document of notification.options.documents) { OfflineApps.allowSite(document); } } }; let secondaryActions = [{ label: gNavigatorBundle.getString("offlineApps.never"), accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"), callback: function() { for (let document of notification.options.documents) { OfflineApps.disallowSite(document); } } }]; let message = gNavigatorBundle.getFormattedString("offlineApps.available", [ host ]); let anchorID = "indexedDB-notification-icon"; let options= { documents : [ aContentWindow.document ] }; notification = PopupNotifications.show(browser, notificationID, message, anchorID, mainAction, secondaryActions, options); } }, allowSite: function(aDocument) { Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION); // When a site is enabled while loading, manifest resources will // start fetching immediately. This one time we need to do it // ourselves. this._startFetching(aDocument); }, disallowSite: function(aDocument) { Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION); }, manage: function() { openAdvancedPreferences("networkTab"); }, _startFetching: function(aDocument) { if (!aDocument.documentElement) { return; } var manifest = aDocument.documentElement.getAttribute("manifest"); if (!manifest) { return; } var manifestURI = makeURI(manifest, aDocument.characterSet, aDocument.documentURIObject); var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"] .getService(Ci.nsIOfflineCacheUpdateService); updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, window); }, ///////////////////////////////////////////////////////////////////////////// // nsIObserver observe: function(aSubject, aTopic, aState) { if (aTopic == "offline-cache-update-completed") { var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate); var uri = cacheUpdate.manifestURI; if (OfflineApps._checkUsage(uri)) { var browser = this._getBrowserForCacheUpdate(cacheUpdate); if (browser) { OfflineApps._warnUsage(browser, cacheUpdate.manifestURI); } } } } }; var IndexedDBPromptHelper = { _permissionsPrompt: "indexedDB-permissions-prompt", _permissionsResponse: "indexedDB-permissions-response", _quotaPrompt: "indexedDB-quota-prompt", _quotaResponse: "indexedDB-quota-response", _quotaCancel: "indexedDB-quota-cancel", _notificationIcon: "indexedDB-notification-icon", init: function IndexedDBPromptHelper_init() { Services.obs.addObserver(this, this._permissionsPrompt, false); Services.obs.addObserver(this, this._quotaPrompt, false); Services.obs.addObserver(this, this._quotaCancel, false); }, uninit: function IndexedDBPromptHelper_uninit() { Services.obs.removeObserver(this, this._permissionsPrompt); Services.obs.removeObserver(this, this._quotaPrompt); Services.obs.removeObserver(this, this._quotaCancel); }, observe: function IndexedDBPromptHelper_observe(subject, topic, data) { if (topic != this._permissionsPrompt && topic != this._quotaPrompt && topic != this._quotaCancel) { throw new Error("Unexpected topic!"); } var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor); var contentWindow = requestor.getInterface(Ci.nsIDOMWindow); var contentDocument = contentWindow.document; var browserWindow = OfflineApps._getBrowserWindowForContentWindow(contentWindow); if (browserWindow != window) { // Must belong to some other window. return; } var browser = OfflineApps._getBrowserForContentWindow(browserWindow, contentWindow); var host = contentDocument.documentURIObject.asciiHost; var message; var responseTopic; if (topic == this._permissionsPrompt) { message = gNavigatorBundle.getFormattedString("offlineApps.available", [ host ]); responseTopic = this._permissionsResponse; } else if (topic == this._quotaPrompt) { message = gNavigatorBundle.getFormattedString("indexedDB.usage", [ host, data ]); responseTopic = this._quotaResponse; } else if (topic == this._quotaCancel) { responseTopic = this._quotaResponse; } const hiddenTimeoutDuration = 30000; // 30 seconds const firstTimeoutDuration = 300000; // 5 minutes var timeoutId; var observer = requestor.getInterface(Ci.nsIObserver); var mainAction = { label: gNavigatorBundle.getString("offlineApps.allow"), accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"), callback: function() { clearTimeout(timeoutId); observer.observe(null, responseTopic, Ci.nsIPermissionManager.ALLOW_ACTION); } }; var secondaryActions = [ { label: gNavigatorBundle.getString("offlineApps.never"), accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"), callback: function() { clearTimeout(timeoutId); observer.observe(null, responseTopic, Ci.nsIPermissionManager.DENY_ACTION); } } ]; // This will be set to the result of PopupNotifications.show() below, or to // the result of PopupNotifications.getNotification() if this is a // quotaCancel notification. var notification; function timeoutNotification() { // Remove the notification. if (notification) { notification.remove(); } // Clear all of our timeout stuff. We may be called directly, not just // when the timeout actually elapses. clearTimeout(timeoutId); // And tell the page that the popup timed out. observer.observe(null, responseTopic, Ci.nsIPermissionManager.UNKNOWN_ACTION); } var options = { eventCallback: function(state) { // Don't do anything if the timeout has not been set yet. if (!timeoutId) { return; } // If the popup is being dismissed start the short timeout. if (state == "dismissed") { clearTimeout(timeoutId); timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration); return; } // If the popup is being re-shown then clear the timeout allowing // unlimited waiting. if (state == "shown") { clearTimeout(timeoutId); } } }; if (topic == this._quotaCancel) { notification = PopupNotifications.getNotification(this._quotaPrompt, browser); timeoutNotification(); return; } notification = PopupNotifications.show(browser, topic, message, this._notificationIcon, mainAction, secondaryActions, options); // Set the timeoutId after the popup has been created, and use the long // timeout value. If the user doesn't notice the popup after this amount of // time then it is most likely not visible and we want to alert the page. timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration); } }; function WindowIsClosing() { let event = document.createEvent("Events"); event.initEvent("WindowIsClosing", true, true); if (!window.dispatchEvent(event)) { return false; } if (!closeWindow(false, warnAboutClosingWindow)) { return false; } for (let browser of gBrowser.browsers) { let ds = browser.docShell; if (ds.contentViewer && !ds.contentViewer.permitUnload()) { return false; } } return true; } /** * Checks if this is the last full *browser* window around. If it is, this will * be communicated like quitting. Otherwise, we warn about closing multiple tabs. * @returns true if closing can proceed, false if it got cancelled. */ function warnAboutClosingWindow() { // Popups aren't considered full browser windows. let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window); if (!isPBWindow && !toolbar.visible) { return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); } // Figure out if there's at least one other browser window around. let e = Services.wm.getEnumerator("navigator:browser"); let otherPBWindowExists = false; let nonPopupPresent = false; while (e.hasMoreElements()) { let win = e.getNext(); if (win != window) { if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) { otherPBWindowExists = true; } if (win.toolbar.visible) { nonPopupPresent = true; } // If the current window is not in private browsing mode we don't need to // look for other pb windows, we can leave the loop when finding the // first non-popup window. If however the current window is in private // browsing mode then we need at least one other pb and one non-popup // window to break out early. if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent) { break; } } } if (isPBWindow && !otherPBWindowExists) { let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"] .createInstance(Ci.nsISupportsPRBool); exitingCanceled.data = false; Services.obs.notifyObservers(exitingCanceled, "last-pb-context-exiting", null); if (exitingCanceled.data) { return false; } } if (nonPopupPresent) { return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); } let os = Services.obs; let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"] .createInstance(Ci.nsISupportsPRBool); os.notifyObservers(closingCanceled, "browser-lastwindow-close-requested", null); if (closingCanceled.data) { return false; } os.notifyObservers(null, "browser-lastwindow-close-granted", null); #ifdef XP_MACOSX // OS X doesn't quit the application when the last window is closed, but keeps // the session alive. Hence don't prompt users to save tabs, but warn about // closing multiple tabs. return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); #else return true; #endif } var MailIntegration = { sendLinkForWindow: function(aWindow) { this.sendMessage(aWindow.location.href, aWindow.document.title); }, sendMessage: function(aBody, aSubject) { // generate a mailto url based on the url and the url's title var mailtoUrl = "mailto:"; if (aBody) { mailtoUrl += "?body=" + encodeURIComponent(aBody); mailtoUrl += "&subject=" + encodeURIComponent(aSubject); } var uri = makeURI(mailtoUrl); // now pass this uri to the operating system this._launchExternalUrl(uri); }, // a generic method which can be used to pass arbitrary urls to the operating // system. // aURL --> a nsIURI which represents the url to launch _launchExternalUrl: function(aURL) { var extProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"] .getService(Ci.nsIExternalProtocolService); if (extProtocolSvc) { extProtocolSvc.loadUrl(aURL); } } }; function BrowserOpenAddonsMgr(aView) { if (aView) { let emWindow; let browserWindow; var receivePong = function (aSubject, aTopic, aData) { let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem) .rootTreeItem .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow); if (!emWindow || browserWin == window /* favor the current window */) { emWindow = aSubject; browserWindow = browserWin; } } Services.obs.addObserver(receivePong, "EM-pong", false); Services.obs.notifyObservers(null, "EM-ping", ""); Services.obs.removeObserver(receivePong, "EM-pong"); if (emWindow) { emWindow.loadView(aView); browserWindow.gBrowser.selectedTab = browserWindow.gBrowser._getTabForContentWindow(emWindow); emWindow.focus(); return; } } var newLoad = !switchToTabHavingURI("about:addons", true); if (aView) { // This must be a new load, else the ping/pong would have // found the window above. Services.obs.addObserver(function observer(aSubject, aTopic, aData) { Services.obs.removeObserver(observer, aTopic); aSubject.loadView(aView); }, "EM-loaded", false); } } function BrowserOpenPermissionsMgr() { switchToTabHavingURI("about:permissions", true); } function AddKeywordForSearchField() { var node = document.popupNode; var charset = node.ownerDocument.characterSet; var docURI = makeURI(node.ownerDocument.URL, charset); var formURI = makeURI(node.form.getAttribute("action"), charset, docURI); var spec = formURI.spec; var isURLEncoded = (node.form.method.toUpperCase() == "POST" && (node.form.enctype == "application/x-www-form-urlencoded" || node.form.enctype == "")); var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill", [node.ownerDocument.title]); var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument); var formData = []; function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) { if (aIsFormUrlEncoded) { return escape(aName + "=" + aValue); } else { return escape(aName) + "=" + escape(aValue); } } for (let el of node.form.elements) { if (!el.type) { // happens with fieldsets continue; } if (el == node) { formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) : // Don't escape "%s", just append escapeNameValuePair(el.name, "", false) + "%s"); continue; } let type = el.type.toLowerCase(); if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) || type == "hidden" || type == "textarea") || ((type == "checkbox" || type == "radio") && el.checked)) { formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded)); } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) { for (var j=0; j < el.options.length; j++) { if (el.options[j].selected) { formData.push(escapeNameValuePair(el.name, el.options[j].value, isURLEncoded)); } } } } var postData; if (isURLEncoded) { postData = formData.join("&"); } else { spec += "?" + formData.join("&"); } PlacesUIUtils.showBookmarkDialog({ action: "add", type: "bookmark", uri: makeURI(spec), title: title, description: description, keyword: "", postData: postData, charSet: charset, hiddenRows: [ "location", "description", "tags", "loadInSidebar" ] }, window); } function SwitchDocumentDirection(aWindow) { // document.dir can also be "auto", in which case it won't change if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") { aWindow.document.dir = "rtl"; } else if (aWindow.document.dir == "rtl") { aWindow.document.dir = "ltr"; } for (var run = 0; run < aWindow.frames.length; run++) { SwitchDocumentDirection(aWindow.frames[run]); } } function convertFromUnicode(charset, str) { try { var unicodeConverter = Components .classes["@mozilla.org/intl/scriptableunicodeconverter"] .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); unicodeConverter.charset = charset; str = unicodeConverter.ConvertFromUnicode(str); return str + unicodeConverter.Finish(); } catch(ex) { return null; } } /** * Re-open a closed tab. * @param aIndex * The index of the tab (via nsSessionStore.getClosedTabData) * @returns a reference to the reopened tab. */ function undoCloseTab(aIndex) { // wallpaper patch to prevent an unnecessary blank tab (bug 343895) var blankTabToRemove = null; if (gBrowser.tabs.length == 1 && !gPrefService.getBoolPref("browser.tabs.autoHide") && isTabEmpty(gBrowser.selectedTab)) { blankTabToRemove = gBrowser.selectedTab; } var tab = null; var ss = Cc["@mozilla.org/browser/sessionstore;1"] .getService(Ci.nsISessionStore); if (ss.getClosedTabCount(window) > (aIndex || 0)) { tab = ss.undoCloseTab(window, aIndex || 0); if (blankTabToRemove) { gBrowser.removeTab(blankTabToRemove); } } return tab; } /** * Re-open a closed window. * @param aIndex * The index of the window (via nsSessionStore.getClosedWindowData) * @returns a reference to the reopened window. */ function undoCloseWindow(aIndex) { let ss = Cc["@mozilla.org/browser/sessionstore;1"] .getService(Ci.nsISessionStore); let window = null; if (ss.getClosedWindowCount() > (aIndex || 0)) { window = ss.undoCloseWindow(aIndex || 0); } return window; } /* * Determines if a tab is "empty", usually used in the context of determining * if it's ok to close the tab. */ function isTabEmpty(aTab) { if (aTab.hasAttribute("busy")) { return false; } let browser = aTab.linkedBrowser; if (!isBlankPageURL(browser.currentURI.spec)) { return false; } // Bug 863515 - Make content.opener checks work in electrolysis. if (!gMultiProcessBrowser && browser.contentWindow.opener) { return false; } if (browser.sessionHistory && browser.sessionHistory.count >= 2) { return false; } return true; } #ifdef MOZ_SERVICES_SYNC function BrowserOpenSyncTabs() { switchToTabHavingURI("about:sync-tabs", true); } #endif /** * Format a URL * eg: * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/"); * > https://addons.mozilla.org/en-US/firefox/3.0a1/ * * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased. */ function formatURL(aFormat, aIsPref) { var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat); } /** * Utility object to handle manipulations of the identity indicators in the UI */ var gIdentityHandler = { // Mode strings used to control CSS display IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information IDENTITY_MODE_MIXED_CONTENT : "unknownIdentity mixedContent", // SSL with unauthenticated content IDENTITY_MODE_MIXED_ACTIVE_CONTENT : "unknownIdentity mixedContent mixedActiveContent", // SSL with unauthenticated content IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI // Cache the most recent SSLStatus and Location seen in checkIdentity _lastStatus : null, _lastLocation : null, _mode : "unknownIdentity", // smart getters get _encryptionLabel () { delete this._encryptionLabel; this._encryptionLabel = {}; this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] = gNavigatorBundle.getString("identity.encrypted"); this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] = gNavigatorBundle.getString("identity.encrypted"); this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] = gNavigatorBundle.getString("identity.unencrypted"); this._encryptionLabel[this.IDENTITY_MODE_MIXED_CONTENT] = gNavigatorBundle.getString("identity.mixed_content"); this._encryptionLabel[this.IDENTITY_MODE_MIXED_ACTIVE_CONTENT] = gNavigatorBundle.getString("identity.mixed_content"); return this._encryptionLabel; }, get _identityPopup () { delete this._identityPopup; return this._identityPopup = document.getElementById("identity-popup"); }, get _identityBox () { delete this._identityBox; return this._identityBox = document.getElementById("identity-box"); }, get _identityPopupContentBox () { delete this._identityPopupContentBox; return this._identityPopupContentBox = document.getElementById("identity-popup-content-box"); }, get _identityPopupContentHost () { delete this._identityPopupContentHost; return this._identityPopupContentHost = document.getElementById("identity-popup-content-host"); }, get _identityPopupContentOwner () { delete this._identityPopupContentOwner; return this._identityPopupContentOwner = document.getElementById("identity-popup-content-owner"); }, get _identityPopupContentSupp () { delete this._identityPopupContentSupp; return this._identityPopupContentSupp = document.getElementById("identity-popup-content-supplemental"); }, get _identityPopupContentVerif () { delete this._identityPopupContentVerif; return this._identityPopupContentVerif = document.getElementById("identity-popup-content-verifier"); }, get _identityPopupEncLabel () { delete this._identityPopupEncLabel; return this._identityPopupEncLabel = document.getElementById("identity-popup-encryption-label"); }, get _identityIconLabel () { delete this._identityIconLabel; return this._identityIconLabel = document.getElementById("identity-icon-label"); }, get _overrideService () { delete this._overrideService; return this._overrideService = Cc["@mozilla.org/security/certoverride;1"] .getService(Ci.nsICertOverrideService); }, get _identityIconCountryLabel () { delete this._identityIconCountryLabel; return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label"); }, get _identityIcon () { delete this._identityIcon; return this._identityIcon = document.getElementById("page-proxy-favicon"); }, /** * Rebuild cache of the elements that may or may not exist depending * on whether there's a location bar. */ _cacheElements : function() { delete this._identityBox; delete this._identityIconLabel; delete this._identityIconCountryLabel; delete this._identityIcon; this._identityBox = document.getElementById("identity-box"); this._identityIconLabel = document.getElementById("identity-icon-label"); this._identityIconCountryLabel = document.getElementById("identity-icon-country-label"); this._identityIcon = document.getElementById("page-proxy-favicon"); }, /** * Handler for mouseclicks on the "More Information" button in the * "identity-popup" panel. */ handleMoreInfoClick : function(event) { displaySecurityInfo(); event.stopPropagation(); }, /** * Helper to parse out the important parts of _lastStatus (of the SSL cert in * particular) for use in constructing identity UI strings */ getIdentityData : function() { var result = {}; var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus); var cert = status.serverCert; // Human readable name of Subject result.subjectOrg = cert.organization; // SubjectName fields, broken up for individual access if (cert.subjectName) { result.subjectNameFields = {}; cert.subjectName.split(",").forEach(function(v) { var field = v.split("="); this[field[0]] = field[1]; }, result.subjectNameFields); // Call out city, state, and country specifically result.city = result.subjectNameFields.L; result.state = result.subjectNameFields.ST; result.country = result.subjectNameFields.C; } // Human readable name of Certificate Authority result.caOrg = cert.issuerOrganization || cert.issuerCommonName; result.cert = cert; return result; }, /** * Determine the identity of the page being displayed by examining its SSL cert * (if available) and, if necessary, update the UI to reflect this. Intended to * be called by onSecurityChange * * @param PRUint32 state * @param JS Object location that mirrors an nsLocation (i.e. has .host and * .hostname and .port) */ checkIdentity : function(state, location) { var currentStatus = gBrowser.securityUI .QueryInterface(Components.interfaces.nsISSLStatusProvider) .SSLStatus; this._lastStatus = currentStatus; this._lastLocation = location; let nsIWebProgressListener = Ci.nsIWebProgressListener; if (location.protocol == "chrome:" || location.protocol == "about:") { this.setMode(this.IDENTITY_MODE_CHROMEUI); } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) { this.setMode(this.IDENTITY_MODE_IDENTIFIED); } else if (state & nsIWebProgressListener.STATE_IS_SECURE) { this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED); } else if (state & nsIWebProgressListener.STATE_IS_BROKEN) { if ((state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) && gPrefService.getBoolPref("security.mixed_content.block_active_content")) { this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_CONTENT); } else { this.setMode(this.IDENTITY_MODE_MIXED_CONTENT); } } else { this.setMode(this.IDENTITY_MODE_UNKNOWN); } // Ensure the doorhanger is shown when mixed active content is blocked. if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) { this.showMixedContentDoorhanger(); } }, /** * Display the Mixed Content Blocker doohanger, providing an option * to the user to override mixed content blocking */ showMixedContentDoorhanger : function() { // If we've already got an active notification, bail out to avoid showing it repeatedly. if (PopupNotifications.getNotification("mixed-content-blocked", gBrowser.selectedBrowser)) { return; } let brandBundle = document.getElementById("bundle_brand"); let brandShortName = brandBundle.getString("brandShortName"); let messageString = gNavigatorBundle.getFormattedString("mixedContentBlocked.message", [brandShortName]); let action = { label: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.label"), accessKey: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.accesskey"), callback: function() { /* NOP */ } }; let secondaryActions = [ { label: gNavigatorBundle.getString("mixedContentBlocked.unblock.label"), accessKey: gNavigatorBundle.getString("mixedContentBlocked.unblock.accesskey"), callback: function() { // Reload the page with the content unblocked BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT); } } ]; let options = { dismissed: true, learnMoreURL: Services.urlFormatter.formatURLPref("browser.mixedcontent.warning.infoURL") }; PopupNotifications.show(gBrowser.selectedBrowser, "mixed-content-blocked", messageString, "mixed-content-blocked-notification-icon", action, secondaryActions, options); }, /** * Return the eTLD+1 version of the current hostname */ getEffectiveHost : function() { try { let baseDomain = Services.eTLD.getBaseDomainFromHost(this._lastLocation.hostname); return this._IDNService.convertToDisplayIDN(baseDomain, {}); } catch(e) { // If something goes wrong (e.g. hostname is an IP address) just fall back // to the full domain. return this._lastLocation.hostname; } }, /** * Update the UI to reflect the specified mode, which should be one of the * IDENTITY_MODE_* constants. */ setMode : function(newMode) { if (!this._identityBox) { // No identity box means the identity box is not visible, in which // case there's nothing to do. return; } this._identityBox.className = newMode; this.setIdentityMessages(newMode); // Update the popup too, if it's open if (this._identityPopup.state == "open") { this.setPopupMessages(newMode); } this._mode = newMode; }, /** * Set up the messages for the primary identity UI based on the specified mode, * and the details of the SSL cert, where applicable * * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants. */ setIdentityMessages : function(newMode) { let icon_label = ""; let tooltip = ""; let icon_country_label = ""; let icon_labels_dir = "ltr"; if (!this._IDNService) { this._IDNService = Cc["@mozilla.org/network/idn-service;1"] .getService(Ci.nsIIDNService); } let punyID = gPrefService.getIntPref("browser.identity.display_punycode", 1); switch (newMode) { case this.IDENTITY_MODE_MIXED_CONTENT: case this.IDENTITY_MODE_DOMAIN_VERIFIED: { let iData = this.getIdentityData(); let label_display = ""; // Honor browser.identity.ssl_domain_display switch (gPrefService.getIntPref("browser.identity.ssl_domain_display")) { case 2 : // Show full domain label_display = this._lastLocation.hostname; break; case 1 : // Show eTLD. label_display = this.getEffectiveHost(); } if (punyID >= 1) { // Display punycode version in identity panel icon_label = this._IDNService.convertUTF8toACE(label_display); } else { icon_label = label_display; } // Verifier is either the CA Org, for a normal cert, or a special string // for certs that are trusted because of a security exception. tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", [iData.caOrg]); // Check whether this site is a security exception. XPConnect does the right // thing here in terms of converting _lastLocation.port from string to int, but // the overrideService doesn't like undefined ports, so make sure we have // something in the default case (bug 432241). // .hostname can return an empty string in some exceptional cases - // hasMatchingOverride does not handle that, so avoid calling it. // Updating the tooltip value in those cases isn't critical. // FIXME: Fixing bug 646690 would probably makes this check unnecessary if (this._lastLocation.hostname && this._overrideService.hasMatchingOverride(this._lastLocation.hostname, (this._lastLocation.port || 443), iData.cert, {}, {})) { tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you"); } break; } case this.IDENTITY_MODE_IDENTIFIED: { // If it's identified, then we can populate the dialog with credentials let iData = this.getIdentityData(); tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", [iData.caOrg]); icon_label = iData.subjectOrg; if (iData.country) { icon_country_label = "(" + iData.country + ")"; } // If the organization name starts with an RTL character, then // swap the positions of the organization and country code labels. // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets // fixed, this test should be replaced by one adhering to the // Unicode Bidirectional Algorithm proper (at the paragraph level). icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ? "rtl" : "ltr"; break; } case this.IDENTITY_MODE_CHROMEUI: break; default: tooltip = gNavigatorBundle.getString("identity.unknown.tooltip"); if (punyID == 2) { // Check for IDN and display if so... let rawHost = this._IDNService.convertUTF8toACE(this._lastLocation.hostname); if (this._IDNService.isACE(rawHost)) { icon_label = rawHost; } } } // Push the appropriate strings out to the UI this._identityBox.tooltipText = tooltip; this._identityIconLabel.value = icon_label; this._identityIconCountryLabel.value = icon_country_label; // Set cropping and direction this._identityIconLabel.crop = icon_country_label ? "end" : "center"; this._identityIconLabel.parentNode.style.direction = icon_labels_dir; // Hide completely if the organization label is empty this._identityIconLabel.parentNode.collapsed = icon_label ? false : true; }, /** * Set up the title and content messages for the identity message popup, * based on the specified mode, and the details of the SSL cert, where * applicable * * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants. */ setPopupMessages : function(newMode) { this._identityPopup.className = newMode; this._identityPopupContentBox.className = newMode; // Set the static strings up front this._identityPopupEncLabel.textContent = this._encryptionLabel[newMode]; // Initialize the optional strings to empty values let supplemental = ""; let verifier = ""; let host = ""; let owner = ""; switch (newMode) { case this.IDENTITY_MODE_DOMAIN_VERIFIED: host = this.getEffectiveHost(); owner = gNavigatorBundle.getString("identity.ownerUnknown2"); verifier = this._identityBox.tooltipText; break; case this.IDENTITY_MODE_IDENTIFIED: { // If it's identified, then we can populate the dialog with credentials let iData = this.getIdentityData(); host = this.getEffectiveHost(); owner = iData.subjectOrg; verifier = this._identityBox.tooltipText; // Build an appropriate supplemental block out of whatever location data we have if (iData.city) { supplemental += iData.city + "\n"; } if (iData.state && iData.country) { supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country", [iData.state, iData.country]); } else if (iData.state) { // State only supplemental += iData.state; } else if (iData.country) { // Country only supplemental += iData.country; } break; } } // Push the appropriate strings out to the UI this._identityPopupContentHost.textContent = host; this._identityPopupContentOwner.textContent = owner; this._identityPopupContentSupp.textContent = supplemental; this._identityPopupContentVerif.textContent = verifier; }, hideIdentityPopup : function() { this._identityPopup.hidePopup(); }, /** * Click handler for the identity-box element in primary chrome. */ handleIdentityButtonEvent : function(event) { event.stopPropagation(); if ((event.type == "click" && event.button != 0) || (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE && event.keyCode != KeyEvent.DOM_VK_RETURN)) { return; // Left click, space or enter only } // Don't allow left click, space or enter if the location // is chrome UI or the location has been modified. if (this._mode == this.IDENTITY_MODE_CHROMEUI || gURLBar.getAttribute("pageproxystate") != "valid") { return; } // Make sure that the display:none style we set in xul is removed now that // the popup is actually needed this._identityPopup.hidden = false; // Update the popup strings this.setPopupMessages(this._identityBox.className); // Add the "open" attribute to the identity box for styling this._identityBox.setAttribute("open", "true"); var self = this; this._identityPopup.addEventListener("popuphidden", function onPopupHidden(e) { e.currentTarget.removeEventListener("popuphidden", onPopupHidden, false); self._identityBox.removeAttribute("open"); }, false); // Now open the popup, anchored off the primary chrome element this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft"); }, onPopupShown : function(event) { document.getElementById('identity-popup-more-info-button').focus(); }, onDragStart: function(event) { if (gURLBar.getAttribute("pageproxystate") != "valid") { return; } var value = content.location.href; var urlString = value + "\n" + content.document.title; var htmlString = "" + value + ""; var dt = event.dataTransfer; dt.setData("text/x-moz-url", urlString); dt.setData("text/uri-list", value); dt.setData("text/plain", value); dt.setData("text/html", htmlString); dt.setDragImage(gProxyFavIcon, 16, 16); } }; function getNotificationBox(aWindow) { var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document); if (foundBrowser) { return gBrowser.getNotificationBox(foundBrowser) } return null; }; function getTabModalPromptBox(aWindow) { var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document); if (foundBrowser) { return gBrowser.getTabModalPromptBox(foundBrowser); } return null; }; /* DEPRECATED */ function getBrowser() { return gBrowser; } function getNavToolbox() { return gNavToolbox; } var gPrivateBrowsingUI = { init: function() { // Do nothing for normal windows if (!PrivateBrowsingUtils.isWindowPrivate(window)) { return; } // Disable the Clear Recent History... menu item when in PB mode // temporary fix until bug 463607 is fixed document.getElementById("Tools:Sanitize").setAttribute("disabled", "true"); if (window.location.href == getBrowserURL()) { #ifdef XP_MACOSX if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { document.documentElement.setAttribute("drawintitlebar", true); } #endif // Adjust the window's title let docElement = document.documentElement; if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { docElement.setAttribute("title", docElement.getAttribute("title_privatebrowsing")); docElement.setAttribute("titlemodifier", docElement.getAttribute("titlemodifier_privatebrowsing")); } docElement.setAttribute("privatebrowsingmode", PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"); gBrowser.updateTitlebar(); if (PrivateBrowsingUtils.permanentPrivateBrowsing) { // Adjust the New Window menu entries [ { normal: "menu_newNavigator", private: "menu_newPrivateWindow" }, { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow" }, ].forEach(function(menu) { let newWindow = document.getElementById(menu.normal); let newPrivateWindow = document.getElementById(menu.private); if (newWindow && newPrivateWindow) { newPrivateWindow.hidden = true; newWindow.label = newPrivateWindow.label; newWindow.accessKey = newPrivateWindow.accessKey; newWindow.command = newPrivateWindow.command; } }); } } if (gURLBar && !PrivateBrowsingUtils.permanentPrivateBrowsing) { // Disable switch to tab autocompletion for private windows // (not for "Always use private browsing" mode) gURLBar.setAttribute("autocompletesearchparam", ""); } } }; /** * Switch to a tab that has a given URI, and focuses its browser window. * If a matching tab is in this window, it will be switched to. Otherwise, other * windows will be searched. * * @param aURI * URI to search for * @param aOpenNew * True to open a new tab and switch to it, if no existing tab is found. * If no suitable window is found, a new one will be opened. * @return True if an existing tab was found, false otherwise */ function switchToTabHavingURI(aURI, aOpenNew) { // This will switch to the tab in aWindow having aURI, if present. function switchIfURIInWindow(aWindow) { // Only switch to the tab if neither the source and desination window are // private and they are not in permanent private borwsing mode if ((PrivateBrowsingUtils.isWindowPrivate(window) || PrivateBrowsingUtils.isWindowPrivate(aWindow)) && !PrivateBrowsingUtils.permanentPrivateBrowsing) { return false; } let browsers = aWindow.gBrowser.browsers; for (let i = 0; i < browsers.length; i++) { let browser = browsers[i]; if (browser.currentURI.equals(aURI)) { // Focus the matching window & tab aWindow.focus(); aWindow.gBrowser.tabContainer.selectedIndex = i; return true; } } return false; } // This can be passed either nsIURI or a string. if (!(aURI instanceof Ci.nsIURI)) { aURI = Services.io.newURI(aURI, null, null); } let isBrowserWindow = !!window.gBrowser; // Prioritise this window. if (isBrowserWindow && switchIfURIInWindow(window)) { return true; } let winEnum = Services.wm.getEnumerator("navigator:browser"); while (winEnum.hasMoreElements()) { let browserWin = winEnum.getNext(); // Skip closed (but not yet destroyed) windows, // and the current window (which was checked earlier). if (browserWin.closed || browserWin == window) { continue; } if (switchIfURIInWindow(browserWin)) { return true; } } // No opened tab has that url. if (aOpenNew) { if (isBrowserWindow && isTabEmpty(gBrowser.selectedTab)) { gBrowser.selectedBrowser.loadURI(aURI.spec); } else { openUILinkIn(aURI.spec, "tab"); } } return false; } function restoreLastSession() { let ss = Cc["@mozilla.org/browser/sessionstore;1"] .getService(Ci.nsISessionStore); ss.restoreLastSession(); } var TabContextMenu = { contextTab: null, _updateToggleMuteMenuItem(aTab, aConditionFn) { ["muted", "soundplaying"].forEach(attr => { if (!aConditionFn || aConditionFn(attr)) { if (aTab.hasAttribute(attr)) { aTab.toggleMuteMenuItem.setAttribute(attr, "true"); } else { aTab.toggleMuteMenuItem.removeAttribute(attr); } } }); }, updateContextMenu: function updateContextMenu(aPopupMenu) { this.contextTab = aPopupMenu.triggerNode.localName == "tab" ? aPopupMenu.triggerNode : gBrowser.selectedTab; let disabled = gBrowser.tabs.length == 1; // Enable the "Close Tab" menuitem when the window doesn't close with the last tab. document.getElementById("context_closeTab").disabled = disabled && gBrowser.tabContainer._closeWindowWithLastTab; var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple"); for (let menuItem of menuItems) { menuItem.disabled = disabled; } disabled = gBrowser.visibleTabs.length == 1; menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible"); for (let menuItem of menuItems) { menuItem.disabled = disabled; } // Session store document.getElementById("context_undoCloseTab").disabled = Cc["@mozilla.org/browser/sessionstore;1"] .getService(Ci.nsISessionStore) .getClosedTabCount(window) == 0; // Only one of pin/unpin should be visible document.getElementById("context_pinTab").hidden = this.contextTab.pinned; document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned; // Disable "Close Tabs to the Right" if there are no tabs // following it and hide it when the user rightclicked on a pinned // tab. document.getElementById("context_closeTabsToTheEnd").disabled = gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0; document.getElementById("context_closeTabsToTheEnd").hidden = this.contextTab.pinned; // Disable "Close other Tabs" if there is only one unpinned tab and // hide it when the user rightclicked on a pinned tab. let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs; document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1; document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned; // Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible. let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs"); bookmarkAllTabs.hidden = this.contextTab.pinned; if (!bookmarkAllTabs.hidden) { PlacesCommandHook.updateBookmarkAllTabsCommand(); } // Adjust the state of the toggle mute menu item. let toggleMute = document.getElementById("context_toggleMuteTab"); if (this.contextTab.hasAttribute("muted")) { toggleMute.label = gNavigatorBundle.getString("unmuteTab.label"); toggleMute.accessKey = gNavigatorBundle.getString("unmuteTab.accesskey"); } else { toggleMute.label = gNavigatorBundle.getString("muteTab.label"); toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey"); } this.contextTab.toggleMuteMenuItem = toggleMute; this._updateToggleMuteMenuItem(this.contextTab); this.contextTab.addEventListener("TabAttrModified", this, false); aPopupMenu.addEventListener("popuphiding", this, false); }, handleEvent(aEvent) { switch (aEvent.type) { case "popuphiding": gBrowser.removeEventListener("TabAttrModified", this); aEvent.target.removeEventListener("popuphiding", this); break; case "TabAttrModified": let tab = aEvent.target; this._updateToggleMuteMenuItem(tab, attr => aEvent.detail.changed.indexOf(attr) >= 0); break; } } }; #ifdef MOZ_DEVTOOLS // Note: Do not delete! It is used for: base/content/nsContextMenu.js XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm"); #endif // Prompt user to restart the browser in safe mode or normally function restart(safeMode) { let promptTitleString = null; let promptMessageString = null; let restartTextString = null; if (safeMode) { promptTitleString = "safeModeRestartPromptTitle"; promptMessageString = "safeModeRestartPromptMessage"; restartTextString = "safeModeRestartButton"; } else { promptTitleString = "restartPromptTitle"; promptMessageString = "restartPromptMessage"; restartTextString = "restartButton"; } let flags = Ci.nsIAppStartup.eAttemptQuit; // Prompt the user to confirm let promptTitle = gNavigatorBundle.getString(promptTitleString); let brandBundle = document.getElementById("bundle_brand"); let brandShortName = brandBundle.getString("brandShortName"); let promptMessage = gNavigatorBundle.getFormattedString(promptMessageString, [brandShortName]); let restartText = gNavigatorBundle.getString(restartTextString); let buttonFlags = (Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING) + (Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL) + Services.prompt.BUTTON_POS_0_DEFAULT; let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage, buttonFlags, restartText, null, null, null, {}); if (rv == 0) { // Notify all windows that an application quit has been requested. let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"] .createInstance(Ci.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); // Something aborted the quit process. if (cancelQuit.data) { return; } if (safeMode) { Services.startup.restartInSafeMode(flags); } else { Services.startup.quit(flags | Ci.nsIAppStartup.eRestart); } } } /* duplicateTabIn duplicates tab in a place specified by the parameter |where|. * * |where| can be: * "tab" new tab * "tabshifted" same as "tab" but in background if default is to select new * tabs, and vice versa * "window" new window * * delta is the offset to the history entry that you want to load. */ function duplicateTabIn(aTab, where, delta) { let newTab = Cc['@mozilla.org/browser/sessionstore;1'] .getService(Ci.nsISessionStore) .duplicateTab(window, aTab, delta); switch (where) { case "window": gBrowser.hideTab(newTab); gBrowser.replaceTabWithWindow(newTab); break; case "tabshifted": // A background tab has been opened, nothing else to do here. break; case "tab": gBrowser.selectedTab = newTab; break; } } function toggleAddonBar() { let addonBar = document.getElementById("addon-bar"); setToolbarVisibility(addonBar, addonBar.collapsed); } XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function() { #ifdef XP_WIN // Only show resizers on Windows 2000 and XP return parseFloat(Services.sysinfo.getProperty("version")) < 6; #else return false; #endif }); var MousePosTracker = { _listeners: [], _x: 0, _y: 0, get _windowUtils() { delete this._windowUtils; return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils); }, addListener: function(listener) { if (this._listeners.indexOf(listener) >= 0) { return; } listener._hover = false; this._listeners.push(listener); this._callListener(listener); }, removeListener: function(listener) { var index = this._listeners.indexOf(listener); if (index < 0) { return; } this._listeners.splice(index, 1); }, handleEvent: function(event) { var fullZoom = this._windowUtils.fullZoom; this._x = event.screenX / fullZoom - window.mozInnerScreenX; this._y = event.screenY / fullZoom - window.mozInnerScreenY; this._listeners.forEach(function(listener) { try { this._callListener(listener); } catch(e) { Cu.reportError(e); } }, this); }, _callListener: function(listener) { let rect = listener.getMouseTargetRect(); let hover = this._x >= rect.left && this._x <= rect.right && this._y >= rect.top && this._y <= rect.bottom; if (hover == listener._hover) { return; } listener._hover = hover; if (hover) { if (listener.onMouseEnter) { listener.onMouseEnter(); } } else { if (listener.onMouseLeave) { listener.onMouseLeave(); } } } }; var BrowserChromeTest = { _cb: null, _ready: false, markAsReady: function() { this._ready = true; if (this._cb) { this._cb(); this._cb = null; } }, runWhenReady: function(cb) { if (this._ready) { cb(); } else { this._cb = cb; } } }; var ToolbarIconColor = { init: function() { this._initialized = true; window.addEventListener("activate", this); window.addEventListener("deactivate", this); Services.obs.addObserver(this, "lightweight-theme-styling-update", false); gPrefService.addObserver("ui.colorChanged", this, false); // If the window isn't active now, we assume that it has never been active // before and will soon become active such that inferFromText will be // called from the initial activate event. if (Services.focus.activeWindow == window) { this.inferFromText(); } }, uninit: function() { this._initialized = false; window.removeEventListener("activate", this); window.removeEventListener("deactivate", this); Services.obs.removeObserver(this, "lightweight-theme-styling-update"); gPrefService.removeObserver("ui.colorChanged", this); }, handleEvent: function(event) { switch (event.type) { case "activate": case "deactivate": this.inferFromText(); break; } }, observe: function(aSubject, aTopic, aData) { switch (aTopic) { case "lightweight-theme-styling-update": // inferFromText needs to run after LightweightThemeConsumer.jsm's // lightweight-theme-styling-update observer. setTimeout(() => { this.inferFromText(); }, 0); break; case "nsPref:changed": // system color change var colorChangedPref = false; try { colorChangedPref = gPrefService.getBoolPref("ui.colorChanged"); } catch(e) {} // if pref indicates change, call inferFromText() on a small delay if (colorChangedPref == true) { setTimeout(() => { this.inferFromText(); }, 300); } break; default: console.error("ToolbarIconColor: Uncaught topic " + aTopic); } }, inferFromText: function() { if (!this._initialized) { return; } function parseRGB(aColorString) { let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/); rgb.shift(); return rgb.map(x => parseInt(x)); } let toolbarSelector = "toolbar:not([collapsed=true])"; #ifdef XP_MACOSX toolbarSelector += ":not([type=menubar])"; #endif // The getComputedStyle calls and setting the brighttext are separated in // two loops to avoid flushing layout and making it dirty repeatedly. let luminances = new Map; for (let toolbar of document.querySelectorAll(toolbarSelector)) { let [r, g, b] = parseRGB(getComputedStyle(toolbar).color); let luminance = (2 * r + 5 * g + b) / 8; luminances.set(toolbar, luminance); } for (let [toolbar, luminance] of luminances) { if (luminance <= 128) { toolbar.removeAttribute("brighttext"); } else { toolbar.setAttribute("brighttext", "true"); } } // Clear pref if set, since we're done applying the color changes. gPrefService.clearUserPref("ui.colorChanged"); } }