michael@0: # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: let Ci = Components.interfaces; michael@0: let Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/NotificationDB.jsm"); michael@0: Cu.import("resource:///modules/RecentWindow.jsm"); michael@0: Cu.import("resource://gre/modules/WindowsPrefSync.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", michael@0: "resource://gre/modules/BrowserUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", michael@0: "resource://gre/modules/CharsetMenu.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils", michael@0: "resource://gre/modules/ShortcutUtils.jsm"); michael@0: michael@0: const nsIWebNavigation = Ci.nsIWebNavigation; michael@0: michael@0: var gLastBrowserCharset = null; michael@0: var gPrevCharset = null; michael@0: var gProxyFavIcon = null; michael@0: var gLastValidURLStr = ""; michael@0: var gInPrintPreviewMode = false; michael@0: var gContextMenu = null; // nsContextMenu instance michael@0: var gMultiProcessBrowser = false; michael@0: michael@0: #ifndef XP_MACOSX michael@0: var gEditUIVisible = true; michael@0: #endif michael@0: michael@0: [ michael@0: ["gBrowser", "content"], michael@0: ["gNavToolbox", "navigator-toolbox"], michael@0: ["gURLBar", "urlbar"], michael@0: ["gNavigatorBundle", "bundle_browser"] michael@0: ].forEach(function (elementGlobal) { michael@0: var [name, id] = elementGlobal; michael@0: window.__defineGetter__(name, function () { michael@0: var element = document.getElementById(id); michael@0: if (!element) michael@0: return null; michael@0: delete window[name]; michael@0: return window[name] = element; michael@0: }); michael@0: window.__defineSetter__(name, function (val) { michael@0: delete window[name]; michael@0: return window[name] = val; michael@0: }); michael@0: }); michael@0: michael@0: // Smart getter for the findbar. If you don't wish to force the creation of michael@0: // the findbar, check gFindBarInitialized first. michael@0: michael@0: this.__defineGetter__("gFindBar", function() { michael@0: return window.gBrowser.getFindBar(); michael@0: }); michael@0: michael@0: this.__defineGetter__("gFindBarInitialized", function() { michael@0: return window.gBrowser.isFindBarInitialized(); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gPrefService", function() { michael@0: return Services.prefs; michael@0: }); michael@0: michael@0: this.__defineGetter__("AddonManager", function() { michael@0: let tmp = {}; michael@0: Cu.import("resource://gre/modules/AddonManager.jsm", tmp); michael@0: return this.AddonManager = tmp.AddonManager; michael@0: }); michael@0: this.__defineSetter__("AddonManager", function (val) { michael@0: delete this.AddonManager; michael@0: return this.AddonManager = val; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", michael@0: "resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", michael@0: "resource://gre/modules/TelemetryStopwatch.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", function() { michael@0: let scope = {}; michael@0: Cu.import("resource:///modules/CustomizeMode.jsm", scope); michael@0: return new scope.CustomizeMode(window); michael@0: }); michael@0: michael@0: #ifdef MOZ_SERVICES_SYNC michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Weave", michael@0: "resource://services-sync/main.js"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () { michael@0: let tmp = {}; michael@0: Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp); michael@0: try { michael@0: return new tmp.PopupNotifications(gBrowser, michael@0: document.getElementById("notification-popup"), michael@0: document.getElementById("notification-popup-box")); michael@0: } catch (ex) { michael@0: Cu.reportError(ex); michael@0: return null; michael@0: } michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() { michael@0: let tmp = {}; michael@0: Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", tmp); michael@0: return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar")); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() { michael@0: let tmp = {}; michael@0: Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", tmp); michael@0: return tmp.BrowserToolboxProcess; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Social", michael@0: "resource:///modules/Social.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs", michael@0: "resource://gre/modules/PageThumbs.jsm"); michael@0: michael@0: #ifdef MOZ_SAFE_BROWSING michael@0: XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing", michael@0: "resource://gre/modules/SafeBrowsing.jsm"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader", michael@0: "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader", michael@0: "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", michael@0: "resource://gre/modules/PrivateBrowsingUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Translation", michael@0: "resource:///modules/translation/Translation.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions", michael@0: "resource:///modules/SitePermissions.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "SessionStore", michael@0: "resource:///modules/sessionstore/SessionStore.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts", michael@0: "resource://gre/modules/FxAccounts.jsm"); michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter", michael@0: "resource:///modules/TabCrashReporter.jsm"); michael@0: #endif michael@0: michael@0: let gInitialPages = [ michael@0: "about:blank", michael@0: "about:newtab", michael@0: "about:home", michael@0: "about:privatebrowsing", michael@0: "about:welcomeback", michael@0: "about:sessionrestore" michael@0: ]; michael@0: michael@0: #include browser-addons.js michael@0: #include browser-customization.js michael@0: #include browser-feeds.js michael@0: #include browser-fullScreen.js michael@0: #include browser-fullZoom.js michael@0: #include browser-places.js michael@0: #include browser-plugins.js michael@0: #include browser-safebrowsing.js michael@0: #include browser-social.js michael@0: #include browser-tabPreviews.js michael@0: #include browser-tabview.js michael@0: #include browser-thumbnails.js michael@0: #include browser-webrtcUI.js michael@0: #include browser-gestureSupport.js michael@0: michael@0: #ifdef MOZ_DATA_REPORTING michael@0: #include browser-data-submission-info-bar.js michael@0: #endif michael@0: michael@0: #ifdef MOZ_SERVICES_SYNC michael@0: #include browser-syncui.js michael@0: #endif michael@0: michael@0: #include browser-fxaccounts.js michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "Win7Features", function () { michael@0: #ifdef XP_WIN michael@0: // Bug 666808 - AeroPeek support for e10s michael@0: if (gMultiProcessBrowser) michael@0: return null; michael@0: michael@0: const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1"; michael@0: if (WINTASKBAR_CONTRACTID in Cc && michael@0: Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) { michael@0: let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek; michael@0: return { michael@0: onOpenWindow: function () { michael@0: AeroPeek.onOpenWindow(window); michael@0: }, michael@0: onCloseWindow: function () { michael@0: AeroPeek.onCloseWindow(window); michael@0: } michael@0: }; michael@0: } michael@0: #endif michael@0: return null; michael@0: }); michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter", michael@0: "@mozilla.org/xre/app-info;1", michael@0: "nsICrashReporter"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "PageMenu", function() { michael@0: let tmp = {}; michael@0: Cu.import("resource://gre/modules/PageMenu.jsm", tmp); michael@0: return new tmp.PageMenu(); michael@0: }); michael@0: michael@0: /** michael@0: * We can avoid adding multiple load event listeners and save some time by adding michael@0: * one listener that calls all real handlers. michael@0: */ michael@0: function pageShowEventHandlers(persisted) { michael@0: charsetLoadListener(); michael@0: XULBrowserWindow.asyncUpdateUI(); michael@0: michael@0: // The PluginClickToPlay events are not fired when navigating using the michael@0: // BF cache. |persisted| is true when the page is loaded from the michael@0: // BF cache, so this code reshows the notification if necessary. michael@0: if (persisted) michael@0: gPluginHandler.reshowClickToPlayNotification(); michael@0: } michael@0: michael@0: function UpdateBackForwardCommands(aWebNavigation) { michael@0: var backBroadcaster = document.getElementById("Browser:Back"); michael@0: var forwardBroadcaster = document.getElementById("Browser:Forward"); michael@0: michael@0: // Avoid setting attributes on broadcasters if the value hasn't changed! michael@0: // Remember, guys, setting attributes on elements is expensive! They michael@0: // get inherited into anonymous content, broadcast to other widgets, etc.! michael@0: // Don't do it if the value hasn't changed! - dwh michael@0: michael@0: var backDisabled = backBroadcaster.hasAttribute("disabled"); michael@0: var forwardDisabled = forwardBroadcaster.hasAttribute("disabled"); michael@0: if (backDisabled == aWebNavigation.canGoBack) { michael@0: if (backDisabled) michael@0: backBroadcaster.removeAttribute("disabled"); michael@0: else michael@0: backBroadcaster.setAttribute("disabled", true); michael@0: } michael@0: michael@0: if (forwardDisabled == aWebNavigation.canGoForward) { michael@0: if (forwardDisabled) michael@0: forwardBroadcaster.removeAttribute("disabled"); michael@0: else michael@0: forwardBroadcaster.setAttribute("disabled", true); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Click-and-Hold implementation for the Back and Forward buttons michael@0: * XXXmano: should this live in toolbarbutton.xml? michael@0: */ michael@0: function SetClickAndHoldHandlers() { michael@0: var timer; michael@0: michael@0: function openMenu(aButton) { michael@0: cancelHold(aButton); michael@0: aButton.firstChild.hidden = false; michael@0: aButton.open = true; michael@0: } michael@0: michael@0: function mousedownHandler(aEvent) { michael@0: if (aEvent.button != 0 || michael@0: aEvent.currentTarget.open || michael@0: aEvent.currentTarget.disabled) michael@0: return; michael@0: michael@0: // Prevent the menupopup from opening immediately michael@0: aEvent.currentTarget.firstChild.hidden = true; michael@0: michael@0: aEvent.currentTarget.addEventListener("mouseout", mouseoutHandler, false); michael@0: aEvent.currentTarget.addEventListener("mouseup", mouseupHandler, false); michael@0: timer = setTimeout(openMenu, 500, aEvent.currentTarget); michael@0: } michael@0: michael@0: function mouseoutHandler(aEvent) { michael@0: let buttonRect = aEvent.currentTarget.getBoundingClientRect(); michael@0: if (aEvent.clientX >= buttonRect.left && michael@0: aEvent.clientX <= buttonRect.right && michael@0: aEvent.clientY >= buttonRect.bottom) michael@0: openMenu(aEvent.currentTarget); michael@0: else michael@0: cancelHold(aEvent.currentTarget); michael@0: } michael@0: michael@0: function mouseupHandler(aEvent) { michael@0: cancelHold(aEvent.currentTarget); michael@0: } michael@0: michael@0: function cancelHold(aButton) { michael@0: clearTimeout(timer); michael@0: aButton.removeEventListener("mouseout", mouseoutHandler, false); michael@0: aButton.removeEventListener("mouseup", mouseupHandler, false); michael@0: } michael@0: michael@0: function clickHandler(aEvent) { michael@0: if (aEvent.button == 0 && michael@0: aEvent.target == aEvent.currentTarget && michael@0: !aEvent.currentTarget.open && michael@0: !aEvent.currentTarget.disabled) { michael@0: let cmdEvent = document.createEvent("xulcommandevent"); michael@0: cmdEvent.initCommandEvent("command", true, true, window, 0, michael@0: aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey, michael@0: aEvent.metaKey, null); michael@0: aEvent.currentTarget.dispatchEvent(cmdEvent); michael@0: } michael@0: } michael@0: michael@0: function _addClickAndHoldListenersOnElement(aElm) { michael@0: aElm.addEventListener("mousedown", mousedownHandler, true); michael@0: aElm.addEventListener("click", clickHandler, true); michael@0: } michael@0: michael@0: // Bug 414797: Clone the back/forward buttons' context menu into both buttons. michael@0: let popup = document.getElementById("backForwardMenu").cloneNode(true); michael@0: popup.removeAttribute("id"); michael@0: // Prevent the back/forward buttons' context attributes from being inherited. michael@0: popup.setAttribute("context", ""); michael@0: michael@0: let backButton = document.getElementById("back-button"); michael@0: backButton.setAttribute("type", "menu"); michael@0: backButton.appendChild(popup); michael@0: _addClickAndHoldListenersOnElement(backButton); michael@0: michael@0: let forwardButton = document.getElementById("forward-button"); michael@0: popup = popup.cloneNode(true); michael@0: forwardButton.setAttribute("type", "menu"); michael@0: forwardButton.appendChild(popup); michael@0: _addClickAndHoldListenersOnElement(forwardButton); michael@0: } michael@0: michael@0: const gSessionHistoryObserver = { michael@0: observe: function(subject, topic, data) michael@0: { michael@0: if (topic != "browser:purge-session-history") michael@0: return; michael@0: michael@0: var backCommand = document.getElementById("Browser:Back"); michael@0: backCommand.setAttribute("disabled", "true"); michael@0: var fwdCommand = document.getElementById("Browser:Forward"); michael@0: fwdCommand.setAttribute("disabled", "true"); michael@0: michael@0: // Hide session restore button on about:home michael@0: window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton"); michael@0: michael@0: if (gURLBar) { michael@0: // Clear undo history of the URL bar michael@0: gURLBar.editor.transactionManager.clear() michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Given a starting docshell and a URI to look up, find the docshell the URI michael@0: * is loaded in. michael@0: * @param aDocument michael@0: * A document to find instead of using just a URI - this is more specific. michael@0: * @param aDocShell michael@0: * The doc shell to start at michael@0: * @param aSoughtURI michael@0: * The URI that we're looking for michael@0: * @returns The doc shell that the sought URI is loaded in. Can be in michael@0: * subframes. michael@0: */ michael@0: function findChildShell(aDocument, aDocShell, aSoughtURI) { michael@0: aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation); michael@0: aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor); michael@0: var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument); michael@0: if ((aDocument && doc == aDocument) || michael@0: (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec)) michael@0: return aDocShell; michael@0: michael@0: var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem); michael@0: for (var i = 0; i < node.childCount; ++i) { michael@0: var docShell = node.getChildAt(i); michael@0: docShell = findChildShell(aDocument, docShell, aSoughtURI); michael@0: if (docShell) michael@0: return docShell; michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: var gPopupBlockerObserver = { michael@0: _reportButton: null, michael@0: michael@0: onReportButtonClick: function (aEvent) michael@0: { michael@0: if (aEvent.button != 0 || aEvent.target != this._reportButton) michael@0: return; michael@0: michael@0: document.getElementById("blockedPopupOptions") michael@0: .openPopup(this._reportButton, "after_end", 0, 2, false, false, aEvent); michael@0: }, michael@0: michael@0: handleEvent: function (aEvent) michael@0: { michael@0: if (aEvent.originalTarget != gBrowser.selectedBrowser) michael@0: return; michael@0: michael@0: if (!this._reportButton && gURLBar) michael@0: this._reportButton = document.getElementById("page-report-button"); michael@0: michael@0: if (!gBrowser.selectedBrowser.blockedPopups) { michael@0: // Hide the icon in the location bar (if the location bar exists) michael@0: if (gURLBar) michael@0: this._reportButton.hidden = true; michael@0: return; michael@0: } michael@0: michael@0: if (gURLBar) michael@0: this._reportButton.hidden = false; michael@0: michael@0: // Only show the notification again if we've not already shown it. Since michael@0: // notifications are per-browser, we don't need to worry about re-adding michael@0: // it. michael@0: if (!gBrowser.selectedBrowser.blockedPopups.reported) { michael@0: if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) { michael@0: var brandBundle = document.getElementById("bundle_brand"); michael@0: var brandShortName = brandBundle.getString("brandShortName"); michael@0: var popupCount = gBrowser.selectedBrowser.blockedPopups.length; michael@0: #ifdef XP_WIN michael@0: var popupButtonText = gNavigatorBundle.getString("popupWarningButton"); michael@0: var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey"); michael@0: #else michael@0: var popupButtonText = gNavigatorBundle.getString("popupWarningButtonUnix"); michael@0: var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButtonUnix.accesskey"); michael@0: #endif michael@0: var messageBase = gNavigatorBundle.getString("popupWarning.message"); michael@0: var message = PluralForm.get(popupCount, messageBase) michael@0: .replace("#1", brandShortName) michael@0: .replace("#2", popupCount); michael@0: michael@0: var notificationBox = gBrowser.getNotificationBox(); michael@0: var notification = notificationBox.getNotificationWithValue("popup-blocked"); michael@0: if (notification) { michael@0: notification.label = message; michael@0: } michael@0: else { michael@0: var buttons = [{ michael@0: label: popupButtonText, michael@0: accessKey: popupButtonAccesskey, michael@0: popup: "blockedPopupOptions", michael@0: callback: null michael@0: }]; michael@0: michael@0: const priority = notificationBox.PRIORITY_WARNING_MEDIUM; michael@0: notificationBox.appendNotification(message, "popup-blocked", michael@0: "chrome://browser/skin/Info.png", michael@0: priority, buttons); michael@0: } michael@0: } michael@0: michael@0: // Record the fact that we've reported this blocked popup, so we don't michael@0: // show it again. michael@0: gBrowser.selectedBrowser.blockedPopups.reported = true; michael@0: } michael@0: }, michael@0: michael@0: toggleAllowPopupsForSite: function (aEvent) michael@0: { michael@0: var pm = Services.perms; michael@0: var shouldBlock = aEvent.target.getAttribute("block") == "true"; michael@0: var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION; michael@0: pm.add(gBrowser.currentURI, "popup", perm); michael@0: michael@0: gBrowser.getNotificationBox().removeCurrentNotification(); michael@0: }, michael@0: michael@0: fillPopupList: function (aEvent) michael@0: { michael@0: // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites michael@0: // we should really walk the blockedPopups and create a list of "allow for " michael@0: // menuitems for the common subset of hosts present in the report, this will michael@0: // make us frame-safe. michael@0: // michael@0: // XXXjst - Note that when this is fixed to work with multi-framed sites, michael@0: // also back out the fix for bug 343772 where michael@0: // nsGlobalWindow::CheckOpenAllow() was changed to also michael@0: // check if the top window's location is whitelisted. michael@0: let browser = gBrowser.selectedBrowser; michael@0: var uri = browser.currentURI; michael@0: var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite"); michael@0: try { michael@0: blockedPopupAllowSite.removeAttribute("hidden"); michael@0: michael@0: var pm = Services.perms; michael@0: if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) { michael@0: // Offer an item to block popups for this site, if a whitelist entry exists michael@0: // already for it. michael@0: let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host || uri.spec]); michael@0: blockedPopupAllowSite.setAttribute("label", blockString); michael@0: blockedPopupAllowSite.setAttribute("block", "true"); michael@0: } michael@0: else { michael@0: // Offer an item to allow popups for this site michael@0: let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host || uri.spec]); michael@0: blockedPopupAllowSite.setAttribute("label", allowString); michael@0: blockedPopupAllowSite.removeAttribute("block"); michael@0: } michael@0: } michael@0: catch (e) { michael@0: blockedPopupAllowSite.setAttribute("hidden", "true"); michael@0: } michael@0: michael@0: if (PrivateBrowsingUtils.isWindowPrivate(window)) michael@0: blockedPopupAllowSite.setAttribute("disabled", "true"); michael@0: else michael@0: blockedPopupAllowSite.removeAttribute("disabled"); michael@0: michael@0: var foundUsablePopupURI = false; michael@0: var blockedPopups = browser.blockedPopups; michael@0: if (blockedPopups) { michael@0: for (let i = 0; i < blockedPopups.length; i++) { michael@0: let blockedPopup = blockedPopups[i]; michael@0: michael@0: // popupWindowURI will be null if the file picker popup is blocked. michael@0: // xxxdz this should make the option say "Show file picker" and do it (Bug 590306) michael@0: if (!blockedPopup.popupWindowURI) michael@0: continue; michael@0: var popupURIspec = blockedPopup.popupWindowURI; michael@0: michael@0: // Sometimes the popup URI that we get back from the blockedPopup michael@0: // isn't useful (for instance, netscape.com's popup URI ends up michael@0: // being "http://www.netscape.com", which isn't really the URI of michael@0: // the popup they're trying to show). This isn't going to be michael@0: // useful to the user, so we won't create a menu item for it. michael@0: if (popupURIspec == "" || popupURIspec == "about:blank" || michael@0: popupURIspec == uri.spec) michael@0: continue; michael@0: michael@0: // Because of the short-circuit above, we may end up in a situation michael@0: // in which we don't have any usable popup addresses to show in michael@0: // the menu, and therefore we shouldn't show the separator. However, michael@0: // since we got past the short-circuit, we must've found at least michael@0: // one usable popup URI and thus we'll turn on the separator later. michael@0: foundUsablePopupURI = true; michael@0: michael@0: var menuitem = document.createElement("menuitem"); michael@0: var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix", michael@0: [popupURIspec]); michael@0: menuitem.setAttribute("label", label); michael@0: menuitem.setAttribute("popupWindowURI", popupURIspec); michael@0: menuitem.setAttribute("popupWindowFeatures", blockedPopup.popupWindowFeatures); michael@0: menuitem.setAttribute("popupWindowName", blockedPopup.popupWindowName); michael@0: menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);"); michael@0: menuitem.setAttribute("popupReportIndex", i); michael@0: menuitem.popupReportBrowser = browser; michael@0: aEvent.target.appendChild(menuitem); michael@0: } michael@0: } michael@0: michael@0: // Show or hide the separator, depending on whether we added any michael@0: // showable popup addresses to the menu. michael@0: var blockedPopupsSeparator = michael@0: document.getElementById("blockedPopupsSeparator"); michael@0: if (foundUsablePopupURI) michael@0: blockedPopupsSeparator.removeAttribute("hidden"); michael@0: else michael@0: blockedPopupsSeparator.setAttribute("hidden", true); michael@0: michael@0: var blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage"); michael@0: var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage"); michael@0: blockedPopupDontShowMessage.setAttribute("checked", !showMessage); michael@0: if (aEvent.target.anchorNode.id == "page-report-button") { michael@0: aEvent.target.anchorNode.setAttribute("open", "true"); michael@0: blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromLocationbar")); michael@0: } else michael@0: blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage")); michael@0: }, michael@0: michael@0: onPopupHiding: function (aEvent) { michael@0: if (aEvent.target.anchorNode.id == "page-report-button") michael@0: aEvent.target.anchorNode.removeAttribute("open"); michael@0: michael@0: let item = aEvent.target.lastChild; michael@0: while (item && item.getAttribute("observes") != "blockedPopupsSeparator") { michael@0: let next = item.previousSibling; michael@0: item.parentNode.removeChild(item); michael@0: item = next; michael@0: } michael@0: }, michael@0: michael@0: showBlockedPopup: function (aEvent) michael@0: { michael@0: var target = aEvent.target; michael@0: var popupReportIndex = target.getAttribute("popupReportIndex"); michael@0: let browser = target.popupReportBrowser; michael@0: browser.unblockPopup(popupReportIndex); michael@0: }, michael@0: michael@0: editPopupSettings: function () michael@0: { michael@0: var host = ""; michael@0: try { michael@0: host = gBrowser.currentURI.host; michael@0: } michael@0: catch (e) { } michael@0: michael@0: var bundlePreferences = document.getElementById("bundle_preferences"); michael@0: var params = { blockVisible : false, michael@0: sessionVisible : false, michael@0: allowVisible : true, michael@0: prefilledHost : host, michael@0: permissionType : "popup", michael@0: windowTitle : bundlePreferences.getString("popuppermissionstitle"), michael@0: introText : bundlePreferences.getString("popuppermissionstext") }; michael@0: var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions"); michael@0: if (existingWindow) { michael@0: existingWindow.initWithParams(params); michael@0: existingWindow.focus(); michael@0: } michael@0: else michael@0: window.openDialog("chrome://browser/content/preferences/permissions.xul", michael@0: "_blank", "resizable,dialog=no,centerscreen", params); michael@0: }, michael@0: michael@0: dontShowMessage: function () michael@0: { michael@0: var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage"); michael@0: gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage); michael@0: gBrowser.getNotificationBox().removeCurrentNotification(); michael@0: } michael@0: }; michael@0: michael@0: const gFormSubmitObserver = { michael@0: QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]), michael@0: michael@0: panel: null, michael@0: michael@0: init: function() michael@0: { michael@0: this.panel = document.getElementById('invalid-form-popup'); michael@0: }, michael@0: michael@0: notifyInvalidSubmit : function (aFormElement, aInvalidElements) michael@0: { michael@0: // We are going to handle invalid form submission attempt by focusing the michael@0: // first invalid element and show the corresponding validation message in a michael@0: // panel attached to the element. michael@0: if (!aInvalidElements.length) { michael@0: return; michael@0: } michael@0: michael@0: // Don't show the popup if the current tab doesn't contain the invalid form. michael@0: if (gBrowser.contentDocument != michael@0: aFormElement.ownerDocument.defaultView.top.document) { michael@0: return; michael@0: } michael@0: michael@0: let element = aInvalidElements.queryElementAt(0, Ci.nsISupports); michael@0: michael@0: if (!(element instanceof HTMLInputElement || michael@0: element instanceof HTMLTextAreaElement || michael@0: element instanceof HTMLSelectElement || michael@0: element instanceof HTMLButtonElement)) { michael@0: return; michael@0: } michael@0: michael@0: this.panel.firstChild.textContent = element.validationMessage; michael@0: michael@0: element.focus(); michael@0: michael@0: // If the user interacts with the element and makes it valid or leaves it, michael@0: // we want to remove the popup. michael@0: // We could check for clicks but a click is already removing the popup. michael@0: function blurHandler() { michael@0: gFormSubmitObserver.panel.hidePopup(); michael@0: }; michael@0: function inputHandler(e) { michael@0: if (e.originalTarget.validity.valid) { michael@0: gFormSubmitObserver.panel.hidePopup(); michael@0: } else { michael@0: // If the element is now invalid for a new reason, we should update the michael@0: // error message. michael@0: if (gFormSubmitObserver.panel.firstChild.textContent != michael@0: e.originalTarget.validationMessage) { michael@0: gFormSubmitObserver.panel.firstChild.textContent = michael@0: e.originalTarget.validationMessage; michael@0: } michael@0: } michael@0: }; michael@0: element.addEventListener("input", inputHandler, false); michael@0: element.addEventListener("blur", blurHandler, false); michael@0: michael@0: // One event to bring them all and in the darkness bind them. michael@0: this.panel.addEventListener("popuphiding", function onPopupHiding(aEvent) { michael@0: aEvent.target.removeEventListener("popuphiding", onPopupHiding, false); michael@0: element.removeEventListener("input", inputHandler, false); michael@0: element.removeEventListener("blur", blurHandler, false); michael@0: }, false); michael@0: michael@0: this.panel.hidden = false; michael@0: michael@0: // We want to show the popup at the middle of checkbox and radio buttons michael@0: // and where the content begin for the other elements. michael@0: let offset = 0; michael@0: let position = ""; michael@0: michael@0: if (element.tagName == 'INPUT' && michael@0: (element.type == 'radio' || element.type == 'checkbox')) { michael@0: position = "bottomcenter topleft"; michael@0: } else { michael@0: let win = element.ownerDocument.defaultView; michael@0: let style = win.getComputedStyle(element, null); michael@0: let utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) michael@0: .getInterface(Components.interfaces.nsIDOMWindowUtils); michael@0: michael@0: if (style.direction == 'rtl') { michael@0: offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth); michael@0: } else { michael@0: offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth); michael@0: } michael@0: michael@0: offset = Math.round(offset * utils.fullZoom); michael@0: michael@0: position = "after_start"; michael@0: } michael@0: michael@0: this.panel.openPopup(element, position, offset, 0); michael@0: } michael@0: }; michael@0: michael@0: var gBrowserInit = { michael@0: delayedStartupFinished: false, michael@0: michael@0: onLoad: function() { michael@0: gMultiProcessBrowser = michael@0: window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsILoadContext) michael@0: .useRemoteTabs; michael@0: michael@0: var mustLoadSidebar = false; michael@0: michael@0: if (!gMultiProcessBrowser) { michael@0: // There is a Content:Click message manually sent from content. michael@0: Cc["@mozilla.org/eventlistenerservice;1"] michael@0: .getService(Ci.nsIEventListenerService) michael@0: .addSystemEventListener(gBrowser, "click", contentAreaClick, true); michael@0: } michael@0: michael@0: gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false); michael@0: michael@0: // Note that the XBL binding is untrusted michael@0: gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true); michael@0: gBrowser.addEventListener("PluginCrashed", gPluginHandler, true); michael@0: gBrowser.addEventListener("PluginOutdated", gPluginHandler, true); michael@0: gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true); michael@0: gBrowser.addEventListener("PluginRemoved", gPluginHandler, true); michael@0: michael@0: gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true); michael@0: michael@0: Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false); michael@0: michael@0: window.addEventListener("AppCommand", HandleAppCommandEvent, true); michael@0: michael@0: // These routines add message listeners. They must run before michael@0: // loading the frame script to ensure that we don't miss any michael@0: // message sent between when the frame script is loaded and when michael@0: // the listener is registered. michael@0: DOMLinkHandler.init(); michael@0: gPageStyleMenu.init(); michael@0: LanguageDetectionListener.init(); michael@0: michael@0: messageManager.loadFrameScript("chrome://browser/content/content.js", true); michael@0: michael@0: // initialize observers and listeners michael@0: // and give C++ access to gBrowser michael@0: XULBrowserWindow.init(); michael@0: window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIXULWindow) michael@0: .XULBrowserWindow = window.XULBrowserWindow; michael@0: window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = michael@0: new nsBrowserAccess(); michael@0: michael@0: // hook up UI through progress listener michael@0: gBrowser.addProgressListener(window.XULBrowserWindow); michael@0: gBrowser.addTabsProgressListener(window.TabsProgressListener); michael@0: michael@0: // setup simple gestures support michael@0: gGestureSupport.init(true); michael@0: michael@0: // setup history swipe animation michael@0: gHistorySwipeAnimation.init(); michael@0: michael@0: if (window.opener && !window.opener.closed && michael@0: PrivateBrowsingUtils.isWindowPrivate(window) == PrivateBrowsingUtils.isWindowPrivate(window.opener)) { michael@0: let openerSidebarBox = window.opener.document.getElementById("sidebar-box"); michael@0: // If the opener had a sidebar, open the same sidebar in our window. michael@0: // The opener can be the hidden window too, if we're coming from the state michael@0: // where no windows are open, and the hidden window has no sidebar box. michael@0: if (openerSidebarBox && !openerSidebarBox.hidden) { michael@0: let sidebarCmd = openerSidebarBox.getAttribute("sidebarcommand"); michael@0: let sidebarCmdElem = document.getElementById(sidebarCmd); michael@0: michael@0: // dynamically generated sidebars will fail this check. michael@0: if (sidebarCmdElem) { michael@0: let sidebarBox = document.getElementById("sidebar-box"); michael@0: let sidebarTitle = document.getElementById("sidebar-title"); michael@0: michael@0: sidebarTitle.setAttribute( michael@0: "value", window.opener.document.getElementById("sidebar-title").getAttribute("value")); michael@0: sidebarBox.setAttribute("width", openerSidebarBox.boxObject.width); michael@0: michael@0: sidebarBox.setAttribute("sidebarcommand", sidebarCmd); michael@0: // Note: we're setting 'src' on sidebarBox, which is a , not on michael@0: // the . This lets us delay the actual load until michael@0: // delayedStartup(). michael@0: sidebarBox.setAttribute( michael@0: "src", window.opener.document.getElementById("sidebar").getAttribute("src")); michael@0: mustLoadSidebar = true; michael@0: michael@0: sidebarBox.hidden = false; michael@0: document.getElementById("sidebar-splitter").hidden = false; michael@0: sidebarCmdElem.setAttribute("checked", "true"); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: let box = document.getElementById("sidebar-box"); michael@0: if (box.hasAttribute("sidebarcommand")) { michael@0: let commandID = box.getAttribute("sidebarcommand"); michael@0: if (commandID) { michael@0: let command = document.getElementById(commandID); michael@0: if (command) { michael@0: mustLoadSidebar = true; michael@0: box.hidden = false; michael@0: document.getElementById("sidebar-splitter").hidden = false; michael@0: command.setAttribute("checked", "true"); michael@0: } michael@0: else { michael@0: // Remove the |sidebarcommand| attribute, because the element it michael@0: // refers to no longer exists, so we should assume this sidebar michael@0: // panel has been uninstalled. (249883) michael@0: box.removeAttribute("sidebarcommand"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Certain kinds of automigration rely on this notification to complete their michael@0: // tasks BEFORE the browser window is shown. michael@0: Services.obs.notifyObservers(null, "browser-window-before-show", ""); michael@0: michael@0: // Set a sane starting width/height for all resolutions on new profiles. michael@0: if (!document.documentElement.hasAttribute("width")) { michael@0: let defaultWidth; michael@0: let defaultHeight; michael@0: michael@0: // Very small: maximize the window michael@0: // Portrait : use about full width and 3/4 height, to view entire pages michael@0: // at once (without being obnoxiously tall) michael@0: // Widescreen: use about half width, to suggest side-by-side page view michael@0: // Otherwise : use 3/4 height and width michael@0: if (screen.availHeight <= 600) { michael@0: document.documentElement.setAttribute("sizemode", "maximized"); michael@0: defaultWidth = 610; michael@0: defaultHeight = 450; michael@0: } michael@0: else { michael@0: if (screen.availWidth <= screen.availHeight) { michael@0: defaultWidth = screen.availWidth * .9; michael@0: defaultHeight = screen.availHeight * .75; michael@0: } michael@0: else if (screen.availWidth >= 2048) { michael@0: defaultWidth = (screen.availWidth / 2) - 20; michael@0: defaultHeight = screen.availHeight - 10; michael@0: } michael@0: else { michael@0: defaultWidth = screen.availWidth * .75; michael@0: defaultHeight = screen.availHeight * .75; michael@0: } michael@0: michael@0: #if MOZ_WIDGET_GTK == 2 michael@0: // On X, we're not currently able to account for the size of the window michael@0: // border. Use 28px as a guess (titlebar + bottom window border) michael@0: defaultHeight -= 28; michael@0: #endif michael@0: } michael@0: document.documentElement.setAttribute("width", defaultWidth); michael@0: document.documentElement.setAttribute("height", defaultHeight); michael@0: } michael@0: michael@0: if (!window.toolbar.visible) { michael@0: // adjust browser UI for popups michael@0: if (gURLBar) { michael@0: gURLBar.setAttribute("readonly", "true"); michael@0: gURLBar.setAttribute("enablehistory", "false"); michael@0: } michael@0: goSetCommandEnabled("cmd_newNavigatorTab", false); michael@0: } michael@0: michael@0: // Misc. inits. michael@0: CombinedStopReload.init(); michael@0: gPrivateBrowsingUI.init(); michael@0: TabsInTitlebar.init(); michael@0: michael@0: #ifdef XP_WIN michael@0: if (window.matchMedia("(-moz-os-version: windows-win8)").matches && michael@0: window.matchMedia("(-moz-windows-default-theme)").matches) { michael@0: let windowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}) michael@0: .Windows8WindowFrameColor.get(); michael@0: michael@0: // Formula from W3C's WCAG 2.0 spec's color ratio and relative luminance, michael@0: // section 1.3.4, http://www.w3.org/TR/WCAG20/ . michael@0: windowFrameColor = windowFrameColor.map((color) => { michael@0: if (color <= 10) { michael@0: return color / 255 / 12.92; michael@0: } michael@0: return Math.pow(((color / 255) + 0.055) / 1.055, 2.4); michael@0: }); michael@0: let backgroundLuminance = windowFrameColor[0] * 0.2126 + michael@0: windowFrameColor[1] * 0.7152 + michael@0: windowFrameColor[2] * 0.0722; michael@0: let foregroundLuminance = 0; // Default to black for foreground text. michael@0: let contrastRatio = (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05); michael@0: if (contrastRatio < 3) { michael@0: document.documentElement.setAttribute("darkwindowframe", "true"); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: ToolbarIconColor.init(); michael@0: michael@0: // Wait until chrome is painted before executing code not critical to making the window visible michael@0: this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar); michael@0: window.addEventListener("MozAfterPaint", this._boundDelayedStartup); michael@0: michael@0: this._loadHandled = true; michael@0: }, michael@0: michael@0: _cancelDelayedStartup: function () { michael@0: window.removeEventListener("MozAfterPaint", this._boundDelayedStartup); michael@0: this._boundDelayedStartup = null; michael@0: }, michael@0: michael@0: _delayedStartup: function(mustLoadSidebar) { michael@0: let tmp = {}; michael@0: Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp); michael@0: let TelemetryTimestamps = tmp.TelemetryTimestamps; michael@0: TelemetryTimestamps.add("delayedStartupStarted"); michael@0: michael@0: this._cancelDelayedStartup(); michael@0: michael@0: // We need to set the MozApplicationManifest event listeners up michael@0: // before we start loading the home pages in case a document has michael@0: // a "manifest" attribute, in which the MozApplicationManifest event michael@0: // will be fired. michael@0: gBrowser.addEventListener("MozApplicationManifest", michael@0: OfflineApps, false); michael@0: // listen for offline apps on social michael@0: let socialBrowser = document.getElementById("social-sidebar-browser"); michael@0: socialBrowser.addEventListener("MozApplicationManifest", michael@0: OfflineApps, false); michael@0: michael@0: let uriToLoad = this._getUriToLoad(); michael@0: var isLoadingBlank = isBlankPageURL(uriToLoad); michael@0: michael@0: // This pageshow listener needs to be registered before we may call michael@0: // swapBrowsersAndCloseOther() to receive pageshow events fired by that. michael@0: gBrowser.addEventListener("pageshow", function(event) { michael@0: // Filter out events that are not about the document load we are interested in michael@0: if (content && event.target == content.document) michael@0: setTimeout(pageShowEventHandlers, 0, event.persisted); michael@0: }, true); michael@0: michael@0: if (uriToLoad && uriToLoad != "about:blank") { michael@0: if (uriToLoad instanceof Ci.nsISupportsArray) { michael@0: let count = uriToLoad.Count(); michael@0: let specs = []; michael@0: for (let i = 0; i < count; i++) { michael@0: let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString); michael@0: specs.push(urisstring.data); michael@0: } michael@0: michael@0: // This function throws for certain malformed URIs, so use exception handling michael@0: // so that we don't disrupt startup michael@0: try { michael@0: gBrowser.loadTabs(specs, false, true); michael@0: } catch (e) {} michael@0: } michael@0: else if (uriToLoad instanceof XULElement) { michael@0: // swap the given tab with the default about:blank tab and then close michael@0: // the original tab in the other window. michael@0: michael@0: // Stop the about:blank load michael@0: gBrowser.stop(); michael@0: // make sure it has a docshell michael@0: gBrowser.docShell; michael@0: michael@0: gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad); michael@0: } michael@0: // window.arguments[2]: referrer (nsIURI) michael@0: // [3]: postData (nsIInputStream) michael@0: // [4]: allowThirdPartyFixup (bool) michael@0: else if (window.arguments.length >= 3) { michael@0: loadURI(uriToLoad, window.arguments[2], window.arguments[3] || null, michael@0: window.arguments[4] || false); michael@0: window.focus(); michael@0: } michael@0: // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3. michael@0: // Such callers expect that window.arguments[0] is handled as a single URI. michael@0: else { michael@0: if (uriToLoad == "about:newtab" && michael@0: Services.prefs.getBoolPref("browser.newtabpage.enabled")) { michael@0: Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true); michael@0: } michael@0: loadOneOrMoreURIs(uriToLoad); michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_SAFE_BROWSING michael@0: // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008. michael@0: setTimeout(function() { SafeBrowsing.init(); }, 2000); michael@0: #endif michael@0: michael@0: Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false); michael@0: Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false); michael@0: Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false); michael@0: Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false); michael@0: Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false); michael@0: Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false); michael@0: Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false); michael@0: michael@0: BrowserOffline.init(); michael@0: OfflineApps.init(); michael@0: IndexedDBPromptHelper.init(); michael@0: CanvasPermissionPromptHelper.init(); michael@0: gFormSubmitObserver.init(); michael@0: gRemoteTabsUI.init(); michael@0: michael@0: // Initialize the full zoom setting. michael@0: // We do this before the session restore service gets initialized so we can michael@0: // apply full zoom settings to tabs restored by the session restore service. michael@0: FullZoom.init(); michael@0: PanelUI.init(); michael@0: LightweightThemeListener.init(); michael@0: WebrtcIndicator.init(); michael@0: michael@0: // Ensure login manager is up and running. michael@0: Services.logins; michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: if (gMultiProcessBrowser) michael@0: TabCrashReporter.init(); michael@0: #endif michael@0: michael@0: if (mustLoadSidebar) { michael@0: let sidebar = document.getElementById("sidebar"); michael@0: let sidebarBox = document.getElementById("sidebar-box"); michael@0: sidebar.setAttribute("src", sidebarBox.getAttribute("src")); michael@0: } michael@0: michael@0: UpdateUrlbarSearchSplitterState(); michael@0: michael@0: if (!isLoadingBlank || !focusAndSelectUrlBar()) michael@0: gBrowser.selectedBrowser.focus(); michael@0: michael@0: // Set up Sanitize Item michael@0: this._initializeSanitizer(); michael@0: michael@0: // Enable/Disable auto-hide tabbar michael@0: gBrowser.tabContainer.updateVisibility(); michael@0: michael@0: BookmarkingUI.init(); michael@0: michael@0: gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false); michael@0: michael@0: var homeButton = document.getElementById("home-button"); michael@0: gHomeButton.updateTooltip(homeButton); michael@0: gHomeButton.updatePersonalToolbarStyle(homeButton); michael@0: michael@0: // BiDi UI michael@0: gBidiUI = isBidiEnabled(); michael@0: if (gBidiUI) { michael@0: document.getElementById("documentDirection-separator").hidden = false; michael@0: document.getElementById("documentDirection-swap").hidden = false; michael@0: document.getElementById("textfieldDirection-separator").hidden = false; michael@0: document.getElementById("textfieldDirection-swap").hidden = false; michael@0: } michael@0: michael@0: // Setup click-and-hold gestures access to the session history michael@0: // menus if global click-and-hold isn't turned on michael@0: if (!getBoolPref("ui.click_hold_context_menus", false)) michael@0: SetClickAndHoldHandlers(); michael@0: michael@0: let NP = {}; michael@0: Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP); michael@0: NP.trackBrowserWindow(window); michael@0: michael@0: PlacesToolbarHelper.init(); michael@0: michael@0: ctrlTab.readPref(); michael@0: gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false); michael@0: michael@0: // Initialize the download manager some time after the app starts so that michael@0: // auto-resume downloads begin (such as after crashing or quitting with michael@0: // active downloads) and speeds up the first-load of the download manager UI. michael@0: // If the user manually opens the download manager before the timeout, the michael@0: // downloads will start right away, and initializing again won't hurt. michael@0: setTimeout(function() { michael@0: try { michael@0: Cu.import("resource:///modules/DownloadsCommon.jsm", {}) michael@0: .DownloadsCommon.initializeAllDataLinks(); michael@0: Cu.import("resource:///modules/DownloadsTaskbar.jsm", {}) michael@0: .DownloadsTaskbar.registerIndicator(window); michael@0: } catch (ex) { michael@0: Cu.reportError(ex); michael@0: } michael@0: }, 10000); michael@0: michael@0: // The object handling the downloads indicator is also initialized here in the michael@0: // delayed startup function, but the actual indicator element is not loaded michael@0: // unless there are downloads to be displayed. michael@0: DownloadsButton.initializeIndicator(); michael@0: michael@0: #ifndef XP_MACOSX michael@0: updateEditUIVisibility(); michael@0: let placesContext = document.getElementById("placesContext"); michael@0: placesContext.addEventListener("popupshowing", updateEditUIVisibility, false); michael@0: placesContext.addEventListener("popuphiding", updateEditUIVisibility, false); michael@0: #endif michael@0: michael@0: gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true); michael@0: gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true); michael@0: gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true); michael@0: michael@0: if (Win7Features) michael@0: Win7Features.onOpenWindow(); michael@0: michael@0: // called when we go into full screen, even if initiated by a web page script michael@0: window.addEventListener("fullscreen", onFullScreen, true); michael@0: michael@0: // Called when we enter DOM full-screen mode. Note we can already be in browser michael@0: // full-screen mode when we enter DOM full-screen mode. michael@0: window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true); michael@0: michael@0: if (window.fullScreen) michael@0: onFullScreen(); michael@0: if (document.mozFullScreen) michael@0: onMozEnteredDomFullscreen(); michael@0: michael@0: #ifdef MOZ_SERVICES_SYNC michael@0: // initialize the sync UI michael@0: gSyncUI.init(); michael@0: gFxAccounts.init(); michael@0: #endif michael@0: michael@0: #ifdef MOZ_DATA_REPORTING michael@0: gDataNotificationInfoBar.init(); michael@0: #endif michael@0: michael@0: gBrowserThumbnails.init(); michael@0: michael@0: // Add Devtools menuitems and listeners michael@0: gDevToolsBrowser.registerBrowserWindow(window); michael@0: michael@0: window.addEventListener("mousemove", MousePosTracker, false); michael@0: window.addEventListener("dragover", MousePosTracker, false); michael@0: michael@0: gNavToolbox.addEventListener("customizationstarting", CustomizationHandler); michael@0: gNavToolbox.addEventListener("customizationchange", CustomizationHandler); michael@0: gNavToolbox.addEventListener("customizationending", CustomizationHandler); michael@0: michael@0: // End startup crash tracking after a delay to catch crashes while restoring michael@0: // tabs and to postpone saving the pref to disk. michael@0: try { michael@0: const startupCrashEndDelay = 30 * 1000; michael@0: setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay); michael@0: } catch (ex) { michael@0: Cu.reportError("Could not end startup crash tracking: " + ex); michael@0: } michael@0: michael@0: if (typeof WindowsPrefSync !== 'undefined') { michael@0: // Pulls in Metro controlled prefs and pushes out Desktop controlled prefs michael@0: WindowsPrefSync.init(); michael@0: } michael@0: michael@0: SessionStore.promiseInitialized.then(() => { michael@0: // Bail out if the window has been closed in the meantime. michael@0: if (window.closed) { michael@0: return; michael@0: } michael@0: michael@0: // Enable the Restore Last Session command if needed michael@0: RestoreLastSessionObserver.init(); michael@0: michael@0: SocialUI.init(); michael@0: TabView.init(); michael@0: michael@0: setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0); michael@0: }); michael@0: this.delayedStartupFinished = true; michael@0: michael@0: Services.obs.notifyObservers(window, "browser-delayed-startup-finished", ""); michael@0: TelemetryTimestamps.add("delayedStartupFinished"); michael@0: }, michael@0: michael@0: // Returns the URI(s) to load at startup. michael@0: _getUriToLoad: function () { michael@0: // window.arguments[0]: URI to load (string), or an nsISupportsArray of michael@0: // nsISupportsStrings to load, or a xul:tab of michael@0: // a tabbrowser, which will be replaced by this michael@0: // window (for this case, all other arguments are michael@0: // ignored). michael@0: if (!window.arguments || !window.arguments[0]) michael@0: return null; michael@0: michael@0: let uri = window.arguments[0]; michael@0: let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"] michael@0: .getService(Ci.nsISessionStartup); michael@0: let defaultArgs = Cc["@mozilla.org/browser/clh;1"] michael@0: .getService(Ci.nsIBrowserHandler) michael@0: .defaultArgs; michael@0: michael@0: // If the given URI matches defaultArgs (the default homepage) we want michael@0: // to block its load if we're going to restore a session anyway. michael@0: if (uri == defaultArgs && sessionStartup.willOverrideHomepage) michael@0: return null; michael@0: michael@0: return uri; michael@0: }, michael@0: michael@0: onUnload: function() { michael@0: // In certain scenarios it's possible for unload to be fired before onload, michael@0: // (e.g. if the window is being closed after browser.js loads but before the michael@0: // load completes). In that case, there's nothing to do here. michael@0: if (!this._loadHandled) michael@0: return; michael@0: michael@0: gDevToolsBrowser.forgetBrowserWindow(window); michael@0: michael@0: let desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar"); michael@0: if (desc && !desc.get) { michael@0: DeveloperToolbar.destroy(); michael@0: } michael@0: michael@0: // First clean up services initialized in gBrowserInit.onLoad (or those whose michael@0: // uninit methods don't depend on the services having been initialized). michael@0: michael@0: CombinedStopReload.uninit(); michael@0: michael@0: gGestureSupport.init(false); michael@0: michael@0: gHistorySwipeAnimation.uninit(); michael@0: michael@0: FullScreen.cleanup(); michael@0: michael@0: #ifdef MOZ_SERVICES_SYNC michael@0: gFxAccounts.uninit(); michael@0: #endif michael@0: michael@0: Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed"); michael@0: michael@0: try { michael@0: gBrowser.removeProgressListener(window.XULBrowserWindow); michael@0: gBrowser.removeTabsProgressListener(window.TabsProgressListener); michael@0: } catch (ex) { michael@0: } michael@0: michael@0: PlacesToolbarHelper.uninit(); michael@0: michael@0: BookmarkingUI.uninit(); michael@0: michael@0: TabsInTitlebar.uninit(); michael@0: michael@0: ToolbarIconColor.uninit(); michael@0: michael@0: var enumerator = Services.wm.getEnumerator(null); michael@0: enumerator.getNext(); michael@0: if (!enumerator.hasMoreElements()) { michael@0: document.persist("sidebar-box", "sidebarcommand"); michael@0: document.persist("sidebar-box", "width"); michael@0: document.persist("sidebar-box", "src"); michael@0: document.persist("sidebar-title", "value"); michael@0: } michael@0: michael@0: // Now either cancel delayedStartup, or clean up the services initialized from michael@0: // it. michael@0: if (this._boundDelayedStartup) { michael@0: this._cancelDelayedStartup(); michael@0: } else { michael@0: if (Win7Features) michael@0: Win7Features.onCloseWindow(); michael@0: michael@0: gPrefService.removeObserver(ctrlTab.prefName, ctrlTab); michael@0: ctrlTab.uninit(); michael@0: TabView.uninit(); michael@0: SocialUI.uninit(); michael@0: gBrowserThumbnails.uninit(); michael@0: FullZoom.destroy(); michael@0: michael@0: Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history"); michael@0: Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled"); michael@0: Services.obs.removeObserver(gXPInstallObserver, "addon-install-started"); michael@0: Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked"); michael@0: Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed"); michael@0: Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete"); michael@0: Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit"); michael@0: michael@0: try { michael@0: gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton); michael@0: } catch (ex) { michael@0: Cu.reportError(ex); michael@0: } michael@0: michael@0: if (typeof WindowsPrefSync !== 'undefined') { michael@0: WindowsPrefSync.uninit(); michael@0: } michael@0: michael@0: BrowserOffline.uninit(); michael@0: OfflineApps.uninit(); michael@0: IndexedDBPromptHelper.uninit(); michael@0: CanvasPermissionPromptHelper.uninit(); michael@0: LightweightThemeListener.uninit(); michael@0: PanelUI.uninit(); michael@0: } michael@0: michael@0: // Final window teardown, do this last. michael@0: window.XULBrowserWindow = null; michael@0: window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIXULWindow) michael@0: .XULBrowserWindow = null; michael@0: window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null; michael@0: }, michael@0: michael@0: #ifdef XP_MACOSX michael@0: // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and michael@0: // nonBrowserWindowShutdown() are used for non-browser windows in michael@0: // macBrowserOverlay michael@0: nonBrowserWindowStartup: function() { michael@0: // Disable inappropriate commands / submenus michael@0: var disabledItems = ['Browser:SavePage', michael@0: 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain', michael@0: 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload', michael@0: 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen', michael@0: 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs', michael@0: 'View:PageInfo', 'Browser:ToggleTabView']; michael@0: var element; michael@0: michael@0: for (let disabledItem of disabledItems) { michael@0: element = document.getElementById(disabledItem); michael@0: if (element) michael@0: element.setAttribute("disabled", "true"); michael@0: } michael@0: michael@0: // If no windows are active (i.e. we're the hidden window), disable the close, minimize michael@0: // and zoom menu commands as well michael@0: if (window.location.href == "chrome://browser/content/hiddenWindow.xul") { michael@0: var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow']; michael@0: for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) { michael@0: element = document.getElementById(hiddenWindowDisabledItem); michael@0: if (element) michael@0: element.setAttribute("disabled", "true"); michael@0: } michael@0: michael@0: // also hide the window-list separator michael@0: element = document.getElementById("sep-window-list"); michael@0: element.setAttribute("hidden", "true"); michael@0: michael@0: // Setup the dock menu. michael@0: let dockMenuElement = document.getElementById("menu_mac_dockmenu"); michael@0: if (dockMenuElement != null) { michael@0: let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"] michael@0: .createInstance(Ci.nsIStandaloneNativeMenu); michael@0: michael@0: try { michael@0: nativeMenu.init(dockMenuElement); michael@0: michael@0: let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"] michael@0: .getService(Ci.nsIMacDockSupport); michael@0: dockSupport.dockMenu = nativeMenu; michael@0: } michael@0: catch (e) { michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (PrivateBrowsingUtils.permanentPrivateBrowsing) { michael@0: document.getElementById("macDockMenuNewWindow").hidden = true; michael@0: } michael@0: michael@0: this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0); michael@0: }, michael@0: michael@0: nonBrowserWindowDelayedStartup: function() { michael@0: this._delayedStartupTimeoutId = null; michael@0: michael@0: // initialise the offline listener michael@0: BrowserOffline.init(); michael@0: michael@0: // Set up Sanitize Item michael@0: this._initializeSanitizer(); michael@0: michael@0: // initialize the private browsing UI michael@0: gPrivateBrowsingUI.init(); michael@0: michael@0: #ifdef MOZ_SERVICES_SYNC michael@0: // initialize the sync UI michael@0: gSyncUI.init(); michael@0: #endif michael@0: }, michael@0: michael@0: nonBrowserWindowShutdown: function() { michael@0: // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do - michael@0: // just cancel the pending timeout and return; michael@0: if (this._delayedStartupTimeoutId) { michael@0: clearTimeout(this._delayedStartupTimeoutId); michael@0: return; michael@0: } michael@0: michael@0: BrowserOffline.uninit(); michael@0: }, michael@0: #endif michael@0: michael@0: _initializeSanitizer: function() { michael@0: const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize"; michael@0: if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) { michael@0: gPrefService.clearUserPref(kDidSanitizeDomain); michael@0: // We need to persist this preference change, since we want to michael@0: // check it at next app start even if the browser exits abruptly michael@0: gPrefService.savePrefFile(null); michael@0: } michael@0: michael@0: /** michael@0: * Migrate Firefox 3.0 privacy.item prefs under one of these conditions: michael@0: * michael@0: * a) User has customized any privacy.item prefs michael@0: * b) privacy.sanitize.sanitizeOnShutdown is set michael@0: */ michael@0: if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) { michael@0: let itemBranch = gPrefService.getBranch("privacy.item."); michael@0: let itemArray = itemBranch.getChildList(""); michael@0: michael@0: // See if any privacy.item prefs are set michael@0: let doMigrate = itemArray.some(function (name) itemBranch.prefHasUserValue(name)); michael@0: // Or if sanitizeOnShutdown is set michael@0: if (!doMigrate) michael@0: doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown"); michael@0: michael@0: if (doMigrate) { michael@0: let cpdBranch = gPrefService.getBranch("privacy.cpd."); michael@0: let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown."); michael@0: for (let name of itemArray) { michael@0: try { michael@0: // don't migrate password or offlineApps clearing in the CRH dialog since michael@0: // there's no UI for those anymore. They default to false. bug 497656 michael@0: if (name != "passwords" && name != "offlineApps") michael@0: cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name)); michael@0: clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name)); michael@0: } michael@0: catch(e) { michael@0: Cu.reportError("Exception thrown during privacy pref migration: " + e); michael@0: } michael@0: } michael@0: } michael@0: michael@0: gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true); michael@0: } michael@0: }, michael@0: } michael@0: michael@0: michael@0: /* Legacy global init functions */ michael@0: var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit); michael@0: var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit); michael@0: #ifdef XP_MACOSX michael@0: var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit); michael@0: var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit); michael@0: var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit); michael@0: #endif michael@0: michael@0: function HandleAppCommandEvent(evt) { michael@0: switch (evt.command) { michael@0: case "Back": michael@0: BrowserBack(); michael@0: break; michael@0: case "Forward": michael@0: BrowserForward(); michael@0: break; michael@0: case "Reload": michael@0: BrowserReloadSkipCache(); michael@0: break; michael@0: case "Stop": michael@0: if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") michael@0: BrowserStop(); michael@0: break; michael@0: case "Search": michael@0: BrowserSearch.webSearch(); michael@0: break; michael@0: case "Bookmarks": michael@0: toggleSidebar('viewBookmarksSidebar'); michael@0: break; michael@0: case "Home": michael@0: BrowserHome(); michael@0: break; michael@0: case "New": michael@0: BrowserOpenTab(); michael@0: break; michael@0: case "Close": michael@0: BrowserCloseTabOrWindow(); michael@0: break; michael@0: case "Find": michael@0: gFindBar.onFindCommand(); michael@0: break; michael@0: case "Help": michael@0: openHelpLink('firefox-help'); michael@0: break; michael@0: case "Open": michael@0: BrowserOpenFileWindow(); michael@0: break; michael@0: case "Print": michael@0: PrintUtils.print(); michael@0: break; michael@0: case "Save": michael@0: saveDocument(window.content.document); michael@0: break; michael@0: case "SendMail": michael@0: MailIntegration.sendLinkForWindow(window.content); michael@0: break; michael@0: default: michael@0: return; michael@0: } michael@0: evt.stopPropagation(); michael@0: evt.preventDefault(); michael@0: } michael@0: michael@0: function gotoHistoryIndex(aEvent) { michael@0: let index = aEvent.target.getAttribute("index"); michael@0: if (!index) michael@0: return false; michael@0: michael@0: let where = whereToOpenLink(aEvent); michael@0: michael@0: if (where == "current") { michael@0: // Normal click. Go there in the current tab and update session history. michael@0: michael@0: try { michael@0: gBrowser.gotoIndex(index); michael@0: } michael@0: catch(ex) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: // Modified click. Go there in a new tab/window. michael@0: michael@0: duplicateTabIn(gBrowser.selectedTab, where, index - gBrowser.sessionHistory.index); michael@0: return true; michael@0: } michael@0: michael@0: function BrowserForward(aEvent) { michael@0: let where = whereToOpenLink(aEvent, false, true); michael@0: michael@0: if (where == "current") { michael@0: try { michael@0: gBrowser.goForward(); michael@0: } michael@0: catch(ex) { michael@0: } michael@0: } michael@0: else { michael@0: duplicateTabIn(gBrowser.selectedTab, where, 1); michael@0: } michael@0: } michael@0: michael@0: function BrowserBack(aEvent) { michael@0: let where = whereToOpenLink(aEvent, false, true); michael@0: michael@0: if (where == "current") { michael@0: try { michael@0: gBrowser.goBack(); michael@0: } michael@0: catch(ex) { michael@0: } michael@0: } michael@0: else { michael@0: duplicateTabIn(gBrowser.selectedTab, where, -1); michael@0: } michael@0: } michael@0: michael@0: function BrowserHandleBackspace() michael@0: { michael@0: switch (gPrefService.getIntPref("browser.backspace_action")) { michael@0: case 0: michael@0: BrowserBack(); michael@0: break; michael@0: case 1: michael@0: goDoCommand("cmd_scrollPageUp"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: function BrowserHandleShiftBackspace() michael@0: { michael@0: switch (gPrefService.getIntPref("browser.backspace_action")) { michael@0: case 0: michael@0: BrowserForward(); michael@0: break; michael@0: case 1: michael@0: goDoCommand("cmd_scrollPageDown"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: function BrowserStop() { michael@0: const stopFlags = nsIWebNavigation.STOP_ALL; michael@0: gBrowser.webNavigation.stop(stopFlags); michael@0: } michael@0: michael@0: function BrowserReloadOrDuplicate(aEvent) { michael@0: var backgroundTabModifier = aEvent.button == 1 || michael@0: #ifdef XP_MACOSX michael@0: aEvent.metaKey; michael@0: #else michael@0: aEvent.ctrlKey; michael@0: #endif michael@0: if (aEvent.shiftKey && !backgroundTabModifier) { michael@0: BrowserReloadSkipCache(); michael@0: return; michael@0: } michael@0: michael@0: let where = whereToOpenLink(aEvent, false, true); michael@0: if (where == "current") michael@0: BrowserReload(); michael@0: else michael@0: duplicateTabIn(gBrowser.selectedTab, where); michael@0: } michael@0: michael@0: function BrowserReload() { michael@0: const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE; michael@0: BrowserReloadWithFlags(reloadFlags); michael@0: } michael@0: michael@0: function BrowserReloadSkipCache() { michael@0: // Bypass proxy and cache. michael@0: const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; michael@0: BrowserReloadWithFlags(reloadFlags); michael@0: } michael@0: michael@0: var BrowserHome = BrowserGoHome; michael@0: function BrowserGoHome(aEvent) { michael@0: if (aEvent && "button" in aEvent && michael@0: aEvent.button == 2) // right-click: do nothing michael@0: return; michael@0: michael@0: var homePage = gHomeButton.getHomePage(); michael@0: var where = whereToOpenLink(aEvent, false, true); michael@0: var urls; michael@0: michael@0: // Home page should open in a new tab when current tab is an app tab michael@0: if (where == "current" && michael@0: gBrowser && michael@0: gBrowser.selectedTab.pinned) michael@0: where = "tab"; michael@0: michael@0: // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages michael@0: switch (where) { michael@0: case "current": michael@0: loadOneOrMoreURIs(homePage); michael@0: break; michael@0: case "tabshifted": michael@0: case "tab": michael@0: urls = homePage.split("|"); michael@0: var loadInBackground = getBoolPref("browser.tabs.loadBookmarksInBackground", false); michael@0: gBrowser.loadTabs(urls, loadInBackground); michael@0: break; michael@0: case "window": michael@0: OpenBrowserWindow(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: function loadOneOrMoreURIs(aURIString) michael@0: { michael@0: #ifdef XP_MACOSX michael@0: // we're not a browser window, pass the URI string to a new browser window michael@0: if (window.location.href != getBrowserURL()) michael@0: { michael@0: window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString); michael@0: return; michael@0: } michael@0: #endif michael@0: // This function throws for certain malformed URIs, so use exception handling michael@0: // so that we don't disrupt startup michael@0: try { michael@0: gBrowser.loadTabs(aURIString.split("|"), false, true); michael@0: } michael@0: catch (e) { michael@0: } michael@0: } michael@0: michael@0: function focusAndSelectUrlBar() { michael@0: if (gURLBar) { michael@0: if (window.fullScreen) michael@0: FullScreen.mouseoverToggle(true); michael@0: michael@0: gURLBar.select(); michael@0: if (document.activeElement == gURLBar.inputField) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: function openLocation() { michael@0: if (focusAndSelectUrlBar()) michael@0: return; michael@0: michael@0: #ifdef XP_MACOSX michael@0: if (window.location.href != getBrowserURL()) { michael@0: var win = getTopWin(); michael@0: if (win) { michael@0: // If there's an open browser window, it should handle this command michael@0: win.focus() michael@0: win.openLocation(); michael@0: } michael@0: else { michael@0: // If there are no open browser windows, open a new one michael@0: window.openDialog("chrome://browser/content/", "_blank", michael@0: "chrome,all,dialog=no", BROWSER_NEW_TAB_URL); michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: function BrowserOpenTab() michael@0: { michael@0: openUILinkIn(BROWSER_NEW_TAB_URL, "tab"); michael@0: } michael@0: michael@0: /* Called from the openLocation dialog. This allows that dialog to instruct michael@0: its opener to open a new window and then step completely out of the way. michael@0: Anything less byzantine is causing horrible crashes, rather believably, michael@0: though oddly only on Linux. */ michael@0: function delayedOpenWindow(chrome, flags, href, postData) michael@0: { michael@0: // The other way to use setTimeout, michael@0: // setTimeout(openDialog, 10, chrome, "_blank", flags, url), michael@0: // doesn't work here. The extra "magic" extra argument setTimeout adds to michael@0: // the callback function would confuse gBrowserInit.onLoad() by making michael@0: // window.arguments[1] be an integer instead of null. michael@0: setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10); michael@0: } michael@0: michael@0: /* Required because the tab needs time to set up its content viewers and get the load of michael@0: the URI kicked off before becoming the active content area. */ michael@0: function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup) michael@0: { michael@0: gBrowser.loadOneTab(aUrl, { michael@0: referrerURI: aReferrer, michael@0: charset: aCharset, michael@0: postData: aPostData, michael@0: inBackground: false, michael@0: allowThirdPartyFixup: aAllowThirdPartyFixup}); michael@0: } michael@0: michael@0: var gLastOpenDirectory = { michael@0: _lastDir: null, michael@0: get path() { michael@0: if (!this._lastDir || !this._lastDir.exists()) { michael@0: try { michael@0: this._lastDir = gPrefService.getComplexValue("browser.open.lastDir", michael@0: Ci.nsILocalFile); michael@0: if (!this._lastDir.exists()) michael@0: this._lastDir = null; michael@0: } michael@0: catch(e) {} michael@0: } michael@0: return this._lastDir; michael@0: }, michael@0: set path(val) { michael@0: try { michael@0: if (!val || !val.isDirectory()) michael@0: return; michael@0: } catch(e) { michael@0: return; michael@0: } michael@0: this._lastDir = val.clone(); michael@0: michael@0: // Don't save the last open directory pref inside the Private Browsing mode michael@0: if (!PrivateBrowsingUtils.isWindowPrivate(window)) michael@0: gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile, michael@0: this._lastDir); michael@0: }, michael@0: reset: function() { michael@0: this._lastDir = null; michael@0: } michael@0: }; michael@0: michael@0: function BrowserOpenFileWindow() michael@0: { michael@0: // Get filepicker component. michael@0: try { michael@0: const nsIFilePicker = Ci.nsIFilePicker; michael@0: let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); michael@0: let fpCallback = function fpCallback_done(aResult) { michael@0: if (aResult == nsIFilePicker.returnOK) { michael@0: try { michael@0: if (fp.file) { michael@0: gLastOpenDirectory.path = michael@0: fp.file.parent.QueryInterface(Ci.nsILocalFile); michael@0: } michael@0: } catch (ex) { michael@0: } michael@0: openUILinkIn(fp.fileURL.spec, "current"); michael@0: } michael@0: }; michael@0: michael@0: fp.init(window, gNavigatorBundle.getString("openFile"), michael@0: nsIFilePicker.modeOpen); michael@0: fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText | michael@0: nsIFilePicker.filterImages | nsIFilePicker.filterXML | michael@0: nsIFilePicker.filterHTML); michael@0: fp.displayDirectory = gLastOpenDirectory.path; michael@0: fp.open(fpCallback); michael@0: } catch (ex) { michael@0: } michael@0: } michael@0: michael@0: function BrowserCloseTabOrWindow() { michael@0: #ifdef XP_MACOSX michael@0: // If we're not a browser window, just close the window michael@0: if (window.location.href != getBrowserURL()) { michael@0: closeWindow(true); michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: // If the current tab is the last one, this will close the window. michael@0: gBrowser.removeCurrentTab({animate: true}); michael@0: } michael@0: michael@0: function BrowserTryToCloseWindow() michael@0: { michael@0: if (WindowIsClosing()) michael@0: window.close(); // WindowIsClosing does all the necessary checks michael@0: } michael@0: michael@0: function loadURI(uri, referrer, postData, allowThirdPartyFixup) { michael@0: if (postData === undefined) michael@0: postData = null; michael@0: michael@0: var flags = nsIWebNavigation.LOAD_FLAGS_NONE; michael@0: if (allowThirdPartyFixup) { michael@0: flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; michael@0: flags |= nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; michael@0: } michael@0: michael@0: try { michael@0: gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData); michael@0: } catch (e) {} michael@0: } michael@0: michael@0: function getShortcutOrURIAndPostData(aURL, aCallback) { michael@0: let mayInheritPrincipal = false; michael@0: let postData = null; michael@0: let shortcutURL = null; michael@0: let keyword = aURL; michael@0: let param = ""; michael@0: michael@0: let offset = aURL.indexOf(" "); michael@0: if (offset > 0) { michael@0: keyword = aURL.substr(0, offset); michael@0: param = aURL.substr(offset + 1); michael@0: } michael@0: michael@0: let engine = Services.search.getEngineByAlias(keyword); michael@0: if (engine) { michael@0: let submission = engine.getSubmission(param); michael@0: postData = submission.postData; michael@0: aCallback({ postData: submission.postData, url: submission.uri.spec, michael@0: mayInheritPrincipal: mayInheritPrincipal }); michael@0: return; michael@0: } michael@0: michael@0: [shortcutURL, postData] = michael@0: PlacesUtils.getURLAndPostDataForKeyword(keyword); michael@0: michael@0: if (!shortcutURL) { michael@0: aCallback({ postData: postData, url: aURL, michael@0: mayInheritPrincipal: mayInheritPrincipal }); michael@0: return; michael@0: } michael@0: michael@0: let escapedPostData = ""; michael@0: if (postData) michael@0: escapedPostData = unescape(postData); michael@0: michael@0: if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { michael@0: let charset = ""; michael@0: const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; michael@0: let matches = shortcutURL.match(re); michael@0: michael@0: let continueOperation = function () { michael@0: // encodeURIComponent produces UTF-8, and cannot be used for other charsets. michael@0: // escape() works in those cases, but it doesn't uri-encode +, @, and /. michael@0: // Therefore we need to manually replace these ASCII characters by their michael@0: // encodeURIComponent result, to match the behavior of nsEscape() with michael@0: // url_XPAlphas michael@0: let encodedParam = ""; michael@0: if (charset && charset != "UTF-8") michael@0: encodedParam = escape(convertFromUnicode(charset, param)). michael@0: replace(/[+@\/]+/g, encodeURIComponent); michael@0: else // Default charset is UTF-8 michael@0: encodedParam = encodeURIComponent(param); michael@0: michael@0: shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); michael@0: michael@0: if (/%s/i.test(escapedPostData)) // POST keyword michael@0: postData = getPostDataStream(escapedPostData, param, encodedParam, michael@0: "application/x-www-form-urlencoded"); michael@0: michael@0: // This URL came from a bookmark, so it's safe to let it inherit the current michael@0: // document's principal. michael@0: mayInheritPrincipal = true; michael@0: michael@0: aCallback({ postData: postData, url: shortcutURL, michael@0: mayInheritPrincipal: mayInheritPrincipal }); michael@0: } michael@0: michael@0: if (matches) { michael@0: [, shortcutURL, charset] = matches; michael@0: continueOperation(); michael@0: } else { michael@0: // Try to get the saved character-set. michael@0: // makeURI throws if URI is invalid. michael@0: // Will return an empty string if character-set is not found. michael@0: try { michael@0: PlacesUtils.getCharsetForURI(makeURI(shortcutURL)) michael@0: .then(c => { charset = c; continueOperation(); }); michael@0: } catch (ex) { michael@0: continueOperation(); michael@0: } michael@0: } michael@0: } michael@0: else if (param) { michael@0: // This keyword doesn't take a parameter, but one was provided. Just return michael@0: // the original URL. michael@0: postData = null; michael@0: michael@0: aCallback({ postData: postData, url: aURL, michael@0: mayInheritPrincipal: mayInheritPrincipal }); michael@0: } else { michael@0: // This URL came from a bookmark, so it's safe to let it inherit the current michael@0: // document's principal. michael@0: mayInheritPrincipal = true; michael@0: michael@0: aCallback({ postData: postData, url: shortcutURL, michael@0: mayInheritPrincipal: mayInheritPrincipal }); michael@0: } michael@0: } michael@0: michael@0: function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) { michael@0: var dataStream = Cc["@mozilla.org/io/string-input-stream;1"]. michael@0: createInstance(Ci.nsIStringInputStream); michael@0: aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword); michael@0: dataStream.data = aStringData; michael@0: michael@0: var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"]. michael@0: createInstance(Ci.nsIMIMEInputStream); michael@0: mimeStream.addHeader("Content-Type", aType); michael@0: mimeStream.addContentLength = true; michael@0: mimeStream.setData(dataStream); michael@0: return mimeStream.QueryInterface(Ci.nsIInputStream); michael@0: } michael@0: michael@0: function getLoadContext() { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsILoadContext); michael@0: } michael@0: michael@0: function readFromClipboard() michael@0: { michael@0: var url; michael@0: michael@0: try { michael@0: // Create transferable that will transfer the text. michael@0: var trans = Components.classes["@mozilla.org/widget/transferable;1"] michael@0: .createInstance(Components.interfaces.nsITransferable); michael@0: trans.init(getLoadContext()); michael@0: michael@0: trans.addDataFlavor("text/unicode"); michael@0: michael@0: // If available, use selection clipboard, otherwise global one michael@0: if (Services.clipboard.supportsSelectionClipboard()) michael@0: Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard); michael@0: else michael@0: Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); michael@0: michael@0: var data = {}; michael@0: var dataLen = {}; michael@0: trans.getTransferData("text/unicode", data, dataLen); michael@0: michael@0: if (data) { michael@0: data = data.value.QueryInterface(Components.interfaces.nsISupportsString); michael@0: url = data.data.substring(0, dataLen.value / 2); michael@0: } michael@0: } catch (ex) { michael@0: } michael@0: michael@0: return url; michael@0: } michael@0: michael@0: function BrowserViewSourceOfDocument(aDocument) michael@0: { michael@0: var pageCookie; michael@0: var webNav; michael@0: michael@0: // Get the document charset michael@0: var docCharset = "charset=" + aDocument.characterSet; michael@0: michael@0: // Get the nsIWebNavigation associated with the document michael@0: try { michael@0: var win; michael@0: var ifRequestor; michael@0: michael@0: // Get the DOMWindow for the requested document. If the DOMWindow michael@0: // cannot be found, then just use the content window... michael@0: // michael@0: // XXX: This is a bit of a hack... michael@0: win = aDocument.defaultView; michael@0: if (win == window) { michael@0: win = content; michael@0: } michael@0: ifRequestor = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor); michael@0: michael@0: webNav = ifRequestor.getInterface(nsIWebNavigation); michael@0: } catch(err) { michael@0: // If nsIWebNavigation cannot be found, just get the one for the whole michael@0: // window... michael@0: webNav = gBrowser.webNavigation; michael@0: } michael@0: // michael@0: // Get the 'PageDescriptor' for the current document. This allows the michael@0: // view-source to access the cached copy of the content rather than michael@0: // refetching it from the network... michael@0: // michael@0: try{ michael@0: var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor); michael@0: michael@0: pageCookie = PageLoader.currentDescriptor; michael@0: } catch(err) { michael@0: // If no page descriptor is available, just use the view-source URL... michael@0: } michael@0: michael@0: top.gViewSourceUtils.viewSource(webNav.currentURI.spec, pageCookie, aDocument); michael@0: } michael@0: michael@0: // doc - document to use for source, or null for this window's document michael@0: // initialTab - name of the initial tab to display, or null for the first tab michael@0: // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted michael@0: function BrowserPageInfo(doc, initialTab, imageElement) { michael@0: var args = {doc: doc, initialTab: initialTab, imageElement: imageElement}; michael@0: var windows = Services.wm.getEnumerator("Browser:page-info"); michael@0: michael@0: var documentURL = doc ? doc.location : window.content.document.location; michael@0: michael@0: // Check for windows matching the url michael@0: while (windows.hasMoreElements()) { michael@0: var currentWindow = windows.getNext(); michael@0: if (currentWindow.closed) { michael@0: continue; michael@0: } michael@0: if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) { michael@0: currentWindow.focus(); michael@0: currentWindow.resetPageInfo(args); michael@0: return currentWindow; michael@0: } michael@0: } michael@0: michael@0: // We didn't find a matching window, so open a new one. michael@0: return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "", michael@0: "chrome,toolbar,dialog=no,resizable", args); michael@0: } michael@0: michael@0: function URLBarSetURI(aURI) { michael@0: var value = gBrowser.userTypedValue; michael@0: var valid = false; michael@0: michael@0: if (value == null) { michael@0: let uri = aURI || gBrowser.currentURI; michael@0: // Strip off "wyciwyg://" and passwords for the location bar michael@0: try { michael@0: uri = Services.uriFixup.createExposableURI(uri); michael@0: } catch (e) {} michael@0: michael@0: // Replace initial page URIs with an empty string michael@0: // only if there's no opener (bug 370555). michael@0: // Bug 863515 - Make content.opener checks work in electrolysis. michael@0: if (gInitialPages.indexOf(uri.spec) != -1) michael@0: value = !gMultiProcessBrowser && content.opener ? uri.spec : ""; michael@0: else michael@0: value = losslessDecodeURI(uri); michael@0: michael@0: valid = !isBlankPageURL(uri.spec); michael@0: } michael@0: michael@0: gURLBar.value = value; michael@0: gURLBar.valueIsTyped = !valid; michael@0: SetPageProxyState(valid ? "valid" : "invalid"); michael@0: } michael@0: michael@0: function losslessDecodeURI(aURI) { michael@0: var value = aURI.spec; michael@0: // Try to decode as UTF-8 if there's no encoding sequence that we would break. michael@0: if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value)) michael@0: try { michael@0: value = decodeURI(value) michael@0: // 1. decodeURI decodes %25 to %, which creates unintended michael@0: // encoding sequences. Re-encode it, unless it's part of michael@0: // a sequence that survived decodeURI, i.e. one for: michael@0: // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#' michael@0: // (RFC 3987 section 3.2) michael@0: // 2. Re-encode whitespace so that it doesn't get eaten away michael@0: // by the location bar (bug 410726). michael@0: .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig, michael@0: encodeURIComponent); michael@0: } catch (e) {} michael@0: michael@0: // Encode invisible characters (C0/C1 control characters, U+007F [DEL], michael@0: // U+00A0 [no-break space], line and paragraph separator, michael@0: // object replacement character) (bug 452979, bug 909264) michael@0: value = value.replace(/[\u0000-\u001f\u007f-\u00a0\u2028\u2029\ufffc]/g, michael@0: encodeURIComponent); michael@0: michael@0: // Encode default ignorable characters (bug 546013) michael@0: // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186). michael@0: // This includes all bidirectional formatting characters. michael@0: // (RFC 3987 sections 3.2 and 4.1 paragraph 6) michael@0: value = value.replace(/[\u00ad\u034f\u061c\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, michael@0: encodeURIComponent); michael@0: return value; michael@0: } michael@0: michael@0: function UpdateUrlbarSearchSplitterState() michael@0: { michael@0: var splitter = document.getElementById("urlbar-search-splitter"); michael@0: var urlbar = document.getElementById("urlbar-container"); michael@0: var searchbar = document.getElementById("search-container"); michael@0: michael@0: if (document.documentElement.getAttribute("customizing") == "true") { michael@0: if (splitter) { michael@0: splitter.remove(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // If the splitter is already in the right place, we don't need to do anything: michael@0: if (splitter && michael@0: ((splitter.nextSibling == searchbar && splitter.previousSibling == urlbar) || michael@0: (splitter.nextSibling == urlbar && splitter.previousSibling == searchbar))) { michael@0: return; michael@0: } michael@0: michael@0: var ibefore = null; michael@0: if (urlbar && searchbar) { michael@0: if (urlbar.nextSibling == searchbar) michael@0: ibefore = searchbar; michael@0: else if (searchbar.nextSibling == urlbar) michael@0: ibefore = urlbar; michael@0: } michael@0: michael@0: if (ibefore) { michael@0: if (!splitter) { michael@0: splitter = document.createElement("splitter"); michael@0: splitter.id = "urlbar-search-splitter"; michael@0: splitter.setAttribute("resizebefore", "flex"); michael@0: splitter.setAttribute("resizeafter", "flex"); michael@0: splitter.setAttribute("skipintoolbarset", "true"); michael@0: splitter.setAttribute("overflows", "false"); michael@0: splitter.className = "chromeclass-toolbar-additional"; michael@0: } michael@0: urlbar.parentNode.insertBefore(splitter, ibefore); michael@0: } else if (splitter) michael@0: splitter.parentNode.removeChild(splitter); michael@0: } michael@0: michael@0: function UpdatePageProxyState() michael@0: { michael@0: if (gURLBar && gURLBar.value != gLastValidURLStr) michael@0: SetPageProxyState("invalid"); michael@0: } michael@0: michael@0: function SetPageProxyState(aState) michael@0: { michael@0: BookmarkingUI.onPageProxyStateChanged(aState); michael@0: michael@0: if (!gURLBar) michael@0: return; michael@0: michael@0: if (!gProxyFavIcon) michael@0: gProxyFavIcon = document.getElementById("page-proxy-favicon"); michael@0: michael@0: gURLBar.setAttribute("pageproxystate", aState); michael@0: gProxyFavIcon.setAttribute("pageproxystate", aState); michael@0: michael@0: // the page proxy state is set to valid via OnLocationChange, which michael@0: // gets called when we switch tabs. michael@0: if (aState == "valid") { michael@0: gLastValidURLStr = gURLBar.value; michael@0: gURLBar.addEventListener("input", UpdatePageProxyState, false); michael@0: } else if (aState == "invalid") { michael@0: gURLBar.removeEventListener("input", UpdatePageProxyState, false); michael@0: } michael@0: } michael@0: michael@0: function PageProxyClickHandler(aEvent) michael@0: { michael@0: if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste")) michael@0: middleMousePaste(aEvent); michael@0: } michael@0: michael@0: /** michael@0: * Handle command events bubbling up from error page content michael@0: * or from about:newtab michael@0: */ michael@0: let BrowserOnClick = { michael@0: handleEvent: function BrowserOnClick_handleEvent(aEvent) { michael@0: if (!aEvent.isTrusted || // Don't trust synthetic events michael@0: aEvent.button == 2) { michael@0: return; michael@0: } michael@0: michael@0: let originalTarget = aEvent.originalTarget; michael@0: let ownerDoc = originalTarget.ownerDocument; michael@0: michael@0: // If the event came from an ssl error page, it is probably either the "Add michael@0: // Exception…" or "Get me out of here!" button michael@0: if (ownerDoc.documentURI.startsWith("about:certerror")) { michael@0: this.onAboutCertError(originalTarget, ownerDoc); michael@0: } michael@0: else if (ownerDoc.documentURI.startsWith("about:blocked")) { michael@0: this.onAboutBlocked(originalTarget, ownerDoc); michael@0: } michael@0: else if (ownerDoc.documentURI.startsWith("about:neterror")) { michael@0: this.onAboutNetError(originalTarget, ownerDoc); michael@0: } michael@0: else if (gMultiProcessBrowser && michael@0: ownerDoc.documentURI.toLowerCase() == "about:newtab") { michael@0: this.onE10sAboutNewTab(aEvent, ownerDoc); michael@0: } michael@0: else if (ownerDoc.documentURI.startsWith("about:tabcrashed")) { michael@0: this.onAboutTabCrashed(aEvent, ownerDoc); michael@0: } michael@0: }, michael@0: michael@0: onAboutCertError: function BrowserOnClick_onAboutCertError(aTargetElm, aOwnerDoc) { michael@0: let elmId = aTargetElm.getAttribute("id"); michael@0: let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI"); michael@0: let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); michael@0: michael@0: switch (elmId) { michael@0: case "exceptionDialogButton": michael@0: if (isTopFrame) { michael@0: secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION); michael@0: } michael@0: let params = { exceptionAdded : false }; michael@0: michael@0: try { michael@0: switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) { michael@0: case 2 : // Pre-fetch & pre-populate michael@0: params.prefetchCert = true; michael@0: case 1 : // Pre-populate michael@0: params.location = aOwnerDoc.location.href; michael@0: } michael@0: } catch (e) { michael@0: Components.utils.reportError("Couldn't get ssl_override pref: " + e); michael@0: } michael@0: michael@0: window.openDialog('chrome://pippki/content/exceptionDialog.xul', michael@0: '','chrome,centerscreen,modal', params); michael@0: michael@0: // If the user added the exception cert, attempt to reload the page michael@0: if (params.exceptionAdded) { michael@0: aOwnerDoc.location.reload(); michael@0: } michael@0: break; michael@0: michael@0: case "getMeOutOfHereButton": michael@0: if (isTopFrame) { michael@0: secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE); michael@0: } michael@0: getMeOutOfHere(); michael@0: break; michael@0: michael@0: case "technicalContent": michael@0: if (isTopFrame) { michael@0: secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_TECHNICAL_DETAILS); michael@0: } michael@0: break; michael@0: michael@0: case "expertContent": michael@0: if (isTopFrame) { michael@0: secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS); michael@0: } michael@0: break; michael@0: michael@0: } michael@0: }, michael@0: michael@0: onAboutBlocked: function BrowserOnClick_onAboutBlocked(aTargetElm, aOwnerDoc) { michael@0: let elmId = aTargetElm.getAttribute("id"); michael@0: let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI"); michael@0: michael@0: // The event came from a button on a malware/phishing block page michael@0: // First check whether it's malware or phishing, so that we can michael@0: // use the right strings/links michael@0: let isMalware = /e=malwareBlocked/.test(aOwnerDoc.documentURI); michael@0: let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_"; michael@0: let nsISecTel = Ci.nsISecurityUITelemetry; michael@0: let isIframe = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); michael@0: bucketName += isIframe ? "TOP_" : "FRAME_"; michael@0: michael@0: switch (elmId) { michael@0: case "getMeOutButton": michael@0: secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]); michael@0: getMeOutOfHere(); michael@0: break; michael@0: michael@0: case "reportButton": michael@0: // This is the "Why is this site blocked" button. For malware, michael@0: // we can fetch a site-specific report, for phishing, we redirect michael@0: // to the generic page describing phishing protection. michael@0: michael@0: // We log even if malware/phishing info URL couldn't be found: michael@0: // the measurement is for how many users clicked the WHY BLOCKED button michael@0: secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]); michael@0: michael@0: if (isMalware) { michael@0: // Get the stop badware "why is this blocked" report url, michael@0: // append the current url, and go there. michael@0: try { michael@0: let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true); michael@0: reportURL += aOwnerDoc.location.href; michael@0: content.location = reportURL; michael@0: } catch (e) { michael@0: Components.utils.reportError("Couldn't get malware report URL: " + e); michael@0: } michael@0: } michael@0: else { // It's a phishing site, not malware michael@0: openHelpLink("phishing-malware", false, "current"); michael@0: } michael@0: break; michael@0: michael@0: case "ignoreWarningButton": michael@0: secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]); michael@0: this.ignoreWarningButton(isMalware); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * This functions prevents navigation from happening directly through the michael@0: * link in about:newtab (which is loaded in the parent and therefore would load michael@0: * the next page also in the parent) and instructs the browser to open the url michael@0: * in the current tab which will make it update the remoteness of the tab. michael@0: */ michael@0: onE10sAboutNewTab: function(aEvent, aOwnerDoc) { michael@0: let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); michael@0: if (!isTopFrame || aEvent.button != 0) { michael@0: return; michael@0: } michael@0: michael@0: let anchorTarget = aEvent.originalTarget.parentNode; michael@0: michael@0: if (anchorTarget instanceof HTMLAnchorElement && michael@0: anchorTarget.classList.contains("newtab-link")) { michael@0: aEvent.preventDefault(); michael@0: openUILinkIn(anchorTarget.href, "current"); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The about:tabcrashed can't do window.reload() because that michael@0: * would reload the page but not use a remote browser. michael@0: */ michael@0: onAboutTabCrashed: function(aEvent, aOwnerDoc) { michael@0: let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); michael@0: if (!isTopFrame) { michael@0: return; michael@0: } michael@0: michael@0: let button = aEvent.originalTarget; michael@0: if (button.id == "tryAgain") { michael@0: #ifdef MOZ_CRASHREPORTER michael@0: if (aOwnerDoc.getElementById("checkSendReport").checked) { michael@0: let browser = gBrowser.getBrowserForDocument(aOwnerDoc); michael@0: TabCrashReporter.submitCrashReport(browser); michael@0: } michael@0: #endif michael@0: openUILinkIn(button.getAttribute("url"), "current"); michael@0: } michael@0: }, michael@0: michael@0: ignoreWarningButton: function BrowserOnClick_ignoreWarningButton(aIsMalware) { michael@0: // Allow users to override and continue through to the site, michael@0: // but add a notify bar as a reminder, so that they don't lose michael@0: // track after, e.g., tab switching. michael@0: gBrowser.loadURIWithFlags(content.location.href, michael@0: nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, michael@0: null, null, null); michael@0: michael@0: Services.perms.add(makeURI(content.location.href), "safe-browsing", michael@0: Ci.nsIPermissionManager.ALLOW_ACTION, michael@0: Ci.nsIPermissionManager.EXPIRE_SESSION); michael@0: michael@0: let buttons = [{ michael@0: label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"), michael@0: accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"), michael@0: callback: function() { getMeOutOfHere(); } michael@0: }]; michael@0: michael@0: let title; michael@0: if (aIsMalware) { michael@0: title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite"); michael@0: buttons[1] = { michael@0: label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"), michael@0: accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"), michael@0: callback: function() { michael@0: openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab'); michael@0: } michael@0: }; michael@0: } else { michael@0: title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery"); michael@0: buttons[1] = { michael@0: label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"), michael@0: accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"), michael@0: callback: function() { michael@0: openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab'); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: let notificationBox = gBrowser.getNotificationBox(); michael@0: let value = "blocked-badware-page"; michael@0: michael@0: let previousNotification = notificationBox.getNotificationWithValue(value); michael@0: if (previousNotification) { michael@0: notificationBox.removeNotification(previousNotification); michael@0: } michael@0: michael@0: let notification = notificationBox.appendNotification( michael@0: title, michael@0: value, michael@0: "chrome://global/skin/icons/blacklist_favicon.png", michael@0: notificationBox.PRIORITY_CRITICAL_HIGH, michael@0: buttons michael@0: ); michael@0: // Persist the notification until the user removes so it michael@0: // doesn't get removed on redirects. michael@0: notification.persistence = -1; michael@0: }, michael@0: michael@0: onAboutNetError: function BrowserOnClick_onAboutNetError(aTargetElm, aOwnerDoc) { michael@0: let elmId = aTargetElm.getAttribute("id"); michael@0: if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI)) michael@0: return; michael@0: Services.io.offline = false; michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Re-direct the browser to a known-safe page. This function is michael@0: * used when, for example, the user browses to a known malware page michael@0: * and is presented with about:blocked. The "Get me out of here!" michael@0: * button should take the user to the default start page so that even michael@0: * when their own homepage is infected, we can get them somewhere safe. michael@0: */ michael@0: function getMeOutOfHere() { michael@0: // Get the start page from the *default* pref branch, not the user's michael@0: var prefs = Services.prefs.getDefaultBranch(null); michael@0: var url = BROWSER_NEW_TAB_URL; michael@0: try { michael@0: url = prefs.getComplexValue("browser.startup.homepage", michael@0: Ci.nsIPrefLocalizedString).data; michael@0: // If url is a pipe-delimited set of pages, just take the first one. michael@0: if (url.contains("|")) michael@0: url = url.split("|")[0]; michael@0: } catch(e) { michael@0: Components.utils.reportError("Couldn't get homepage pref: " + e); michael@0: } michael@0: content.location = url; michael@0: } michael@0: michael@0: function BrowserFullScreen() michael@0: { michael@0: window.fullScreen = !window.fullScreen; michael@0: } michael@0: michael@0: function _checkDefaultAndSwitchToMetro() { michael@0: #ifdef HAVE_SHELL_SERVICE michael@0: #ifdef XP_WIN michael@0: #ifdef MOZ_METRO michael@0: let shell = Components.classes["@mozilla.org/browser/shell-service;1"]. michael@0: getService(Components.interfaces.nsIShellService); michael@0: let isDefault = shell.isDefaultBrowser(false, false); michael@0: michael@0: if (isDefault) { michael@0: let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]. michael@0: getService(Components.interfaces.nsIAppStartup); michael@0: michael@0: Services.prefs.setBoolPref('browser.sessionstore.resume_session_once', true); michael@0: michael@0: let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] michael@0: .createInstance(Ci.nsISupportsPRBool); michael@0: Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); michael@0: michael@0: if (!cancelQuit.data) { michael@0: appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit | michael@0: Components.interfaces.nsIAppStartup.eRestartTouchEnvironment); michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: #endif michael@0: #endif michael@0: #endif michael@0: } michael@0: michael@0: function SwitchToMetro() { michael@0: #ifdef HAVE_SHELL_SERVICE michael@0: #ifdef XP_WIN michael@0: #ifdef MOZ_METRO michael@0: if (this._checkDefaultAndSwitchToMetro()) { michael@0: return; michael@0: } michael@0: michael@0: let shell = Components.classes["@mozilla.org/browser/shell-service;1"]. michael@0: getService(Components.interfaces.nsIShellService); michael@0: michael@0: shell.setDefaultBrowser(false, false); michael@0: michael@0: let intervalID = window.setInterval(this._checkDefaultAndSwitchToMetro, 1000); michael@0: window.setTimeout(function() { window.clearInterval(intervalID); }, 10000); michael@0: #endif michael@0: #endif michael@0: #endif michael@0: } michael@0: michael@0: function onFullScreen(event) { michael@0: FullScreen.toggle(event); michael@0: } michael@0: michael@0: function onMozEnteredDomFullscreen(event) { michael@0: FullScreen.enterDomFullscreen(event); michael@0: } michael@0: michael@0: function getWebNavigation() michael@0: { michael@0: return gBrowser.webNavigation; michael@0: } michael@0: michael@0: function BrowserReloadWithFlags(reloadFlags) { michael@0: let url = gBrowser.currentURI.spec; michael@0: if (gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, url)) { michael@0: // If the remoteness has changed, the new browser doesn't have any michael@0: // information of what was loaded before, so we need to load the previous michael@0: // URL again. michael@0: gBrowser.loadURIWithFlags(url, reloadFlags); michael@0: return; michael@0: } michael@0: michael@0: /* First, we'll try to use the session history object to reload so michael@0: * that framesets are handled properly. If we're in a special michael@0: * window (such as view-source) that has no session history, fall michael@0: * back on using the web navigation's reload method. michael@0: */ michael@0: michael@0: var webNav = gBrowser.webNavigation; michael@0: try { michael@0: var sh = webNav.sessionHistory; michael@0: if (sh) michael@0: webNav = sh.QueryInterface(nsIWebNavigation); michael@0: } catch (e) { michael@0: } michael@0: michael@0: try { michael@0: webNav.reload(reloadFlags); michael@0: } catch (e) { michael@0: } michael@0: } michael@0: michael@0: var PrintPreviewListener = { michael@0: _printPreviewTab: null, michael@0: _tabBeforePrintPreview: null, michael@0: michael@0: getPrintPreviewBrowser: function () { michael@0: if (!this._printPreviewTab) { michael@0: this._tabBeforePrintPreview = gBrowser.selectedTab; michael@0: this._printPreviewTab = gBrowser.loadOneTab("about:blank", michael@0: { inBackground: false }); michael@0: gBrowser.selectedTab = this._printPreviewTab; michael@0: } michael@0: return gBrowser.getBrowserForTab(this._printPreviewTab); michael@0: }, michael@0: getSourceBrowser: function () { michael@0: return this._tabBeforePrintPreview ? michael@0: this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser; michael@0: }, michael@0: getNavToolbox: function () { michael@0: return gNavToolbox; michael@0: }, michael@0: onEnter: function () { michael@0: gInPrintPreviewMode = true; michael@0: this._toggleAffectedChrome(); michael@0: }, michael@0: onExit: function () { michael@0: gBrowser.selectedTab = this._tabBeforePrintPreview; michael@0: this._tabBeforePrintPreview = null; michael@0: gInPrintPreviewMode = false; michael@0: this._toggleAffectedChrome(); michael@0: gBrowser.removeTab(this._printPreviewTab); michael@0: this._printPreviewTab = null; michael@0: }, michael@0: _toggleAffectedChrome: function () { michael@0: gNavToolbox.collapsed = gInPrintPreviewMode; michael@0: michael@0: if (gInPrintPreviewMode) michael@0: this._hideChrome(); michael@0: else michael@0: this._showChrome(); michael@0: michael@0: if (this._chromeState.sidebarOpen) michael@0: toggleSidebar(this._sidebarCommand); michael@0: michael@0: TabsInTitlebar.allowedBy("print-preview", !gInPrintPreviewMode); michael@0: }, michael@0: _hideChrome: function () { michael@0: this._chromeState = {}; michael@0: michael@0: var sidebar = document.getElementById("sidebar-box"); michael@0: this._chromeState.sidebarOpen = !sidebar.hidden; michael@0: this._sidebarCommand = sidebar.getAttribute("sidebarcommand"); michael@0: michael@0: var notificationBox = gBrowser.getNotificationBox(); michael@0: this._chromeState.notificationsOpen = !notificationBox.notificationsHidden; michael@0: notificationBox.notificationsHidden = true; michael@0: michael@0: document.getElementById("sidebar").setAttribute("src", "about:blank"); michael@0: gBrowser.updateWindowResizers(); michael@0: michael@0: this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden; michael@0: if (gFindBarInitialized) michael@0: gFindBar.close(); michael@0: michael@0: var globalNotificationBox = document.getElementById("global-notificationbox"); michael@0: this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden; michael@0: globalNotificationBox.notificationsHidden = true; michael@0: michael@0: this._chromeState.syncNotificationsOpen = false; michael@0: var syncNotifications = document.getElementById("sync-notifications"); michael@0: if (syncNotifications) { michael@0: this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden; michael@0: syncNotifications.notificationsHidden = true; michael@0: } michael@0: }, michael@0: _showChrome: function () { michael@0: if (this._chromeState.notificationsOpen) michael@0: gBrowser.getNotificationBox().notificationsHidden = false; michael@0: michael@0: if (this._chromeState.findOpen) michael@0: gFindBar.open(); michael@0: michael@0: if (this._chromeState.globalNotificationsOpen) michael@0: document.getElementById("global-notificationbox").notificationsHidden = false; michael@0: michael@0: if (this._chromeState.syncNotificationsOpen) michael@0: document.getElementById("sync-notifications").notificationsHidden = false; michael@0: } michael@0: } michael@0: michael@0: function getMarkupDocumentViewer() michael@0: { michael@0: return gBrowser.markupDocumentViewer; michael@0: } michael@0: michael@0: // This function is obsolete. Newer code should use instead. michael@0: function FillInHTMLTooltip(tipElement) michael@0: { michael@0: document.getElementById("aHTMLTooltip").fillInPageTooltip(tipElement); michael@0: } michael@0: michael@0: var browserDragAndDrop = { michael@0: canDropLink: function (aEvent) Services.droppedLinkHandler.canDropLink(aEvent, true), michael@0: michael@0: dragOver: function (aEvent) michael@0: { michael@0: if (this.canDropLink(aEvent)) { michael@0: aEvent.preventDefault(); michael@0: } michael@0: }, michael@0: michael@0: drop: function (aEvent, aName, aDisallowInherit) { michael@0: return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit); michael@0: } michael@0: }; michael@0: michael@0: var homeButtonObserver = { michael@0: onDrop: function (aEvent) michael@0: { michael@0: // disallow setting home pages that inherit the principal michael@0: let url = browserDragAndDrop.drop(aEvent, {}, true); michael@0: setTimeout(openHomeDialog, 0, url); michael@0: }, michael@0: michael@0: onDragOver: function (aEvent) michael@0: { michael@0: browserDragAndDrop.dragOver(aEvent); michael@0: aEvent.dropEffect = "link"; michael@0: }, michael@0: onDragExit: function (aEvent) michael@0: { michael@0: } michael@0: } michael@0: michael@0: function openHomeDialog(aURL) michael@0: { michael@0: var promptTitle = gNavigatorBundle.getString("droponhometitle"); michael@0: var promptMsg = gNavigatorBundle.getString("droponhomemsg"); michael@0: var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg, michael@0: Services.prompt.STD_YES_NO_BUTTONS, michael@0: null, null, null, null, {value:0}); michael@0: michael@0: if (pressedVal == 0) { michael@0: try { michael@0: var str = Components.classes["@mozilla.org/supports-string;1"] michael@0: .createInstance(Components.interfaces.nsISupportsString); michael@0: str.data = aURL; michael@0: gPrefService.setComplexValue("browser.startup.homepage", michael@0: Components.interfaces.nsISupportsString, str); michael@0: } catch (ex) { michael@0: dump("Failed to set the home page.\n"+ex+"\n"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: var newTabButtonObserver = { michael@0: onDragOver: function (aEvent) michael@0: { michael@0: browserDragAndDrop.dragOver(aEvent); michael@0: }, michael@0: michael@0: onDragExit: function (aEvent) michael@0: { michael@0: }, michael@0: michael@0: onDrop: function (aEvent) michael@0: { michael@0: let url = browserDragAndDrop.drop(aEvent, { }); michael@0: getShortcutOrURIAndPostData(url, data => { michael@0: if (data.url) { michael@0: // allow third-party services to fixup this URL michael@0: openNewTabWith(data.url, null, data.postData, aEvent, true); michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: michael@0: var newWindowButtonObserver = { michael@0: onDragOver: function (aEvent) michael@0: { michael@0: browserDragAndDrop.dragOver(aEvent); michael@0: }, michael@0: onDragExit: function (aEvent) michael@0: { michael@0: }, michael@0: onDrop: function (aEvent) michael@0: { michael@0: let url = browserDragAndDrop.drop(aEvent, { }); michael@0: getShortcutOrURIAndPostData(url, data => { michael@0: if (data.url) { michael@0: // allow third-party services to fixup this URL michael@0: openNewWindowWith(data.url, null, data.postData, true); michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: michael@0: const DOMLinkHandler = { michael@0: init: function() { michael@0: let mm = window.messageManager; michael@0: mm.addMessageListener("Link:AddFeed", this); michael@0: mm.addMessageListener("Link:AddIcon", this); michael@0: mm.addMessageListener("Link:AddSearch", this); michael@0: }, michael@0: michael@0: receiveMessage: function (aMsg) { michael@0: switch (aMsg.name) { michael@0: case "Link:AddFeed": michael@0: let link = {type: aMsg.data.type, href: aMsg.data.href, title: aMsg.data.title}; michael@0: FeedHandler.addFeed(link, aMsg.target); michael@0: break; michael@0: michael@0: case "Link:AddIcon": michael@0: return this.addIcon(aMsg.target, aMsg.data.url); michael@0: break; michael@0: michael@0: case "Link:AddSearch": michael@0: this.addSearch(aMsg.target, aMsg.data.engine, aMsg.data.url); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: addIcon: function(aBrowser, aURL) { michael@0: if (gBrowser.isFailedIcon(aURL)) michael@0: return false; michael@0: michael@0: let tab = gBrowser._getTabForBrowser(aBrowser); michael@0: if (!tab) michael@0: return false; michael@0: michael@0: gBrowser.setIcon(tab, aURL); michael@0: return true; michael@0: }, michael@0: michael@0: addSearch: function(aBrowser, aEngine, aURL) { michael@0: let tab = gBrowser._getTabForBrowser(aBrowser); michael@0: if (!tab) michael@0: return false; michael@0: michael@0: BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL)); michael@0: }, michael@0: } michael@0: michael@0: const BrowserSearch = { michael@0: addEngine: function(browser, engine, uri) { michael@0: if (!this.searchBar) michael@0: return; michael@0: michael@0: // Check to see whether we've already added an engine with this title michael@0: if (browser.engines) { michael@0: if (browser.engines.some(function (e) e.title == engine.title)) michael@0: return; michael@0: } michael@0: michael@0: // Append the URI and an appropriate title to the browser data. michael@0: // Use documentURIObject in the check for shouldLoadFavIcon so that we michael@0: // do the right thing with about:-style error pages. Bug 453442 michael@0: var iconURL = null; michael@0: if (gBrowser.shouldLoadFavIcon(uri)) michael@0: iconURL = uri.prePath + "/favicon.ico"; michael@0: michael@0: var hidden = false; michael@0: // If this engine (identified by title) is already in the list, add it michael@0: // to the list of hidden engines rather than to the main list. michael@0: // XXX This will need to be changed when engines are identified by URL; michael@0: // see bug 335102. michael@0: if (Services.search.getEngineByName(engine.title)) michael@0: hidden = true; michael@0: michael@0: var engines = (hidden ? browser.hiddenEngines : browser.engines) || []; michael@0: michael@0: engines.push({ uri: engine.href, michael@0: title: engine.title, michael@0: icon: iconURL }); michael@0: michael@0: if (hidden) michael@0: browser.hiddenEngines = engines; michael@0: else michael@0: browser.engines = engines; michael@0: }, michael@0: michael@0: /** michael@0: * Gives focus to the search bar, if it is present on the toolbar, or loads michael@0: * the default engine's search form otherwise. For Mac, opens a new window michael@0: * or focuses an existing window, if necessary. michael@0: */ michael@0: webSearch: function BrowserSearch_webSearch() { michael@0: #ifdef XP_MACOSX michael@0: if (window.location.href != getBrowserURL()) { michael@0: var win = getTopWin(); michael@0: if (win) { michael@0: // If there's an open browser window, it should handle this command michael@0: win.focus(); michael@0: win.BrowserSearch.webSearch(); michael@0: } else { michael@0: // If there are no open browser windows, open a new one michael@0: var observer = function observer(subject, topic, data) { michael@0: if (subject == win) { michael@0: BrowserSearch.webSearch(); michael@0: Services.obs.removeObserver(observer, "browser-delayed-startup-finished"); michael@0: } michael@0: } michael@0: win = window.openDialog(getBrowserURL(), "_blank", michael@0: "chrome,all,dialog=no", "about:blank"); michael@0: Services.obs.addObserver(observer, "browser-delayed-startup-finished", false); michael@0: } michael@0: return; michael@0: } michael@0: #endif michael@0: let openSearchPageIfFieldIsNotActive = function(aSearchBar) { michael@0: if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) michael@0: openUILinkIn("about:home", "current"); michael@0: }; michael@0: michael@0: let searchBar = this.searchBar; michael@0: let placement = CustomizableUI.getPlacementOfWidget("search-container"); michael@0: let focusSearchBar = () => { michael@0: searchBar = this.searchBar; michael@0: searchBar.select(); michael@0: openSearchPageIfFieldIsNotActive(searchBar); michael@0: }; michael@0: if (placement && placement.area == CustomizableUI.AREA_PANEL) { michael@0: // The panel is not constructed until the first time it is shown. michael@0: PanelUI.show().then(focusSearchBar); michael@0: return; michael@0: } michael@0: if (placement && placement.area == CustomizableUI.AREA_NAVBAR && searchBar && michael@0: searchBar.parentNode.getAttribute("overflowedItem") == "true") { michael@0: let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR); michael@0: navBar.overflowable.show().then(() => { michael@0: focusSearchBar(); michael@0: }); michael@0: return; michael@0: } michael@0: if (searchBar) { michael@0: if (window.fullScreen) michael@0: FullScreen.mouseoverToggle(true); michael@0: searchBar.select(); michael@0: } michael@0: openSearchPageIfFieldIsNotActive(searchBar); michael@0: }, michael@0: michael@0: /** michael@0: * Loads a search results page, given a set of search terms. Uses the current michael@0: * engine if the search bar is visible, or the default engine otherwise. michael@0: * michael@0: * @param searchText michael@0: * The search terms to use for the search. michael@0: * michael@0: * @param useNewTab michael@0: * Boolean indicating whether or not the search should load in a new michael@0: * tab. michael@0: * michael@0: * @param purpose [optional] michael@0: * A string meant to indicate the context of the search request. This michael@0: * allows the search service to provide a different nsISearchSubmission michael@0: * depending on e.g. where the search is triggered in the UI. michael@0: * michael@0: * @return engine The search engine used to perform a search, or null if no michael@0: * search was performed. michael@0: */ michael@0: _loadSearch: function (searchText, useNewTab, purpose) { michael@0: let engine; michael@0: michael@0: // If the search bar is visible, use the current engine, otherwise, fall michael@0: // back to the default engine. michael@0: if (isElementVisible(this.searchBar)) michael@0: engine = Services.search.currentEngine; michael@0: else michael@0: engine = Services.search.defaultEngine; michael@0: michael@0: let submission = engine.getSubmission(searchText, null, purpose); // HTML response michael@0: michael@0: // getSubmission can return null if the engine doesn't have a URL michael@0: // with a text/html response type. This is unlikely (since michael@0: // SearchService._addEngineToStore() should fail for such an engine), michael@0: // but let's be on the safe side. michael@0: if (!submission) { michael@0: return null; michael@0: } michael@0: michael@0: let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground"); michael@0: openLinkIn(submission.uri.spec, michael@0: useNewTab ? "tab" : "current", michael@0: { postData: submission.postData, michael@0: inBackground: inBackground, michael@0: relatedToCurrent: true }); michael@0: michael@0: return engine; michael@0: }, michael@0: michael@0: /** michael@0: * Just like _loadSearch, but preserving an old API. michael@0: * michael@0: * @return string Name of the search engine used to perform a search or null michael@0: * if a search was not performed. michael@0: */ michael@0: loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) { michael@0: let engine = BrowserSearch._loadSearch(searchText, useNewTab, purpose); michael@0: if (!engine) { michael@0: return null; michael@0: } michael@0: return engine.name; michael@0: }, michael@0: michael@0: /** michael@0: * Perform a search initiated from the context menu. michael@0: * michael@0: * This should only be called from the context menu. See michael@0: * BrowserSearch.loadSearch for the preferred API. michael@0: */ michael@0: loadSearchFromContext: function (terms) { michael@0: let engine = BrowserSearch._loadSearch(terms, true, "contextmenu"); michael@0: if (engine) { michael@0: BrowserSearch.recordSearchInHealthReport(engine, "contextmenu"); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Returns the search bar element if it is present in the toolbar, null otherwise. michael@0: */ michael@0: get searchBar() { michael@0: return document.getElementById("searchbar"); michael@0: }, michael@0: michael@0: loadAddEngines: function BrowserSearch_loadAddEngines() { michael@0: var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow"); michael@0: var where = newWindowPref == 3 ? "tab" : "window"; michael@0: var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true); michael@0: openUILinkIn(searchEnginesURL, where); michael@0: }, michael@0: michael@0: /** michael@0: * Helper to record a search with Firefox Health Report. michael@0: * michael@0: * FHR records only search counts and nothing pertaining to the search itself. michael@0: * michael@0: * @param engine michael@0: * (nsISearchEngine) The engine handling the search. michael@0: * @param source michael@0: * (string) Where the search originated from. See the FHR michael@0: * SearchesProvider for allowed values. michael@0: */ michael@0: recordSearchInHealthReport: function (engine, source) { michael@0: #ifdef MOZ_SERVICES_HEALTHREPORT michael@0: let reporter = Cc["@mozilla.org/datareporting/service;1"] michael@0: .getService() michael@0: .wrappedJSObject michael@0: .healthReporter; michael@0: michael@0: // This can happen if the FHR component of the data reporting service is michael@0: // disabled. This is controlled by a pref that most will never use. michael@0: if (!reporter) { michael@0: return; michael@0: } michael@0: michael@0: reporter.onInit().then(function record() { michael@0: try { michael@0: reporter.getProvider("org.mozilla.searches").recordSearch(engine, source); michael@0: } catch (ex) { michael@0: Cu.reportError(ex); michael@0: } michael@0: }); michael@0: #endif michael@0: }, michael@0: }; michael@0: michael@0: function FillHistoryMenu(aParent) { michael@0: // Lazily add the hover listeners on first showing and never remove them michael@0: if (!aParent.hasStatusListener) { michael@0: // Show history item's uri in the status bar when hovering, and clear on exit michael@0: aParent.addEventListener("DOMMenuItemActive", function(aEvent) { michael@0: // Only the current page should have the checked attribute, so skip it michael@0: if (!aEvent.target.hasAttribute("checked")) michael@0: XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri")); michael@0: }, false); michael@0: aParent.addEventListener("DOMMenuItemInactive", function() { michael@0: XULBrowserWindow.setOverLink(""); michael@0: }, false); michael@0: michael@0: aParent.hasStatusListener = true; michael@0: } michael@0: michael@0: // Remove old entries if any michael@0: var children = aParent.childNodes; michael@0: for (var i = children.length - 1; i >= 0; --i) { michael@0: if (children[i].hasAttribute("index")) michael@0: aParent.removeChild(children[i]); michael@0: } michael@0: michael@0: var webNav = gBrowser.webNavigation; michael@0: var sessionHistory = webNav.sessionHistory; michael@0: michael@0: var count = sessionHistory.count; michael@0: if (count <= 1) // don't display the popup for a single item michael@0: return false; michael@0: michael@0: const MAX_HISTORY_MENU_ITEMS = 15; michael@0: var index = sessionHistory.index; michael@0: var half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2); michael@0: var start = Math.max(index - half_length, 0); michael@0: var end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count); michael@0: if (end == count) michael@0: start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0); michael@0: michael@0: var tooltipBack = gNavigatorBundle.getString("tabHistory.goBack"); michael@0: var tooltipCurrent = gNavigatorBundle.getString("tabHistory.current"); michael@0: var tooltipForward = gNavigatorBundle.getString("tabHistory.goForward"); michael@0: michael@0: for (var j = end - 1; j >= start; j--) { michael@0: let item = document.createElement("menuitem"); michael@0: let entry = sessionHistory.getEntryAtIndex(j, false); michael@0: let uri = entry.URI.spec; michael@0: michael@0: item.setAttribute("uri", uri); michael@0: item.setAttribute("label", entry.title || uri); michael@0: item.setAttribute("index", j); michael@0: michael@0: if (j != index) { michael@0: PlacesUtils.favicons.getFaviconURLForPage(entry.URI, function (aURI) { michael@0: if (aURI) { michael@0: let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec; michael@0: item.style.listStyleImage = "url(" + iconURL + ")"; michael@0: } michael@0: }); michael@0: } michael@0: michael@0: if (j < index) { michael@0: item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon"; michael@0: item.setAttribute("tooltiptext", tooltipBack); michael@0: } else if (j == index) { michael@0: item.setAttribute("type", "radio"); michael@0: item.setAttribute("checked", "true"); michael@0: item.className = "unified-nav-current"; michael@0: item.setAttribute("tooltiptext", tooltipCurrent); michael@0: } else { michael@0: item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon"; michael@0: item.setAttribute("tooltiptext", tooltipForward); michael@0: } michael@0: michael@0: aParent.appendChild(item); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: function addToUrlbarHistory(aUrlToAdd) { michael@0: if (!PrivateBrowsingUtils.isWindowPrivate(window) && michael@0: aUrlToAdd && michael@0: !aUrlToAdd.contains(" ") && michael@0: !/[\x00-\x1F]/.test(aUrlToAdd)) michael@0: PlacesUIUtils.markPageAsTyped(aUrlToAdd); michael@0: } michael@0: michael@0: function toJavaScriptConsole() michael@0: { michael@0: toOpenWindowByType("global:console", "chrome://global/content/console.xul"); michael@0: } michael@0: michael@0: function BrowserDownloadsUI() michael@0: { michael@0: Cc["@mozilla.org/download-manager-ui;1"]. michael@0: getService(Ci.nsIDownloadManagerUI).show(window); michael@0: } michael@0: michael@0: function toOpenWindowByType(inType, uri, features) michael@0: { michael@0: var topWindow = Services.wm.getMostRecentWindow(inType); michael@0: michael@0: if (topWindow) michael@0: topWindow.focus(); michael@0: else if (features) michael@0: window.open(uri, "_blank", features); michael@0: else michael@0: window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"); michael@0: } michael@0: michael@0: function OpenBrowserWindow(options) michael@0: { michael@0: var telemetryObj = {}; michael@0: TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj); michael@0: michael@0: function newDocumentShown(doc, topic, data) { michael@0: if (topic == "document-shown" && michael@0: doc != document && michael@0: doc.defaultView == win) { michael@0: Services.obs.removeObserver(newDocumentShown, "document-shown"); michael@0: Services.obs.removeObserver(windowClosed, "domwindowclosed"); michael@0: TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj); michael@0: } michael@0: } michael@0: michael@0: function windowClosed(subject) { michael@0: if (subject == win) { michael@0: Services.obs.removeObserver(newDocumentShown, "document-shown"); michael@0: Services.obs.removeObserver(windowClosed, "domwindowclosed"); michael@0: } michael@0: } michael@0: michael@0: // Make sure to remove the 'document-shown' observer in case the window michael@0: // is being closed right after it was opened to avoid leaking. michael@0: Services.obs.addObserver(newDocumentShown, "document-shown", false); michael@0: Services.obs.addObserver(windowClosed, "domwindowclosed", false); michael@0: michael@0: var charsetArg = new String(); michael@0: var handler = Components.classes["@mozilla.org/browser/clh;1"] michael@0: .getService(Components.interfaces.nsIBrowserHandler); michael@0: var defaultArgs = handler.defaultArgs; michael@0: var wintype = document.documentElement.getAttribute('windowtype'); michael@0: michael@0: var extraFeatures = ""; michael@0: if (options && options.private) { michael@0: extraFeatures = ",private"; michael@0: if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { michael@0: // Force the new window to load about:privatebrowsing instead of the default home page michael@0: defaultArgs = "about:privatebrowsing"; michael@0: } michael@0: } else { michael@0: extraFeatures = ",non-private"; michael@0: } michael@0: michael@0: if (options && options.remote) { michael@0: let omtcEnabled = gPrefService.getBoolPref("layers.offmainthreadcomposition.enabled"); michael@0: if (!omtcEnabled) { michael@0: alert("To use out-of-process tabs, you must set the layers.offmainthreadcomposition.enabled preference and restart. Opening a normal window instead."); michael@0: } else { michael@0: extraFeatures += ",remote"; michael@0: } michael@0: } else if (options && options.remote === false) { michael@0: extraFeatures += ",non-remote"; michael@0: } michael@0: michael@0: // if and only if the current window is a browser window and it has a document with a character michael@0: // set, then extract the current charset menu setting from the current document and use it to michael@0: // initialize the new browser window... michael@0: var win; michael@0: if (window && (wintype == "navigator:browser") && window.content && window.content.document) michael@0: { michael@0: var DocCharset = window.content.document.characterSet; michael@0: charsetArg = "charset="+DocCharset; michael@0: michael@0: //we should "inherit" the charset menu setting in a new window michael@0: win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg); michael@0: } michael@0: else // forget about the charset information. michael@0: { michael@0: win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs); michael@0: } michael@0: michael@0: return win; michael@0: } michael@0: michael@0: // Only here for backwards compat, we should remove this soon michael@0: function BrowserCustomizeToolbar() { michael@0: gCustomizeMode.enter(); michael@0: } michael@0: michael@0: /** michael@0: * Update the global flag that tracks whether or not any edit UI (the Edit menu, michael@0: * edit-related items in the context menu, and edit-related toolbar buttons michael@0: * is visible, then update the edit commands' enabled state accordingly. We use michael@0: * this flag to skip updating the edit commands on focus or selection changes michael@0: * when no UI is visible to improve performance (including pageload performance, michael@0: * since focus changes when you load a new page). michael@0: * michael@0: * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands' michael@0: * enabled state so the UI will reflect it appropriately. michael@0: * michael@0: * If the UI isn't visible, we enable all edit commands so keyboard shortcuts michael@0: * still work and just lazily disable them as needed when the user presses a michael@0: * shortcut. michael@0: * michael@0: * This doesn't work on Mac, since Mac menus flash when users press their michael@0: * keyboard shortcuts, so edit UI is essentially always visible on the Mac, michael@0: * and we need to always update the edit commands. Thus on Mac this function michael@0: * is a no op. michael@0: */ michael@0: function updateEditUIVisibility() michael@0: { michael@0: #ifndef XP_MACOSX michael@0: let editMenuPopupState = document.getElementById("menu_EditPopup").state; michael@0: let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state; michael@0: let placesContextMenuPopupState = document.getElementById("placesContext").state; michael@0: michael@0: // The UI is visible if the Edit menu is opening or open, if the context menu michael@0: // is open, or if the toolbar has been customized to include the Cut, Copy, michael@0: // or Paste toolbar buttons. michael@0: gEditUIVisible = editMenuPopupState == "showing" || michael@0: editMenuPopupState == "open" || michael@0: contextMenuPopupState == "showing" || michael@0: contextMenuPopupState == "open" || michael@0: placesContextMenuPopupState == "showing" || michael@0: placesContextMenuPopupState == "open" || michael@0: document.getElementById("edit-controls") ? true : false; michael@0: michael@0: // If UI is visible, update the edit commands' enabled state to reflect michael@0: // whether or not they are actually enabled for the current focus/selection. michael@0: if (gEditUIVisible) michael@0: goUpdateGlobalEditMenuItems(); michael@0: michael@0: // Otherwise, enable all commands, so that keyboard shortcuts still work, michael@0: // then lazily determine their actual enabled state when the user presses michael@0: // a keyboard shortcut. michael@0: else { michael@0: goSetCommandEnabled("cmd_undo", true); michael@0: goSetCommandEnabled("cmd_redo", true); michael@0: goSetCommandEnabled("cmd_cut", true); michael@0: goSetCommandEnabled("cmd_copy", true); michael@0: goSetCommandEnabled("cmd_paste", true); michael@0: goSetCommandEnabled("cmd_selectAll", true); michael@0: goSetCommandEnabled("cmd_delete", true); michael@0: goSetCommandEnabled("cmd_switchTextDirection", true); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: /** michael@0: * Makes the Character Encoding menu enabled or disabled as appropriate. michael@0: * To be called when the View menu or the app menu is opened. michael@0: */ michael@0: function updateCharacterEncodingMenuState() michael@0: { michael@0: let charsetMenu = document.getElementById("charsetMenu"); michael@0: // gBrowser is null on Mac when the menubar shows in the context of michael@0: // non-browser windows. The above elements may be null depending on michael@0: // what parts of the menubar are present. E.g. no app menu on Mac. michael@0: if (gBrowser && michael@0: gBrowser.docShell && michael@0: gBrowser.docShell.mayEnableCharacterEncodingMenu) { michael@0: if (charsetMenu) { michael@0: charsetMenu.removeAttribute("disabled"); michael@0: } michael@0: } else { michael@0: if (charsetMenu) { michael@0: charsetMenu.setAttribute("disabled", "true"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Returns true if |aMimeType| is text-based, false otherwise. michael@0: * michael@0: * @param aMimeType michael@0: * The MIME type to check. michael@0: * michael@0: * If adding types to this function, please also check the similar michael@0: * function in findbar.xml michael@0: */ michael@0: function mimeTypeIsTextBased(aMimeType) michael@0: { michael@0: return aMimeType.startsWith("text/") || michael@0: aMimeType.endsWith("+xml") || michael@0: aMimeType == "application/x-javascript" || michael@0: aMimeType == "application/javascript" || michael@0: aMimeType == "application/json" || michael@0: aMimeType == "application/xml" || michael@0: aMimeType == "mozilla.application/cached-xul"; michael@0: } michael@0: michael@0: var XULBrowserWindow = { michael@0: // Stored Status, Link and Loading values michael@0: status: "", michael@0: defaultStatus: "", michael@0: overLink: "", michael@0: startTime: 0, michael@0: statusText: "", michael@0: isBusy: false, michael@0: // Left here for add-on compatibility, see bug 752434 michael@0: inContentWhitelist: [], michael@0: michael@0: QueryInterface: function (aIID) { michael@0: if (aIID.equals(Ci.nsIWebProgressListener) || michael@0: aIID.equals(Ci.nsIWebProgressListener2) || michael@0: aIID.equals(Ci.nsISupportsWeakReference) || michael@0: aIID.equals(Ci.nsIXULBrowserWindow) || michael@0: aIID.equals(Ci.nsISupports)) michael@0: return this; michael@0: throw Cr.NS_NOINTERFACE; michael@0: }, michael@0: michael@0: get stopCommand () { michael@0: delete this.stopCommand; michael@0: return this.stopCommand = document.getElementById("Browser:Stop"); michael@0: }, michael@0: get reloadCommand () { michael@0: delete this.reloadCommand; michael@0: return this.reloadCommand = document.getElementById("Browser:Reload"); michael@0: }, michael@0: get statusTextField () { michael@0: return gBrowser.getStatusPanel(); michael@0: }, michael@0: get isImage () { michael@0: delete this.isImage; michael@0: return this.isImage = document.getElementById("isImage"); michael@0: }, michael@0: michael@0: init: function () { michael@0: // Initialize the security button's state and tooltip text. michael@0: var securityUI = gBrowser.securityUI; michael@0: this.onSecurityChange(null, null, securityUI.state); michael@0: }, michael@0: michael@0: setJSStatus: function () { michael@0: // unsupported michael@0: }, michael@0: michael@0: setDefaultStatus: function (status) { michael@0: this.defaultStatus = status; michael@0: this.updateStatusField(); michael@0: }, michael@0: michael@0: setOverLink: function (url, anchorElt) { michael@0: // Encode bidirectional formatting characters. michael@0: // (RFC 3987 sections 3.2 and 4.1 paragraph 6) michael@0: url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, michael@0: encodeURIComponent); michael@0: michael@0: if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */) michael@0: url = trimURL(url); michael@0: michael@0: this.overLink = url; michael@0: LinkTargetDisplay.update(); michael@0: }, michael@0: michael@0: showTooltip: function (x, y, tooltip) { michael@0: // The x,y coordinates are relative to the element using michael@0: // the chrome zoom level. michael@0: let elt = document.getElementById("remoteBrowserTooltip"); michael@0: elt.label = tooltip; michael@0: michael@0: let anchor = gBrowser.selectedBrowser; michael@0: elt.openPopupAtScreen(anchor.boxObject.screenX + x, anchor.boxObject.screenY + y, false, null); michael@0: }, michael@0: michael@0: hideTooltip: function () { michael@0: let elt = document.getElementById("remoteBrowserTooltip"); michael@0: elt.hidePopup(); michael@0: }, michael@0: michael@0: updateStatusField: function () { michael@0: var text, type, types = ["overLink"]; michael@0: if (this._busyUI) michael@0: types.push("status"); michael@0: types.push("defaultStatus"); michael@0: for (type of types) { michael@0: text = this[type]; michael@0: if (text) michael@0: break; michael@0: } michael@0: michael@0: // check the current value so we don't trigger an attribute change michael@0: // and cause needless (slow!) UI updates michael@0: if (this.statusText != text) { michael@0: let field = this.statusTextField; michael@0: field.setAttribute("previoustype", field.getAttribute("type")); michael@0: field.setAttribute("type", type); michael@0: field.label = text; michael@0: field.setAttribute("crop", type == "overLink" ? "center" : "end"); michael@0: this.statusText = text; michael@0: } michael@0: }, michael@0: michael@0: // Called before links are navigated to to allow us to retarget them if needed. michael@0: onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) { michael@0: let target = this._onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab); michael@0: SocialUI.closeSocialPanelForLinkTraversal(target, linkNode); michael@0: return target; michael@0: }, michael@0: michael@0: _onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) { michael@0: // Don't modify non-default targets or targets that aren't in top-level app michael@0: // tab docshells (isAppTab will be false for app tab subframes). michael@0: if (originalTarget != "" || !isAppTab) michael@0: return originalTarget; michael@0: michael@0: // External links from within app tabs should always open in new tabs michael@0: // instead of replacing the app tab's page (Bug 575561) michael@0: let linkHost; michael@0: let docHost; michael@0: try { michael@0: linkHost = linkURI.host; michael@0: docHost = linkNode.ownerDocument.documentURIObject.host; michael@0: } catch(e) { michael@0: // nsIURI.host can throw for non-nsStandardURL nsIURIs. michael@0: // If we fail to get either host, just return originalTarget. michael@0: return originalTarget; michael@0: } michael@0: michael@0: if (docHost == linkHost) michael@0: return originalTarget; michael@0: michael@0: // Special case: ignore "www" prefix if it is part of host string michael@0: let [longHost, shortHost] = michael@0: linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost]; michael@0: if (longHost == "www." + shortHost) michael@0: return originalTarget; michael@0: michael@0: return "_blank"; michael@0: }, michael@0: michael@0: onProgressChange: function (aWebProgress, aRequest, michael@0: aCurSelfProgress, aMaxSelfProgress, michael@0: aCurTotalProgress, aMaxTotalProgress) { michael@0: // Do nothing. michael@0: }, michael@0: michael@0: onProgressChange64: function (aWebProgress, aRequest, michael@0: aCurSelfProgress, aMaxSelfProgress, michael@0: aCurTotalProgress, aMaxTotalProgress) { michael@0: return this.onProgressChange(aWebProgress, aRequest, michael@0: aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, michael@0: aMaxTotalProgress); michael@0: }, michael@0: michael@0: // This function fires only for the currently selected tab. michael@0: onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) { michael@0: const nsIWebProgressListener = Ci.nsIWebProgressListener; michael@0: const nsIChannel = Ci.nsIChannel; michael@0: michael@0: let browser = gBrowser.selectedBrowser; michael@0: michael@0: if (aStateFlags & nsIWebProgressListener.STATE_START && michael@0: aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { michael@0: michael@0: if (aRequest && aWebProgress.isTopLevel) { michael@0: // clear out feed data michael@0: browser.feeds = null; michael@0: michael@0: // clear out search-engine data michael@0: browser.engines = null; michael@0: } michael@0: michael@0: this.isBusy = true; michael@0: michael@0: if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { michael@0: this._busyUI = true; michael@0: michael@0: // XXX: This needs to be based on window activity... michael@0: this.stopCommand.removeAttribute("disabled"); michael@0: CombinedStopReload.switchToStop(); michael@0: } michael@0: } michael@0: else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { michael@0: // This (thanks to the filter) is a network stop or the last michael@0: // request stop outside of loading the document, stop throbbers michael@0: // and progress bars and such michael@0: if (aRequest) { michael@0: let msg = ""; michael@0: let location; michael@0: // Get the URI either from a channel or a pseudo-object michael@0: if (aRequest instanceof nsIChannel || "URI" in aRequest) { michael@0: location = aRequest.URI; michael@0: michael@0: // For keyword URIs clear the user typed value since they will be changed into real URIs michael@0: if (location.scheme == "keyword" && aWebProgress.isTopLevel) michael@0: gBrowser.userTypedValue = null; michael@0: michael@0: if (location.spec != "about:blank") { michael@0: switch (aStatus) { michael@0: case Components.results.NS_ERROR_NET_TIMEOUT: michael@0: msg = gNavigatorBundle.getString("nv_timeout"); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: this.status = ""; michael@0: this.setDefaultStatus(msg); michael@0: michael@0: // Disable menu entries for images, enable otherwise michael@0: if (browser.documentContentType && mimeTypeIsTextBased(browser.documentContentType)) michael@0: this.isImage.removeAttribute('disabled'); michael@0: else michael@0: this.isImage.setAttribute('disabled', 'true'); michael@0: } michael@0: michael@0: this.isBusy = false; michael@0: michael@0: if (this._busyUI) { michael@0: this._busyUI = false; michael@0: michael@0: this.stopCommand.setAttribute("disabled", "true"); michael@0: CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) { michael@0: var location = aLocationURI ? aLocationURI.spec : ""; michael@0: michael@0: // Hide the form invalid popup. michael@0: if (gFormSubmitObserver.panel) { michael@0: gFormSubmitObserver.panel.hidePopup(); michael@0: } michael@0: michael@0: let pageTooltip = document.getElementById("aHTMLTooltip"); michael@0: let tooltipNode = pageTooltip.triggerNode; michael@0: if (tooltipNode) { michael@0: // Optimise for the common case michael@0: if (aWebProgress.isTopLevel) { michael@0: pageTooltip.hidePopup(); michael@0: } michael@0: else { michael@0: for (let tooltipWindow = tooltipNode.ownerDocument.defaultView; michael@0: tooltipWindow != tooltipWindow.parent; michael@0: tooltipWindow = tooltipWindow.parent) { michael@0: if (tooltipWindow == aWebProgress.DOMWindow) { michael@0: pageTooltip.hidePopup(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: let browser = gBrowser.selectedBrowser; michael@0: michael@0: // Disable menu entries for images, enable otherwise michael@0: if (browser.documentContentType && mimeTypeIsTextBased(browser.documentContentType)) michael@0: this.isImage.removeAttribute('disabled'); michael@0: else michael@0: this.isImage.setAttribute('disabled', 'true'); michael@0: michael@0: this.hideOverLinkImmediately = true; michael@0: this.setOverLink("", null); michael@0: this.hideOverLinkImmediately = false; michael@0: michael@0: // We should probably not do this if the value has changed since the user michael@0: // searched michael@0: // Update urlbar only if a new page was loaded on the primary content area michael@0: // Do not update urlbar if there was a subframe navigation michael@0: michael@0: if (aWebProgress.isTopLevel) { michael@0: if ((location == "about:blank" && (gMultiProcessBrowser || !content.opener)) || michael@0: location == "") { // Second condition is for new tabs, otherwise michael@0: // reload function is enabled until tab is refreshed. michael@0: this.reloadCommand.setAttribute("disabled", "true"); michael@0: } else { michael@0: this.reloadCommand.removeAttribute("disabled"); michael@0: } michael@0: michael@0: if (gURLBar) { michael@0: URLBarSetURI(aLocationURI); michael@0: michael@0: BookmarkingUI.onLocationChange(); michael@0: SocialUI.updateState(); michael@0: } michael@0: michael@0: // Utility functions for disabling find michael@0: var shouldDisableFind = function shouldDisableFind(aDocument) { michael@0: let docElt = aDocument.documentElement; michael@0: return docElt && docElt.getAttribute("disablefastfind") == "true"; michael@0: } michael@0: michael@0: var disableFindCommands = function disableFindCommands(aDisable) { michael@0: let findCommands = [document.getElementById("cmd_find"), michael@0: document.getElementById("cmd_findAgain"), michael@0: document.getElementById("cmd_findPrevious")]; michael@0: for (let elt of findCommands) { michael@0: if (aDisable) michael@0: elt.setAttribute("disabled", "true"); michael@0: else michael@0: elt.removeAttribute("disabled"); michael@0: } michael@0: } michael@0: michael@0: var onContentRSChange = function onContentRSChange(e) { michael@0: if (e.target.readyState != "interactive" && e.target.readyState != "complete") michael@0: return; michael@0: michael@0: e.target.removeEventListener("readystatechange", onContentRSChange); michael@0: disableFindCommands(shouldDisableFind(e.target)); michael@0: } michael@0: michael@0: // Disable find commands in documents that ask for them to be disabled. michael@0: if (!gMultiProcessBrowser && aLocationURI && michael@0: (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) { michael@0: // Don't need to re-enable/disable find commands for same-document location changes michael@0: // (e.g. the replaceStates in about:addons) michael@0: if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) { michael@0: if (content.document.readyState == "interactive" || content.document.readyState == "complete") michael@0: disableFindCommands(shouldDisableFind(content.document)); michael@0: else { michael@0: content.document.addEventListener("readystatechange", onContentRSChange); michael@0: } michael@0: } michael@0: } else michael@0: disableFindCommands(false); michael@0: michael@0: // Try not to instantiate gCustomizeMode as much as possible, michael@0: // so don't use CustomizeMode.jsm to check for URI or customizing. michael@0: let customizingURI = "about:customizing"; michael@0: if (location == customizingURI) { michael@0: gCustomizeMode.enter(); michael@0: } else if (location != customizingURI && michael@0: (CustomizationHandler.isEnteringCustomizeMode || michael@0: CustomizationHandler.isCustomizing())) { michael@0: gCustomizeMode.exit(); michael@0: } michael@0: } michael@0: UpdateBackForwardCommands(gBrowser.webNavigation); michael@0: michael@0: gGestureSupport.restoreRotationState(); michael@0: michael@0: // See bug 358202, when tabs are switched during a drag operation, michael@0: // timers don't fire on windows (bug 203573) michael@0: if (aRequest) michael@0: setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0); michael@0: else michael@0: this.asyncUpdateUI(); michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: if (aLocationURI) { michael@0: let uri = aLocationURI.clone(); michael@0: try { michael@0: // If the current URI contains a username/password, remove it. michael@0: uri.userPass = ""; michael@0: } catch (ex) { /* Ignore failures on about: URIs. */ } michael@0: michael@0: try { michael@0: gCrashReporter.annotateCrashReport("URL", uri.spec); michael@0: } catch (ex if ex.result == Components.results.NS_ERROR_NOT_INITIALIZED) { michael@0: // Don't make noise when the crash reporter is built but not enabled. michael@0: } michael@0: } michael@0: #endif michael@0: }, michael@0: michael@0: asyncUpdateUI: function () { michael@0: FeedHandler.updateFeeds(); michael@0: }, michael@0: michael@0: // Left here for add-on compatibility, see bug 752434 michael@0: hideChromeForLocation: function() {}, michael@0: michael@0: onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { michael@0: this.status = aMessage; michael@0: this.updateStatusField(); michael@0: }, michael@0: michael@0: // Properties used to cache security state used to update the UI michael@0: _state: null, michael@0: _lastLocation: null, michael@0: michael@0: onSecurityChange: function (aWebProgress, aRequest, aState) { michael@0: // Don't need to do anything if the data we use to update the UI hasn't michael@0: // changed michael@0: let uri = gBrowser.currentURI; michael@0: let spec = uri.spec; michael@0: if (this._state == aState && michael@0: this._lastLocation == spec) michael@0: return; michael@0: this._state = aState; michael@0: this._lastLocation = spec; michael@0: michael@0: // aState is defined as a bitmask that may be extended in the future. michael@0: // We filter out any unknown bits before testing for known values. michael@0: const wpl = Components.interfaces.nsIWebProgressListener; michael@0: const wpl_security_bits = wpl.STATE_IS_SECURE | michael@0: wpl.STATE_IS_BROKEN | michael@0: wpl.STATE_IS_INSECURE; michael@0: var level; michael@0: michael@0: switch (this._state & wpl_security_bits) { michael@0: case wpl.STATE_IS_SECURE: michael@0: level = "high"; michael@0: break; michael@0: case wpl.STATE_IS_BROKEN: michael@0: level = "broken"; michael@0: break; michael@0: } michael@0: michael@0: if (level) { michael@0: // We don't style the Location Bar based on the the 'level' attribute michael@0: // anymore, but still set it for third-party themes. michael@0: if (gURLBar) michael@0: gURLBar.setAttribute("level", level); michael@0: } else { michael@0: if (gURLBar) michael@0: gURLBar.removeAttribute("level"); michael@0: } michael@0: michael@0: try { michael@0: uri = Services.uriFixup.createExposableURI(uri); michael@0: } catch (e) {} michael@0: gIdentityHandler.checkIdentity(this._state, uri); michael@0: }, michael@0: michael@0: // simulate all change notifications after switching tabs michael@0: onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) { michael@0: if (FullZoom.updateBackgroundTabs) michael@0: FullZoom.onLocationChange(gBrowser.currentURI, true); michael@0: var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; michael@0: var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP; michael@0: // use a pseudo-object instead of a (potentially nonexistent) channel for getting michael@0: // a correct error message - and make sure that the UI is always either in michael@0: // loading (STATE_START) or done (STATE_STOP) mode michael@0: this.onStateChange( michael@0: gBrowser.webProgress, michael@0: { URI: gBrowser.currentURI }, michael@0: loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START, michael@0: aStatus michael@0: ); michael@0: // status message and progress value are undefined if we're done with loading michael@0: if (loadingDone) michael@0: return; michael@0: this.onStatusChange(gBrowser.webProgress, null, 0, aMessage); michael@0: } michael@0: }; michael@0: michael@0: var LinkTargetDisplay = { michael@0: get DELAY_SHOW() { michael@0: delete this.DELAY_SHOW; michael@0: return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay"); michael@0: }, michael@0: michael@0: DELAY_HIDE: 250, michael@0: _timer: 0, michael@0: michael@0: get _isVisible () XULBrowserWindow.statusTextField.label != "", michael@0: michael@0: update: function () { michael@0: clearTimeout(this._timer); michael@0: window.removeEventListener("mousemove", this, true); michael@0: michael@0: if (!XULBrowserWindow.overLink) { michael@0: if (XULBrowserWindow.hideOverLinkImmediately) michael@0: this._hide(); michael@0: else michael@0: this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE); michael@0: return; michael@0: } michael@0: michael@0: if (this._isVisible) { michael@0: XULBrowserWindow.updateStatusField(); michael@0: } else { michael@0: // Let the display appear when the mouse doesn't move within the delay michael@0: this._showDelayed(); michael@0: window.addEventListener("mousemove", this, true); michael@0: } michael@0: }, michael@0: michael@0: handleEvent: function (event) { michael@0: switch (event.type) { michael@0: case "mousemove": michael@0: // Restart the delay since the mouse was moved michael@0: clearTimeout(this._timer); michael@0: this._showDelayed(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: _showDelayed: function () { michael@0: this._timer = setTimeout(function (self) { michael@0: XULBrowserWindow.updateStatusField(); michael@0: window.removeEventListener("mousemove", self, true); michael@0: }, this.DELAY_SHOW, this); michael@0: }, michael@0: michael@0: _hide: function () { michael@0: clearTimeout(this._timer); michael@0: michael@0: XULBrowserWindow.updateStatusField(); michael@0: } michael@0: }; michael@0: michael@0: var CombinedStopReload = { michael@0: init: function () { michael@0: if (this._initialized) michael@0: return; michael@0: michael@0: let reload = document.getElementById("urlbar-reload-button"); michael@0: let stop = document.getElementById("urlbar-stop-button"); michael@0: if (!stop || !reload || reload.nextSibling != stop) michael@0: return; michael@0: michael@0: this._initialized = true; michael@0: if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") michael@0: reload.setAttribute("displaystop", "true"); michael@0: stop.addEventListener("click", this, false); michael@0: this.reload = reload; michael@0: this.stop = stop; michael@0: }, michael@0: michael@0: uninit: function () { michael@0: if (!this._initialized) michael@0: return; michael@0: michael@0: this._cancelTransition(); michael@0: this._initialized = false; michael@0: this.stop.removeEventListener("click", this, false); michael@0: this.reload = null; michael@0: this.stop = null; michael@0: }, michael@0: michael@0: handleEvent: function (event) { michael@0: // the only event we listen to is "click" on the stop button michael@0: if (event.button == 0 && michael@0: !this.stop.disabled) michael@0: this._stopClicked = true; michael@0: }, michael@0: michael@0: switchToStop: function () { michael@0: if (!this._initialized) michael@0: return; michael@0: michael@0: this._cancelTransition(); michael@0: this.reload.setAttribute("displaystop", "true"); michael@0: }, michael@0: michael@0: switchToReload: function (aDelay) { michael@0: if (!this._initialized) michael@0: return; michael@0: michael@0: this.reload.removeAttribute("displaystop"); michael@0: michael@0: if (!aDelay || this._stopClicked) { michael@0: this._stopClicked = false; michael@0: this._cancelTransition(); michael@0: this.reload.disabled = XULBrowserWindow.reloadCommand michael@0: .getAttribute("disabled") == "true"; michael@0: return; michael@0: } michael@0: michael@0: if (this._timer) michael@0: return; michael@0: michael@0: // Temporarily disable the reload button to prevent the user from michael@0: // accidentally reloading the page when intending to click the stop button michael@0: this.reload.disabled = true; michael@0: this._timer = setTimeout(function (self) { michael@0: self._timer = 0; michael@0: self.reload.disabled = XULBrowserWindow.reloadCommand michael@0: .getAttribute("disabled") == "true"; michael@0: }, 650, this); michael@0: }, michael@0: michael@0: _cancelTransition: function () { michael@0: if (this._timer) { michael@0: clearTimeout(this._timer); michael@0: this._timer = 0; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: var TabsProgressListener = { michael@0: onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { michael@0: // Collect telemetry data about tab load times. michael@0: if (aWebProgress.isTopLevel) { michael@0: if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) { michael@0: if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) { michael@0: TelemetryStopwatch.start("FX_PAGE_LOAD_MS", aBrowser); michael@0: Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true); michael@0: } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { michael@0: TelemetryStopwatch.finish("FX_PAGE_LOAD_MS", aBrowser); michael@0: } michael@0: } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && michael@0: aStatus == Cr.NS_BINDING_ABORTED) { michael@0: TelemetryStopwatch.cancel("FX_PAGE_LOAD_MS", aBrowser); michael@0: } michael@0: } michael@0: michael@0: // Attach a listener to watch for "click" events bubbling up from error michael@0: // pages and other similar pages (like about:newtab). This lets us fix bugs michael@0: // like 401575 which require error page UI to do privileged things, without michael@0: // letting error pages have any privilege themselves. michael@0: // We can't look for this during onLocationChange since at that point the michael@0: // document URI is not yet the about:-uri of the error page. michael@0: michael@0: let isRemoteBrowser = aBrowser.isRemoteBrowser; michael@0: // We check isRemoteBrowser here to avoid requesting the doc CPOW michael@0: let doc = isRemoteBrowser ? null : aWebProgress.DOMWindow.document; michael@0: michael@0: if (!isRemoteBrowser && michael@0: aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && michael@0: Components.isSuccessCode(aStatus) && michael@0: doc.documentURI.startsWith("about:") && michael@0: !doc.documentURI.toLowerCase().startsWith("about:blank") && michael@0: !doc.documentURI.toLowerCase().startsWith("about:home") && michael@0: !doc.documentElement.hasAttribute("hasBrowserHandlers")) { michael@0: // STATE_STOP may be received twice for documents, thus store an michael@0: // attribute to ensure handling it just once. michael@0: doc.documentElement.setAttribute("hasBrowserHandlers", "true"); michael@0: aBrowser.addEventListener("click", BrowserOnClick, true); michael@0: aBrowser.addEventListener("pagehide", function onPageHide(event) { michael@0: if (event.target.defaultView.frameElement) michael@0: return; michael@0: aBrowser.removeEventListener("click", BrowserOnClick, true); michael@0: aBrowser.removeEventListener("pagehide", onPageHide, true); michael@0: if (event.target.documentElement) michael@0: event.target.documentElement.removeAttribute("hasBrowserHandlers"); michael@0: }, true); michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: if (doc.documentURI.startsWith("about:tabcrashed")) michael@0: TabCrashReporter.onAboutTabCrashedLoad(aBrowser); michael@0: #endif michael@0: } michael@0: }, michael@0: michael@0: onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI, michael@0: aFlags) { michael@0: // Filter out location changes caused by anchor navigation michael@0: // or history.push/pop/replaceState. michael@0: if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) michael@0: return; michael@0: michael@0: // Filter out location changes in sub documents. michael@0: if (!aWebProgress.isTopLevel) michael@0: return; michael@0: michael@0: // Only need to call locationChange if the PopupNotifications object michael@0: // for this window has already been initialized (i.e. its getter no michael@0: // longer exists) michael@0: if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) michael@0: PopupNotifications.locationChange(aBrowser); michael@0: michael@0: gBrowser.getNotificationBox(aBrowser).removeTransientNotifications(); michael@0: michael@0: FullZoom.onLocationChange(aLocationURI, false, aBrowser); michael@0: }, michael@0: michael@0: onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) { michael@0: if (gPrefService.getBoolPref("accessibility.blockautorefresh")) { michael@0: let brandBundle = document.getElementById("bundle_brand"); michael@0: let brandShortName = brandBundle.getString("brandShortName"); michael@0: let refreshButtonText = michael@0: gNavigatorBundle.getString("refreshBlocked.goButton"); michael@0: let refreshButtonAccesskey = michael@0: gNavigatorBundle.getString("refreshBlocked.goButton.accesskey"); michael@0: let message = michael@0: gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel" michael@0: : "refreshBlocked.redirectLabel", michael@0: [brandShortName]); michael@0: let docShell = aWebProgress.DOMWindow michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell); michael@0: let notificationBox = gBrowser.getNotificationBox(aBrowser); michael@0: let notification = notificationBox.getNotificationWithValue("refresh-blocked"); michael@0: if (notification) { michael@0: notification.label = message; michael@0: notification.refreshURI = aURI; michael@0: notification.delay = aDelay; michael@0: notification.docShell = docShell; michael@0: } else { michael@0: let buttons = [{ michael@0: label: refreshButtonText, michael@0: accessKey: refreshButtonAccesskey, michael@0: callback: function (aNotification, aButton) { michael@0: var refreshURI = aNotification.docShell michael@0: .QueryInterface(Ci.nsIRefreshURI); michael@0: refreshURI.forceRefreshURI(aNotification.refreshURI, michael@0: aNotification.delay, true); michael@0: } michael@0: }]; michael@0: notification = michael@0: notificationBox.appendNotification(message, "refresh-blocked", michael@0: "chrome://browser/skin/Info.png", michael@0: notificationBox.PRIORITY_INFO_MEDIUM, michael@0: buttons); michael@0: notification.refreshURI = aURI; michael@0: notification.delay = aDelay; michael@0: notification.docShell = docShell; michael@0: } michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: function nsBrowserAccess() { } michael@0: michael@0: nsBrowserAccess.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]), michael@0: michael@0: _openURIInNewTab: function(aURI, aOpener, aIsExternal) { michael@0: let win, needToFocusWin; michael@0: michael@0: // try the current window. if we're in a popup, fall back on the most recent browser window michael@0: if (window.toolbar.visible) michael@0: win = window; michael@0: else { michael@0: let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window); michael@0: win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate}); michael@0: needToFocusWin = true; michael@0: } michael@0: michael@0: if (!win) { michael@0: // we couldn't find a suitable window, a new one needs to be opened. michael@0: return null; michael@0: } michael@0: michael@0: if (aIsExternal && (!aURI || aURI.spec == "about:blank")) { michael@0: win.BrowserOpenTab(); // this also focuses the location bar michael@0: win.focus(); michael@0: return win.gBrowser.selectedBrowser; michael@0: } michael@0: michael@0: let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"); michael@0: let referrer = aOpener ? makeURI(aOpener.location.href) : null; michael@0: michael@0: let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", { michael@0: referrerURI: referrer, michael@0: fromExternal: aIsExternal, michael@0: inBackground: loadInBackground}); michael@0: let browser = win.gBrowser.getBrowserForTab(tab); michael@0: michael@0: if (needToFocusWin || (!loadInBackground && aIsExternal)) michael@0: win.focus(); michael@0: michael@0: return browser; michael@0: }, michael@0: michael@0: openURI: function (aURI, aOpener, aWhere, aContext) { michael@0: var newWindow = null; michael@0: var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); michael@0: michael@0: if (isExternal && aURI && aURI.schemeIs("chrome")) { michael@0: dump("use -chrome command-line option to load external chrome urls\n"); michael@0: return null; michael@0: } michael@0: michael@0: if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { michael@0: if (isExternal && michael@0: gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external")) michael@0: aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external"); michael@0: else michael@0: aWhere = gPrefService.getIntPref("browser.link.open_newwindow"); michael@0: } michael@0: switch (aWhere) { michael@0: case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW : michael@0: // FIXME: Bug 408379. So how come this doesn't send the michael@0: // referrer like the other loads do? michael@0: var url = aURI ? aURI.spec : "about:blank"; michael@0: // Pass all params to openDialog to ensure that "url" isn't passed through michael@0: // loadOneOrMoreURIs, which splits based on "|" michael@0: newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null); michael@0: break; michael@0: case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB : michael@0: let browser = this._openURIInNewTab(aURI, aOpener, isExternal); michael@0: if (browser) michael@0: newWindow = browser.contentWindow; michael@0: break; michael@0: default : // OPEN_CURRENTWINDOW or an illegal value michael@0: newWindow = content; michael@0: if (aURI) { michael@0: let referrer = aOpener ? makeURI(aOpener.location.href) : null; michael@0: let loadflags = isExternal ? michael@0: Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL : michael@0: Ci.nsIWebNavigation.LOAD_FLAGS_NONE; michael@0: gBrowser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null); michael@0: } michael@0: if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground")) michael@0: window.focus(); michael@0: } michael@0: return newWindow; michael@0: }, michael@0: michael@0: openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) { michael@0: if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) { michael@0: dump("Error: openURIInFrame can only open in new tabs"); michael@0: return null; michael@0: } michael@0: michael@0: var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); michael@0: let browser = this._openURIInNewTab(aURI, aOpener, isExternal); michael@0: if (browser) michael@0: return browser.QueryInterface(Ci.nsIFrameLoaderOwner); michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: isTabContentWindow: function (aWindow) { michael@0: return gBrowser.browsers.some(function (browser) browser.contentWindow == aWindow); michael@0: }, michael@0: michael@0: get contentWindow() { michael@0: return gBrowser.contentWindow; michael@0: } michael@0: } michael@0: michael@0: function getTogglableToolbars() { michael@0: let toolbarNodes = Array.slice(gNavToolbox.childNodes); michael@0: toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars); michael@0: toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname")); michael@0: return toolbarNodes; michael@0: } michael@0: michael@0: function onViewToolbarsPopupShowing(aEvent, aInsertPoint) { michael@0: var popup = aEvent.target; michael@0: if (popup != aEvent.currentTarget) michael@0: return; michael@0: michael@0: // Empty the menu michael@0: for (var i = popup.childNodes.length-1; i >= 0; --i) { michael@0: var deadItem = popup.childNodes[i]; michael@0: if (deadItem.hasAttribute("toolbarId")) michael@0: popup.removeChild(deadItem); michael@0: } michael@0: michael@0: var firstMenuItem = aInsertPoint || popup.firstChild; michael@0: michael@0: let toolbarNodes = getTogglableToolbars(); michael@0: michael@0: for (let toolbar of toolbarNodes) { michael@0: let menuItem = document.createElement("menuitem"); michael@0: let hidingAttribute = toolbar.getAttribute("type") == "menubar" ? michael@0: "autohide" : "collapsed"; michael@0: menuItem.setAttribute("id", "toggle_" + toolbar.id); michael@0: menuItem.setAttribute("toolbarId", toolbar.id); michael@0: menuItem.setAttribute("type", "checkbox"); michael@0: menuItem.setAttribute("label", toolbar.getAttribute("toolbarname")); michael@0: menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true"); michael@0: menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey")); michael@0: if (popup.id != "toolbar-context-menu") michael@0: menuItem.setAttribute("key", toolbar.getAttribute("key")); michael@0: michael@0: popup.insertBefore(menuItem, firstMenuItem); michael@0: michael@0: menuItem.addEventListener("command", onViewToolbarCommand, false); michael@0: } michael@0: michael@0: michael@0: let moveToPanel = popup.querySelector(".customize-context-moveToPanel"); michael@0: let removeFromToolbar = popup.querySelector(".customize-context-removeFromToolbar"); michael@0: // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items. michael@0: if (!moveToPanel || !removeFromToolbar) { michael@0: return; michael@0: } michael@0: michael@0: // triggerNode can be a nested child element of a toolbaritem. michael@0: let toolbarItem = popup.triggerNode; michael@0: michael@0: if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") { michael@0: toolbarItem = toolbarItem.firstChild; michael@0: } else if (toolbarItem && toolbarItem.localName != "toolbar") { michael@0: while (toolbarItem && toolbarItem.parentNode) { michael@0: let parent = toolbarItem.parentNode; michael@0: if ((parent.classList && parent.classList.contains("customization-target")) || michael@0: parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well. michael@0: parent.localName == "toolbarpaletteitem" || michael@0: parent.localName == "toolbar") michael@0: break; michael@0: toolbarItem = parent; michael@0: } michael@0: } else { michael@0: toolbarItem = null; michael@0: } michael@0: michael@0: // Right-clicking on an empty part of the tabstrip will exit michael@0: // the above loop with toolbarItem being the xul:document. michael@0: // That has no parentNode, and we should disable the items in michael@0: // this case. michael@0: let movable = toolbarItem && toolbarItem.parentNode && michael@0: CustomizableUI.isWidgetRemovable(toolbarItem); michael@0: if (movable) { michael@0: moveToPanel.removeAttribute("disabled"); michael@0: removeFromToolbar.removeAttribute("disabled"); michael@0: } else { michael@0: moveToPanel.setAttribute("disabled", true); michael@0: removeFromToolbar.setAttribute("disabled", true); michael@0: } michael@0: } michael@0: michael@0: function onViewToolbarCommand(aEvent) { michael@0: var toolbarId = aEvent.originalTarget.getAttribute("toolbarId"); michael@0: var isVisible = aEvent.originalTarget.getAttribute("checked") == "true"; michael@0: CustomizableUI.setToolbarVisibility(toolbarId, isVisible); michael@0: } michael@0: michael@0: function setToolbarVisibility(toolbar, isVisible, persist=true) { michael@0: let hidingAttribute; michael@0: if (toolbar.getAttribute("type") == "menubar") { michael@0: hidingAttribute = "autohide"; michael@0: #ifdef MOZ_WIDGET_GTK michael@0: Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible); michael@0: #endif michael@0: } else { michael@0: hidingAttribute = "collapsed"; michael@0: } michael@0: michael@0: toolbar.setAttribute(hidingAttribute, !isVisible); michael@0: if (persist) { michael@0: document.persist(toolbar.id, hidingAttribute); michael@0: } michael@0: michael@0: let eventParams = { michael@0: detail: { michael@0: visible: isVisible michael@0: }, michael@0: bubbles: true michael@0: }; michael@0: let event = new CustomEvent("toolbarvisibilitychange", eventParams); michael@0: toolbar.dispatchEvent(event); michael@0: michael@0: PlacesToolbarHelper.init(); michael@0: BookmarkingUI.onToolbarVisibilityChange(); michael@0: gBrowser.updateWindowResizers(); michael@0: if (isVisible) michael@0: ToolbarIconColor.inferFromText(); michael@0: } michael@0: michael@0: var TabsInTitlebar = { michael@0: init: function () { michael@0: #ifdef CAN_DRAW_IN_TITLEBAR michael@0: this._readPref(); michael@0: Services.prefs.addObserver(this._prefName, this, false); michael@0: michael@0: // We need to update the appearance of the titlebar when the menu changes michael@0: // from the active to the inactive state. We can't, however, rely on michael@0: // DOMMenuBarInactive, because the menu fires this event and then removes michael@0: // the inactive attribute after an event-loop spin. michael@0: // michael@0: // Because updating the appearance involves sampling the heights and margins michael@0: // of various elements, it's important that the layout be more or less michael@0: // settled before updating the titlebar. So instead of listening to michael@0: // DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to michael@0: // watch the "invalid" attribute directly. michael@0: let menu = document.getElementById("toolbar-menubar"); michael@0: this._menuObserver = new MutationObserver(this._onMenuMutate); michael@0: this._menuObserver.observe(menu, {attributes: true}); michael@0: michael@0: this.onAreaReset = function(aArea) { michael@0: if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR) michael@0: this._update(true); michael@0: }; michael@0: this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) { michael@0: if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR) michael@0: this._update(true); michael@0: }; michael@0: CustomizableUI.addListener(this); michael@0: michael@0: this._initialized = true; michael@0: #endif michael@0: }, michael@0: michael@0: allowedBy: function (condition, allow) { michael@0: #ifdef CAN_DRAW_IN_TITLEBAR michael@0: if (allow) { michael@0: if (condition in this._disallowed) { michael@0: delete this._disallowed[condition]; michael@0: this._update(true); michael@0: } michael@0: } else { michael@0: if (!(condition in this._disallowed)) { michael@0: this._disallowed[condition] = null; michael@0: this._update(true); michael@0: } michael@0: } michael@0: #endif michael@0: }, michael@0: michael@0: updateAppearance: function updateAppearance(aForce) { michael@0: #ifdef CAN_DRAW_IN_TITLEBAR michael@0: this._update(aForce); michael@0: #endif michael@0: }, michael@0: michael@0: get enabled() { michael@0: return document.documentElement.getAttribute("tabsintitlebar") == "true"; michael@0: }, michael@0: michael@0: #ifdef CAN_DRAW_IN_TITLEBAR michael@0: observe: function (subject, topic, data) { michael@0: if (topic == "nsPref:changed") michael@0: this._readPref(); michael@0: }, michael@0: michael@0: _onMenuMutate: function (aMutations) { michael@0: for (let mutation of aMutations) { michael@0: if (mutation.attributeName == "inactive" || michael@0: mutation.attributeName == "autohide") { michael@0: TabsInTitlebar._update(true); michael@0: return; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _initialized: false, michael@0: _disallowed: {}, michael@0: _prefName: "browser.tabs.drawInTitlebar", michael@0: _lastSizeMode: null, michael@0: michael@0: _readPref: function () { michael@0: this.allowedBy("pref", michael@0: Services.prefs.getBoolPref(this._prefName)); michael@0: }, michael@0: michael@0: _update: function (aForce=false) { michael@0: function $(id) document.getElementById(id); michael@0: function rect(ele) ele.getBoundingClientRect(); michael@0: function verticalMargins(cstyle) parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop); michael@0: michael@0: if (!this._initialized || window.fullScreen) michael@0: return; michael@0: michael@0: let allowed = true; michael@0: michael@0: if (!aForce) { michael@0: // _update is called on resize events, because the window is not ready michael@0: // after sizemode events. However, we only care about the event when the michael@0: // sizemode is different from the last time we updated the appearance of michael@0: // the tabs in the titlebar. michael@0: let sizemode = document.documentElement.getAttribute("sizemode"); michael@0: if (this._lastSizeMode == sizemode) { michael@0: return; michael@0: } michael@0: this._lastSizeMode = sizemode; michael@0: } michael@0: michael@0: for (let something in this._disallowed) { michael@0: allowed = false; michael@0: break; michael@0: } michael@0: michael@0: let titlebar = $("titlebar"); michael@0: let titlebarContent = $("titlebar-content"); michael@0: let menubar = $("toolbar-menubar"); michael@0: michael@0: if (allowed) { michael@0: // We set the tabsintitlebar attribute first so that our CSS for michael@0: // tabsintitlebar manifests before we do our measurements. michael@0: document.documentElement.setAttribute("tabsintitlebar", "true"); michael@0: updateTitlebarDisplay(); michael@0: michael@0: // Try to avoid reflows in this code by calculating dimensions first and michael@0: // then later set the properties affecting layout together in a batch. michael@0: michael@0: // Get the full height of the tabs toolbar: michael@0: let tabsToolbar = $("TabsToolbar"); michael@0: let fullTabsHeight = rect(tabsToolbar).height; michael@0: // Buttons first: michael@0: let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width; michael@0: michael@0: #ifdef XP_MACOSX michael@0: let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width; michael@0: // No need to look up the menubar stuff on OS X: michael@0: let menuHeight = 0; michael@0: let fullMenuHeight = 0; michael@0: // Instead, look up the titlebar padding: michael@0: let titlebarPadding = parseInt(window.getComputedStyle(titlebar).paddingTop, 10); michael@0: #else michael@0: // Otherwise, get the height and margins separately for the menubar michael@0: let menuHeight = rect(menubar).height; michael@0: let menuStyles = window.getComputedStyle(menubar); michael@0: let fullMenuHeight = verticalMargins(menuStyles) + menuHeight; michael@0: let tabsStyles = window.getComputedStyle(tabsToolbar); michael@0: fullTabsHeight += verticalMargins(tabsStyles); michael@0: #endif michael@0: michael@0: // If the navbar overlaps the tabbar using negative margins, we need to take those into michael@0: // account so we don't overlap it michael@0: let navbarMarginTop = parseFloat(window.getComputedStyle($("nav-bar")).marginTop); michael@0: navbarMarginTop = Math.min(navbarMarginTop, 0); michael@0: michael@0: // And get the height of what's in the titlebar: michael@0: let titlebarContentHeight = rect(titlebarContent).height; michael@0: michael@0: // Begin setting CSS properties which will cause a reflow michael@0: michael@0: // If the menubar is around (menuHeight is non-zero), try to adjust michael@0: // its full height (i.e. including margins) to match the titlebar, michael@0: // by changing the menubar's bottom padding michael@0: if (menuHeight) { michael@0: // Calculate the difference between the titlebar's height and that of the menubar michael@0: let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight; michael@0: let paddingBottom; michael@0: // The titlebar is bigger: michael@0: if (menuTitlebarDelta > 0) { michael@0: fullMenuHeight += menuTitlebarDelta; michael@0: // If there is already padding on the menubar, we need to add that michael@0: // to the difference so the total padding is correct: michael@0: if ((paddingBottom = menuStyles.paddingBottom)) { michael@0: menuTitlebarDelta += parseFloat(paddingBottom); michael@0: } michael@0: menubar.style.paddingBottom = menuTitlebarDelta + "px"; michael@0: // The menubar is bigger, but has bottom padding we can remove: michael@0: } else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) { michael@0: let existingPadding = parseFloat(paddingBottom); michael@0: // menuTitlebarDelta is negative; work out what's left, but don't set negative padding: michael@0: let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta); michael@0: menubar.style.paddingBottom = desiredPadding + "px"; michael@0: // We've changed the menu height now: michael@0: fullMenuHeight += desiredPadding - existingPadding; michael@0: } michael@0: } michael@0: michael@0: // Next, we calculate how much we need to stretch the titlebar down to michael@0: // go all the way to the bottom of the tab strip, if necessary. michael@0: let tabAndMenuHeight = fullTabsHeight + fullMenuHeight; michael@0: michael@0: if (tabAndMenuHeight > titlebarContentHeight) { michael@0: // We need to increase the titlebar content's outer height (ie including margins) michael@0: // to match the tab and menu height: michael@0: let extraMargin = tabAndMenuHeight - titlebarContentHeight; michael@0: // We need to reduce the height by the amount of navbar overlap michael@0: // (this value is 0 or negative): michael@0: extraMargin += navbarMarginTop; michael@0: // On non-OSX, we can just use bottom margin: michael@0: #ifndef XP_MACOSX michael@0: titlebarContent.style.marginBottom = extraMargin + "px"; michael@0: #endif michael@0: titlebarContentHeight += extraMargin; michael@0: } michael@0: michael@0: // Then we bring up the titlebar by the same amount, but we add any negative margin: michael@0: titlebar.style.marginBottom = "-" + titlebarContentHeight + "px"; michael@0: michael@0: michael@0: // Finally, size the placeholders: michael@0: #ifdef XP_MACOSX michael@0: this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth); michael@0: #endif michael@0: this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth); michael@0: michael@0: if (!this._draghandles) { michael@0: this._draghandles = {}; michael@0: let tmp = {}; michael@0: Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp); michael@0: michael@0: let mouseDownCheck = function () { michael@0: return !this._dragBindingAlive && TabsInTitlebar.enabled; michael@0: }; michael@0: michael@0: this._draghandles.tabsToolbar = new tmp.WindowDraggingElement(tabsToolbar); michael@0: this._draghandles.tabsToolbar.mouseDownCheck = mouseDownCheck; michael@0: michael@0: this._draghandles.navToolbox = new tmp.WindowDraggingElement(gNavToolbox); michael@0: this._draghandles.navToolbox.mouseDownCheck = mouseDownCheck; michael@0: } michael@0: } else { michael@0: document.documentElement.removeAttribute("tabsintitlebar"); michael@0: updateTitlebarDisplay(); michael@0: michael@0: #ifdef XP_MACOSX michael@0: let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width; michael@0: this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth); michael@0: #endif michael@0: // Reset the margins and padding that might have been modified: michael@0: titlebarContent.style.marginTop = ""; michael@0: titlebarContent.style.marginBottom = ""; michael@0: titlebar.style.marginBottom = ""; michael@0: menubar.style.paddingBottom = ""; michael@0: } michael@0: michael@0: ToolbarIconColor.inferFromText(); michael@0: }, michael@0: michael@0: _sizePlaceholder: function (type, width) { michael@0: Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"), michael@0: function (node) { node.width = width; }); michael@0: }, michael@0: #endif michael@0: michael@0: uninit: function () { michael@0: #ifdef CAN_DRAW_IN_TITLEBAR michael@0: this._initialized = false; michael@0: Services.prefs.removeObserver(this._prefName, this); michael@0: this._menuObserver.disconnect(); michael@0: CustomizableUI.removeListener(this); michael@0: #endif michael@0: } michael@0: }; michael@0: michael@0: #ifdef CAN_DRAW_IN_TITLEBAR michael@0: function updateTitlebarDisplay() { michael@0: michael@0: #ifdef XP_MACOSX michael@0: // OS X and the other platforms differ enough to necessitate this kind of michael@0: // special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR, michael@0: // we draw in the OS X titlebar when putting the tabs up there. However, OS X michael@0: // also draws in the titlebar when a lightweight theme is applied, regardless michael@0: // of whether or not the tabs are drawn in the titlebar. michael@0: if (TabsInTitlebar.enabled) { michael@0: document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1"); michael@0: document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1"); michael@0: document.documentElement.removeAttribute("drawtitle"); michael@0: } else { michael@0: // We set chromemargin-nonlwtheme to "" instead of removing it as a way of michael@0: // making sure that LightweightThemeConsumer doesn't take it upon itself to michael@0: // detect this value again if and when we do a lwtheme state change. michael@0: document.documentElement.setAttribute("chromemargin-nonlwtheme", ""); michael@0: let isCustomizing = document.documentElement.hasAttribute("customizing"); michael@0: let hasLWTheme = document.documentElement.hasAttribute("lwtheme"); michael@0: let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window); michael@0: if ((!hasLWTheme || isCustomizing) && !isPrivate) { michael@0: document.documentElement.removeAttribute("chromemargin"); michael@0: } michael@0: document.documentElement.setAttribute("drawtitle", "true"); michael@0: } michael@0: michael@0: #else michael@0: michael@0: if (TabsInTitlebar.enabled) michael@0: document.documentElement.setAttribute("chromemargin", "0,2,2,2"); michael@0: else michael@0: document.documentElement.removeAttribute("chromemargin"); michael@0: #endif michael@0: } michael@0: #endif michael@0: michael@0: #ifdef CAN_DRAW_IN_TITLEBAR michael@0: function onTitlebarMaxClick() { michael@0: if (window.windowState == window.STATE_MAXIMIZED) michael@0: window.restore(); michael@0: else michael@0: window.maximize(); michael@0: } michael@0: #endif michael@0: michael@0: function displaySecurityInfo() michael@0: { michael@0: BrowserPageInfo(null, "securityTab"); michael@0: } michael@0: michael@0: /** michael@0: * Opens or closes the sidebar identified by commandID. michael@0: * michael@0: * @param commandID a string identifying the sidebar to toggle; see the michael@0: * note below. (Optional if a sidebar is already open.) michael@0: * @param forceOpen boolean indicating whether the sidebar should be michael@0: * opened regardless of its current state (optional). michael@0: * @note michael@0: * We expect to find a xul:broadcaster element with the specified ID. michael@0: * The following attributes on that element may be used and/or modified: michael@0: * - id (required) the string to match commandID. The convention michael@0: * is to use this naming scheme: 'viewSidebar'. michael@0: * - sidebarurl (required) specifies the URL to load in this sidebar. michael@0: * - sidebartitle or label (in that order) specify the title to michael@0: * display on the sidebar. michael@0: * - checked indicates whether the sidebar is currently displayed. michael@0: * Note that toggleSidebar updates this attribute when michael@0: * it changes the sidebar's visibility. michael@0: * - group this attribute must be set to "sidebar". michael@0: */ michael@0: function toggleSidebar(commandID, forceOpen) { michael@0: michael@0: var sidebarBox = document.getElementById("sidebar-box"); michael@0: if (!commandID) michael@0: commandID = sidebarBox.getAttribute("sidebarcommand"); michael@0: michael@0: var sidebarBroadcaster = document.getElementById(commandID); michael@0: var sidebar = document.getElementById("sidebar"); // xul:browser michael@0: var sidebarTitle = document.getElementById("sidebar-title"); michael@0: var sidebarSplitter = document.getElementById("sidebar-splitter"); michael@0: michael@0: if (sidebarBroadcaster.getAttribute("checked") == "true") { michael@0: if (!forceOpen) { michael@0: // Replace the document currently displayed in the sidebar with about:blank michael@0: // so that we can free memory by unloading the page. We need to explicitly michael@0: // create a new content viewer because the old one doesn't get destroyed michael@0: // until about:blank has loaded (which does not happen as long as the michael@0: // element is hidden). michael@0: sidebar.setAttribute("src", "about:blank"); michael@0: sidebar.docShell.createAboutBlankContentViewer(null); michael@0: michael@0: sidebarBroadcaster.removeAttribute("checked"); michael@0: sidebarBox.setAttribute("sidebarcommand", ""); michael@0: sidebarTitle.value = ""; michael@0: sidebarBox.hidden = true; michael@0: sidebarSplitter.hidden = true; michael@0: gBrowser.selectedBrowser.focus(); michael@0: } else { michael@0: fireSidebarFocusedEvent(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // now we need to show the specified sidebar michael@0: michael@0: // ..but first update the 'checked' state of all sidebar broadcasters michael@0: var broadcasters = document.getElementsByAttribute("group", "sidebar"); michael@0: for (let broadcaster of broadcasters) { michael@0: // skip elements that observe sidebar broadcasters and random michael@0: // other elements michael@0: if (broadcaster.localName != "broadcaster") michael@0: continue; michael@0: michael@0: if (broadcaster != sidebarBroadcaster) michael@0: broadcaster.removeAttribute("checked"); michael@0: else michael@0: sidebarBroadcaster.setAttribute("checked", "true"); michael@0: } michael@0: michael@0: sidebarBox.hidden = false; michael@0: sidebarSplitter.hidden = false; michael@0: michael@0: var url = sidebarBroadcaster.getAttribute("sidebarurl"); michael@0: var title = sidebarBroadcaster.getAttribute("sidebartitle"); michael@0: if (!title) michael@0: title = sidebarBroadcaster.getAttribute("label"); michael@0: sidebar.setAttribute("src", url); // kick off async load michael@0: sidebarBox.setAttribute("sidebarcommand", sidebarBroadcaster.id); michael@0: sidebarTitle.value = title; michael@0: michael@0: // We set this attribute here in addition to setting it on the michael@0: // element itself, because the code in gBrowserInit.onUnload persists this michael@0: // attribute, not the "src" of the . The reason it michael@0: // does that is that we want to delay sidebar load a bit when a browser michael@0: // window opens. See delayedStartup(). michael@0: sidebarBox.setAttribute("src", url); michael@0: michael@0: if (sidebar.contentDocument.location.href != url) michael@0: sidebar.addEventListener("load", sidebarOnLoad, true); michael@0: else // older code handled this case, so we do it too michael@0: fireSidebarFocusedEvent(); michael@0: } michael@0: michael@0: function sidebarOnLoad(event) { michael@0: var sidebar = document.getElementById("sidebar"); michael@0: sidebar.removeEventListener("load", sidebarOnLoad, true); michael@0: // We're handling the 'load' event before it bubbles up to the usual michael@0: // (non-capturing) event handlers. Let it bubble up before firing the michael@0: // SidebarFocused event. michael@0: setTimeout(fireSidebarFocusedEvent, 0); michael@0: } michael@0: michael@0: /** michael@0: * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar michael@0: * a chance to adjust focus as needed. An additional event is needed, because michael@0: * we don't want to focus the sidebar when it's opened on startup or in a new michael@0: * window, only when the user opens the sidebar. michael@0: */ michael@0: function fireSidebarFocusedEvent() { michael@0: var sidebar = document.getElementById("sidebar"); michael@0: var event = document.createEvent("Events"); michael@0: event.initEvent("SidebarFocused", true, false); michael@0: sidebar.contentWindow.dispatchEvent(event); michael@0: } michael@0: michael@0: michael@0: var gHomeButton = { michael@0: prefDomain: "browser.startup.homepage", michael@0: observe: function (aSubject, aTopic, aPrefName) michael@0: { michael@0: if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain) michael@0: return; michael@0: michael@0: this.updateTooltip(); michael@0: }, michael@0: michael@0: updateTooltip: function (homeButton) michael@0: { michael@0: if (!homeButton) michael@0: homeButton = document.getElementById("home-button"); michael@0: if (homeButton) { michael@0: var homePage = this.getHomePage(); michael@0: homePage = homePage.replace(/\|/g,', '); michael@0: if (homePage.toLowerCase() == "about:home") michael@0: homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip")); michael@0: else michael@0: homeButton.setAttribute("tooltiptext", homePage); michael@0: } michael@0: }, michael@0: michael@0: getHomePage: function () michael@0: { michael@0: var url; michael@0: try { michael@0: url = gPrefService.getComplexValue(this.prefDomain, michael@0: Components.interfaces.nsIPrefLocalizedString).data; michael@0: } catch (e) { michael@0: } michael@0: michael@0: // use this if we can't find the pref michael@0: if (!url) { michael@0: var configBundle = Services.strings michael@0: .createBundle("chrome://branding/locale/browserconfig.properties"); michael@0: url = configBundle.GetStringFromName(this.prefDomain); michael@0: } michael@0: michael@0: return url; michael@0: }, michael@0: michael@0: updatePersonalToolbarStyle: function (homeButton) michael@0: { michael@0: if (!homeButton) michael@0: homeButton = document.getElementById("home-button"); michael@0: if (homeButton) michael@0: homeButton.className = homeButton.parentNode.id == "PersonalToolbar" michael@0: || homeButton.parentNode.parentNode.id == "PersonalToolbar" ? michael@0: homeButton.className.replace("toolbarbutton-1", "bookmark-item") : michael@0: homeButton.className.replace("bookmark-item", "toolbarbutton-1"); michael@0: }, michael@0: }; michael@0: michael@0: const nodeToTooltipMap = { michael@0: "bookmarks-menu-button": "bookmarksMenuButton.tooltip", michael@0: #ifdef XP_MACOSX michael@0: "print-button": "printButton.tooltip", michael@0: #endif michael@0: "new-window-button": "newWindowButton.tooltip", michael@0: "fullscreen-button": "fullscreenButton.tooltip", michael@0: "tabview-button": "tabviewButton.tooltip", michael@0: }; michael@0: const nodeToShortcutMap = { michael@0: "bookmarks-menu-button": "manBookmarkKb", michael@0: #ifdef XP_MACOSX michael@0: "print-button": "printKb", michael@0: #endif michael@0: "new-window-button": "key_newNavigator", michael@0: "fullscreen-button": "key_fullScreen", michael@0: "tabview-button": "key_tabview", michael@0: }; michael@0: const gDynamicTooltipCache = new Map(); michael@0: function UpdateDynamicShortcutTooltipText(aTooltip) { michael@0: let nodeId = aTooltip.triggerNode.id; michael@0: if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) { michael@0: let strId = nodeToTooltipMap[nodeId]; michael@0: let args = []; michael@0: if (nodeId in nodeToShortcutMap) { michael@0: let shortcutId = nodeToShortcutMap[nodeId]; michael@0: let shortcut = document.getElementById(shortcutId); michael@0: if (shortcut) { michael@0: args.push(ShortcutUtils.prettifyShortcut(shortcut)); michael@0: } michael@0: } michael@0: gDynamicTooltipCache.set(nodeId, gNavigatorBundle.getFormattedString(strId, args)); michael@0: } michael@0: aTooltip.setAttribute("label", gDynamicTooltipCache.get(nodeId)); michael@0: } michael@0: michael@0: /** michael@0: * Gets the selected text in the active browser. Leading and trailing michael@0: * whitespace is removed, and consecutive whitespace is replaced by a single michael@0: * space. A maximum of 150 characters will be returned, regardless of the value michael@0: * of aCharLen. michael@0: * michael@0: * @param aCharLen michael@0: * The maximum number of characters to return. michael@0: */ michael@0: function getBrowserSelection(aCharLen) { michael@0: // selections of more than 150 characters aren't useful michael@0: const kMaxSelectionLen = 150; michael@0: const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen); michael@0: michael@0: let [element, focusedWindow] = BrowserUtils.getFocusSync(document); michael@0: var selection = focusedWindow.getSelection().toString(); michael@0: // try getting a selected text in text input. michael@0: if (!selection) { michael@0: var isOnTextInput = function isOnTextInput(elem) { michael@0: // we avoid to return a value if a selection is in password field. michael@0: // ref. bug 565717 michael@0: return elem instanceof HTMLTextAreaElement || michael@0: (elem instanceof HTMLInputElement && elem.mozIsTextField(true)); michael@0: }; michael@0: michael@0: if (isOnTextInput(element)) { michael@0: selection = element.QueryInterface(Ci.nsIDOMNSEditableElement) michael@0: .editor.selection.toString(); michael@0: } michael@0: } michael@0: michael@0: if (selection) { michael@0: if (selection.length > charLen) { michael@0: // only use the first charLen important chars. see bug 221361 michael@0: var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}"); michael@0: pattern.test(selection); michael@0: selection = RegExp.lastMatch; michael@0: } michael@0: michael@0: selection = selection.trim().replace(/\s+/g, " "); michael@0: michael@0: if (selection.length > charLen) michael@0: selection = selection.substr(0, charLen); michael@0: } michael@0: return selection; michael@0: } michael@0: michael@0: var gWebPanelURI; michael@0: function openWebPanel(aTitle, aURI) michael@0: { michael@0: // Ensure that the web panels sidebar is open. michael@0: toggleSidebar('viewWebPanelsSidebar', true); michael@0: michael@0: // Set the title of the panel. michael@0: document.getElementById("sidebar-title").value = aTitle; michael@0: michael@0: // Tell the Web Panels sidebar to load the bookmark. michael@0: var sidebar = document.getElementById("sidebar"); michael@0: if (sidebar.docShell && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) { michael@0: sidebar.contentWindow.loadWebPanel(aURI); michael@0: if (gWebPanelURI) { michael@0: gWebPanelURI = ""; michael@0: sidebar.removeEventListener("load", asyncOpenWebPanel, true); michael@0: } michael@0: } michael@0: else { michael@0: // The panel is still being constructed. Attach an onload handler. michael@0: if (!gWebPanelURI) michael@0: sidebar.addEventListener("load", asyncOpenWebPanel, true); michael@0: gWebPanelURI = aURI; michael@0: } michael@0: } michael@0: michael@0: function asyncOpenWebPanel(event) michael@0: { michael@0: var sidebar = document.getElementById("sidebar"); michael@0: if (gWebPanelURI && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) michael@0: sidebar.contentWindow.loadWebPanel(gWebPanelURI); michael@0: gWebPanelURI = ""; michael@0: sidebar.removeEventListener("load", asyncOpenWebPanel, true); michael@0: } michael@0: michael@0: /* michael@0: * - [ Dependencies ] --------------------------------------------------------- michael@0: * utilityOverlay.js: michael@0: * - gatherTextUnder michael@0: */ michael@0: michael@0: /** michael@0: * Extracts linkNode and href for the current click target. michael@0: * michael@0: * @param event michael@0: * The click event. michael@0: * @return [href, linkNode]. michael@0: * michael@0: * @note linkNode will be null if the click wasn't on an anchor michael@0: * element (or XLink). michael@0: */ michael@0: function hrefAndLinkNodeForClickEvent(event) michael@0: { michael@0: function isHTMLLink(aNode) michael@0: { michael@0: // Be consistent with what nsContextMenu.js does. michael@0: return ((aNode instanceof HTMLAnchorElement && aNode.href) || michael@0: (aNode instanceof HTMLAreaElement && aNode.href) || michael@0: aNode instanceof HTMLLinkElement); michael@0: } michael@0: michael@0: let node = event.target; michael@0: while (node && !isHTMLLink(node)) { michael@0: node = node.parentNode; michael@0: } michael@0: michael@0: if (node) michael@0: return [node.href, node]; michael@0: michael@0: // If there is no linkNode, try simple XLink. michael@0: let href, baseURI; michael@0: node = event.target; michael@0: while (node && !href) { michael@0: if (node.nodeType == Node.ELEMENT_NODE) { michael@0: href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); michael@0: if (href) michael@0: baseURI = node.baseURI; michael@0: } michael@0: node = node.parentNode; michael@0: } michael@0: michael@0: // In case of XLink, we don't return the node we got href from since michael@0: // callers expect -like elements. michael@0: return [href ? makeURLAbsolute(baseURI, href) : null, null]; michael@0: } michael@0: michael@0: /** michael@0: * Called whenever the user clicks in the content area. michael@0: * michael@0: * @param event michael@0: * The click event. michael@0: * @param isPanelClick michael@0: * Whether the event comes from a web panel. michael@0: * @note default event is prevented if the click is handled. michael@0: */ michael@0: function contentAreaClick(event, isPanelClick) michael@0: { michael@0: if (!event.isTrusted || event.defaultPrevented || event.button == 2) michael@0: return; michael@0: michael@0: let [href, linkNode] = hrefAndLinkNodeForClickEvent(event); michael@0: if (!href) { michael@0: // Not a link, handle middle mouse navigation. michael@0: if (event.button == 1 && michael@0: gPrefService.getBoolPref("middlemouse.contentLoadURL") && michael@0: !gPrefService.getBoolPref("general.autoScroll")) { michael@0: middleMousePaste(event); michael@0: event.preventDefault(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // This code only applies if we have a linkNode (i.e. clicks on real anchor michael@0: // elements, as opposed to XLink). michael@0: if (linkNode && event.button == 0 && michael@0: !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { michael@0: // A Web panel's links should target the main content area. Do this michael@0: // if no modifier keys are down and if there's no target or the target michael@0: // equals _main (the IE convention) or _content (the Mozilla convention). michael@0: let target = linkNode.target; michael@0: let mainTarget = !target || target == "_content" || target == "_main"; michael@0: if (isPanelClick && mainTarget) { michael@0: // javascript and data links should be executed in the current browser. michael@0: if (linkNode.getAttribute("onclick") || michael@0: href.startsWith("javascript:") || michael@0: href.startsWith("data:")) michael@0: return; michael@0: michael@0: try { michael@0: urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal); michael@0: } michael@0: catch(ex) { michael@0: // Prevent loading unsecure destinations. michael@0: event.preventDefault(); michael@0: return; michael@0: } michael@0: michael@0: loadURI(href, null, null, false); michael@0: event.preventDefault(); michael@0: return; michael@0: } michael@0: michael@0: if (linkNode.getAttribute("rel") == "sidebar") { michael@0: // This is the Opera convention for a special link that, when clicked, michael@0: // allows to add a sidebar panel. The link's title attribute contains michael@0: // the title that should be used for the sidebar panel. michael@0: PlacesUIUtils.showBookmarkDialog({ action: "add" michael@0: , type: "bookmark" michael@0: , uri: makeURI(href) michael@0: , title: linkNode.getAttribute("title") michael@0: , loadBookmarkInSidebar: true michael@0: , hiddenRows: [ "description" michael@0: , "location" michael@0: , "keyword" ] michael@0: }, window); michael@0: event.preventDefault(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: handleLinkClick(event, href, linkNode); michael@0: michael@0: // Mark the page as a user followed link. This is done so that history can michael@0: // distinguish automatic embed visits from user activated ones. For example michael@0: // pages loaded in frames are embed visits and lost with the session, while michael@0: // visits across frames should be preserved. michael@0: try { michael@0: if (!PrivateBrowsingUtils.isWindowPrivate(window)) michael@0: PlacesUIUtils.markPageAsFollowedLink(href); michael@0: } catch (ex) { /* Skip invalid URIs. */ } michael@0: } michael@0: michael@0: /** michael@0: * Handles clicks on links. michael@0: * michael@0: * @return true if the click event was handled, false otherwise. michael@0: */ michael@0: function handleLinkClick(event, href, linkNode) { michael@0: if (event.button == 2) // right click michael@0: return false; michael@0: michael@0: var where = whereToOpenLink(event); michael@0: if (where == "current") michael@0: return false; michael@0: michael@0: var doc = event.target.ownerDocument; michael@0: michael@0: if (where == "save") { michael@0: saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true, michael@0: true, doc.documentURIObject, doc); michael@0: event.preventDefault(); michael@0: return true; michael@0: } michael@0: michael@0: var referrerURI = doc.documentURIObject; michael@0: // if the mixedContentChannel is present and the referring URI passes michael@0: // a same origin check with the target URI, we can preserve the users michael@0: // decision of disabling MCB on a page for it's child tabs. michael@0: var persistDisableMCBInChildTab = false; michael@0: michael@0: if (where == "tab" && gBrowser.docShell.mixedContentChannel) { michael@0: const sm = Services.scriptSecurityManager; michael@0: try { michael@0: var targetURI = makeURI(href); michael@0: sm.checkSameOriginURI(referrerURI, targetURI, false); michael@0: persistDisableMCBInChildTab = true; michael@0: } michael@0: catch (e) { } michael@0: } michael@0: michael@0: urlSecurityCheck(href, doc.nodePrincipal); michael@0: openLinkIn(href, where, { referrerURI: referrerURI, michael@0: charset: doc.characterSet, michael@0: disableMCB: persistDisableMCBInChildTab}); michael@0: event.preventDefault(); michael@0: return true; michael@0: } michael@0: michael@0: function middleMousePaste(event) { michael@0: let clipboard = readFromClipboard(); michael@0: if (!clipboard) michael@0: return; michael@0: michael@0: // Strip embedded newlines and surrounding whitespace, to match the URL michael@0: // bar's behavior (stripsurroundingwhitespace) michael@0: clipboard = clipboard.replace(/\s*\n\s*/g, ""); michael@0: michael@0: // if it's not the current tab, we don't need to do anything because the michael@0: // browser doesn't exist. michael@0: let where = whereToOpenLink(event, true, false); michael@0: let lastLocationChange; michael@0: if (where == "current") { michael@0: lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; michael@0: } michael@0: michael@0: getShortcutOrURIAndPostData(clipboard, data => { michael@0: try { michael@0: makeURI(data.url); michael@0: } catch (ex) { michael@0: // Not a valid URI. michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: addToUrlbarHistory(data.url); michael@0: } catch (ex) { michael@0: // Things may go wrong when adding url to session history, michael@0: // but don't let that interfere with the loading of the url. michael@0: Cu.reportError(ex); michael@0: } michael@0: michael@0: if (where != "current" || michael@0: lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { michael@0: openUILink(data.url, event, michael@0: { ignoreButton: true, michael@0: disallowInheritPrincipal: !data.mayInheritPrincipal }); michael@0: } michael@0: }); michael@0: michael@0: event.stopPropagation(); michael@0: } michael@0: michael@0: function handleDroppedLink(event, url, name) michael@0: { michael@0: let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; michael@0: michael@0: getShortcutOrURIAndPostData(url, data => { michael@0: if (data.url && michael@0: lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) michael@0: loadURI(data.url, null, data.postData, false); michael@0: }); michael@0: michael@0: // Keep the event from being handled by the dragDrop listeners michael@0: // built-in to gecko if they happen to be above us. michael@0: event.preventDefault(); michael@0: }; michael@0: michael@0: function BrowserSetForcedCharacterSet(aCharset) michael@0: { michael@0: if (aCharset) { michael@0: gBrowser.docShell.gatherCharsetMenuTelemetry(); michael@0: gBrowser.docShell.charset = aCharset; michael@0: // Save the forced character-set michael@0: if (!PrivateBrowsingUtils.isWindowPrivate(window)) michael@0: PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset); michael@0: } michael@0: BrowserCharsetReload(); michael@0: } michael@0: michael@0: function BrowserCharsetReload() michael@0: { michael@0: BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE); michael@0: } michael@0: michael@0: function charsetMenuGetElement(parent, charset) { michael@0: return parent.getElementsByAttribute("charset", charset)[0]; michael@0: } michael@0: michael@0: function UpdateCurrentCharset(target) { michael@0: // extract the charset from DOM michael@0: var wnd = document.commandDispatcher.focusedWindow; michael@0: if ((window == wnd) || (wnd == null)) wnd = window.content; michael@0: michael@0: // Uncheck previous item michael@0: if (gPrevCharset) { michael@0: var pref_item = charsetMenuGetElement(target, gPrevCharset); michael@0: if (pref_item) michael@0: pref_item.setAttribute('checked', 'false'); michael@0: } michael@0: michael@0: var menuitem = charsetMenuGetElement(target, CharsetMenu.foldCharset(wnd.document.characterSet)); michael@0: if (menuitem) { michael@0: menuitem.setAttribute('checked', 'true'); michael@0: } michael@0: } michael@0: michael@0: function charsetLoadListener() { michael@0: var charset = CharsetMenu.foldCharset(window.content.document.characterSet); michael@0: michael@0: if (charset.length > 0 && (charset != gLastBrowserCharset)) { michael@0: gPrevCharset = gLastBrowserCharset; michael@0: gLastBrowserCharset = charset; michael@0: } michael@0: } michael@0: michael@0: var gPageStyleMenu = { michael@0: michael@0: // This maps from a element (or, more specifically, a michael@0: // browser's permanentKey) to a CPOW that gives synchronous access michael@0: // to the list of style sheets in a content window. The use of the michael@0: // permanentKey is to avoid issues with docshell swapping. michael@0: _pageStyleSyncHandlers: new WeakMap(), michael@0: michael@0: init: function() { michael@0: let mm = window.messageManager; michael@0: mm.addMessageListener("PageStyle:SetSyncHandler", (msg) => { michael@0: this._pageStyleSyncHandlers.set(msg.target.permanentKey, msg.objects.syncHandler); michael@0: }); michael@0: }, michael@0: michael@0: getAllStyleSheets: function () { michael@0: let handler = this._pageStyleSyncHandlers.get(gBrowser.selectedBrowser.permanentKey); michael@0: try { michael@0: return handler.getAllStyleSheets(); michael@0: } catch (ex) { michael@0: // In case the child died or timed out. michael@0: return []; michael@0: } michael@0: }, michael@0: michael@0: _getStyleSheetInfo: function (browser) { michael@0: let handler = this._pageStyleSyncHandlers.get(gBrowser.selectedBrowser.permanentKey); michael@0: try { michael@0: return handler.getStyleSheetInfo(); michael@0: } catch (ex) { michael@0: // In case the child died or timed out. michael@0: return {styleSheets: [], authorStyleDisabled: false, preferredStyleSheetSet: true}; michael@0: } michael@0: }, michael@0: michael@0: fillPopup: function (menuPopup) { michael@0: let styleSheetInfo = this._getStyleSheetInfo(gBrowser.selectedBrowser); michael@0: var noStyle = menuPopup.firstChild; michael@0: var persistentOnly = noStyle.nextSibling; michael@0: var sep = persistentOnly.nextSibling; michael@0: while (sep.nextSibling) michael@0: menuPopup.removeChild(sep.nextSibling); michael@0: michael@0: let styleSheets = styleSheetInfo.styleSheets; michael@0: var currentStyleSheets = {}; michael@0: var styleDisabled = styleSheetInfo.authorStyleDisabled; michael@0: var haveAltSheets = false; michael@0: var altStyleSelected = false; michael@0: michael@0: for (let currentStyleSheet of styleSheets) { michael@0: if (!currentStyleSheet.disabled) michael@0: altStyleSelected = true; michael@0: michael@0: haveAltSheets = true; michael@0: michael@0: let lastWithSameTitle = null; michael@0: if (currentStyleSheet.title in currentStyleSheets) michael@0: lastWithSameTitle = currentStyleSheets[currentStyleSheet.title]; michael@0: michael@0: if (!lastWithSameTitle) { michael@0: let menuItem = document.createElement("menuitem"); michael@0: menuItem.setAttribute("type", "radio"); michael@0: menuItem.setAttribute("label", currentStyleSheet.title); michael@0: menuItem.setAttribute("data", currentStyleSheet.title); michael@0: menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled); michael@0: menuItem.setAttribute("oncommand", "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));"); michael@0: menuPopup.appendChild(menuItem); michael@0: currentStyleSheets[currentStyleSheet.title] = menuItem; michael@0: } else if (currentStyleSheet.disabled) { michael@0: lastWithSameTitle.removeAttribute("checked"); michael@0: } michael@0: } michael@0: michael@0: noStyle.setAttribute("checked", styleDisabled); michael@0: persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled); michael@0: persistentOnly.hidden = styleSheetInfo.preferredStyleSheetSet ? haveAltSheets : false; michael@0: sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets; michael@0: }, michael@0: michael@0: switchStyleSheet: function (title) { michael@0: let mm = gBrowser.selectedBrowser.messageManager; michael@0: mm.sendAsyncMessage("PageStyle:Switch", {title: title}); michael@0: }, michael@0: michael@0: disableStyle: function () { michael@0: let mm = gBrowser.selectedBrowser.messageManager; michael@0: mm.sendAsyncMessage("PageStyle:Disable"); michael@0: }, michael@0: }; michael@0: michael@0: /* Legacy global page-style functions */ michael@0: var getAllStyleSheets = gPageStyleMenu.getAllStyleSheets.bind(gPageStyleMenu); michael@0: var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu); michael@0: function stylesheetSwitchAll(contentWindow, title) { michael@0: // We ignore the contentWindow param. Add-ons don't appear to use michael@0: // it, and it's difficult to support in e10s (where it will be a michael@0: // CPOW). michael@0: gPageStyleMenu.switchStyleSheet(title); michael@0: } michael@0: function setStyleDisabled(disabled) { michael@0: if (disabled) michael@0: gPageStyleMenu.disableStyle(); michael@0: } michael@0: michael@0: michael@0: var LanguageDetectionListener = { michael@0: init: function() { michael@0: window.messageManager.addMessageListener("LanguageDetection:Result", msg => { michael@0: Translation.languageDetected(msg.target, msg.data); michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: michael@0: var BrowserOffline = { michael@0: _inited: false, michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // BrowserOffline Public Methods michael@0: init: function () michael@0: { michael@0: if (!this._uiElement) michael@0: this._uiElement = document.getElementById("workOfflineMenuitemState"); michael@0: michael@0: Services.obs.addObserver(this, "network:offline-status-changed", false); michael@0: michael@0: this._updateOfflineUI(Services.io.offline); michael@0: michael@0: this._inited = true; michael@0: }, michael@0: michael@0: uninit: function () michael@0: { michael@0: if (this._inited) { michael@0: Services.obs.removeObserver(this, "network:offline-status-changed"); michael@0: } michael@0: }, michael@0: michael@0: toggleOfflineStatus: function () michael@0: { michael@0: var ioService = Services.io; michael@0: michael@0: // Stop automatic management of the offline status michael@0: try { michael@0: ioService.manageOfflineStatus = false; michael@0: } catch (ex) { michael@0: } michael@0: michael@0: if (!ioService.offline && !this._canGoOffline()) { michael@0: this._updateOfflineUI(false); michael@0: return; michael@0: } michael@0: michael@0: ioService.offline = !ioService.offline; michael@0: }, michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // nsIObserver michael@0: observe: function (aSubject, aTopic, aState) michael@0: { michael@0: if (aTopic != "network:offline-status-changed") michael@0: return; michael@0: michael@0: this._updateOfflineUI(aState == "offline"); michael@0: }, michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // BrowserOffline Implementation Methods michael@0: _canGoOffline: function () michael@0: { michael@0: try { michael@0: var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); michael@0: Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null); michael@0: michael@0: // Something aborted the quit process. michael@0: if (cancelGoOffline.data) michael@0: return false; michael@0: } michael@0: catch (ex) { michael@0: } michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: _uiElement: null, michael@0: _updateOfflineUI: function (aOffline) michael@0: { michael@0: var offlineLocked = gPrefService.prefIsLocked("network.online"); michael@0: if (offlineLocked) michael@0: this._uiElement.setAttribute("disabled", "true"); michael@0: michael@0: this._uiElement.setAttribute("checked", aOffline); michael@0: } michael@0: }; michael@0: michael@0: var OfflineApps = { michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // OfflineApps Public Methods michael@0: init: function () michael@0: { michael@0: Services.obs.addObserver(this, "offline-cache-update-completed", false); michael@0: }, michael@0: michael@0: uninit: function () michael@0: { michael@0: Services.obs.removeObserver(this, "offline-cache-update-completed"); michael@0: }, michael@0: michael@0: handleEvent: function(event) { michael@0: if (event.type == "MozApplicationManifest") { michael@0: this.offlineAppRequested(event.originalTarget.defaultView); michael@0: } michael@0: }, michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // OfflineApps Implementation Methods michael@0: michael@0: // XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow michael@0: // were taken from browser/components/feeds/src/WebContentConverter. michael@0: _getBrowserWindowForContentWindow: function(aContentWindow) { michael@0: return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow) michael@0: .wrappedJSObject; michael@0: }, michael@0: michael@0: _getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) { michael@0: // This depends on pseudo APIs of browser.js and tabbrowser.xml michael@0: aContentWindow = aContentWindow.top; michael@0: var browsers = aBrowserWindow.gBrowser.browsers; michael@0: for (let browser of browsers) { michael@0: if (browser.contentWindow == aContentWindow) michael@0: return browser; michael@0: } michael@0: // handle other browser/iframe elements that may need popupnotifications michael@0: let browser = aContentWindow michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell) michael@0: .chromeEventHandler; michael@0: if (browser.getAttribute("popupnotificationanchor")) michael@0: return browser; michael@0: return null; michael@0: }, michael@0: michael@0: _getManifestURI: function(aWindow) { michael@0: if (!aWindow.document.documentElement) michael@0: return null; michael@0: michael@0: var attr = aWindow.document.documentElement.getAttribute("manifest"); michael@0: if (!attr) michael@0: return null; michael@0: michael@0: try { michael@0: var contentURI = makeURI(aWindow.location.href, null, null); michael@0: return makeURI(attr, aWindow.document.characterSet, contentURI); michael@0: } catch (e) { michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: // A cache update isn't tied to a specific window. Try to find michael@0: // the best browser in which to warn the user about space usage michael@0: _getBrowserForCacheUpdate: function(aCacheUpdate) { michael@0: // Prefer the current browser michael@0: var uri = this._getManifestURI(content); michael@0: if (uri && uri.equals(aCacheUpdate.manifestURI)) { michael@0: return gBrowser.selectedBrowser; michael@0: } michael@0: michael@0: var browsers = gBrowser.browsers; michael@0: for (let browser of browsers) { michael@0: uri = this._getManifestURI(browser.contentWindow); michael@0: if (uri && uri.equals(aCacheUpdate.manifestURI)) { michael@0: return browser; michael@0: } michael@0: } michael@0: michael@0: // is this from a non-tab browser/iframe? michael@0: browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]"); michael@0: for (let browser of browsers) { michael@0: uri = this._getManifestURI(browser.contentWindow); michael@0: if (uri && uri.equals(aCacheUpdate.manifestURI)) { michael@0: return browser; michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: _warnUsage: function(aBrowser, aURI) { michael@0: if (!aBrowser) michael@0: return; michael@0: michael@0: let mainAction = { michael@0: label: gNavigatorBundle.getString("offlineApps.manageUsage"), michael@0: accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"), michael@0: callback: OfflineApps.manage michael@0: }; michael@0: michael@0: let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn"); michael@0: let message = gNavigatorBundle.getFormattedString("offlineApps.usage", michael@0: [ aURI.host, michael@0: warnQuota / 1024 ]); michael@0: michael@0: let anchorID = "indexedDB-notification-icon"; michael@0: PopupNotifications.show(aBrowser, "offline-app-usage", message, michael@0: anchorID, mainAction); michael@0: michael@0: // Now that we've warned once, prevent the warning from showing up michael@0: // again. michael@0: Services.perms.add(aURI, "offline-app", michael@0: Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN); michael@0: }, michael@0: michael@0: // XXX: duplicated in preferences/advanced.js michael@0: _getOfflineAppUsage: function (host, groups) michael@0: { michael@0: var cacheService = Cc["@mozilla.org/network/application-cache-service;1"]. michael@0: getService(Ci.nsIApplicationCacheService); michael@0: if (!groups) michael@0: groups = cacheService.getGroups(); michael@0: michael@0: var usage = 0; michael@0: for (let group of groups) { michael@0: var uri = Services.io.newURI(group, null, null); michael@0: if (uri.asciiHost == host) { michael@0: var cache = cacheService.getActiveCache(group); michael@0: usage += cache.usage; michael@0: } michael@0: } michael@0: michael@0: return usage; michael@0: }, michael@0: michael@0: _checkUsage: function(aURI) { michael@0: // if the user has already allowed excessive usage, don't bother checking michael@0: if (Services.perms.testExactPermission(aURI, "offline-app") != michael@0: Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) { michael@0: var usage = this._getOfflineAppUsage(aURI.asciiHost); michael@0: var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn"); michael@0: if (usage >= warnQuota * 1024) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: offlineAppRequested: function(aContentWindow) { michael@0: if (!gPrefService.getBoolPref("browser.offline-apps.notify")) { michael@0: return; michael@0: } michael@0: michael@0: let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow); michael@0: let browser = this._getBrowserForContentWindow(browserWindow, michael@0: aContentWindow); michael@0: michael@0: let currentURI = aContentWindow.document.documentURIObject; michael@0: michael@0: // don't bother showing UI if the user has already made a decision michael@0: if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION) michael@0: return; michael@0: michael@0: try { michael@0: if (gPrefService.getBoolPref("offline-apps.allow_by_default")) { michael@0: // all pages can use offline capabilities, no need to ask the user michael@0: return; michael@0: } michael@0: } catch(e) { michael@0: // this pref isn't set by default, ignore failures michael@0: } michael@0: michael@0: let host = currentURI.asciiHost; michael@0: let notificationID = "offline-app-requested-" + host; michael@0: let notification = PopupNotifications.getNotification(notificationID, browser); michael@0: michael@0: if (notification) { michael@0: notification.options.documents.push(aContentWindow.document); michael@0: } else { michael@0: let mainAction = { michael@0: label: gNavigatorBundle.getString("offlineApps.allow"), michael@0: accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"), michael@0: callback: function() { michael@0: for (let document of notification.options.documents) { michael@0: OfflineApps.allowSite(document); michael@0: } michael@0: } michael@0: }; michael@0: let secondaryActions = [{ michael@0: label: gNavigatorBundle.getString("offlineApps.never"), michael@0: accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"), michael@0: callback: function() { michael@0: for (let document of notification.options.documents) { michael@0: OfflineApps.disallowSite(document); michael@0: } michael@0: } michael@0: }]; michael@0: let message = gNavigatorBundle.getFormattedString("offlineApps.available", michael@0: [ host ]); michael@0: let anchorID = "indexedDB-notification-icon"; michael@0: let options= { michael@0: documents : [ aContentWindow.document ] michael@0: }; michael@0: notification = PopupNotifications.show(browser, notificationID, message, michael@0: anchorID, mainAction, michael@0: secondaryActions, options); michael@0: } michael@0: }, michael@0: michael@0: allowSite: function(aDocument) { michael@0: Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION); michael@0: michael@0: // When a site is enabled while loading, manifest resources will michael@0: // start fetching immediately. This one time we need to do it michael@0: // ourselves. michael@0: this._startFetching(aDocument); michael@0: }, michael@0: michael@0: disallowSite: function(aDocument) { michael@0: Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION); michael@0: }, michael@0: michael@0: manage: function() { michael@0: openAdvancedPreferences("networkTab"); michael@0: }, michael@0: michael@0: _startFetching: function(aDocument) { michael@0: if (!aDocument.documentElement) michael@0: return; michael@0: michael@0: var manifest = aDocument.documentElement.getAttribute("manifest"); michael@0: if (!manifest) michael@0: return; michael@0: michael@0: var manifestURI = makeURI(manifest, aDocument.characterSet, michael@0: aDocument.documentURIObject); michael@0: michael@0: var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]. michael@0: getService(Ci.nsIOfflineCacheUpdateService); michael@0: updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, window); michael@0: }, michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // nsIObserver michael@0: observe: function (aSubject, aTopic, aState) michael@0: { michael@0: if (aTopic == "offline-cache-update-completed") { michael@0: var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate); michael@0: michael@0: var uri = cacheUpdate.manifestURI; michael@0: if (OfflineApps._checkUsage(uri)) { michael@0: var browser = this._getBrowserForCacheUpdate(cacheUpdate); michael@0: if (browser) { michael@0: OfflineApps._warnUsage(browser, cacheUpdate.manifestURI); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: var IndexedDBPromptHelper = { michael@0: _permissionsPrompt: "indexedDB-permissions-prompt", michael@0: _permissionsResponse: "indexedDB-permissions-response", michael@0: michael@0: _quotaPrompt: "indexedDB-quota-prompt", michael@0: _quotaResponse: "indexedDB-quota-response", michael@0: _quotaCancel: "indexedDB-quota-cancel", michael@0: michael@0: _notificationIcon: "indexedDB-notification-icon", michael@0: michael@0: init: michael@0: function IndexedDBPromptHelper_init() { michael@0: Services.obs.addObserver(this, this._permissionsPrompt, false); michael@0: Services.obs.addObserver(this, this._quotaPrompt, false); michael@0: Services.obs.addObserver(this, this._quotaCancel, false); michael@0: }, michael@0: michael@0: uninit: michael@0: function IndexedDBPromptHelper_uninit() { michael@0: Services.obs.removeObserver(this, this._permissionsPrompt); michael@0: Services.obs.removeObserver(this, this._quotaPrompt); michael@0: Services.obs.removeObserver(this, this._quotaCancel); michael@0: }, michael@0: michael@0: observe: michael@0: function IndexedDBPromptHelper_observe(subject, topic, data) { michael@0: if (topic != this._permissionsPrompt && michael@0: topic != this._quotaPrompt && michael@0: topic != this._quotaCancel) { michael@0: throw new Error("Unexpected topic!"); michael@0: } michael@0: michael@0: var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor); michael@0: michael@0: var contentWindow = requestor.getInterface(Ci.nsIDOMWindow); michael@0: var contentDocument = contentWindow.document; michael@0: var browserWindow = michael@0: OfflineApps._getBrowserWindowForContentWindow(contentWindow); michael@0: michael@0: if (browserWindow != window) { michael@0: // Must belong to some other window. michael@0: return; michael@0: } michael@0: michael@0: var browser = michael@0: OfflineApps._getBrowserForContentWindow(browserWindow, contentWindow); michael@0: michael@0: var host = contentDocument.documentURIObject.asciiHost; michael@0: michael@0: var message; michael@0: var responseTopic; michael@0: if (topic == this._permissionsPrompt) { michael@0: message = gNavigatorBundle.getFormattedString("offlineApps.available", michael@0: [ host ]); michael@0: responseTopic = this._permissionsResponse; michael@0: } michael@0: else if (topic == this._quotaPrompt) { michael@0: message = gNavigatorBundle.getFormattedString("indexedDB.usage", michael@0: [ host, data ]); michael@0: responseTopic = this._quotaResponse; michael@0: } michael@0: else if (topic == this._quotaCancel) { michael@0: responseTopic = this._quotaResponse; michael@0: } michael@0: michael@0: const hiddenTimeoutDuration = 30000; // 30 seconds michael@0: const firstTimeoutDuration = 300000; // 5 minutes michael@0: michael@0: var timeoutId; michael@0: michael@0: var observer = requestor.getInterface(Ci.nsIObserver); michael@0: michael@0: var mainAction = { michael@0: label: gNavigatorBundle.getString("offlineApps.allow"), michael@0: accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"), michael@0: callback: function() { michael@0: clearTimeout(timeoutId); michael@0: observer.observe(null, responseTopic, michael@0: Ci.nsIPermissionManager.ALLOW_ACTION); michael@0: } michael@0: }; michael@0: michael@0: var secondaryActions = [ michael@0: { michael@0: label: gNavigatorBundle.getString("offlineApps.never"), michael@0: accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"), michael@0: callback: function() { michael@0: clearTimeout(timeoutId); michael@0: observer.observe(null, responseTopic, michael@0: Ci.nsIPermissionManager.DENY_ACTION); michael@0: } michael@0: } michael@0: ]; michael@0: michael@0: // This will be set to the result of PopupNotifications.show() below, or to michael@0: // the result of PopupNotifications.getNotification() if this is a michael@0: // quotaCancel notification. michael@0: var notification; michael@0: michael@0: function timeoutNotification() { michael@0: // Remove the notification. michael@0: if (notification) { michael@0: notification.remove(); michael@0: } michael@0: michael@0: // Clear all of our timeout stuff. We may be called directly, not just michael@0: // when the timeout actually elapses. michael@0: clearTimeout(timeoutId); michael@0: michael@0: // And tell the page that the popup timed out. michael@0: observer.observe(null, responseTopic, michael@0: Ci.nsIPermissionManager.UNKNOWN_ACTION); michael@0: } michael@0: michael@0: var options = { michael@0: eventCallback: function(state) { michael@0: // Don't do anything if the timeout has not been set yet. michael@0: if (!timeoutId) { michael@0: return; michael@0: } michael@0: michael@0: // If the popup is being dismissed start the short timeout. michael@0: if (state == "dismissed") { michael@0: clearTimeout(timeoutId); michael@0: timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration); michael@0: return; michael@0: } michael@0: michael@0: // If the popup is being re-shown then clear the timeout allowing michael@0: // unlimited waiting. michael@0: if (state == "shown") { michael@0: clearTimeout(timeoutId); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: if (topic == this._quotaCancel) { michael@0: notification = PopupNotifications.getNotification(this._quotaPrompt, michael@0: browser); michael@0: timeoutNotification(); michael@0: return; michael@0: } michael@0: michael@0: notification = PopupNotifications.show(browser, topic, message, michael@0: this._notificationIcon, mainAction, michael@0: secondaryActions, options); michael@0: michael@0: // Set the timeoutId after the popup has been created, and use the long michael@0: // timeout value. If the user doesn't notice the popup after this amount of michael@0: // time then it is most likely not visible and we want to alert the page. michael@0: timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration); michael@0: } michael@0: }; michael@0: michael@0: var CanvasPermissionPromptHelper = { michael@0: _permissionsPrompt: "canvas-permissions-prompt", michael@0: _notificationIcon: "canvas-notification-icon", michael@0: michael@0: init: michael@0: function CanvasPermissionPromptHelper_init() { michael@0: if (document.styleSheets && (document.styleSheets.length > 0)) try { michael@0: let ruleText = "panel[popupid=canvas-permissions-prompt] description { white-space: pre-wrap; }"; michael@0: let sheet = document.styleSheets[0]; michael@0: sheet.insertRule(ruleText, sheet.cssRules.length); michael@0: } catch (e) {}; michael@0: michael@0: Services.obs.addObserver(this, this._permissionsPrompt, false); michael@0: }, michael@0: michael@0: uninit: michael@0: function CanvasPermissionPromptHelper_uninit() { michael@0: Services.obs.removeObserver(this, this._permissionsPrompt, false); michael@0: }, michael@0: michael@0: // aSubject is an nsIDOMWindow. michael@0: // aData is an URL string. michael@0: observe: michael@0: function CanvasPermissionPromptHelper_observe(aSubject, aTopic, aData) { michael@0: if ((aTopic != this._permissionsPrompt) || !aData) michael@0: throw new Error("Unexpected topic or missing URL"); michael@0: michael@0: var uri = makeURI(aData); michael@0: var contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow); michael@0: var contentDocument = contentWindow.document; michael@0: var browserWindow = michael@0: OfflineApps._getBrowserWindowForContentWindow(contentWindow); michael@0: michael@0: if (browserWindow != window) { michael@0: // Must belong to some other window. michael@0: return; michael@0: } michael@0: michael@0: // If canvas prompt is already displayed, just return. This is OK (and michael@0: // more efficient) since this permission is associated with the top michael@0: // browser's URL. michael@0: if (PopupNotifications.getNotification(aTopic, browser)) michael@0: return; michael@0: michael@0: var bundleSvc = Cc["@mozilla.org/intl/stringbundle;1"]. michael@0: getService(Ci.nsIStringBundleService); michael@0: var torBtnBundle; michael@0: try { michael@0: torBtnBundle = bundleSvc.createBundle( michael@0: "chrome://torbutton/locale/torbutton.properties"); michael@0: } catch (e) {} michael@0: michael@0: var message = getLocalizedString("canvas.siteprompt", [ uri.asciiHost ]); michael@0: michael@0: var mainAction = { michael@0: label: getLocalizedString("canvas.notNow"), michael@0: accessKey: getLocalizedString("canvas.notNowAccessKey"), michael@0: callback: function() { michael@0: return null; michael@0: } michael@0: }; michael@0: michael@0: var secondaryActions = [ michael@0: { michael@0: label: getLocalizedString("canvas.never"), michael@0: accessKey: getLocalizedString("canvas.neverAccessKey"), michael@0: callback: function() { michael@0: setCanvasPermission(uri, Ci.nsIPermissionManager.DENY_ACTION); michael@0: } michael@0: }, michael@0: { michael@0: label: getLocalizedString("canvas.allow"), michael@0: accessKey: getLocalizedString("canvas.allowAccessKey"), michael@0: callback: function() { michael@0: setCanvasPermission(uri, Ci.nsIPermissionManager.ALLOW_ACTION); michael@0: } michael@0: } michael@0: ]; michael@0: michael@0: // Since we have a process in place to perform localization for the michael@0: // Torbutton extension, get our strings from the extension if possible. michael@0: function getLocalizedString(aID, aParams) { michael@0: var s; michael@0: if (torBtnBundle) try { michael@0: if (aParams) michael@0: s = torBtnBundle.formatStringFromName(aID, aParams, aParams.length); michael@0: else michael@0: s = torBtnBundle.GetStringFromName(aID); michael@0: } catch (e) {} michael@0: michael@0: if (!s) { michael@0: if (aParams) michael@0: s = gNavigatorBundle.getFormattedString(aID, aParams); michael@0: else michael@0: s = gNavigatorBundle.getString(aID); michael@0: } michael@0: michael@0: return s; michael@0: } michael@0: michael@0: function setCanvasPermission(aURI, aPerm) { michael@0: Services.perms.add(aURI, "canvas/extractData", aPerm, michael@0: Ci.nsIPermissionManager.EXPIRE_NEVER); michael@0: } michael@0: michael@0: var browser = OfflineApps._getBrowserForContentWindow(browserWindow, michael@0: contentWindow); michael@0: notification = PopupNotifications.show(browser, aTopic, message, michael@0: this._notificationIcon, mainAction, michael@0: secondaryActions, null); michael@0: } michael@0: }; michael@0: michael@0: function WindowIsClosing() michael@0: { michael@0: if (TabView.isVisible()) { michael@0: TabView.hide(); michael@0: return false; michael@0: } michael@0: michael@0: if (!closeWindow(false, warnAboutClosingWindow)) michael@0: return false; michael@0: michael@0: // Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process michael@0: if (gMultiProcessBrowser) michael@0: return true; michael@0: michael@0: for (let browser of gBrowser.browsers) { michael@0: let ds = browser.docShell; michael@0: if (ds.contentViewer && !ds.contentViewer.permitUnload()) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Checks if this is the last full *browser* window around. If it is, this will michael@0: * be communicated like quitting. Otherwise, we warn about closing multiple tabs. michael@0: * @returns true if closing can proceed, false if it got cancelled. michael@0: */ michael@0: function warnAboutClosingWindow() { michael@0: // Popups aren't considered full browser windows; we also ignore private windows. michael@0: let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window) && michael@0: !PrivateBrowsingUtils.permanentPrivateBrowsing; michael@0: if (!isPBWindow && !toolbar.visible) michael@0: return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); michael@0: michael@0: // Figure out if there's at least one other browser window around. michael@0: let e = Services.wm.getEnumerator("navigator:browser"); michael@0: let otherPBWindowExists = false; michael@0: let nonPopupPresent = false; michael@0: while (e.hasMoreElements()) { michael@0: let win = e.getNext(); michael@0: if (!win.closed && win != window) { michael@0: if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) michael@0: otherPBWindowExists = true; michael@0: if (win.toolbar.visible) michael@0: nonPopupPresent = true; michael@0: // If the current window is not in private browsing mode we don't need to michael@0: // look for other pb windows, we can leave the loop when finding the michael@0: // first non-popup window. If however the current window is in private michael@0: // browsing mode then we need at least one other pb and one non-popup michael@0: // window to break out early. michael@0: if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (isPBWindow && !otherPBWindowExists) { michael@0: let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"]. michael@0: createInstance(Ci.nsISupportsPRBool); michael@0: exitingCanceled.data = false; michael@0: Services.obs.notifyObservers(exitingCanceled, michael@0: "last-pb-context-exiting", michael@0: null); michael@0: if (exitingCanceled.data) michael@0: return false; michael@0: } michael@0: michael@0: if (nonPopupPresent) { michael@0: return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); michael@0: } michael@0: michael@0: let os = Services.obs; michael@0: michael@0: let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"]. michael@0: createInstance(Ci.nsISupportsPRBool); michael@0: os.notifyObservers(closingCanceled, michael@0: "browser-lastwindow-close-requested", null); michael@0: if (closingCanceled.data) michael@0: return false; michael@0: michael@0: os.notifyObservers(null, "browser-lastwindow-close-granted", null); michael@0: michael@0: #ifdef XP_MACOSX michael@0: // OS X doesn't quit the application when the last window is closed, but keeps michael@0: // the session alive. Hence don't prompt users to save tabs, but warn about michael@0: // closing multiple tabs. michael@0: return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); michael@0: #else michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: var MailIntegration = { michael@0: sendLinkForWindow: function (aWindow) { michael@0: this.sendMessage(aWindow.location.href, michael@0: aWindow.document.title); michael@0: }, michael@0: michael@0: sendMessage: function (aBody, aSubject) { michael@0: // generate a mailto url based on the url and the url's title michael@0: var mailtoUrl = "mailto:"; michael@0: if (aBody) { michael@0: mailtoUrl += "?body=" + encodeURIComponent(aBody); michael@0: mailtoUrl += "&subject=" + encodeURIComponent(aSubject); michael@0: } michael@0: michael@0: var uri = makeURI(mailtoUrl); michael@0: michael@0: // now pass this uri to the operating system michael@0: this._launchExternalUrl(uri); michael@0: }, michael@0: michael@0: // a generic method which can be used to pass arbitrary urls to the operating michael@0: // system. michael@0: // aURL --> a nsIURI which represents the url to launch michael@0: _launchExternalUrl: function (aURL) { michael@0: var extProtocolSvc = michael@0: Cc["@mozilla.org/uriloader/external-protocol-service;1"] michael@0: .getService(Ci.nsIExternalProtocolService); michael@0: if (extProtocolSvc) michael@0: extProtocolSvc.loadUrl(aURL); michael@0: } michael@0: }; michael@0: michael@0: function BrowserOpenAddonsMgr(aView) { michael@0: if (aView) { michael@0: let emWindow; michael@0: let browserWindow; michael@0: michael@0: var receivePong = function receivePong(aSubject, aTopic, aData) { michael@0: let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow); michael@0: if (!emWindow || browserWin == window /* favor the current window */) { michael@0: emWindow = aSubject; michael@0: browserWindow = browserWin; michael@0: } michael@0: } michael@0: Services.obs.addObserver(receivePong, "EM-pong", false); michael@0: Services.obs.notifyObservers(null, "EM-ping", ""); michael@0: Services.obs.removeObserver(receivePong, "EM-pong"); michael@0: michael@0: if (emWindow) { michael@0: emWindow.loadView(aView); michael@0: browserWindow.gBrowser.selectedTab = michael@0: browserWindow.gBrowser._getTabForContentWindow(emWindow); michael@0: emWindow.focus(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: var newLoad = !switchToTabHavingURI("about:addons", true); michael@0: michael@0: if (aView) { michael@0: // This must be a new load, else the ping/pong would have michael@0: // found the window above. michael@0: Services.obs.addObserver(function observer(aSubject, aTopic, aData) { michael@0: Services.obs.removeObserver(observer, aTopic); michael@0: aSubject.loadView(aView); michael@0: }, "EM-loaded", false); michael@0: } michael@0: } michael@0: michael@0: function GetSearchFieldBookmarkData(node) { michael@0: var charset = node.ownerDocument.characterSet; michael@0: michael@0: var formBaseURI = makeURI(node.form.baseURI, michael@0: charset); michael@0: michael@0: var formURI = makeURI(node.form.getAttribute("action"), michael@0: charset, michael@0: formBaseURI); michael@0: michael@0: var spec = formURI.spec; michael@0: michael@0: var isURLEncoded = michael@0: (node.form.method.toUpperCase() == "POST" michael@0: && (node.form.enctype == "application/x-www-form-urlencoded" || michael@0: node.form.enctype == "")); michael@0: michael@0: var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill", michael@0: [node.ownerDocument.title]); michael@0: var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument); michael@0: michael@0: var formData = []; michael@0: michael@0: function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) { michael@0: if (aIsFormUrlEncoded) michael@0: return escape(aName + "=" + aValue); michael@0: else michael@0: return escape(aName) + "=" + escape(aValue); michael@0: } michael@0: michael@0: for (let el of node.form.elements) { michael@0: if (!el.type) // happens with fieldsets michael@0: continue; michael@0: michael@0: if (el == node) { michael@0: formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) : michael@0: // Don't escape "%s", just append michael@0: escapeNameValuePair(el.name, "", false) + "%s"); michael@0: continue; michael@0: } michael@0: michael@0: let type = el.type.toLowerCase(); michael@0: michael@0: if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) || michael@0: type == "hidden" || type == "textarea") || michael@0: ((type == "checkbox" || type == "radio") && el.checked)) { michael@0: formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded)); michael@0: } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) { michael@0: for (var j=0; j < el.options.length; j++) { michael@0: if (el.options[j].selected) michael@0: formData.push(escapeNameValuePair(el.name, el.options[j].value, michael@0: isURLEncoded)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: var postData; michael@0: michael@0: if (isURLEncoded) michael@0: postData = formData.join("&"); michael@0: else michael@0: spec += "?" + formData.join("&"); michael@0: michael@0: return { michael@0: spec: spec, michael@0: title: title, michael@0: description: description, michael@0: postData: postData, michael@0: charSet: charset michael@0: }; michael@0: } michael@0: michael@0: michael@0: function AddKeywordForSearchField() { michael@0: bookmarkData = GetSearchFieldBookmarkData(document.popupNode); michael@0: michael@0: PlacesUIUtils.showBookmarkDialog({ action: "add" michael@0: , type: "bookmark" michael@0: , uri: makeURI(bookmarkData.spec) michael@0: , title: bookmarkData.title michael@0: , description: bookmarkData.description michael@0: , keyword: "" michael@0: , postData: bookmarkData.postData michael@0: , charSet: bookmarkData.charset michael@0: , hiddenRows: [ "location" michael@0: , "description" michael@0: , "tags" michael@0: , "loadInSidebar" ] michael@0: }, window); michael@0: } michael@0: michael@0: function SwitchDocumentDirection(aWindow) { michael@0: // document.dir can also be "auto", in which case it won't change michael@0: if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") { michael@0: aWindow.document.dir = "rtl"; michael@0: } else if (aWindow.document.dir == "rtl") { michael@0: aWindow.document.dir = "ltr"; michael@0: } michael@0: for (var run = 0; run < aWindow.frames.length; run++) michael@0: SwitchDocumentDirection(aWindow.frames[run]); michael@0: } michael@0: michael@0: function convertFromUnicode(charset, str) michael@0: { michael@0: try { michael@0: var unicodeConverter = Components michael@0: .classes["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); michael@0: unicodeConverter.charset = charset; michael@0: str = unicodeConverter.ConvertFromUnicode(str); michael@0: return str + unicodeConverter.Finish(); michael@0: } catch(ex) { michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Re-open a closed tab. michael@0: * @param aIndex michael@0: * The index of the tab (via SessionStore.getClosedTabData) michael@0: * @returns a reference to the reopened tab. michael@0: */ michael@0: function undoCloseTab(aIndex) { michael@0: // wallpaper patch to prevent an unnecessary blank tab (bug 343895) michael@0: var blankTabToRemove = null; michael@0: if (gBrowser.tabs.length == 1 && isTabEmpty(gBrowser.selectedTab)) michael@0: blankTabToRemove = gBrowser.selectedTab; michael@0: michael@0: var tab = null; michael@0: if (SessionStore.getClosedTabCount(window) > (aIndex || 0)) { michael@0: TabView.prepareUndoCloseTab(blankTabToRemove); michael@0: tab = SessionStore.undoCloseTab(window, aIndex || 0); michael@0: TabView.afterUndoCloseTab(); michael@0: michael@0: if (blankTabToRemove) michael@0: gBrowser.removeTab(blankTabToRemove); michael@0: } michael@0: michael@0: return tab; michael@0: } michael@0: michael@0: /** michael@0: * Re-open a closed window. michael@0: * @param aIndex michael@0: * The index of the window (via SessionStore.getClosedWindowData) michael@0: * @returns a reference to the reopened window. michael@0: */ michael@0: function undoCloseWindow(aIndex) { michael@0: let window = null; michael@0: if (SessionStore.getClosedWindowCount() > (aIndex || 0)) michael@0: window = SessionStore.undoCloseWindow(aIndex || 0); michael@0: michael@0: return window; michael@0: } michael@0: michael@0: /* michael@0: * Determines if a tab is "empty", usually used in the context of determining michael@0: * if it's ok to close the tab. michael@0: */ michael@0: function isTabEmpty(aTab) { michael@0: if (aTab.hasAttribute("busy")) michael@0: return false; michael@0: michael@0: let browser = aTab.linkedBrowser; michael@0: if (!isBlankPageURL(browser.currentURI.spec)) michael@0: return false; michael@0: michael@0: // Bug 863515 - Make content.opener checks work in electrolysis. michael@0: if (!gMultiProcessBrowser && browser.contentWindow.opener) michael@0: return false; michael@0: michael@0: if (browser.sessionHistory && browser.sessionHistory.count >= 2) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: #ifdef MOZ_SERVICES_SYNC michael@0: function BrowserOpenSyncTabs() { michael@0: switchToTabHavingURI("about:sync-tabs", true); michael@0: } michael@0: #endif michael@0: michael@0: /** michael@0: * Format a URL michael@0: * eg: michael@0: * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/"); michael@0: * > https://addons.mozilla.org/en-US/firefox/3.0a1/ michael@0: * michael@0: * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased. michael@0: */ michael@0: function formatURL(aFormat, aIsPref) { michael@0: var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); michael@0: return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat); michael@0: } michael@0: michael@0: /** michael@0: * Utility object to handle manipulations of the identity indicators in the UI michael@0: */ michael@0: var gIdentityHandler = { michael@0: // Mode strings used to control CSS display michael@0: IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information michael@0: IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification michael@0: IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information michael@0: IDENTITY_MODE_MIXED_DISPLAY_LOADED : "unknownIdentity mixedContent mixedDisplayContent", // SSL with unauthenticated display content michael@0: IDENTITY_MODE_MIXED_ACTIVE_LOADED : "unknownIdentity mixedContent mixedActiveContent", // SSL with unauthenticated active (and perhaps also display) content michael@0: IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED : "unknownIdentity mixedContent mixedDisplayContentLoadedActiveBlocked", // SSL with unauthenticated display content; unauthenticated active content is blocked. michael@0: IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI michael@0: michael@0: // Cache the most recent SSLStatus and Location seen in checkIdentity michael@0: _lastStatus : null, michael@0: _lastUri : null, michael@0: _mode : "unknownIdentity", michael@0: michael@0: // smart getters michael@0: get _encryptionLabel () { michael@0: delete this._encryptionLabel; michael@0: this._encryptionLabel = {}; michael@0: this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] = michael@0: gNavigatorBundle.getString("identity.encrypted2"); michael@0: this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] = michael@0: gNavigatorBundle.getString("identity.encrypted2"); michael@0: this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] = michael@0: gNavigatorBundle.getString("identity.unencrypted"); michael@0: this._encryptionLabel[this.IDENTITY_MODE_MIXED_DISPLAY_LOADED] = michael@0: gNavigatorBundle.getString("identity.mixed_display_loaded"); michael@0: this._encryptionLabel[this.IDENTITY_MODE_MIXED_ACTIVE_LOADED] = michael@0: gNavigatorBundle.getString("identity.mixed_active_loaded2"); michael@0: this._encryptionLabel[this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED] = michael@0: gNavigatorBundle.getString("identity.mixed_display_loaded"); michael@0: return this._encryptionLabel; michael@0: }, michael@0: get _identityPopup () { michael@0: delete this._identityPopup; michael@0: return this._identityPopup = document.getElementById("identity-popup"); michael@0: }, michael@0: get _identityBox () { michael@0: delete this._identityBox; michael@0: return this._identityBox = document.getElementById("identity-box"); michael@0: }, michael@0: get _identityPopupContentBox () { michael@0: delete this._identityPopupContentBox; michael@0: return this._identityPopupContentBox = michael@0: document.getElementById("identity-popup-content-box"); michael@0: }, michael@0: get _identityPopupChromeLabel () { michael@0: delete this._identityPopupChromeLabel; michael@0: return this._identityPopupChromeLabel = michael@0: document.getElementById("identity-popup-chromeLabel"); michael@0: }, michael@0: get _identityPopupContentHost () { michael@0: delete this._identityPopupContentHost; michael@0: return this._identityPopupContentHost = michael@0: document.getElementById("identity-popup-content-host"); michael@0: }, michael@0: get _identityPopupContentOwner () { michael@0: delete this._identityPopupContentOwner; michael@0: return this._identityPopupContentOwner = michael@0: document.getElementById("identity-popup-content-owner"); michael@0: }, michael@0: get _identityPopupContentSupp () { michael@0: delete this._identityPopupContentSupp; michael@0: return this._identityPopupContentSupp = michael@0: document.getElementById("identity-popup-content-supplemental"); michael@0: }, michael@0: get _identityPopupContentVerif () { michael@0: delete this._identityPopupContentVerif; michael@0: return this._identityPopupContentVerif = michael@0: document.getElementById("identity-popup-content-verifier"); michael@0: }, michael@0: get _identityPopupEncLabel () { michael@0: delete this._identityPopupEncLabel; michael@0: return this._identityPopupEncLabel = michael@0: document.getElementById("identity-popup-encryption-label"); michael@0: }, michael@0: get _identityIconLabel () { michael@0: delete this._identityIconLabel; michael@0: return this._identityIconLabel = document.getElementById("identity-icon-label"); michael@0: }, michael@0: get _overrideService () { michael@0: delete this._overrideService; michael@0: return this._overrideService = Cc["@mozilla.org/security/certoverride;1"] michael@0: .getService(Ci.nsICertOverrideService); michael@0: }, michael@0: get _identityIconCountryLabel () { michael@0: delete this._identityIconCountryLabel; michael@0: return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label"); michael@0: }, michael@0: get _identityIcon () { michael@0: delete this._identityIcon; michael@0: return this._identityIcon = document.getElementById("page-proxy-favicon"); michael@0: }, michael@0: get _permissionsContainer () { michael@0: delete this._permissionsContainer; michael@0: return this._permissionsContainer = document.getElementById("identity-popup-permissions"); michael@0: }, michael@0: get _permissionList () { michael@0: delete this._permissionList; michael@0: return this._permissionList = document.getElementById("identity-popup-permission-list"); michael@0: }, michael@0: michael@0: /** michael@0: * Rebuild cache of the elements that may or may not exist depending michael@0: * on whether there's a location bar. michael@0: */ michael@0: _cacheElements : function() { michael@0: delete this._identityBox; michael@0: delete this._identityIconLabel; michael@0: delete this._identityIconCountryLabel; michael@0: delete this._identityIcon; michael@0: delete this._permissionsContainer; michael@0: delete this._permissionList; michael@0: this._identityBox = document.getElementById("identity-box"); michael@0: this._identityIconLabel = document.getElementById("identity-icon-label"); michael@0: this._identityIconCountryLabel = document.getElementById("identity-icon-country-label"); michael@0: this._identityIcon = document.getElementById("page-proxy-favicon"); michael@0: this._permissionsContainer = document.getElementById("identity-popup-permissions"); michael@0: this._permissionList = document.getElementById("identity-popup-permission-list"); michael@0: }, michael@0: michael@0: /** michael@0: * Handler for commands on the help button in the "identity-popup" panel. michael@0: */ michael@0: handleHelpCommand : function(event) { michael@0: openHelpLink("secure-connection"); michael@0: this._identityPopup.hidePopup(); michael@0: }, michael@0: michael@0: /** michael@0: * Handler for mouseclicks on the "More Information" button in the michael@0: * "identity-popup" panel. michael@0: */ michael@0: handleMoreInfoClick : function(event) { michael@0: displaySecurityInfo(); michael@0: event.stopPropagation(); michael@0: this._identityPopup.hidePopup(); michael@0: }, michael@0: michael@0: /** michael@0: * Helper to parse out the important parts of _lastStatus (of the SSL cert in michael@0: * particular) for use in constructing identity UI strings michael@0: */ michael@0: getIdentityData : function() { michael@0: var result = {}; michael@0: var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus); michael@0: var cert = status.serverCert; michael@0: michael@0: // Human readable name of Subject michael@0: result.subjectOrg = cert.organization; michael@0: michael@0: // SubjectName fields, broken up for individual access michael@0: if (cert.subjectName) { michael@0: result.subjectNameFields = {}; michael@0: cert.subjectName.split(",").forEach(function(v) { michael@0: var field = v.split("="); michael@0: this[field[0]] = field[1]; michael@0: }, result.subjectNameFields); michael@0: michael@0: // Call out city, state, and country specifically michael@0: result.city = result.subjectNameFields.L; michael@0: result.state = result.subjectNameFields.ST; michael@0: result.country = result.subjectNameFields.C; michael@0: } michael@0: michael@0: // Human readable name of Certificate Authority michael@0: result.caOrg = cert.issuerOrganization || cert.issuerCommonName; michael@0: result.cert = cert; michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Determine the identity of the page being displayed by examining its SSL cert michael@0: * (if available) and, if necessary, update the UI to reflect this. Intended to michael@0: * be called by onSecurityChange michael@0: * michael@0: * @param PRUint32 state michael@0: * @param nsIURI uri The address for which the UI should be updated. michael@0: */ michael@0: checkIdentity : function(state, uri) { michael@0: var currentStatus = gBrowser.securityUI michael@0: .QueryInterface(Components.interfaces.nsISSLStatusProvider) michael@0: .SSLStatus; michael@0: this._lastStatus = currentStatus; michael@0: this._lastUri = uri; michael@0: michael@0: let nsIWebProgressListener = Ci.nsIWebProgressListener; michael@0: michael@0: // For some URIs like data: we can't get a host and so can't do michael@0: // anything useful here. michael@0: let unknown = false; michael@0: try { michael@0: uri.host; michael@0: } catch (e) { unknown = true; } michael@0: michael@0: // Chrome URIs however get special treatment. Some chrome URIs are michael@0: // whitelisted to provide a positive security signal to the user. michael@0: let whitelist = /^about:(accounts|addons|app-manager|config|crashes|customizing|healthreport|home|newaddon|permissions|preferences|privatebrowsing|sessionrestore|support|welcomeback)/i; michael@0: let isChromeUI = uri.schemeIs("about") && whitelist.test(uri.spec); michael@0: if (isChromeUI) { michael@0: this.setMode(this.IDENTITY_MODE_CHROMEUI); michael@0: } else if (unknown) { michael@0: this.setMode(this.IDENTITY_MODE_UNKNOWN); michael@0: } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) { michael@0: this.setMode(this.IDENTITY_MODE_IDENTIFIED); michael@0: } else if (state & nsIWebProgressListener.STATE_IS_SECURE) { michael@0: this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED); michael@0: } else if (state & nsIWebProgressListener.STATE_IS_BROKEN) { michael@0: if ((state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) && michael@0: gPrefService.getBoolPref("security.mixed_content.block_active_content")) { michael@0: this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_LOADED); michael@0: } else if ((state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) && michael@0: gPrefService.getBoolPref("security.mixed_content.block_active_content")) { michael@0: this.setMode(this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED); michael@0: } else { michael@0: this.setMode(this.IDENTITY_MODE_MIXED_DISPLAY_LOADED); michael@0: } michael@0: } else { michael@0: this.setMode(this.IDENTITY_MODE_UNKNOWN); michael@0: } michael@0: michael@0: // Ensure the doorhanger is shown when mixed active content is blocked. michael@0: if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) michael@0: this.showMixedContentDoorhanger(); michael@0: }, michael@0: michael@0: /** michael@0: * Display the Mixed Content Blocker doohanger, providing an option michael@0: * to the user to override mixed content blocking michael@0: */ michael@0: showMixedContentDoorhanger : function() { michael@0: // If we've already got an active notification, bail out to avoid showing it repeatedly. michael@0: if (PopupNotifications.getNotification("mixed-content-blocked", gBrowser.selectedBrowser)) michael@0: return; michael@0: michael@0: let brandBundle = document.getElementById("bundle_brand"); michael@0: let brandShortName = brandBundle.getString("brandShortName"); michael@0: let messageString = gNavigatorBundle.getFormattedString("mixedContentBlocked.message", [brandShortName]); michael@0: let action = { michael@0: label: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.label"), michael@0: accessKey: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.accesskey"), michael@0: callback: function() { /* NOP */ } michael@0: }; michael@0: let secondaryActions = [ michael@0: { michael@0: label: gNavigatorBundle.getString("mixedContentBlocked.unblock.label"), michael@0: accessKey: gNavigatorBundle.getString("mixedContentBlocked.unblock.accesskey"), michael@0: callback: function() { michael@0: // Use telemetry to measure how often unblocking happens michael@0: const kMIXED_CONTENT_UNBLOCK_EVENT = 2; michael@0: let histogram = michael@0: Services.telemetry.getHistogramById("MIXED_CONTENT_UNBLOCK_COUNTER"); michael@0: histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT); michael@0: // Reload the page with the content unblocked michael@0: BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT); michael@0: } michael@0: } michael@0: ]; michael@0: let options = { michael@0: dismissed: true, michael@0: learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "mixed-content", michael@0: }; michael@0: PopupNotifications.show(gBrowser.selectedBrowser, "mixed-content-blocked", michael@0: messageString, "mixed-content-blocked-notification-icon", michael@0: action, secondaryActions, options); michael@0: }, michael@0: michael@0: /** michael@0: * Return the eTLD+1 version of the current hostname michael@0: */ michael@0: getEffectiveHost : function() { michael@0: if (!this._IDNService) michael@0: this._IDNService = Cc["@mozilla.org/network/idn-service;1"] michael@0: .getService(Ci.nsIIDNService); michael@0: try { michael@0: let baseDomain = michael@0: Services.eTLD.getBaseDomainFromHost(this._lastUri.host); michael@0: return this._IDNService.convertToDisplayIDN(baseDomain, {}); michael@0: } catch (e) { michael@0: // If something goes wrong (e.g. host is an IP address) just fail back michael@0: // to the full domain. michael@0: return this._lastUri.host; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Update the UI to reflect the specified mode, which should be one of the michael@0: * IDENTITY_MODE_* constants. michael@0: */ michael@0: setMode : function(newMode) { michael@0: if (!this._identityBox) { michael@0: // No identity box means the identity box is not visible, in which michael@0: // case there's nothing to do. michael@0: return; michael@0: } michael@0: michael@0: this._identityPopup.className = newMode; michael@0: this._identityBox.className = newMode; michael@0: this.setIdentityMessages(newMode); michael@0: michael@0: // Update the popup too, if it's open michael@0: if (this._identityPopup.state == "open") michael@0: this.setPopupMessages(newMode); michael@0: michael@0: this._mode = newMode; michael@0: }, michael@0: michael@0: /** michael@0: * Set up the messages for the primary identity UI based on the specified mode, michael@0: * and the details of the SSL cert, where applicable michael@0: * michael@0: * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants. michael@0: */ michael@0: setIdentityMessages : function(newMode) { michael@0: let icon_label = ""; michael@0: let tooltip = ""; michael@0: let icon_country_label = ""; michael@0: let icon_labels_dir = "ltr"; michael@0: michael@0: switch (newMode) { michael@0: case this.IDENTITY_MODE_DOMAIN_VERIFIED: { michael@0: let iData = this.getIdentityData(); michael@0: michael@0: // Verifier is either the CA Org, for a normal cert, or a special string michael@0: // for certs that are trusted because of a security exception. michael@0: tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", michael@0: [iData.caOrg]); michael@0: michael@0: // This can't throw, because URI's with a host that throw don't end up in this case. michael@0: let host = this._lastUri.host; michael@0: let port = 443; michael@0: try { michael@0: if (this._lastUri.port > 0) michael@0: port = this._lastUri.port; michael@0: } catch (e) {} michael@0: michael@0: if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {})) michael@0: tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you"); michael@0: michael@0: break; } michael@0: case this.IDENTITY_MODE_IDENTIFIED: { michael@0: // If it's identified, then we can populate the dialog with credentials michael@0: let iData = this.getIdentityData(); michael@0: tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", michael@0: [iData.caOrg]); michael@0: icon_label = iData.subjectOrg; michael@0: if (iData.country) michael@0: icon_country_label = "(" + iData.country + ")"; michael@0: michael@0: // If the organization name starts with an RTL character, then michael@0: // swap the positions of the organization and country code labels. michael@0: // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI michael@0: // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets michael@0: // fixed, this test should be replaced by one adhering to the michael@0: // Unicode Bidirectional Algorithm proper (at the paragraph level). michael@0: icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ? michael@0: "rtl" : "ltr"; michael@0: break; } michael@0: case this.IDENTITY_MODE_CHROMEUI: michael@0: let brandBundle = document.getElementById("bundle_brand"); michael@0: icon_label = brandBundle.getString("brandShortName"); michael@0: break; michael@0: default: michael@0: tooltip = gNavigatorBundle.getString("identity.unknown.tooltip"); michael@0: } michael@0: michael@0: // Push the appropriate strings out to the UI michael@0: this._identityBox.tooltipText = tooltip; michael@0: this._identityIconLabel.value = icon_label; michael@0: this._identityIconCountryLabel.value = icon_country_label; michael@0: // Set cropping and direction michael@0: this._identityIconLabel.crop = icon_country_label ? "end" : "center"; michael@0: this._identityIconLabel.parentNode.style.direction = icon_labels_dir; michael@0: // Hide completely if the organization label is empty michael@0: this._identityIconLabel.parentNode.collapsed = icon_label ? false : true; michael@0: }, michael@0: michael@0: /** michael@0: * Set up the title and content messages for the identity message popup, michael@0: * based on the specified mode, and the details of the SSL cert, where michael@0: * applicable michael@0: * michael@0: * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants. michael@0: */ michael@0: setPopupMessages : function(newMode) { michael@0: michael@0: this._identityPopup.className = newMode; michael@0: this._identityPopupContentBox.className = newMode; michael@0: michael@0: // Set the static strings up front michael@0: this._identityPopupEncLabel.textContent = this._encryptionLabel[newMode]; michael@0: michael@0: // Initialize the optional strings to empty values michael@0: let supplemental = ""; michael@0: let verifier = ""; michael@0: let host = ""; michael@0: let owner = ""; michael@0: michael@0: switch (newMode) { michael@0: case this.IDENTITY_MODE_DOMAIN_VERIFIED: michael@0: host = this.getEffectiveHost(); michael@0: owner = gNavigatorBundle.getString("identity.ownerUnknown2"); michael@0: verifier = this._identityBox.tooltipText; michael@0: break; michael@0: case this.IDENTITY_MODE_IDENTIFIED: { michael@0: // If it's identified, then we can populate the dialog with credentials michael@0: let iData = this.getIdentityData(); michael@0: host = this.getEffectiveHost(); michael@0: owner = iData.subjectOrg; michael@0: verifier = this._identityBox.tooltipText; michael@0: michael@0: // Build an appropriate supplemental block out of whatever location data we have michael@0: if (iData.city) michael@0: supplemental += iData.city + "\n"; michael@0: if (iData.state && iData.country) michael@0: supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country", michael@0: [iData.state, iData.country]); michael@0: else if (iData.state) // State only michael@0: supplemental += iData.state; michael@0: else if (iData.country) // Country only michael@0: supplemental += iData.country; michael@0: break; } michael@0: case this.IDENTITY_MODE_CHROMEUI: { michael@0: let brandBundle = document.getElementById("bundle_brand"); michael@0: let brandShortName = brandBundle.getString("brandShortName"); michael@0: this._identityPopupChromeLabel.textContent = gNavigatorBundle.getFormattedString("identity.chrome", michael@0: [brandShortName]); michael@0: break; } michael@0: } michael@0: michael@0: // Push the appropriate strings out to the UI michael@0: this._identityPopupContentHost.textContent = host; michael@0: this._identityPopupContentOwner.textContent = owner; michael@0: this._identityPopupContentSupp.textContent = supplemental; michael@0: this._identityPopupContentVerif.textContent = verifier; michael@0: }, michael@0: michael@0: /** michael@0: * Click handler for the identity-box element in primary chrome. michael@0: */ michael@0: handleIdentityButtonEvent : function(event) { michael@0: event.stopPropagation(); michael@0: michael@0: if ((event.type == "click" && event.button != 0) || michael@0: (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE && michael@0: event.keyCode != KeyEvent.DOM_VK_RETURN)) { michael@0: return; // Left click, space or enter only michael@0: } michael@0: michael@0: // Don't allow left click, space or enter if the location has been modified. michael@0: if (gURLBar.getAttribute("pageproxystate") != "valid") { michael@0: return; michael@0: } michael@0: michael@0: // Make sure that the display:none style we set in xul is removed now that michael@0: // the popup is actually needed michael@0: this._identityPopup.hidden = false; michael@0: michael@0: // Update the popup strings michael@0: this.setPopupMessages(this._identityBox.className); michael@0: michael@0: this.updateSitePermissions(); michael@0: michael@0: // Add the "open" attribute to the identity box for styling michael@0: this._identityBox.setAttribute("open", "true"); michael@0: var self = this; michael@0: this._identityPopup.addEventListener("popuphidden", function onPopupHidden(e) { michael@0: e.currentTarget.removeEventListener("popuphidden", onPopupHidden, false); michael@0: self._identityBox.removeAttribute("open"); michael@0: }, false); michael@0: michael@0: // Now open the popup, anchored off the primary chrome element michael@0: this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft"); michael@0: }, michael@0: michael@0: onPopupShown : function(event) { michael@0: document.getElementById('identity-popup-more-info-button').focus(); michael@0: michael@0: this._identityPopup.addEventListener("blur", this, true); michael@0: this._identityPopup.addEventListener("popuphidden", this); michael@0: }, michael@0: michael@0: onDragStart: function (event) { michael@0: if (gURLBar.getAttribute("pageproxystate") != "valid") michael@0: return; michael@0: michael@0: var value = content.location.href; michael@0: var urlString = value + "\n" + content.document.title; michael@0: var htmlString = "" + value + ""; michael@0: michael@0: var dt = event.dataTransfer; michael@0: dt.setData("text/x-moz-url", urlString); michael@0: dt.setData("text/uri-list", value); michael@0: dt.setData("text/plain", value); michael@0: dt.setData("text/html", htmlString); michael@0: dt.setDragImage(gProxyFavIcon, 16, 16); michael@0: }, michael@0: michael@0: handleEvent: function (event) { michael@0: switch (event.type) { michael@0: case "blur": michael@0: // Focus hasn't moved yet, need to wait until after the blur event. michael@0: setTimeout(() => { michael@0: if (document.activeElement && michael@0: document.activeElement.compareDocumentPosition(this._identityPopup) & michael@0: Node.DOCUMENT_POSITION_CONTAINS) michael@0: return; michael@0: michael@0: this._identityPopup.hidePopup(); michael@0: }, 0); michael@0: break; michael@0: case "popuphidden": michael@0: this._identityPopup.removeEventListener("blur", this, true); michael@0: this._identityPopup.removeEventListener("popuphidden", this); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: updateSitePermissions: function () { michael@0: while (this._permissionList.hasChildNodes()) michael@0: this._permissionList.removeChild(this._permissionList.lastChild); michael@0: michael@0: let uri = gBrowser.currentURI; michael@0: michael@0: for (let permission of SitePermissions.listPermissions()) { michael@0: let state = SitePermissions.get(uri, permission); michael@0: michael@0: if (state == SitePermissions.UNKNOWN) michael@0: continue; michael@0: michael@0: let item = this._createPermissionItem(permission, state); michael@0: this._permissionList.appendChild(item); michael@0: } michael@0: michael@0: this._permissionsContainer.hidden = !this._permissionList.hasChildNodes(); michael@0: }, michael@0: michael@0: setPermission: function (aPermission, aState) { michael@0: if (aState == SitePermissions.getDefault(aPermission)) michael@0: SitePermissions.remove(gBrowser.currentURI, aPermission); michael@0: else michael@0: SitePermissions.set(gBrowser.currentURI, aPermission, aState); michael@0: }, michael@0: michael@0: _createPermissionItem: function (aPermission, aState) { michael@0: let menulist = document.createElement("menulist"); michael@0: let menupopup = document.createElement("menupopup"); michael@0: for (let state of SitePermissions.getAvailableStates(aPermission)) { michael@0: let menuitem = document.createElement("menuitem"); michael@0: menuitem.setAttribute("value", state); michael@0: menuitem.setAttribute("label", SitePermissions.getStateLabel(aPermission, state)); michael@0: menupopup.appendChild(menuitem); michael@0: } michael@0: menulist.appendChild(menupopup); michael@0: menulist.setAttribute("value", aState); michael@0: menulist.setAttribute("oncommand", "gIdentityHandler.setPermission('" + michael@0: aPermission + "', this.value)"); michael@0: menulist.setAttribute("id", "identity-popup-permission:" + aPermission); michael@0: michael@0: let label = document.createElement("label"); michael@0: label.setAttribute("flex", "1"); michael@0: label.setAttribute("control", menulist.getAttribute("id")); michael@0: label.setAttribute("value", SitePermissions.getPermissionLabel(aPermission)); michael@0: michael@0: let container = document.createElement("hbox"); michael@0: container.setAttribute("align", "center"); michael@0: container.appendChild(label); michael@0: container.appendChild(menulist); michael@0: return container; michael@0: } michael@0: }; michael@0: michael@0: function getNotificationBox(aWindow) { michael@0: var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document); michael@0: if (foundBrowser) michael@0: return gBrowser.getNotificationBox(foundBrowser) michael@0: return null; michael@0: }; michael@0: michael@0: function getTabModalPromptBox(aWindow) { michael@0: var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document); michael@0: if (foundBrowser) michael@0: return gBrowser.getTabModalPromptBox(foundBrowser); michael@0: return null; michael@0: }; michael@0: michael@0: /* DEPRECATED */ michael@0: function getBrowser() gBrowser; michael@0: function getNavToolbox() gNavToolbox; michael@0: michael@0: let gPrivateBrowsingUI = { michael@0: init: function PBUI_init() { michael@0: // Do nothing for normal windows michael@0: if (!PrivateBrowsingUtils.isWindowPrivate(window)) { michael@0: return; michael@0: } michael@0: michael@0: // Disable the Clear Recent History... menu item when in PB mode michael@0: // temporary fix until bug 463607 is fixed michael@0: document.getElementById("Tools:Sanitize").setAttribute("disabled", "true"); michael@0: michael@0: if (window.location.href == getBrowserURL()) { michael@0: // Adjust the window's title michael@0: let docElement = document.documentElement; michael@0: if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { michael@0: docElement.setAttribute("title", michael@0: docElement.getAttribute("title_privatebrowsing")); michael@0: docElement.setAttribute("titlemodifier", michael@0: docElement.getAttribute("titlemodifier_privatebrowsing")); michael@0: } michael@0: docElement.setAttribute("privatebrowsingmode", michael@0: PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"); michael@0: gBrowser.updateTitlebar(); michael@0: michael@0: if (PrivateBrowsingUtils.permanentPrivateBrowsing) { michael@0: // Adjust the New Window menu entries michael@0: [ michael@0: { normal: "menu_newNavigator", private: "menu_newPrivateWindow" }, michael@0: ].forEach(function(menu) { michael@0: let newWindow = document.getElementById(menu.normal); michael@0: let newPrivateWindow = document.getElementById(menu.private); michael@0: if (newWindow && newPrivateWindow) { michael@0: newPrivateWindow.hidden = true; michael@0: newWindow.label = newPrivateWindow.label; michael@0: newWindow.accessKey = newPrivateWindow.accessKey; michael@0: newWindow.command = newPrivateWindow.command; michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: michael@0: if (gURLBar && michael@0: !PrivateBrowsingUtils.permanentPrivateBrowsing) { michael@0: // Disable switch to tab autocompletion for private windows michael@0: // (not for "Always use private browsing" mode) michael@0: gURLBar.setAttribute("autocompletesearchparam", ""); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: let gRemoteTabsUI = { michael@0: init: function() { michael@0: if (window.location.href != getBrowserURL()) { michael@0: return; michael@0: } michael@0: michael@0: let remoteTabs = gPrefService.getBoolPref("browser.tabs.remote"); michael@0: let autostart = gPrefService.getBoolPref("browser.tabs.remote.autostart"); michael@0: michael@0: let newRemoteWindow = document.getElementById("menu_newRemoteWindow"); michael@0: let newNonRemoteWindow = document.getElementById("menu_newNonRemoteWindow"); michael@0: michael@0: if (!remoteTabs) { michael@0: newRemoteWindow.hidden = true; michael@0: newNonRemoteWindow.hidden = true; michael@0: return; michael@0: } michael@0: michael@0: newRemoteWindow.hidden = autostart; michael@0: newNonRemoteWindow.hidden = !autostart; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Switch to a tab that has a given URI, and focusses its browser window. michael@0: * If a matching tab is in this window, it will be switched to. Otherwise, other michael@0: * windows will be searched. michael@0: * michael@0: * @param aURI michael@0: * URI to search for michael@0: * @param aOpenNew michael@0: * True to open a new tab and switch to it, if no existing tab is found. michael@0: * If no suitable window is found, a new one will be opened. michael@0: * @param aOpenParams michael@0: * If switching to this URI results in us opening a tab, aOpenParams michael@0: * will be the parameter object that gets passed to openUILinkIn. Please michael@0: * see the documentation for openUILinkIn to see what parameters can be michael@0: * passed via this object. michael@0: * @return True if an existing tab was found, false otherwise michael@0: */ michael@0: function switchToTabHavingURI(aURI, aOpenNew, aOpenParams) { michael@0: // Certain URLs can be switched to irrespective of the source or destination michael@0: // window being in private browsing mode: michael@0: const kPrivateBrowsingWhitelist = new Set([ michael@0: "about:customizing", michael@0: ]); michael@0: // This will switch to the tab in aWindow having aURI, if present. michael@0: function switchIfURIInWindow(aWindow) { michael@0: // Only switch to the tab if neither the source nor the destination window michael@0: // are private and they are not in permanent private browsing mode michael@0: if (!kPrivateBrowsingWhitelist.has(aURI.spec) && michael@0: (PrivateBrowsingUtils.isWindowPrivate(window) || michael@0: PrivateBrowsingUtils.isWindowPrivate(aWindow)) && michael@0: !PrivateBrowsingUtils.permanentPrivateBrowsing) { michael@0: return false; michael@0: } michael@0: michael@0: let browsers = aWindow.gBrowser.browsers; michael@0: for (let i = 0; i < browsers.length; i++) { michael@0: let browser = browsers[i]; michael@0: if (browser.currentURI.equals(aURI)) { michael@0: // Focus the matching window & tab michael@0: aWindow.focus(); michael@0: aWindow.gBrowser.tabContainer.selectedIndex = i; michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // This can be passed either nsIURI or a string. michael@0: if (!(aURI instanceof Ci.nsIURI)) michael@0: aURI = Services.io.newURI(aURI, null, null); michael@0: michael@0: let isBrowserWindow = !!window.gBrowser; michael@0: michael@0: // Prioritise this window. michael@0: if (isBrowserWindow && switchIfURIInWindow(window)) michael@0: return true; michael@0: michael@0: let winEnum = Services.wm.getEnumerator("navigator:browser"); michael@0: while (winEnum.hasMoreElements()) { michael@0: let browserWin = winEnum.getNext(); michael@0: // Skip closed (but not yet destroyed) windows, michael@0: // and the current window (which was checked earlier). michael@0: if (browserWin.closed || browserWin == window) michael@0: continue; michael@0: if (switchIfURIInWindow(browserWin)) michael@0: return true; michael@0: } michael@0: michael@0: // No opened tab has that url. michael@0: if (aOpenNew) { michael@0: if (isBrowserWindow && isTabEmpty(gBrowser.selectedTab)) michael@0: openUILinkIn(aURI.spec, "current", aOpenParams); michael@0: else michael@0: openUILinkIn(aURI.spec, "tab", aOpenParams); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: let RestoreLastSessionObserver = { michael@0: init: function () { michael@0: if (SessionStore.canRestoreLastSession && michael@0: !PrivateBrowsingUtils.isWindowPrivate(window)) { michael@0: Services.obs.addObserver(this, "sessionstore-last-session-cleared", true); michael@0: goSetCommandEnabled("Browser:RestoreLastSession", true); michael@0: } michael@0: }, michael@0: michael@0: observe: function () { michael@0: // The last session can only be restored once so there's michael@0: // no way we need to re-enable our menu item. michael@0: Services.obs.removeObserver(this, "sessionstore-last-session-cleared"); michael@0: goSetCommandEnabled("Browser:RestoreLastSession", false); michael@0: }, michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, michael@0: Ci.nsISupportsWeakReference]) michael@0: }; michael@0: michael@0: function restoreLastSession() { michael@0: SessionStore.restoreLastSession(); michael@0: } michael@0: michael@0: var TabContextMenu = { michael@0: contextTab: null, michael@0: updateContextMenu: function updateContextMenu(aPopupMenu) { michael@0: this.contextTab = aPopupMenu.triggerNode.localName == "tab" ? michael@0: aPopupMenu.triggerNode : gBrowser.selectedTab; michael@0: let disabled = gBrowser.tabs.length == 1; michael@0: michael@0: var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple"); michael@0: for (let menuItem of menuItems) michael@0: menuItem.disabled = disabled; michael@0: michael@0: disabled = gBrowser.visibleTabs.length == 1; michael@0: menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible"); michael@0: for (let menuItem of menuItems) michael@0: menuItem.disabled = disabled; michael@0: michael@0: // Session store michael@0: document.getElementById("context_undoCloseTab").disabled = michael@0: SessionStore.getClosedTabCount(window) == 0; michael@0: michael@0: // Only one of pin/unpin should be visible michael@0: document.getElementById("context_pinTab").hidden = this.contextTab.pinned; michael@0: document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned; michael@0: michael@0: // Disable "Close Tabs to the Right" if there are no tabs michael@0: // following it and hide it when the user rightclicked on a pinned michael@0: // tab. michael@0: document.getElementById("context_closeTabsToTheEnd").disabled = michael@0: gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0; michael@0: document.getElementById("context_closeTabsToTheEnd").hidden = this.contextTab.pinned; michael@0: michael@0: // Disable "Close other Tabs" if there is only one unpinned tab and michael@0: // hide it when the user rightclicked on a pinned tab. michael@0: let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs; michael@0: document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1; michael@0: document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned; michael@0: michael@0: // Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible. michael@0: let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs"); michael@0: bookmarkAllTabs.hidden = this.contextTab.pinned; michael@0: if (!bookmarkAllTabs.hidden) michael@0: PlacesCommandHook.updateBookmarkAllTabsCommand(); michael@0: michael@0: // Hide "Move to Group" if it's a pinned tab. michael@0: document.getElementById("context_tabViewMenu").hidden = michael@0: (this.contextTab.pinned || !TabView.firstUseExperienced); michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", michael@0: "resource:///modules/devtools/gDevTools.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "gDevToolsBrowser", michael@0: "resource:///modules/devtools/gDevTools.jsm"); michael@0: michael@0: Object.defineProperty(this, "HUDService", { michael@0: get: function HUDService_getter() { michael@0: let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; michael@0: return devtools.require("devtools/webconsole/hudservice"); michael@0: }, michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: michael@0: // Prompt user to restart the browser in safe mode michael@0: function safeModeRestart() michael@0: { michael@0: // prompt the user to confirm michael@0: let promptTitle = gNavigatorBundle.getString("safeModeRestartPromptTitle"); michael@0: let promptMessage = michael@0: gNavigatorBundle.getString("safeModeRestartPromptMessage"); michael@0: let restartText = gNavigatorBundle.getString("safeModeRestartButton"); michael@0: let buttonFlags = (Services.prompt.BUTTON_POS_0 * michael@0: Services.prompt.BUTTON_TITLE_IS_STRING) + michael@0: (Services.prompt.BUTTON_POS_1 * michael@0: Services.prompt.BUTTON_TITLE_CANCEL) + michael@0: Services.prompt.BUTTON_POS_0_DEFAULT; michael@0: michael@0: let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage, michael@0: buttonFlags, restartText, null, null, michael@0: null, {}); michael@0: if (rv != 0) michael@0: return; michael@0: michael@0: let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] michael@0: .createInstance(Ci.nsISupportsPRBool); michael@0: Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); michael@0: michael@0: if (!cancelQuit.data) { michael@0: Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit); michael@0: } michael@0: } michael@0: michael@0: /* duplicateTabIn duplicates tab in a place specified by the parameter |where|. michael@0: * michael@0: * |where| can be: michael@0: * "tab" new tab michael@0: * "tabshifted" same as "tab" but in background if default is to select new michael@0: * tabs, and vice versa michael@0: * "window" new window michael@0: * michael@0: * delta is the offset to the history entry that you want to load. michael@0: */ michael@0: function duplicateTabIn(aTab, where, delta) { michael@0: let newTab = SessionStore.duplicateTab(window, aTab, delta); michael@0: michael@0: switch (where) { michael@0: case "window": michael@0: gBrowser.hideTab(newTab); michael@0: gBrowser.replaceTabWithWindow(newTab); michael@0: break; michael@0: case "tabshifted": michael@0: // A background tab has been opened, nothing else to do here. michael@0: break; michael@0: case "tab": michael@0: gBrowser.selectedTab = newTab; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: var Scratchpad = { michael@0: openScratchpad: function SP_openScratchpad() { michael@0: return this.ScratchpadManager.openScratchpad(); michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() { michael@0: let tmp = {}; michael@0: Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", tmp); michael@0: return tmp.ScratchpadManager; michael@0: }); michael@0: michael@0: var ResponsiveUI = { michael@0: toggle: function RUI_toggle() { michael@0: this.ResponsiveUIManager.toggle(window, gBrowser.selectedTab); michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() { michael@0: let tmp = {}; michael@0: Cu.import("resource:///modules/devtools/responsivedesign.jsm", tmp); michael@0: return tmp.ResponsiveUIManager; michael@0: }); michael@0: michael@0: function openEyedropper() { michael@0: var eyedropper = new this.Eyedropper(this); michael@0: eyedropper.open(); michael@0: } michael@0: michael@0: Object.defineProperty(this, "Eyedropper", { michael@0: get: function() { michael@0: let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; michael@0: return devtools.require("devtools/eyedropper/eyedropper").Eyedropper; michael@0: }, michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () { michael@0: #ifdef XP_WIN michael@0: // Only show resizers on Windows 2000 and XP michael@0: return parseFloat(Services.sysinfo.getProperty("version")) < 6; michael@0: #else michael@0: return false; michael@0: #endif michael@0: }); michael@0: michael@0: var MousePosTracker = { michael@0: _listeners: new Set(), michael@0: _x: 0, michael@0: _y: 0, michael@0: get _windowUtils() { michael@0: delete this._windowUtils; michael@0: return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils); michael@0: }, michael@0: michael@0: addListener: function (listener) { michael@0: if (this._listeners.has(listener)) michael@0: return; michael@0: michael@0: listener._hover = false; michael@0: this._listeners.add(listener); michael@0: michael@0: this._callListener(listener); michael@0: }, michael@0: michael@0: removeListener: function (listener) { michael@0: this._listeners.delete(listener); michael@0: }, michael@0: michael@0: handleEvent: function (event) { michael@0: var fullZoom = this._windowUtils.fullZoom; michael@0: this._x = event.screenX / fullZoom - window.mozInnerScreenX; michael@0: this._y = event.screenY / fullZoom - window.mozInnerScreenY; michael@0: michael@0: this._listeners.forEach(function (listener) { michael@0: try { michael@0: this._callListener(listener); michael@0: } catch (e) { michael@0: Cu.reportError(e); michael@0: } michael@0: }, this); michael@0: }, michael@0: michael@0: _callListener: function (listener) { michael@0: let rect = listener.getMouseTargetRect(); michael@0: let hover = this._x >= rect.left && michael@0: this._x <= rect.right && michael@0: this._y >= rect.top && michael@0: this._y <= rect.bottom; michael@0: michael@0: if (hover == listener._hover) michael@0: return; michael@0: michael@0: listener._hover = hover; michael@0: michael@0: if (hover) { michael@0: if (listener.onMouseEnter) michael@0: listener.onMouseEnter(); michael@0: } else { michael@0: if (listener.onMouseLeave) michael@0: listener.onMouseLeave(); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function focusNextFrame(event) { michael@0: let fm = Services.focus; michael@0: let dir = event.shiftKey ? fm.MOVEFOCUS_BACKWARDDOC : fm.MOVEFOCUS_FORWARDDOC; michael@0: let element = fm.moveFocus(window, null, dir, fm.FLAG_BYKEY); michael@0: if (element.ownerDocument == document) michael@0: focusAndSelectUrlBar(); michael@0: } michael@0: let BrowserChromeTest = { michael@0: _cb: null, michael@0: _ready: false, michael@0: markAsReady: function () { michael@0: this._ready = true; michael@0: if (this._cb) { michael@0: this._cb(); michael@0: this._cb = null; michael@0: } michael@0: }, michael@0: runWhenReady: function (cb) { michael@0: if (this._ready) michael@0: cb(); michael@0: else michael@0: this._cb = cb; michael@0: } michael@0: }; michael@0: michael@0: function BrowserOpenNewTabOrWindow(event) { michael@0: if (event.shiftKey) { michael@0: OpenBrowserWindow(); michael@0: } else { michael@0: BrowserOpenTab(); michael@0: } michael@0: } michael@0: michael@0: let ToolbarIconColor = { michael@0: init: function () { michael@0: this._initialized = true; michael@0: michael@0: window.addEventListener("activate", this); michael@0: window.addEventListener("deactivate", this); michael@0: Services.obs.addObserver(this, "lightweight-theme-styling-update", false); michael@0: michael@0: // If the window isn't active now, we assume that it has never been active michael@0: // before and will soon become active such that inferFromText will be michael@0: // called from the initial activate event. michael@0: if (Services.focus.activeWindow == window) michael@0: this.inferFromText(); michael@0: }, michael@0: michael@0: uninit: function () { michael@0: this._initialized = false; michael@0: michael@0: window.removeEventListener("activate", this); michael@0: window.removeEventListener("deactivate", this); michael@0: Services.obs.removeObserver(this, "lightweight-theme-styling-update"); michael@0: }, michael@0: michael@0: handleEvent: function (event) { michael@0: switch (event.type) { michael@0: case "activate": michael@0: case "deactivate": michael@0: this.inferFromText(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: observe: function (aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "lightweight-theme-styling-update": michael@0: // inferFromText needs to run after LightweightThemeConsumer.jsm's michael@0: // lightweight-theme-styling-update observer. michael@0: setTimeout(() => { this.inferFromText(); }, 0); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: inferFromText: function () { michael@0: if (!this._initialized) michael@0: return; michael@0: michael@0: function parseRGB(aColorString) { michael@0: let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/); michael@0: rgb.shift(); michael@0: return rgb.map(x => parseInt(x)); michael@0: } michael@0: michael@0: let toolbarSelector = "#navigator-toolbox > toolbar:not([collapsed=true]):not(#addon-bar)"; michael@0: #ifdef XP_MACOSX michael@0: toolbarSelector += ":not([type=menubar])"; michael@0: #endif michael@0: michael@0: for (let toolbar of document.querySelectorAll(toolbarSelector)) { michael@0: let [r, g, b] = parseRGB(getComputedStyle(toolbar).color); michael@0: let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b; michael@0: if (luminance <= 110) michael@0: toolbar.removeAttribute("brighttext"); michael@0: else michael@0: toolbar.setAttribute("brighttext", "true"); michael@0: } michael@0: } michael@0: }