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