browser/base/content/browser.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 5
michael@0 6 let Ci = Components.interfaces;
michael@0 7 let Cu = Components.utils;
michael@0 8
michael@0 9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 10 Cu.import("resource://gre/modules/NotificationDB.jsm");
michael@0 11 Cu.import("resource:///modules/RecentWindow.jsm");
michael@0 12 Cu.import("resource://gre/modules/WindowsPrefSync.jsm");
michael@0 13
michael@0 14 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
michael@0 15 "resource://gre/modules/BrowserUtils.jsm");
michael@0 16 XPCOMUtils.defineLazyModuleGetter(this, "Task",
michael@0 17 "resource://gre/modules/Task.jsm");
michael@0 18 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
michael@0 19 "resource://gre/modules/CharsetMenu.jsm");
michael@0 20 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
michael@0 21 "resource://gre/modules/ShortcutUtils.jsm");
michael@0 22
michael@0 23 const nsIWebNavigation = Ci.nsIWebNavigation;
michael@0 24
michael@0 25 var gLastBrowserCharset = null;
michael@0 26 var gPrevCharset = null;
michael@0 27 var gProxyFavIcon = null;
michael@0 28 var gLastValidURLStr = "";
michael@0 29 var gInPrintPreviewMode = false;
michael@0 30 var gContextMenu = null; // nsContextMenu instance
michael@0 31 var gMultiProcessBrowser = false;
michael@0 32
michael@0 33 #ifndef XP_MACOSX
michael@0 34 var gEditUIVisible = true;
michael@0 35 #endif
michael@0 36
michael@0 37 [
michael@0 38 ["gBrowser", "content"],
michael@0 39 ["gNavToolbox", "navigator-toolbox"],
michael@0 40 ["gURLBar", "urlbar"],
michael@0 41 ["gNavigatorBundle", "bundle_browser"]
michael@0 42 ].forEach(function (elementGlobal) {
michael@0 43 var [name, id] = elementGlobal;
michael@0 44 window.__defineGetter__(name, function () {
michael@0 45 var element = document.getElementById(id);
michael@0 46 if (!element)
michael@0 47 return null;
michael@0 48 delete window[name];
michael@0 49 return window[name] = element;
michael@0 50 });
michael@0 51 window.__defineSetter__(name, function (val) {
michael@0 52 delete window[name];
michael@0 53 return window[name] = val;
michael@0 54 });
michael@0 55 });
michael@0 56
michael@0 57 // Smart getter for the findbar. If you don't wish to force the creation of
michael@0 58 // the findbar, check gFindBarInitialized first.
michael@0 59
michael@0 60 this.__defineGetter__("gFindBar", function() {
michael@0 61 return window.gBrowser.getFindBar();
michael@0 62 });
michael@0 63
michael@0 64 this.__defineGetter__("gFindBarInitialized", function() {
michael@0 65 return window.gBrowser.isFindBarInitialized();
michael@0 66 });
michael@0 67
michael@0 68 XPCOMUtils.defineLazyGetter(this, "gPrefService", function() {
michael@0 69 return Services.prefs;
michael@0 70 });
michael@0 71
michael@0 72 this.__defineGetter__("AddonManager", function() {
michael@0 73 let tmp = {};
michael@0 74 Cu.import("resource://gre/modules/AddonManager.jsm", tmp);
michael@0 75 return this.AddonManager = tmp.AddonManager;
michael@0 76 });
michael@0 77 this.__defineSetter__("AddonManager", function (val) {
michael@0 78 delete this.AddonManager;
michael@0 79 return this.AddonManager = val;
michael@0 80 });
michael@0 81
michael@0 82 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
michael@0 83 "resource://gre/modules/PluralForm.jsm");
michael@0 84
michael@0 85 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
michael@0 86 "resource://gre/modules/TelemetryStopwatch.jsm");
michael@0 87
michael@0 88 XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", function() {
michael@0 89 let scope = {};
michael@0 90 Cu.import("resource:///modules/CustomizeMode.jsm", scope);
michael@0 91 return new scope.CustomizeMode(window);
michael@0 92 });
michael@0 93
michael@0 94 #ifdef MOZ_SERVICES_SYNC
michael@0 95 XPCOMUtils.defineLazyModuleGetter(this, "Weave",
michael@0 96 "resource://services-sync/main.js");
michael@0 97 #endif
michael@0 98
michael@0 99 XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
michael@0 100 let tmp = {};
michael@0 101 Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
michael@0 102 try {
michael@0 103 return new tmp.PopupNotifications(gBrowser,
michael@0 104 document.getElementById("notification-popup"),
michael@0 105 document.getElementById("notification-popup-box"));
michael@0 106 } catch (ex) {
michael@0 107 Cu.reportError(ex);
michael@0 108 return null;
michael@0 109 }
michael@0 110 });
michael@0 111
michael@0 112 XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
michael@0 113 let tmp = {};
michael@0 114 Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", tmp);
michael@0 115 return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar"));
michael@0 116 });
michael@0 117
michael@0 118 XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
michael@0 119 let tmp = {};
michael@0 120 Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", tmp);
michael@0 121 return tmp.BrowserToolboxProcess;
michael@0 122 });
michael@0 123
michael@0 124 XPCOMUtils.defineLazyModuleGetter(this, "Social",
michael@0 125 "resource:///modules/Social.jsm");
michael@0 126
michael@0 127 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
michael@0 128 "resource://gre/modules/PageThumbs.jsm");
michael@0 129
michael@0 130 #ifdef MOZ_SAFE_BROWSING
michael@0 131 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
michael@0 132 "resource://gre/modules/SafeBrowsing.jsm");
michael@0 133 #endif
michael@0 134
michael@0 135 XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
michael@0 136 "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
michael@0 137
michael@0 138 XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader",
michael@0 139 "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader");
michael@0 140
michael@0 141 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
michael@0 142 "resource://gre/modules/PrivateBrowsingUtils.jsm");
michael@0 143
michael@0 144 XPCOMUtils.defineLazyModuleGetter(this, "Translation",
michael@0 145 "resource:///modules/translation/Translation.jsm");
michael@0 146
michael@0 147 XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
michael@0 148 "resource:///modules/SitePermissions.jsm");
michael@0 149
michael@0 150 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
michael@0 151 "resource:///modules/sessionstore/SessionStore.jsm");
michael@0 152
michael@0 153 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
michael@0 154 "resource://gre/modules/FxAccounts.jsm");
michael@0 155
michael@0 156 #ifdef MOZ_CRASHREPORTER
michael@0 157 XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
michael@0 158 "resource:///modules/TabCrashReporter.jsm");
michael@0 159 #endif
michael@0 160
michael@0 161 let gInitialPages = [
michael@0 162 "about:blank",
michael@0 163 "about:newtab",
michael@0 164 "about:home",
michael@0 165 "about:privatebrowsing",
michael@0 166 "about:welcomeback",
michael@0 167 "about:sessionrestore"
michael@0 168 ];
michael@0 169
michael@0 170 #include browser-addons.js
michael@0 171 #include browser-customization.js
michael@0 172 #include browser-feeds.js
michael@0 173 #include browser-fullScreen.js
michael@0 174 #include browser-fullZoom.js
michael@0 175 #include browser-places.js
michael@0 176 #include browser-plugins.js
michael@0 177 #include browser-safebrowsing.js
michael@0 178 #include browser-social.js
michael@0 179 #include browser-tabPreviews.js
michael@0 180 #include browser-tabview.js
michael@0 181 #include browser-thumbnails.js
michael@0 182 #include browser-webrtcUI.js
michael@0 183 #include browser-gestureSupport.js
michael@0 184
michael@0 185 #ifdef MOZ_DATA_REPORTING
michael@0 186 #include browser-data-submission-info-bar.js
michael@0 187 #endif
michael@0 188
michael@0 189 #ifdef MOZ_SERVICES_SYNC
michael@0 190 #include browser-syncui.js
michael@0 191 #endif
michael@0 192
michael@0 193 #include browser-fxaccounts.js
michael@0 194
michael@0 195 XPCOMUtils.defineLazyGetter(this, "Win7Features", function () {
michael@0 196 #ifdef XP_WIN
michael@0 197 // Bug 666808 - AeroPeek support for e10s
michael@0 198 if (gMultiProcessBrowser)
michael@0 199 return null;
michael@0 200
michael@0 201 const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
michael@0 202 if (WINTASKBAR_CONTRACTID in Cc &&
michael@0 203 Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
michael@0 204 let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek;
michael@0 205 return {
michael@0 206 onOpenWindow: function () {
michael@0 207 AeroPeek.onOpenWindow(window);
michael@0 208 },
michael@0 209 onCloseWindow: function () {
michael@0 210 AeroPeek.onCloseWindow(window);
michael@0 211 }
michael@0 212 };
michael@0 213 }
michael@0 214 #endif
michael@0 215 return null;
michael@0 216 });
michael@0 217
michael@0 218 #ifdef MOZ_CRASHREPORTER
michael@0 219 XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
michael@0 220 "@mozilla.org/xre/app-info;1",
michael@0 221 "nsICrashReporter");
michael@0 222 #endif
michael@0 223
michael@0 224 XPCOMUtils.defineLazyGetter(this, "PageMenu", function() {
michael@0 225 let tmp = {};
michael@0 226 Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
michael@0 227 return new tmp.PageMenu();
michael@0 228 });
michael@0 229
michael@0 230 /**
michael@0 231 * We can avoid adding multiple load event listeners and save some time by adding
michael@0 232 * one listener that calls all real handlers.
michael@0 233 */
michael@0 234 function pageShowEventHandlers(persisted) {
michael@0 235 charsetLoadListener();
michael@0 236 XULBrowserWindow.asyncUpdateUI();
michael@0 237
michael@0 238 // The PluginClickToPlay events are not fired when navigating using the
michael@0 239 // BF cache. |persisted| is true when the page is loaded from the
michael@0 240 // BF cache, so this code reshows the notification if necessary.
michael@0 241 if (persisted)
michael@0 242 gPluginHandler.reshowClickToPlayNotification();
michael@0 243 }
michael@0 244
michael@0 245 function UpdateBackForwardCommands(aWebNavigation) {
michael@0 246 var backBroadcaster = document.getElementById("Browser:Back");
michael@0 247 var forwardBroadcaster = document.getElementById("Browser:Forward");
michael@0 248
michael@0 249 // Avoid setting attributes on broadcasters if the value hasn't changed!
michael@0 250 // Remember, guys, setting attributes on elements is expensive! They
michael@0 251 // get inherited into anonymous content, broadcast to other widgets, etc.!
michael@0 252 // Don't do it if the value hasn't changed! - dwh
michael@0 253
michael@0 254 var backDisabled = backBroadcaster.hasAttribute("disabled");
michael@0 255 var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
michael@0 256 if (backDisabled == aWebNavigation.canGoBack) {
michael@0 257 if (backDisabled)
michael@0 258 backBroadcaster.removeAttribute("disabled");
michael@0 259 else
michael@0 260 backBroadcaster.setAttribute("disabled", true);
michael@0 261 }
michael@0 262
michael@0 263 if (forwardDisabled == aWebNavigation.canGoForward) {
michael@0 264 if (forwardDisabled)
michael@0 265 forwardBroadcaster.removeAttribute("disabled");
michael@0 266 else
michael@0 267 forwardBroadcaster.setAttribute("disabled", true);
michael@0 268 }
michael@0 269 }
michael@0 270
michael@0 271 /**
michael@0 272 * Click-and-Hold implementation for the Back and Forward buttons
michael@0 273 * XXXmano: should this live in toolbarbutton.xml?
michael@0 274 */
michael@0 275 function SetClickAndHoldHandlers() {
michael@0 276 var timer;
michael@0 277
michael@0 278 function openMenu(aButton) {
michael@0 279 cancelHold(aButton);
michael@0 280 aButton.firstChild.hidden = false;
michael@0 281 aButton.open = true;
michael@0 282 }
michael@0 283
michael@0 284 function mousedownHandler(aEvent) {
michael@0 285 if (aEvent.button != 0 ||
michael@0 286 aEvent.currentTarget.open ||
michael@0 287 aEvent.currentTarget.disabled)
michael@0 288 return;
michael@0 289
michael@0 290 // Prevent the menupopup from opening immediately
michael@0 291 aEvent.currentTarget.firstChild.hidden = true;
michael@0 292
michael@0 293 aEvent.currentTarget.addEventListener("mouseout", mouseoutHandler, false);
michael@0 294 aEvent.currentTarget.addEventListener("mouseup", mouseupHandler, false);
michael@0 295 timer = setTimeout(openMenu, 500, aEvent.currentTarget);
michael@0 296 }
michael@0 297
michael@0 298 function mouseoutHandler(aEvent) {
michael@0 299 let buttonRect = aEvent.currentTarget.getBoundingClientRect();
michael@0 300 if (aEvent.clientX >= buttonRect.left &&
michael@0 301 aEvent.clientX <= buttonRect.right &&
michael@0 302 aEvent.clientY >= buttonRect.bottom)
michael@0 303 openMenu(aEvent.currentTarget);
michael@0 304 else
michael@0 305 cancelHold(aEvent.currentTarget);
michael@0 306 }
michael@0 307
michael@0 308 function mouseupHandler(aEvent) {
michael@0 309 cancelHold(aEvent.currentTarget);
michael@0 310 }
michael@0 311
michael@0 312 function cancelHold(aButton) {
michael@0 313 clearTimeout(timer);
michael@0 314 aButton.removeEventListener("mouseout", mouseoutHandler, false);
michael@0 315 aButton.removeEventListener("mouseup", mouseupHandler, false);
michael@0 316 }
michael@0 317
michael@0 318 function clickHandler(aEvent) {
michael@0 319 if (aEvent.button == 0 &&
michael@0 320 aEvent.target == aEvent.currentTarget &&
michael@0 321 !aEvent.currentTarget.open &&
michael@0 322 !aEvent.currentTarget.disabled) {
michael@0 323 let cmdEvent = document.createEvent("xulcommandevent");
michael@0 324 cmdEvent.initCommandEvent("command", true, true, window, 0,
michael@0 325 aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
michael@0 326 aEvent.metaKey, null);
michael@0 327 aEvent.currentTarget.dispatchEvent(cmdEvent);
michael@0 328 }
michael@0 329 }
michael@0 330
michael@0 331 function _addClickAndHoldListenersOnElement(aElm) {
michael@0 332 aElm.addEventListener("mousedown", mousedownHandler, true);
michael@0 333 aElm.addEventListener("click", clickHandler, true);
michael@0 334 }
michael@0 335
michael@0 336 // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
michael@0 337 let popup = document.getElementById("backForwardMenu").cloneNode(true);
michael@0 338 popup.removeAttribute("id");
michael@0 339 // Prevent the back/forward buttons' context attributes from being inherited.
michael@0 340 popup.setAttribute("context", "");
michael@0 341
michael@0 342 let backButton = document.getElementById("back-button");
michael@0 343 backButton.setAttribute("type", "menu");
michael@0 344 backButton.appendChild(popup);
michael@0 345 _addClickAndHoldListenersOnElement(backButton);
michael@0 346
michael@0 347 let forwardButton = document.getElementById("forward-button");
michael@0 348 popup = popup.cloneNode(true);
michael@0 349 forwardButton.setAttribute("type", "menu");
michael@0 350 forwardButton.appendChild(popup);
michael@0 351 _addClickAndHoldListenersOnElement(forwardButton);
michael@0 352 }
michael@0 353
michael@0 354 const gSessionHistoryObserver = {
michael@0 355 observe: function(subject, topic, data)
michael@0 356 {
michael@0 357 if (topic != "browser:purge-session-history")
michael@0 358 return;
michael@0 359
michael@0 360 var backCommand = document.getElementById("Browser:Back");
michael@0 361 backCommand.setAttribute("disabled", "true");
michael@0 362 var fwdCommand = document.getElementById("Browser:Forward");
michael@0 363 fwdCommand.setAttribute("disabled", "true");
michael@0 364
michael@0 365 // Hide session restore button on about:home
michael@0 366 window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton");
michael@0 367
michael@0 368 if (gURLBar) {
michael@0 369 // Clear undo history of the URL bar
michael@0 370 gURLBar.editor.transactionManager.clear()
michael@0 371 }
michael@0 372 }
michael@0 373 };
michael@0 374
michael@0 375 /**
michael@0 376 * Given a starting docshell and a URI to look up, find the docshell the URI
michael@0 377 * is loaded in.
michael@0 378 * @param aDocument
michael@0 379 * A document to find instead of using just a URI - this is more specific.
michael@0 380 * @param aDocShell
michael@0 381 * The doc shell to start at
michael@0 382 * @param aSoughtURI
michael@0 383 * The URI that we're looking for
michael@0 384 * @returns The doc shell that the sought URI is loaded in. Can be in
michael@0 385 * subframes.
michael@0 386 */
michael@0 387 function findChildShell(aDocument, aDocShell, aSoughtURI) {
michael@0 388 aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation);
michael@0 389 aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
michael@0 390 var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument);
michael@0 391 if ((aDocument && doc == aDocument) ||
michael@0 392 (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec))
michael@0 393 return aDocShell;
michael@0 394
michael@0 395 var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
michael@0 396 for (var i = 0; i < node.childCount; ++i) {
michael@0 397 var docShell = node.getChildAt(i);
michael@0 398 docShell = findChildShell(aDocument, docShell, aSoughtURI);
michael@0 399 if (docShell)
michael@0 400 return docShell;
michael@0 401 }
michael@0 402 return null;
michael@0 403 }
michael@0 404
michael@0 405 var gPopupBlockerObserver = {
michael@0 406 _reportButton: null,
michael@0 407
michael@0 408 onReportButtonClick: function (aEvent)
michael@0 409 {
michael@0 410 if (aEvent.button != 0 || aEvent.target != this._reportButton)
michael@0 411 return;
michael@0 412
michael@0 413 document.getElementById("blockedPopupOptions")
michael@0 414 .openPopup(this._reportButton, "after_end", 0, 2, false, false, aEvent);
michael@0 415 },
michael@0 416
michael@0 417 handleEvent: function (aEvent)
michael@0 418 {
michael@0 419 if (aEvent.originalTarget != gBrowser.selectedBrowser)
michael@0 420 return;
michael@0 421
michael@0 422 if (!this._reportButton && gURLBar)
michael@0 423 this._reportButton = document.getElementById("page-report-button");
michael@0 424
michael@0 425 if (!gBrowser.selectedBrowser.blockedPopups) {
michael@0 426 // Hide the icon in the location bar (if the location bar exists)
michael@0 427 if (gURLBar)
michael@0 428 this._reportButton.hidden = true;
michael@0 429 return;
michael@0 430 }
michael@0 431
michael@0 432 if (gURLBar)
michael@0 433 this._reportButton.hidden = false;
michael@0 434
michael@0 435 // Only show the notification again if we've not already shown it. Since
michael@0 436 // notifications are per-browser, we don't need to worry about re-adding
michael@0 437 // it.
michael@0 438 if (!gBrowser.selectedBrowser.blockedPopups.reported) {
michael@0 439 if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) {
michael@0 440 var brandBundle = document.getElementById("bundle_brand");
michael@0 441 var brandShortName = brandBundle.getString("brandShortName");
michael@0 442 var popupCount = gBrowser.selectedBrowser.blockedPopups.length;
michael@0 443 #ifdef XP_WIN
michael@0 444 var popupButtonText = gNavigatorBundle.getString("popupWarningButton");
michael@0 445 var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey");
michael@0 446 #else
michael@0 447 var popupButtonText = gNavigatorBundle.getString("popupWarningButtonUnix");
michael@0 448 var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButtonUnix.accesskey");
michael@0 449 #endif
michael@0 450 var messageBase = gNavigatorBundle.getString("popupWarning.message");
michael@0 451 var message = PluralForm.get(popupCount, messageBase)
michael@0 452 .replace("#1", brandShortName)
michael@0 453 .replace("#2", popupCount);
michael@0 454
michael@0 455 var notificationBox = gBrowser.getNotificationBox();
michael@0 456 var notification = notificationBox.getNotificationWithValue("popup-blocked");
michael@0 457 if (notification) {
michael@0 458 notification.label = message;
michael@0 459 }
michael@0 460 else {
michael@0 461 var buttons = [{
michael@0 462 label: popupButtonText,
michael@0 463 accessKey: popupButtonAccesskey,
michael@0 464 popup: "blockedPopupOptions",
michael@0 465 callback: null
michael@0 466 }];
michael@0 467
michael@0 468 const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
michael@0 469 notificationBox.appendNotification(message, "popup-blocked",
michael@0 470 "chrome://browser/skin/Info.png",
michael@0 471 priority, buttons);
michael@0 472 }
michael@0 473 }
michael@0 474
michael@0 475 // Record the fact that we've reported this blocked popup, so we don't
michael@0 476 // show it again.
michael@0 477 gBrowser.selectedBrowser.blockedPopups.reported = true;
michael@0 478 }
michael@0 479 },
michael@0 480
michael@0 481 toggleAllowPopupsForSite: function (aEvent)
michael@0 482 {
michael@0 483 var pm = Services.perms;
michael@0 484 var shouldBlock = aEvent.target.getAttribute("block") == "true";
michael@0 485 var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
michael@0 486 pm.add(gBrowser.currentURI, "popup", perm);
michael@0 487
michael@0 488 gBrowser.getNotificationBox().removeCurrentNotification();
michael@0 489 },
michael@0 490
michael@0 491 fillPopupList: function (aEvent)
michael@0 492 {
michael@0 493 // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
michael@0 494 // we should really walk the blockedPopups and create a list of "allow for <host>"
michael@0 495 // menuitems for the common subset of hosts present in the report, this will
michael@0 496 // make us frame-safe.
michael@0 497 //
michael@0 498 // XXXjst - Note that when this is fixed to work with multi-framed sites,
michael@0 499 // also back out the fix for bug 343772 where
michael@0 500 // nsGlobalWindow::CheckOpenAllow() was changed to also
michael@0 501 // check if the top window's location is whitelisted.
michael@0 502 let browser = gBrowser.selectedBrowser;
michael@0 503 var uri = browser.currentURI;
michael@0 504 var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite");
michael@0 505 try {
michael@0 506 blockedPopupAllowSite.removeAttribute("hidden");
michael@0 507
michael@0 508 var pm = Services.perms;
michael@0 509 if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) {
michael@0 510 // Offer an item to block popups for this site, if a whitelist entry exists
michael@0 511 // already for it.
michael@0 512 let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host || uri.spec]);
michael@0 513 blockedPopupAllowSite.setAttribute("label", blockString);
michael@0 514 blockedPopupAllowSite.setAttribute("block", "true");
michael@0 515 }
michael@0 516 else {
michael@0 517 // Offer an item to allow popups for this site
michael@0 518 let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host || uri.spec]);
michael@0 519 blockedPopupAllowSite.setAttribute("label", allowString);
michael@0 520 blockedPopupAllowSite.removeAttribute("block");
michael@0 521 }
michael@0 522 }
michael@0 523 catch (e) {
michael@0 524 blockedPopupAllowSite.setAttribute("hidden", "true");
michael@0 525 }
michael@0 526
michael@0 527 if (PrivateBrowsingUtils.isWindowPrivate(window))
michael@0 528 blockedPopupAllowSite.setAttribute("disabled", "true");
michael@0 529 else
michael@0 530 blockedPopupAllowSite.removeAttribute("disabled");
michael@0 531
michael@0 532 var foundUsablePopupURI = false;
michael@0 533 var blockedPopups = browser.blockedPopups;
michael@0 534 if (blockedPopups) {
michael@0 535 for (let i = 0; i < blockedPopups.length; i++) {
michael@0 536 let blockedPopup = blockedPopups[i];
michael@0 537
michael@0 538 // popupWindowURI will be null if the file picker popup is blocked.
michael@0 539 // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
michael@0 540 if (!blockedPopup.popupWindowURI)
michael@0 541 continue;
michael@0 542 var popupURIspec = blockedPopup.popupWindowURI;
michael@0 543
michael@0 544 // Sometimes the popup URI that we get back from the blockedPopup
michael@0 545 // isn't useful (for instance, netscape.com's popup URI ends up
michael@0 546 // being "http://www.netscape.com", which isn't really the URI of
michael@0 547 // the popup they're trying to show). This isn't going to be
michael@0 548 // useful to the user, so we won't create a menu item for it.
michael@0 549 if (popupURIspec == "" || popupURIspec == "about:blank" ||
michael@0 550 popupURIspec == uri.spec)
michael@0 551 continue;
michael@0 552
michael@0 553 // Because of the short-circuit above, we may end up in a situation
michael@0 554 // in which we don't have any usable popup addresses to show in
michael@0 555 // the menu, and therefore we shouldn't show the separator. However,
michael@0 556 // since we got past the short-circuit, we must've found at least
michael@0 557 // one usable popup URI and thus we'll turn on the separator later.
michael@0 558 foundUsablePopupURI = true;
michael@0 559
michael@0 560 var menuitem = document.createElement("menuitem");
michael@0 561 var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix",
michael@0 562 [popupURIspec]);
michael@0 563 menuitem.setAttribute("label", label);
michael@0 564 menuitem.setAttribute("popupWindowURI", popupURIspec);
michael@0 565 menuitem.setAttribute("popupWindowFeatures", blockedPopup.popupWindowFeatures);
michael@0 566 menuitem.setAttribute("popupWindowName", blockedPopup.popupWindowName);
michael@0 567 menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);");
michael@0 568 menuitem.setAttribute("popupReportIndex", i);
michael@0 569 menuitem.popupReportBrowser = browser;
michael@0 570 aEvent.target.appendChild(menuitem);
michael@0 571 }
michael@0 572 }
michael@0 573
michael@0 574 // Show or hide the separator, depending on whether we added any
michael@0 575 // showable popup addresses to the menu.
michael@0 576 var blockedPopupsSeparator =
michael@0 577 document.getElementById("blockedPopupsSeparator");
michael@0 578 if (foundUsablePopupURI)
michael@0 579 blockedPopupsSeparator.removeAttribute("hidden");
michael@0 580 else
michael@0 581 blockedPopupsSeparator.setAttribute("hidden", true);
michael@0 582
michael@0 583 var blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage");
michael@0 584 var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
michael@0 585 blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
michael@0 586 if (aEvent.target.anchorNode.id == "page-report-button") {
michael@0 587 aEvent.target.anchorNode.setAttribute("open", "true");
michael@0 588 blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromLocationbar"));
michael@0 589 } else
michael@0 590 blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage"));
michael@0 591 },
michael@0 592
michael@0 593 onPopupHiding: function (aEvent) {
michael@0 594 if (aEvent.target.anchorNode.id == "page-report-button")
michael@0 595 aEvent.target.anchorNode.removeAttribute("open");
michael@0 596
michael@0 597 let item = aEvent.target.lastChild;
michael@0 598 while (item && item.getAttribute("observes") != "blockedPopupsSeparator") {
michael@0 599 let next = item.previousSibling;
michael@0 600 item.parentNode.removeChild(item);
michael@0 601 item = next;
michael@0 602 }
michael@0 603 },
michael@0 604
michael@0 605 showBlockedPopup: function (aEvent)
michael@0 606 {
michael@0 607 var target = aEvent.target;
michael@0 608 var popupReportIndex = target.getAttribute("popupReportIndex");
michael@0 609 let browser = target.popupReportBrowser;
michael@0 610 browser.unblockPopup(popupReportIndex);
michael@0 611 },
michael@0 612
michael@0 613 editPopupSettings: function ()
michael@0 614 {
michael@0 615 var host = "";
michael@0 616 try {
michael@0 617 host = gBrowser.currentURI.host;
michael@0 618 }
michael@0 619 catch (e) { }
michael@0 620
michael@0 621 var bundlePreferences = document.getElementById("bundle_preferences");
michael@0 622 var params = { blockVisible : false,
michael@0 623 sessionVisible : false,
michael@0 624 allowVisible : true,
michael@0 625 prefilledHost : host,
michael@0 626 permissionType : "popup",
michael@0 627 windowTitle : bundlePreferences.getString("popuppermissionstitle"),
michael@0 628 introText : bundlePreferences.getString("popuppermissionstext") };
michael@0 629 var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions");
michael@0 630 if (existingWindow) {
michael@0 631 existingWindow.initWithParams(params);
michael@0 632 existingWindow.focus();
michael@0 633 }
michael@0 634 else
michael@0 635 window.openDialog("chrome://browser/content/preferences/permissions.xul",
michael@0 636 "_blank", "resizable,dialog=no,centerscreen", params);
michael@0 637 },
michael@0 638
michael@0 639 dontShowMessage: function ()
michael@0 640 {
michael@0 641 var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
michael@0 642 gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage);
michael@0 643 gBrowser.getNotificationBox().removeCurrentNotification();
michael@0 644 }
michael@0 645 };
michael@0 646
michael@0 647 const gFormSubmitObserver = {
michael@0 648 QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
michael@0 649
michael@0 650 panel: null,
michael@0 651
michael@0 652 init: function()
michael@0 653 {
michael@0 654 this.panel = document.getElementById('invalid-form-popup');
michael@0 655 },
michael@0 656
michael@0 657 notifyInvalidSubmit : function (aFormElement, aInvalidElements)
michael@0 658 {
michael@0 659 // We are going to handle invalid form submission attempt by focusing the
michael@0 660 // first invalid element and show the corresponding validation message in a
michael@0 661 // panel attached to the element.
michael@0 662 if (!aInvalidElements.length) {
michael@0 663 return;
michael@0 664 }
michael@0 665
michael@0 666 // Don't show the popup if the current tab doesn't contain the invalid form.
michael@0 667 if (gBrowser.contentDocument !=
michael@0 668 aFormElement.ownerDocument.defaultView.top.document) {
michael@0 669 return;
michael@0 670 }
michael@0 671
michael@0 672 let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
michael@0 673
michael@0 674 if (!(element instanceof HTMLInputElement ||
michael@0 675 element instanceof HTMLTextAreaElement ||
michael@0 676 element instanceof HTMLSelectElement ||
michael@0 677 element instanceof HTMLButtonElement)) {
michael@0 678 return;
michael@0 679 }
michael@0 680
michael@0 681 this.panel.firstChild.textContent = element.validationMessage;
michael@0 682
michael@0 683 element.focus();
michael@0 684
michael@0 685 // If the user interacts with the element and makes it valid or leaves it,
michael@0 686 // we want to remove the popup.
michael@0 687 // We could check for clicks but a click is already removing the popup.
michael@0 688 function blurHandler() {
michael@0 689 gFormSubmitObserver.panel.hidePopup();
michael@0 690 };
michael@0 691 function inputHandler(e) {
michael@0 692 if (e.originalTarget.validity.valid) {
michael@0 693 gFormSubmitObserver.panel.hidePopup();
michael@0 694 } else {
michael@0 695 // If the element is now invalid for a new reason, we should update the
michael@0 696 // error message.
michael@0 697 if (gFormSubmitObserver.panel.firstChild.textContent !=
michael@0 698 e.originalTarget.validationMessage) {
michael@0 699 gFormSubmitObserver.panel.firstChild.textContent =
michael@0 700 e.originalTarget.validationMessage;
michael@0 701 }
michael@0 702 }
michael@0 703 };
michael@0 704 element.addEventListener("input", inputHandler, false);
michael@0 705 element.addEventListener("blur", blurHandler, false);
michael@0 706
michael@0 707 // One event to bring them all and in the darkness bind them.
michael@0 708 this.panel.addEventListener("popuphiding", function onPopupHiding(aEvent) {
michael@0 709 aEvent.target.removeEventListener("popuphiding", onPopupHiding, false);
michael@0 710 element.removeEventListener("input", inputHandler, false);
michael@0 711 element.removeEventListener("blur", blurHandler, false);
michael@0 712 }, false);
michael@0 713
michael@0 714 this.panel.hidden = false;
michael@0 715
michael@0 716 // We want to show the popup at the middle of checkbox and radio buttons
michael@0 717 // and where the content begin for the other elements.
michael@0 718 let offset = 0;
michael@0 719 let position = "";
michael@0 720
michael@0 721 if (element.tagName == 'INPUT' &&
michael@0 722 (element.type == 'radio' || element.type == 'checkbox')) {
michael@0 723 position = "bottomcenter topleft";
michael@0 724 } else {
michael@0 725 let win = element.ownerDocument.defaultView;
michael@0 726 let style = win.getComputedStyle(element, null);
michael@0 727 let utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 728 .getInterface(Components.interfaces.nsIDOMWindowUtils);
michael@0 729
michael@0 730 if (style.direction == 'rtl') {
michael@0 731 offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
michael@0 732 } else {
michael@0 733 offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
michael@0 734 }
michael@0 735
michael@0 736 offset = Math.round(offset * utils.fullZoom);
michael@0 737
michael@0 738 position = "after_start";
michael@0 739 }
michael@0 740
michael@0 741 this.panel.openPopup(element, position, offset, 0);
michael@0 742 }
michael@0 743 };
michael@0 744
michael@0 745 var gBrowserInit = {
michael@0 746 delayedStartupFinished: false,
michael@0 747
michael@0 748 onLoad: function() {
michael@0 749 gMultiProcessBrowser =
michael@0 750 window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 751 .getInterface(Ci.nsIWebNavigation)
michael@0 752 .QueryInterface(Ci.nsILoadContext)
michael@0 753 .useRemoteTabs;
michael@0 754
michael@0 755 var mustLoadSidebar = false;
michael@0 756
michael@0 757 if (!gMultiProcessBrowser) {
michael@0 758 // There is a Content:Click message manually sent from content.
michael@0 759 Cc["@mozilla.org/eventlistenerservice;1"]
michael@0 760 .getService(Ci.nsIEventListenerService)
michael@0 761 .addSystemEventListener(gBrowser, "click", contentAreaClick, true);
michael@0 762 }
michael@0 763
michael@0 764 gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
michael@0 765
michael@0 766 // Note that the XBL binding is untrusted
michael@0 767 gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true);
michael@0 768 gBrowser.addEventListener("PluginCrashed", gPluginHandler, true);
michael@0 769 gBrowser.addEventListener("PluginOutdated", gPluginHandler, true);
michael@0 770 gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true);
michael@0 771 gBrowser.addEventListener("PluginRemoved", gPluginHandler, true);
michael@0 772
michael@0 773 gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);
michael@0 774
michael@0 775 Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
michael@0 776
michael@0 777 window.addEventListener("AppCommand", HandleAppCommandEvent, true);
michael@0 778
michael@0 779 // These routines add message listeners. They must run before
michael@0 780 // loading the frame script to ensure that we don't miss any
michael@0 781 // message sent between when the frame script is loaded and when
michael@0 782 // the listener is registered.
michael@0 783 DOMLinkHandler.init();
michael@0 784 gPageStyleMenu.init();
michael@0 785 LanguageDetectionListener.init();
michael@0 786
michael@0 787 messageManager.loadFrameScript("chrome://browser/content/content.js", true);
michael@0 788
michael@0 789 // initialize observers and listeners
michael@0 790 // and give C++ access to gBrowser
michael@0 791 XULBrowserWindow.init();
michael@0 792 window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 793 .getInterface(nsIWebNavigation)
michael@0 794 .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
michael@0 795 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 796 .getInterface(Ci.nsIXULWindow)
michael@0 797 .XULBrowserWindow = window.XULBrowserWindow;
michael@0 798 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
michael@0 799 new nsBrowserAccess();
michael@0 800
michael@0 801 // hook up UI through progress listener
michael@0 802 gBrowser.addProgressListener(window.XULBrowserWindow);
michael@0 803 gBrowser.addTabsProgressListener(window.TabsProgressListener);
michael@0 804
michael@0 805 // setup simple gestures support
michael@0 806 gGestureSupport.init(true);
michael@0 807
michael@0 808 // setup history swipe animation
michael@0 809 gHistorySwipeAnimation.init();
michael@0 810
michael@0 811 if (window.opener && !window.opener.closed &&
michael@0 812 PrivateBrowsingUtils.isWindowPrivate(window) == PrivateBrowsingUtils.isWindowPrivate(window.opener)) {
michael@0 813 let openerSidebarBox = window.opener.document.getElementById("sidebar-box");
michael@0 814 // If the opener had a sidebar, open the same sidebar in our window.
michael@0 815 // The opener can be the hidden window too, if we're coming from the state
michael@0 816 // where no windows are open, and the hidden window has no sidebar box.
michael@0 817 if (openerSidebarBox && !openerSidebarBox.hidden) {
michael@0 818 let sidebarCmd = openerSidebarBox.getAttribute("sidebarcommand");
michael@0 819 let sidebarCmdElem = document.getElementById(sidebarCmd);
michael@0 820
michael@0 821 // dynamically generated sidebars will fail this check.
michael@0 822 if (sidebarCmdElem) {
michael@0 823 let sidebarBox = document.getElementById("sidebar-box");
michael@0 824 let sidebarTitle = document.getElementById("sidebar-title");
michael@0 825
michael@0 826 sidebarTitle.setAttribute(
michael@0 827 "value", window.opener.document.getElementById("sidebar-title").getAttribute("value"));
michael@0 828 sidebarBox.setAttribute("width", openerSidebarBox.boxObject.width);
michael@0 829
michael@0 830 sidebarBox.setAttribute("sidebarcommand", sidebarCmd);
michael@0 831 // Note: we're setting 'src' on sidebarBox, which is a <vbox>, not on
michael@0 832 // the <browser id="sidebar">. This lets us delay the actual load until
michael@0 833 // delayedStartup().
michael@0 834 sidebarBox.setAttribute(
michael@0 835 "src", window.opener.document.getElementById("sidebar").getAttribute("src"));
michael@0 836 mustLoadSidebar = true;
michael@0 837
michael@0 838 sidebarBox.hidden = false;
michael@0 839 document.getElementById("sidebar-splitter").hidden = false;
michael@0 840 sidebarCmdElem.setAttribute("checked", "true");
michael@0 841 }
michael@0 842 }
michael@0 843 }
michael@0 844 else {
michael@0 845 let box = document.getElementById("sidebar-box");
michael@0 846 if (box.hasAttribute("sidebarcommand")) {
michael@0 847 let commandID = box.getAttribute("sidebarcommand");
michael@0 848 if (commandID) {
michael@0 849 let command = document.getElementById(commandID);
michael@0 850 if (command) {
michael@0 851 mustLoadSidebar = true;
michael@0 852 box.hidden = false;
michael@0 853 document.getElementById("sidebar-splitter").hidden = false;
michael@0 854 command.setAttribute("checked", "true");
michael@0 855 }
michael@0 856 else {
michael@0 857 // Remove the |sidebarcommand| attribute, because the element it
michael@0 858 // refers to no longer exists, so we should assume this sidebar
michael@0 859 // panel has been uninstalled. (249883)
michael@0 860 box.removeAttribute("sidebarcommand");
michael@0 861 }
michael@0 862 }
michael@0 863 }
michael@0 864 }
michael@0 865
michael@0 866 // Certain kinds of automigration rely on this notification to complete their
michael@0 867 // tasks BEFORE the browser window is shown.
michael@0 868 Services.obs.notifyObservers(null, "browser-window-before-show", "");
michael@0 869
michael@0 870 // Set a sane starting width/height for all resolutions on new profiles.
michael@0 871 if (!document.documentElement.hasAttribute("width")) {
michael@0 872 let defaultWidth;
michael@0 873 let defaultHeight;
michael@0 874
michael@0 875 // Very small: maximize the window
michael@0 876 // Portrait : use about full width and 3/4 height, to view entire pages
michael@0 877 // at once (without being obnoxiously tall)
michael@0 878 // Widescreen: use about half width, to suggest side-by-side page view
michael@0 879 // Otherwise : use 3/4 height and width
michael@0 880 if (screen.availHeight <= 600) {
michael@0 881 document.documentElement.setAttribute("sizemode", "maximized");
michael@0 882 defaultWidth = 610;
michael@0 883 defaultHeight = 450;
michael@0 884 }
michael@0 885 else {
michael@0 886 if (screen.availWidth <= screen.availHeight) {
michael@0 887 defaultWidth = screen.availWidth * .9;
michael@0 888 defaultHeight = screen.availHeight * .75;
michael@0 889 }
michael@0 890 else if (screen.availWidth >= 2048) {
michael@0 891 defaultWidth = (screen.availWidth / 2) - 20;
michael@0 892 defaultHeight = screen.availHeight - 10;
michael@0 893 }
michael@0 894 else {
michael@0 895 defaultWidth = screen.availWidth * .75;
michael@0 896 defaultHeight = screen.availHeight * .75;
michael@0 897 }
michael@0 898
michael@0 899 #if MOZ_WIDGET_GTK == 2
michael@0 900 // On X, we're not currently able to account for the size of the window
michael@0 901 // border. Use 28px as a guess (titlebar + bottom window border)
michael@0 902 defaultHeight -= 28;
michael@0 903 #endif
michael@0 904 }
michael@0 905 document.documentElement.setAttribute("width", defaultWidth);
michael@0 906 document.documentElement.setAttribute("height", defaultHeight);
michael@0 907 }
michael@0 908
michael@0 909 if (!window.toolbar.visible) {
michael@0 910 // adjust browser UI for popups
michael@0 911 if (gURLBar) {
michael@0 912 gURLBar.setAttribute("readonly", "true");
michael@0 913 gURLBar.setAttribute("enablehistory", "false");
michael@0 914 }
michael@0 915 goSetCommandEnabled("cmd_newNavigatorTab", false);
michael@0 916 }
michael@0 917
michael@0 918 // Misc. inits.
michael@0 919 CombinedStopReload.init();
michael@0 920 gPrivateBrowsingUI.init();
michael@0 921 TabsInTitlebar.init();
michael@0 922
michael@0 923 #ifdef XP_WIN
michael@0 924 if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
michael@0 925 window.matchMedia("(-moz-windows-default-theme)").matches) {
michael@0 926 let windowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
michael@0 927 .Windows8WindowFrameColor.get();
michael@0 928
michael@0 929 // Formula from W3C's WCAG 2.0 spec's color ratio and relative luminance,
michael@0 930 // section 1.3.4, http://www.w3.org/TR/WCAG20/ .
michael@0 931 windowFrameColor = windowFrameColor.map((color) => {
michael@0 932 if (color <= 10) {
michael@0 933 return color / 255 / 12.92;
michael@0 934 }
michael@0 935 return Math.pow(((color / 255) + 0.055) / 1.055, 2.4);
michael@0 936 });
michael@0 937 let backgroundLuminance = windowFrameColor[0] * 0.2126 +
michael@0 938 windowFrameColor[1] * 0.7152 +
michael@0 939 windowFrameColor[2] * 0.0722;
michael@0 940 let foregroundLuminance = 0; // Default to black for foreground text.
michael@0 941 let contrastRatio = (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05);
michael@0 942 if (contrastRatio < 3) {
michael@0 943 document.documentElement.setAttribute("darkwindowframe", "true");
michael@0 944 }
michael@0 945 }
michael@0 946 #endif
michael@0 947
michael@0 948 ToolbarIconColor.init();
michael@0 949
michael@0 950 // Wait until chrome is painted before executing code not critical to making the window visible
michael@0 951 this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar);
michael@0 952 window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
michael@0 953
michael@0 954 this._loadHandled = true;
michael@0 955 },
michael@0 956
michael@0 957 _cancelDelayedStartup: function () {
michael@0 958 window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
michael@0 959 this._boundDelayedStartup = null;
michael@0 960 },
michael@0 961
michael@0 962 _delayedStartup: function(mustLoadSidebar) {
michael@0 963 let tmp = {};
michael@0 964 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp);
michael@0 965 let TelemetryTimestamps = tmp.TelemetryTimestamps;
michael@0 966 TelemetryTimestamps.add("delayedStartupStarted");
michael@0 967
michael@0 968 this._cancelDelayedStartup();
michael@0 969
michael@0 970 // We need to set the MozApplicationManifest event listeners up
michael@0 971 // before we start loading the home pages in case a document has
michael@0 972 // a "manifest" attribute, in which the MozApplicationManifest event
michael@0 973 // will be fired.
michael@0 974 gBrowser.addEventListener("MozApplicationManifest",
michael@0 975 OfflineApps, false);
michael@0 976 // listen for offline apps on social
michael@0 977 let socialBrowser = document.getElementById("social-sidebar-browser");
michael@0 978 socialBrowser.addEventListener("MozApplicationManifest",
michael@0 979 OfflineApps, false);
michael@0 980
michael@0 981 let uriToLoad = this._getUriToLoad();
michael@0 982 var isLoadingBlank = isBlankPageURL(uriToLoad);
michael@0 983
michael@0 984 // This pageshow listener needs to be registered before we may call
michael@0 985 // swapBrowsersAndCloseOther() to receive pageshow events fired by that.
michael@0 986 gBrowser.addEventListener("pageshow", function(event) {
michael@0 987 // Filter out events that are not about the document load we are interested in
michael@0 988 if (content && event.target == content.document)
michael@0 989 setTimeout(pageShowEventHandlers, 0, event.persisted);
michael@0 990 }, true);
michael@0 991
michael@0 992 if (uriToLoad && uriToLoad != "about:blank") {
michael@0 993 if (uriToLoad instanceof Ci.nsISupportsArray) {
michael@0 994 let count = uriToLoad.Count();
michael@0 995 let specs = [];
michael@0 996 for (let i = 0; i < count; i++) {
michael@0 997 let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString);
michael@0 998 specs.push(urisstring.data);
michael@0 999 }
michael@0 1000
michael@0 1001 // This function throws for certain malformed URIs, so use exception handling
michael@0 1002 // so that we don't disrupt startup
michael@0 1003 try {
michael@0 1004 gBrowser.loadTabs(specs, false, true);
michael@0 1005 } catch (e) {}
michael@0 1006 }
michael@0 1007 else if (uriToLoad instanceof XULElement) {
michael@0 1008 // swap the given tab with the default about:blank tab and then close
michael@0 1009 // the original tab in the other window.
michael@0 1010
michael@0 1011 // Stop the about:blank load
michael@0 1012 gBrowser.stop();
michael@0 1013 // make sure it has a docshell
michael@0 1014 gBrowser.docShell;
michael@0 1015
michael@0 1016 gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad);
michael@0 1017 }
michael@0 1018 // window.arguments[2]: referrer (nsIURI)
michael@0 1019 // [3]: postData (nsIInputStream)
michael@0 1020 // [4]: allowThirdPartyFixup (bool)
michael@0 1021 else if (window.arguments.length >= 3) {
michael@0 1022 loadURI(uriToLoad, window.arguments[2], window.arguments[3] || null,
michael@0 1023 window.arguments[4] || false);
michael@0 1024 window.focus();
michael@0 1025 }
michael@0 1026 // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
michael@0 1027 // Such callers expect that window.arguments[0] is handled as a single URI.
michael@0 1028 else {
michael@0 1029 if (uriToLoad == "about:newtab" &&
michael@0 1030 Services.prefs.getBoolPref("browser.newtabpage.enabled")) {
michael@0 1031 Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
michael@0 1032 }
michael@0 1033 loadOneOrMoreURIs(uriToLoad);
michael@0 1034 }
michael@0 1035 }
michael@0 1036
michael@0 1037 #ifdef MOZ_SAFE_BROWSING
michael@0 1038 // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
michael@0 1039 setTimeout(function() { SafeBrowsing.init(); }, 2000);
michael@0 1040 #endif
michael@0 1041
michael@0 1042 Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
michael@0 1043 Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
michael@0 1044 Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
michael@0 1045 Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
michael@0 1046 Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
michael@0 1047 Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
michael@0 1048 Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
michael@0 1049
michael@0 1050 BrowserOffline.init();
michael@0 1051 OfflineApps.init();
michael@0 1052 IndexedDBPromptHelper.init();
michael@0 1053 CanvasPermissionPromptHelper.init();
michael@0 1054 gFormSubmitObserver.init();
michael@0 1055 gRemoteTabsUI.init();
michael@0 1056
michael@0 1057 // Initialize the full zoom setting.
michael@0 1058 // We do this before the session restore service gets initialized so we can
michael@0 1059 // apply full zoom settings to tabs restored by the session restore service.
michael@0 1060 FullZoom.init();
michael@0 1061 PanelUI.init();
michael@0 1062 LightweightThemeListener.init();
michael@0 1063 WebrtcIndicator.init();
michael@0 1064
michael@0 1065 // Ensure login manager is up and running.
michael@0 1066 Services.logins;
michael@0 1067
michael@0 1068 #ifdef MOZ_CRASHREPORTER
michael@0 1069 if (gMultiProcessBrowser)
michael@0 1070 TabCrashReporter.init();
michael@0 1071 #endif
michael@0 1072
michael@0 1073 if (mustLoadSidebar) {
michael@0 1074 let sidebar = document.getElementById("sidebar");
michael@0 1075 let sidebarBox = document.getElementById("sidebar-box");
michael@0 1076 sidebar.setAttribute("src", sidebarBox.getAttribute("src"));
michael@0 1077 }
michael@0 1078
michael@0 1079 UpdateUrlbarSearchSplitterState();
michael@0 1080
michael@0 1081 if (!isLoadingBlank || !focusAndSelectUrlBar())
michael@0 1082 gBrowser.selectedBrowser.focus();
michael@0 1083
michael@0 1084 // Set up Sanitize Item
michael@0 1085 this._initializeSanitizer();
michael@0 1086
michael@0 1087 // Enable/Disable auto-hide tabbar
michael@0 1088 gBrowser.tabContainer.updateVisibility();
michael@0 1089
michael@0 1090 BookmarkingUI.init();
michael@0 1091
michael@0 1092 gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
michael@0 1093
michael@0 1094 var homeButton = document.getElementById("home-button");
michael@0 1095 gHomeButton.updateTooltip(homeButton);
michael@0 1096 gHomeButton.updatePersonalToolbarStyle(homeButton);
michael@0 1097
michael@0 1098 // BiDi UI
michael@0 1099 gBidiUI = isBidiEnabled();
michael@0 1100 if (gBidiUI) {
michael@0 1101 document.getElementById("documentDirection-separator").hidden = false;
michael@0 1102 document.getElementById("documentDirection-swap").hidden = false;
michael@0 1103 document.getElementById("textfieldDirection-separator").hidden = false;
michael@0 1104 document.getElementById("textfieldDirection-swap").hidden = false;
michael@0 1105 }
michael@0 1106
michael@0 1107 // Setup click-and-hold gestures access to the session history
michael@0 1108 // menus if global click-and-hold isn't turned on
michael@0 1109 if (!getBoolPref("ui.click_hold_context_menus", false))
michael@0 1110 SetClickAndHoldHandlers();
michael@0 1111
michael@0 1112 let NP = {};
michael@0 1113 Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP);
michael@0 1114 NP.trackBrowserWindow(window);
michael@0 1115
michael@0 1116 PlacesToolbarHelper.init();
michael@0 1117
michael@0 1118 ctrlTab.readPref();
michael@0 1119 gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
michael@0 1120
michael@0 1121 // Initialize the download manager some time after the app starts so that
michael@0 1122 // auto-resume downloads begin (such as after crashing or quitting with
michael@0 1123 // active downloads) and speeds up the first-load of the download manager UI.
michael@0 1124 // If the user manually opens the download manager before the timeout, the
michael@0 1125 // downloads will start right away, and initializing again won't hurt.
michael@0 1126 setTimeout(function() {
michael@0 1127 try {
michael@0 1128 Cu.import("resource:///modules/DownloadsCommon.jsm", {})
michael@0 1129 .DownloadsCommon.initializeAllDataLinks();
michael@0 1130 Cu.import("resource:///modules/DownloadsTaskbar.jsm", {})
michael@0 1131 .DownloadsTaskbar.registerIndicator(window);
michael@0 1132 } catch (ex) {
michael@0 1133 Cu.reportError(ex);
michael@0 1134 }
michael@0 1135 }, 10000);
michael@0 1136
michael@0 1137 // The object handling the downloads indicator is also initialized here in the
michael@0 1138 // delayed startup function, but the actual indicator element is not loaded
michael@0 1139 // unless there are downloads to be displayed.
michael@0 1140 DownloadsButton.initializeIndicator();
michael@0 1141
michael@0 1142 #ifndef XP_MACOSX
michael@0 1143 updateEditUIVisibility();
michael@0 1144 let placesContext = document.getElementById("placesContext");
michael@0 1145 placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
michael@0 1146 placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
michael@0 1147 #endif
michael@0 1148
michael@0 1149 gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
michael@0 1150 gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
michael@0 1151 gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
michael@0 1152
michael@0 1153 if (Win7Features)
michael@0 1154 Win7Features.onOpenWindow();
michael@0 1155
michael@0 1156 // called when we go into full screen, even if initiated by a web page script
michael@0 1157 window.addEventListener("fullscreen", onFullScreen, true);
michael@0 1158
michael@0 1159 // Called when we enter DOM full-screen mode. Note we can already be in browser
michael@0 1160 // full-screen mode when we enter DOM full-screen mode.
michael@0 1161 window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true);
michael@0 1162
michael@0 1163 if (window.fullScreen)
michael@0 1164 onFullScreen();
michael@0 1165 if (document.mozFullScreen)
michael@0 1166 onMozEnteredDomFullscreen();
michael@0 1167
michael@0 1168 #ifdef MOZ_SERVICES_SYNC
michael@0 1169 // initialize the sync UI
michael@0 1170 gSyncUI.init();
michael@0 1171 gFxAccounts.init();
michael@0 1172 #endif
michael@0 1173
michael@0 1174 #ifdef MOZ_DATA_REPORTING
michael@0 1175 gDataNotificationInfoBar.init();
michael@0 1176 #endif
michael@0 1177
michael@0 1178 gBrowserThumbnails.init();
michael@0 1179
michael@0 1180 // Add Devtools menuitems and listeners
michael@0 1181 gDevToolsBrowser.registerBrowserWindow(window);
michael@0 1182
michael@0 1183 window.addEventListener("mousemove", MousePosTracker, false);
michael@0 1184 window.addEventListener("dragover", MousePosTracker, false);
michael@0 1185
michael@0 1186 gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
michael@0 1187 gNavToolbox.addEventListener("customizationchange", CustomizationHandler);
michael@0 1188 gNavToolbox.addEventListener("customizationending", CustomizationHandler);
michael@0 1189
michael@0 1190 // End startup crash tracking after a delay to catch crashes while restoring
michael@0 1191 // tabs and to postpone saving the pref to disk.
michael@0 1192 try {
michael@0 1193 const startupCrashEndDelay = 30 * 1000;
michael@0 1194 setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay);
michael@0 1195 } catch (ex) {
michael@0 1196 Cu.reportError("Could not end startup crash tracking: " + ex);
michael@0 1197 }
michael@0 1198
michael@0 1199 if (typeof WindowsPrefSync !== 'undefined') {
michael@0 1200 // Pulls in Metro controlled prefs and pushes out Desktop controlled prefs
michael@0 1201 WindowsPrefSync.init();
michael@0 1202 }
michael@0 1203
michael@0 1204 SessionStore.promiseInitialized.then(() => {
michael@0 1205 // Bail out if the window has been closed in the meantime.
michael@0 1206 if (window.closed) {
michael@0 1207 return;
michael@0 1208 }
michael@0 1209
michael@0 1210 // Enable the Restore Last Session command if needed
michael@0 1211 RestoreLastSessionObserver.init();
michael@0 1212
michael@0 1213 SocialUI.init();
michael@0 1214 TabView.init();
michael@0 1215
michael@0 1216 setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
michael@0 1217 });
michael@0 1218 this.delayedStartupFinished = true;
michael@0 1219
michael@0 1220 Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
michael@0 1221 TelemetryTimestamps.add("delayedStartupFinished");
michael@0 1222 },
michael@0 1223
michael@0 1224 // Returns the URI(s) to load at startup.
michael@0 1225 _getUriToLoad: function () {
michael@0 1226 // window.arguments[0]: URI to load (string), or an nsISupportsArray of
michael@0 1227 // nsISupportsStrings to load, or a xul:tab of
michael@0 1228 // a tabbrowser, which will be replaced by this
michael@0 1229 // window (for this case, all other arguments are
michael@0 1230 // ignored).
michael@0 1231 if (!window.arguments || !window.arguments[0])
michael@0 1232 return null;
michael@0 1233
michael@0 1234 let uri = window.arguments[0];
michael@0 1235 let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"]
michael@0 1236 .getService(Ci.nsISessionStartup);
michael@0 1237 let defaultArgs = Cc["@mozilla.org/browser/clh;1"]
michael@0 1238 .getService(Ci.nsIBrowserHandler)
michael@0 1239 .defaultArgs;
michael@0 1240
michael@0 1241 // If the given URI matches defaultArgs (the default homepage) we want
michael@0 1242 // to block its load if we're going to restore a session anyway.
michael@0 1243 if (uri == defaultArgs && sessionStartup.willOverrideHomepage)
michael@0 1244 return null;
michael@0 1245
michael@0 1246 return uri;
michael@0 1247 },
michael@0 1248
michael@0 1249 onUnload: function() {
michael@0 1250 // In certain scenarios it's possible for unload to be fired before onload,
michael@0 1251 // (e.g. if the window is being closed after browser.js loads but before the
michael@0 1252 // load completes). In that case, there's nothing to do here.
michael@0 1253 if (!this._loadHandled)
michael@0 1254 return;
michael@0 1255
michael@0 1256 gDevToolsBrowser.forgetBrowserWindow(window);
michael@0 1257
michael@0 1258 let desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar");
michael@0 1259 if (desc && !desc.get) {
michael@0 1260 DeveloperToolbar.destroy();
michael@0 1261 }
michael@0 1262
michael@0 1263 // First clean up services initialized in gBrowserInit.onLoad (or those whose
michael@0 1264 // uninit methods don't depend on the services having been initialized).
michael@0 1265
michael@0 1266 CombinedStopReload.uninit();
michael@0 1267
michael@0 1268 gGestureSupport.init(false);
michael@0 1269
michael@0 1270 gHistorySwipeAnimation.uninit();
michael@0 1271
michael@0 1272 FullScreen.cleanup();
michael@0 1273
michael@0 1274 #ifdef MOZ_SERVICES_SYNC
michael@0 1275 gFxAccounts.uninit();
michael@0 1276 #endif
michael@0 1277
michael@0 1278 Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
michael@0 1279
michael@0 1280 try {
michael@0 1281 gBrowser.removeProgressListener(window.XULBrowserWindow);
michael@0 1282 gBrowser.removeTabsProgressListener(window.TabsProgressListener);
michael@0 1283 } catch (ex) {
michael@0 1284 }
michael@0 1285
michael@0 1286 PlacesToolbarHelper.uninit();
michael@0 1287
michael@0 1288 BookmarkingUI.uninit();
michael@0 1289
michael@0 1290 TabsInTitlebar.uninit();
michael@0 1291
michael@0 1292 ToolbarIconColor.uninit();
michael@0 1293
michael@0 1294 var enumerator = Services.wm.getEnumerator(null);
michael@0 1295 enumerator.getNext();
michael@0 1296 if (!enumerator.hasMoreElements()) {
michael@0 1297 document.persist("sidebar-box", "sidebarcommand");
michael@0 1298 document.persist("sidebar-box", "width");
michael@0 1299 document.persist("sidebar-box", "src");
michael@0 1300 document.persist("sidebar-title", "value");
michael@0 1301 }
michael@0 1302
michael@0 1303 // Now either cancel delayedStartup, or clean up the services initialized from
michael@0 1304 // it.
michael@0 1305 if (this._boundDelayedStartup) {
michael@0 1306 this._cancelDelayedStartup();
michael@0 1307 } else {
michael@0 1308 if (Win7Features)
michael@0 1309 Win7Features.onCloseWindow();
michael@0 1310
michael@0 1311 gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
michael@0 1312 ctrlTab.uninit();
michael@0 1313 TabView.uninit();
michael@0 1314 SocialUI.uninit();
michael@0 1315 gBrowserThumbnails.uninit();
michael@0 1316 FullZoom.destroy();
michael@0 1317
michael@0 1318 Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
michael@0 1319 Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
michael@0 1320 Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
michael@0 1321 Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
michael@0 1322 Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
michael@0 1323 Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
michael@0 1324 Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit");
michael@0 1325
michael@0 1326 try {
michael@0 1327 gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
michael@0 1328 } catch (ex) {
michael@0 1329 Cu.reportError(ex);
michael@0 1330 }
michael@0 1331
michael@0 1332 if (typeof WindowsPrefSync !== 'undefined') {
michael@0 1333 WindowsPrefSync.uninit();
michael@0 1334 }
michael@0 1335
michael@0 1336 BrowserOffline.uninit();
michael@0 1337 OfflineApps.uninit();
michael@0 1338 IndexedDBPromptHelper.uninit();
michael@0 1339 CanvasPermissionPromptHelper.uninit();
michael@0 1340 LightweightThemeListener.uninit();
michael@0 1341 PanelUI.uninit();
michael@0 1342 }
michael@0 1343
michael@0 1344 // Final window teardown, do this last.
michael@0 1345 window.XULBrowserWindow = null;
michael@0 1346 window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 1347 .getInterface(Ci.nsIWebNavigation)
michael@0 1348 .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
michael@0 1349 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 1350 .getInterface(Ci.nsIXULWindow)
michael@0 1351 .XULBrowserWindow = null;
michael@0 1352 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null;
michael@0 1353 },
michael@0 1354
michael@0 1355 #ifdef XP_MACOSX
michael@0 1356 // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and
michael@0 1357 // nonBrowserWindowShutdown() are used for non-browser windows in
michael@0 1358 // macBrowserOverlay
michael@0 1359 nonBrowserWindowStartup: function() {
michael@0 1360 // Disable inappropriate commands / submenus
michael@0 1361 var disabledItems = ['Browser:SavePage',
michael@0 1362 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain',
michael@0 1363 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload',
michael@0 1364 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen',
michael@0 1365 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs',
michael@0 1366 'View:PageInfo', 'Browser:ToggleTabView'];
michael@0 1367 var element;
michael@0 1368
michael@0 1369 for (let disabledItem of disabledItems) {
michael@0 1370 element = document.getElementById(disabledItem);
michael@0 1371 if (element)
michael@0 1372 element.setAttribute("disabled", "true");
michael@0 1373 }
michael@0 1374
michael@0 1375 // If no windows are active (i.e. we're the hidden window), disable the close, minimize
michael@0 1376 // and zoom menu commands as well
michael@0 1377 if (window.location.href == "chrome://browser/content/hiddenWindow.xul") {
michael@0 1378 var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow'];
michael@0 1379 for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) {
michael@0 1380 element = document.getElementById(hiddenWindowDisabledItem);
michael@0 1381 if (element)
michael@0 1382 element.setAttribute("disabled", "true");
michael@0 1383 }
michael@0 1384
michael@0 1385 // also hide the window-list separator
michael@0 1386 element = document.getElementById("sep-window-list");
michael@0 1387 element.setAttribute("hidden", "true");
michael@0 1388
michael@0 1389 // Setup the dock menu.
michael@0 1390 let dockMenuElement = document.getElementById("menu_mac_dockmenu");
michael@0 1391 if (dockMenuElement != null) {
michael@0 1392 let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"]
michael@0 1393 .createInstance(Ci.nsIStandaloneNativeMenu);
michael@0 1394
michael@0 1395 try {
michael@0 1396 nativeMenu.init(dockMenuElement);
michael@0 1397
michael@0 1398 let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
michael@0 1399 .getService(Ci.nsIMacDockSupport);
michael@0 1400 dockSupport.dockMenu = nativeMenu;
michael@0 1401 }
michael@0 1402 catch (e) {
michael@0 1403 }
michael@0 1404 }
michael@0 1405 }
michael@0 1406
michael@0 1407 if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
michael@0 1408 document.getElementById("macDockMenuNewWindow").hidden = true;
michael@0 1409 }
michael@0 1410
michael@0 1411 this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0);
michael@0 1412 },
michael@0 1413
michael@0 1414 nonBrowserWindowDelayedStartup: function() {
michael@0 1415 this._delayedStartupTimeoutId = null;
michael@0 1416
michael@0 1417 // initialise the offline listener
michael@0 1418 BrowserOffline.init();
michael@0 1419
michael@0 1420 // Set up Sanitize Item
michael@0 1421 this._initializeSanitizer();
michael@0 1422
michael@0 1423 // initialize the private browsing UI
michael@0 1424 gPrivateBrowsingUI.init();
michael@0 1425
michael@0 1426 #ifdef MOZ_SERVICES_SYNC
michael@0 1427 // initialize the sync UI
michael@0 1428 gSyncUI.init();
michael@0 1429 #endif
michael@0 1430 },
michael@0 1431
michael@0 1432 nonBrowserWindowShutdown: function() {
michael@0 1433 // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do -
michael@0 1434 // just cancel the pending timeout and return;
michael@0 1435 if (this._delayedStartupTimeoutId) {
michael@0 1436 clearTimeout(this._delayedStartupTimeoutId);
michael@0 1437 return;
michael@0 1438 }
michael@0 1439
michael@0 1440 BrowserOffline.uninit();
michael@0 1441 },
michael@0 1442 #endif
michael@0 1443
michael@0 1444 _initializeSanitizer: function() {
michael@0 1445 const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize";
michael@0 1446 if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) {
michael@0 1447 gPrefService.clearUserPref(kDidSanitizeDomain);
michael@0 1448 // We need to persist this preference change, since we want to
michael@0 1449 // check it at next app start even if the browser exits abruptly
michael@0 1450 gPrefService.savePrefFile(null);
michael@0 1451 }
michael@0 1452
michael@0 1453 /**
michael@0 1454 * Migrate Firefox 3.0 privacy.item prefs under one of these conditions:
michael@0 1455 *
michael@0 1456 * a) User has customized any privacy.item prefs
michael@0 1457 * b) privacy.sanitize.sanitizeOnShutdown is set
michael@0 1458 */
michael@0 1459 if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) {
michael@0 1460 let itemBranch = gPrefService.getBranch("privacy.item.");
michael@0 1461 let itemArray = itemBranch.getChildList("");
michael@0 1462
michael@0 1463 // See if any privacy.item prefs are set
michael@0 1464 let doMigrate = itemArray.some(function (name) itemBranch.prefHasUserValue(name));
michael@0 1465 // Or if sanitizeOnShutdown is set
michael@0 1466 if (!doMigrate)
michael@0 1467 doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown");
michael@0 1468
michael@0 1469 if (doMigrate) {
michael@0 1470 let cpdBranch = gPrefService.getBranch("privacy.cpd.");
michael@0 1471 let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown.");
michael@0 1472 for (let name of itemArray) {
michael@0 1473 try {
michael@0 1474 // don't migrate password or offlineApps clearing in the CRH dialog since
michael@0 1475 // there's no UI for those anymore. They default to false. bug 497656
michael@0 1476 if (name != "passwords" && name != "offlineApps")
michael@0 1477 cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name));
michael@0 1478 clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name));
michael@0 1479 }
michael@0 1480 catch(e) {
michael@0 1481 Cu.reportError("Exception thrown during privacy pref migration: " + e);
michael@0 1482 }
michael@0 1483 }
michael@0 1484 }
michael@0 1485
michael@0 1486 gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true);
michael@0 1487 }
michael@0 1488 },
michael@0 1489 }
michael@0 1490
michael@0 1491
michael@0 1492 /* Legacy global init functions */
michael@0 1493 var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit);
michael@0 1494 var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit);
michael@0 1495 #ifdef XP_MACOSX
michael@0 1496 var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit);
michael@0 1497 var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit);
michael@0 1498 var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit);
michael@0 1499 #endif
michael@0 1500
michael@0 1501 function HandleAppCommandEvent(evt) {
michael@0 1502 switch (evt.command) {
michael@0 1503 case "Back":
michael@0 1504 BrowserBack();
michael@0 1505 break;
michael@0 1506 case "Forward":
michael@0 1507 BrowserForward();
michael@0 1508 break;
michael@0 1509 case "Reload":
michael@0 1510 BrowserReloadSkipCache();
michael@0 1511 break;
michael@0 1512 case "Stop":
michael@0 1513 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
michael@0 1514 BrowserStop();
michael@0 1515 break;
michael@0 1516 case "Search":
michael@0 1517 BrowserSearch.webSearch();
michael@0 1518 break;
michael@0 1519 case "Bookmarks":
michael@0 1520 toggleSidebar('viewBookmarksSidebar');
michael@0 1521 break;
michael@0 1522 case "Home":
michael@0 1523 BrowserHome();
michael@0 1524 break;
michael@0 1525 case "New":
michael@0 1526 BrowserOpenTab();
michael@0 1527 break;
michael@0 1528 case "Close":
michael@0 1529 BrowserCloseTabOrWindow();
michael@0 1530 break;
michael@0 1531 case "Find":
michael@0 1532 gFindBar.onFindCommand();
michael@0 1533 break;
michael@0 1534 case "Help":
michael@0 1535 openHelpLink('firefox-help');
michael@0 1536 break;
michael@0 1537 case "Open":
michael@0 1538 BrowserOpenFileWindow();
michael@0 1539 break;
michael@0 1540 case "Print":
michael@0 1541 PrintUtils.print();
michael@0 1542 break;
michael@0 1543 case "Save":
michael@0 1544 saveDocument(window.content.document);
michael@0 1545 break;
michael@0 1546 case "SendMail":
michael@0 1547 MailIntegration.sendLinkForWindow(window.content);
michael@0 1548 break;
michael@0 1549 default:
michael@0 1550 return;
michael@0 1551 }
michael@0 1552 evt.stopPropagation();
michael@0 1553 evt.preventDefault();
michael@0 1554 }
michael@0 1555
michael@0 1556 function gotoHistoryIndex(aEvent) {
michael@0 1557 let index = aEvent.target.getAttribute("index");
michael@0 1558 if (!index)
michael@0 1559 return false;
michael@0 1560
michael@0 1561 let where = whereToOpenLink(aEvent);
michael@0 1562
michael@0 1563 if (where == "current") {
michael@0 1564 // Normal click. Go there in the current tab and update session history.
michael@0 1565
michael@0 1566 try {
michael@0 1567 gBrowser.gotoIndex(index);
michael@0 1568 }
michael@0 1569 catch(ex) {
michael@0 1570 return false;
michael@0 1571 }
michael@0 1572 return true;
michael@0 1573 }
michael@0 1574 // Modified click. Go there in a new tab/window.
michael@0 1575
michael@0 1576 duplicateTabIn(gBrowser.selectedTab, where, index - gBrowser.sessionHistory.index);
michael@0 1577 return true;
michael@0 1578 }
michael@0 1579
michael@0 1580 function BrowserForward(aEvent) {
michael@0 1581 let where = whereToOpenLink(aEvent, false, true);
michael@0 1582
michael@0 1583 if (where == "current") {
michael@0 1584 try {
michael@0 1585 gBrowser.goForward();
michael@0 1586 }
michael@0 1587 catch(ex) {
michael@0 1588 }
michael@0 1589 }
michael@0 1590 else {
michael@0 1591 duplicateTabIn(gBrowser.selectedTab, where, 1);
michael@0 1592 }
michael@0 1593 }
michael@0 1594
michael@0 1595 function BrowserBack(aEvent) {
michael@0 1596 let where = whereToOpenLink(aEvent, false, true);
michael@0 1597
michael@0 1598 if (where == "current") {
michael@0 1599 try {
michael@0 1600 gBrowser.goBack();
michael@0 1601 }
michael@0 1602 catch(ex) {
michael@0 1603 }
michael@0 1604 }
michael@0 1605 else {
michael@0 1606 duplicateTabIn(gBrowser.selectedTab, where, -1);
michael@0 1607 }
michael@0 1608 }
michael@0 1609
michael@0 1610 function BrowserHandleBackspace()
michael@0 1611 {
michael@0 1612 switch (gPrefService.getIntPref("browser.backspace_action")) {
michael@0 1613 case 0:
michael@0 1614 BrowserBack();
michael@0 1615 break;
michael@0 1616 case 1:
michael@0 1617 goDoCommand("cmd_scrollPageUp");
michael@0 1618 break;
michael@0 1619 }
michael@0 1620 }
michael@0 1621
michael@0 1622 function BrowserHandleShiftBackspace()
michael@0 1623 {
michael@0 1624 switch (gPrefService.getIntPref("browser.backspace_action")) {
michael@0 1625 case 0:
michael@0 1626 BrowserForward();
michael@0 1627 break;
michael@0 1628 case 1:
michael@0 1629 goDoCommand("cmd_scrollPageDown");
michael@0 1630 break;
michael@0 1631 }
michael@0 1632 }
michael@0 1633
michael@0 1634 function BrowserStop() {
michael@0 1635 const stopFlags = nsIWebNavigation.STOP_ALL;
michael@0 1636 gBrowser.webNavigation.stop(stopFlags);
michael@0 1637 }
michael@0 1638
michael@0 1639 function BrowserReloadOrDuplicate(aEvent) {
michael@0 1640 var backgroundTabModifier = aEvent.button == 1 ||
michael@0 1641 #ifdef XP_MACOSX
michael@0 1642 aEvent.metaKey;
michael@0 1643 #else
michael@0 1644 aEvent.ctrlKey;
michael@0 1645 #endif
michael@0 1646 if (aEvent.shiftKey && !backgroundTabModifier) {
michael@0 1647 BrowserReloadSkipCache();
michael@0 1648 return;
michael@0 1649 }
michael@0 1650
michael@0 1651 let where = whereToOpenLink(aEvent, false, true);
michael@0 1652 if (where == "current")
michael@0 1653 BrowserReload();
michael@0 1654 else
michael@0 1655 duplicateTabIn(gBrowser.selectedTab, where);
michael@0 1656 }
michael@0 1657
michael@0 1658 function BrowserReload() {
michael@0 1659 const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE;
michael@0 1660 BrowserReloadWithFlags(reloadFlags);
michael@0 1661 }
michael@0 1662
michael@0 1663 function BrowserReloadSkipCache() {
michael@0 1664 // Bypass proxy and cache.
michael@0 1665 const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
michael@0 1666 BrowserReloadWithFlags(reloadFlags);
michael@0 1667 }
michael@0 1668
michael@0 1669 var BrowserHome = BrowserGoHome;
michael@0 1670 function BrowserGoHome(aEvent) {
michael@0 1671 if (aEvent && "button" in aEvent &&
michael@0 1672 aEvent.button == 2) // right-click: do nothing
michael@0 1673 return;
michael@0 1674
michael@0 1675 var homePage = gHomeButton.getHomePage();
michael@0 1676 var where = whereToOpenLink(aEvent, false, true);
michael@0 1677 var urls;
michael@0 1678
michael@0 1679 // Home page should open in a new tab when current tab is an app tab
michael@0 1680 if (where == "current" &&
michael@0 1681 gBrowser &&
michael@0 1682 gBrowser.selectedTab.pinned)
michael@0 1683 where = "tab";
michael@0 1684
michael@0 1685 // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages
michael@0 1686 switch (where) {
michael@0 1687 case "current":
michael@0 1688 loadOneOrMoreURIs(homePage);
michael@0 1689 break;
michael@0 1690 case "tabshifted":
michael@0 1691 case "tab":
michael@0 1692 urls = homePage.split("|");
michael@0 1693 var loadInBackground = getBoolPref("browser.tabs.loadBookmarksInBackground", false);
michael@0 1694 gBrowser.loadTabs(urls, loadInBackground);
michael@0 1695 break;
michael@0 1696 case "window":
michael@0 1697 OpenBrowserWindow();
michael@0 1698 break;
michael@0 1699 }
michael@0 1700 }
michael@0 1701
michael@0 1702 function loadOneOrMoreURIs(aURIString)
michael@0 1703 {
michael@0 1704 #ifdef XP_MACOSX
michael@0 1705 // we're not a browser window, pass the URI string to a new browser window
michael@0 1706 if (window.location.href != getBrowserURL())
michael@0 1707 {
michael@0 1708 window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString);
michael@0 1709 return;
michael@0 1710 }
michael@0 1711 #endif
michael@0 1712 // This function throws for certain malformed URIs, so use exception handling
michael@0 1713 // so that we don't disrupt startup
michael@0 1714 try {
michael@0 1715 gBrowser.loadTabs(aURIString.split("|"), false, true);
michael@0 1716 }
michael@0 1717 catch (e) {
michael@0 1718 }
michael@0 1719 }
michael@0 1720
michael@0 1721 function focusAndSelectUrlBar() {
michael@0 1722 if (gURLBar) {
michael@0 1723 if (window.fullScreen)
michael@0 1724 FullScreen.mouseoverToggle(true);
michael@0 1725
michael@0 1726 gURLBar.select();
michael@0 1727 if (document.activeElement == gURLBar.inputField)
michael@0 1728 return true;
michael@0 1729 }
michael@0 1730 return false;
michael@0 1731 }
michael@0 1732
michael@0 1733 function openLocation() {
michael@0 1734 if (focusAndSelectUrlBar())
michael@0 1735 return;
michael@0 1736
michael@0 1737 #ifdef XP_MACOSX
michael@0 1738 if (window.location.href != getBrowserURL()) {
michael@0 1739 var win = getTopWin();
michael@0 1740 if (win) {
michael@0 1741 // If there's an open browser window, it should handle this command
michael@0 1742 win.focus()
michael@0 1743 win.openLocation();
michael@0 1744 }
michael@0 1745 else {
michael@0 1746 // If there are no open browser windows, open a new one
michael@0 1747 window.openDialog("chrome://browser/content/", "_blank",
michael@0 1748 "chrome,all,dialog=no", BROWSER_NEW_TAB_URL);
michael@0 1749 }
michael@0 1750 }
michael@0 1751 #endif
michael@0 1752 }
michael@0 1753
michael@0 1754 function BrowserOpenTab()
michael@0 1755 {
michael@0 1756 openUILinkIn(BROWSER_NEW_TAB_URL, "tab");
michael@0 1757 }
michael@0 1758
michael@0 1759 /* Called from the openLocation dialog. This allows that dialog to instruct
michael@0 1760 its opener to open a new window and then step completely out of the way.
michael@0 1761 Anything less byzantine is causing horrible crashes, rather believably,
michael@0 1762 though oddly only on Linux. */
michael@0 1763 function delayedOpenWindow(chrome, flags, href, postData)
michael@0 1764 {
michael@0 1765 // The other way to use setTimeout,
michael@0 1766 // setTimeout(openDialog, 10, chrome, "_blank", flags, url),
michael@0 1767 // doesn't work here. The extra "magic" extra argument setTimeout adds to
michael@0 1768 // the callback function would confuse gBrowserInit.onLoad() by making
michael@0 1769 // window.arguments[1] be an integer instead of null.
michael@0 1770 setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10);
michael@0 1771 }
michael@0 1772
michael@0 1773 /* Required because the tab needs time to set up its content viewers and get the load of
michael@0 1774 the URI kicked off before becoming the active content area. */
michael@0 1775 function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup)
michael@0 1776 {
michael@0 1777 gBrowser.loadOneTab(aUrl, {
michael@0 1778 referrerURI: aReferrer,
michael@0 1779 charset: aCharset,
michael@0 1780 postData: aPostData,
michael@0 1781 inBackground: false,
michael@0 1782 allowThirdPartyFixup: aAllowThirdPartyFixup});
michael@0 1783 }
michael@0 1784
michael@0 1785 var gLastOpenDirectory = {
michael@0 1786 _lastDir: null,
michael@0 1787 get path() {
michael@0 1788 if (!this._lastDir || !this._lastDir.exists()) {
michael@0 1789 try {
michael@0 1790 this._lastDir = gPrefService.getComplexValue("browser.open.lastDir",
michael@0 1791 Ci.nsILocalFile);
michael@0 1792 if (!this._lastDir.exists())
michael@0 1793 this._lastDir = null;
michael@0 1794 }
michael@0 1795 catch(e) {}
michael@0 1796 }
michael@0 1797 return this._lastDir;
michael@0 1798 },
michael@0 1799 set path(val) {
michael@0 1800 try {
michael@0 1801 if (!val || !val.isDirectory())
michael@0 1802 return;
michael@0 1803 } catch(e) {
michael@0 1804 return;
michael@0 1805 }
michael@0 1806 this._lastDir = val.clone();
michael@0 1807
michael@0 1808 // Don't save the last open directory pref inside the Private Browsing mode
michael@0 1809 if (!PrivateBrowsingUtils.isWindowPrivate(window))
michael@0 1810 gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile,
michael@0 1811 this._lastDir);
michael@0 1812 },
michael@0 1813 reset: function() {
michael@0 1814 this._lastDir = null;
michael@0 1815 }
michael@0 1816 };
michael@0 1817
michael@0 1818 function BrowserOpenFileWindow()
michael@0 1819 {
michael@0 1820 // Get filepicker component.
michael@0 1821 try {
michael@0 1822 const nsIFilePicker = Ci.nsIFilePicker;
michael@0 1823 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
michael@0 1824 let fpCallback = function fpCallback_done(aResult) {
michael@0 1825 if (aResult == nsIFilePicker.returnOK) {
michael@0 1826 try {
michael@0 1827 if (fp.file) {
michael@0 1828 gLastOpenDirectory.path =
michael@0 1829 fp.file.parent.QueryInterface(Ci.nsILocalFile);
michael@0 1830 }
michael@0 1831 } catch (ex) {
michael@0 1832 }
michael@0 1833 openUILinkIn(fp.fileURL.spec, "current");
michael@0 1834 }
michael@0 1835 };
michael@0 1836
michael@0 1837 fp.init(window, gNavigatorBundle.getString("openFile"),
michael@0 1838 nsIFilePicker.modeOpen);
michael@0 1839 fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
michael@0 1840 nsIFilePicker.filterImages | nsIFilePicker.filterXML |
michael@0 1841 nsIFilePicker.filterHTML);
michael@0 1842 fp.displayDirectory = gLastOpenDirectory.path;
michael@0 1843 fp.open(fpCallback);
michael@0 1844 } catch (ex) {
michael@0 1845 }
michael@0 1846 }
michael@0 1847
michael@0 1848 function BrowserCloseTabOrWindow() {
michael@0 1849 #ifdef XP_MACOSX
michael@0 1850 // If we're not a browser window, just close the window
michael@0 1851 if (window.location.href != getBrowserURL()) {
michael@0 1852 closeWindow(true);
michael@0 1853 return;
michael@0 1854 }
michael@0 1855 #endif
michael@0 1856
michael@0 1857 // If the current tab is the last one, this will close the window.
michael@0 1858 gBrowser.removeCurrentTab({animate: true});
michael@0 1859 }
michael@0 1860
michael@0 1861 function BrowserTryToCloseWindow()
michael@0 1862 {
michael@0 1863 if (WindowIsClosing())
michael@0 1864 window.close(); // WindowIsClosing does all the necessary checks
michael@0 1865 }
michael@0 1866
michael@0 1867 function loadURI(uri, referrer, postData, allowThirdPartyFixup) {
michael@0 1868 if (postData === undefined)
michael@0 1869 postData = null;
michael@0 1870
michael@0 1871 var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
michael@0 1872 if (allowThirdPartyFixup) {
michael@0 1873 flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
michael@0 1874 flags |= nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
michael@0 1875 }
michael@0 1876
michael@0 1877 try {
michael@0 1878 gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData);
michael@0 1879 } catch (e) {}
michael@0 1880 }
michael@0 1881
michael@0 1882 function getShortcutOrURIAndPostData(aURL, aCallback) {
michael@0 1883 let mayInheritPrincipal = false;
michael@0 1884 let postData = null;
michael@0 1885 let shortcutURL = null;
michael@0 1886 let keyword = aURL;
michael@0 1887 let param = "";
michael@0 1888
michael@0 1889 let offset = aURL.indexOf(" ");
michael@0 1890 if (offset > 0) {
michael@0 1891 keyword = aURL.substr(0, offset);
michael@0 1892 param = aURL.substr(offset + 1);
michael@0 1893 }
michael@0 1894
michael@0 1895 let engine = Services.search.getEngineByAlias(keyword);
michael@0 1896 if (engine) {
michael@0 1897 let submission = engine.getSubmission(param);
michael@0 1898 postData = submission.postData;
michael@0 1899 aCallback({ postData: submission.postData, url: submission.uri.spec,
michael@0 1900 mayInheritPrincipal: mayInheritPrincipal });
michael@0 1901 return;
michael@0 1902 }
michael@0 1903
michael@0 1904 [shortcutURL, postData] =
michael@0 1905 PlacesUtils.getURLAndPostDataForKeyword(keyword);
michael@0 1906
michael@0 1907 if (!shortcutURL) {
michael@0 1908 aCallback({ postData: postData, url: aURL,
michael@0 1909 mayInheritPrincipal: mayInheritPrincipal });
michael@0 1910 return;
michael@0 1911 }
michael@0 1912
michael@0 1913 let escapedPostData = "";
michael@0 1914 if (postData)
michael@0 1915 escapedPostData = unescape(postData);
michael@0 1916
michael@0 1917 if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) {
michael@0 1918 let charset = "";
michael@0 1919 const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
michael@0 1920 let matches = shortcutURL.match(re);
michael@0 1921
michael@0 1922 let continueOperation = function () {
michael@0 1923 // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
michael@0 1924 // escape() works in those cases, but it doesn't uri-encode +, @, and /.
michael@0 1925 // Therefore we need to manually replace these ASCII characters by their
michael@0 1926 // encodeURIComponent result, to match the behavior of nsEscape() with
michael@0 1927 // url_XPAlphas
michael@0 1928 let encodedParam = "";
michael@0 1929 if (charset && charset != "UTF-8")
michael@0 1930 encodedParam = escape(convertFromUnicode(charset, param)).
michael@0 1931 replace(/[+@\/]+/g, encodeURIComponent);
michael@0 1932 else // Default charset is UTF-8
michael@0 1933 encodedParam = encodeURIComponent(param);
michael@0 1934
michael@0 1935 shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
michael@0 1936
michael@0 1937 if (/%s/i.test(escapedPostData)) // POST keyword
michael@0 1938 postData = getPostDataStream(escapedPostData, param, encodedParam,
michael@0 1939 "application/x-www-form-urlencoded");
michael@0 1940
michael@0 1941 // This URL came from a bookmark, so it's safe to let it inherit the current
michael@0 1942 // document's principal.
michael@0 1943 mayInheritPrincipal = true;
michael@0 1944
michael@0 1945 aCallback({ postData: postData, url: shortcutURL,
michael@0 1946 mayInheritPrincipal: mayInheritPrincipal });
michael@0 1947 }
michael@0 1948
michael@0 1949 if (matches) {
michael@0 1950 [, shortcutURL, charset] = matches;
michael@0 1951 continueOperation();
michael@0 1952 } else {
michael@0 1953 // Try to get the saved character-set.
michael@0 1954 // makeURI throws if URI is invalid.
michael@0 1955 // Will return an empty string if character-set is not found.
michael@0 1956 try {
michael@0 1957 PlacesUtils.getCharsetForURI(makeURI(shortcutURL))
michael@0 1958 .then(c => { charset = c; continueOperation(); });
michael@0 1959 } catch (ex) {
michael@0 1960 continueOperation();
michael@0 1961 }
michael@0 1962 }
michael@0 1963 }
michael@0 1964 else if (param) {
michael@0 1965 // This keyword doesn't take a parameter, but one was provided. Just return
michael@0 1966 // the original URL.
michael@0 1967 postData = null;
michael@0 1968
michael@0 1969 aCallback({ postData: postData, url: aURL,
michael@0 1970 mayInheritPrincipal: mayInheritPrincipal });
michael@0 1971 } else {
michael@0 1972 // This URL came from a bookmark, so it's safe to let it inherit the current
michael@0 1973 // document's principal.
michael@0 1974 mayInheritPrincipal = true;
michael@0 1975
michael@0 1976 aCallback({ postData: postData, url: shortcutURL,
michael@0 1977 mayInheritPrincipal: mayInheritPrincipal });
michael@0 1978 }
michael@0 1979 }
michael@0 1980
michael@0 1981 function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
michael@0 1982 var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].
michael@0 1983 createInstance(Ci.nsIStringInputStream);
michael@0 1984 aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
michael@0 1985 dataStream.data = aStringData;
michael@0 1986
michael@0 1987 var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"].
michael@0 1988 createInstance(Ci.nsIMIMEInputStream);
michael@0 1989 mimeStream.addHeader("Content-Type", aType);
michael@0 1990 mimeStream.addContentLength = true;
michael@0 1991 mimeStream.setData(dataStream);
michael@0 1992 return mimeStream.QueryInterface(Ci.nsIInputStream);
michael@0 1993 }
michael@0 1994
michael@0 1995 function getLoadContext() {
michael@0 1996 return window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 1997 .getInterface(Ci.nsIWebNavigation)
michael@0 1998 .QueryInterface(Ci.nsILoadContext);
michael@0 1999 }
michael@0 2000
michael@0 2001 function readFromClipboard()
michael@0 2002 {
michael@0 2003 var url;
michael@0 2004
michael@0 2005 try {
michael@0 2006 // Create transferable that will transfer the text.
michael@0 2007 var trans = Components.classes["@mozilla.org/widget/transferable;1"]
michael@0 2008 .createInstance(Components.interfaces.nsITransferable);
michael@0 2009 trans.init(getLoadContext());
michael@0 2010
michael@0 2011 trans.addDataFlavor("text/unicode");
michael@0 2012
michael@0 2013 // If available, use selection clipboard, otherwise global one
michael@0 2014 if (Services.clipboard.supportsSelectionClipboard())
michael@0 2015 Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard);
michael@0 2016 else
michael@0 2017 Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
michael@0 2018
michael@0 2019 var data = {};
michael@0 2020 var dataLen = {};
michael@0 2021 trans.getTransferData("text/unicode", data, dataLen);
michael@0 2022
michael@0 2023 if (data) {
michael@0 2024 data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
michael@0 2025 url = data.data.substring(0, dataLen.value / 2);
michael@0 2026 }
michael@0 2027 } catch (ex) {
michael@0 2028 }
michael@0 2029
michael@0 2030 return url;
michael@0 2031 }
michael@0 2032
michael@0 2033 function BrowserViewSourceOfDocument(aDocument)
michael@0 2034 {
michael@0 2035 var pageCookie;
michael@0 2036 var webNav;
michael@0 2037
michael@0 2038 // Get the document charset
michael@0 2039 var docCharset = "charset=" + aDocument.characterSet;
michael@0 2040
michael@0 2041 // Get the nsIWebNavigation associated with the document
michael@0 2042 try {
michael@0 2043 var win;
michael@0 2044 var ifRequestor;
michael@0 2045
michael@0 2046 // Get the DOMWindow for the requested document. If the DOMWindow
michael@0 2047 // cannot be found, then just use the content window...
michael@0 2048 //
michael@0 2049 // XXX: This is a bit of a hack...
michael@0 2050 win = aDocument.defaultView;
michael@0 2051 if (win == window) {
michael@0 2052 win = content;
michael@0 2053 }
michael@0 2054 ifRequestor = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
michael@0 2055
michael@0 2056 webNav = ifRequestor.getInterface(nsIWebNavigation);
michael@0 2057 } catch(err) {
michael@0 2058 // If nsIWebNavigation cannot be found, just get the one for the whole
michael@0 2059 // window...
michael@0 2060 webNav = gBrowser.webNavigation;
michael@0 2061 }
michael@0 2062 //
michael@0 2063 // Get the 'PageDescriptor' for the current document. This allows the
michael@0 2064 // view-source to access the cached copy of the content rather than
michael@0 2065 // refetching it from the network...
michael@0 2066 //
michael@0 2067 try{
michael@0 2068 var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor);
michael@0 2069
michael@0 2070 pageCookie = PageLoader.currentDescriptor;
michael@0 2071 } catch(err) {
michael@0 2072 // If no page descriptor is available, just use the view-source URL...
michael@0 2073 }
michael@0 2074
michael@0 2075 top.gViewSourceUtils.viewSource(webNav.currentURI.spec, pageCookie, aDocument);
michael@0 2076 }
michael@0 2077
michael@0 2078 // doc - document to use for source, or null for this window's document
michael@0 2079 // initialTab - name of the initial tab to display, or null for the first tab
michael@0 2080 // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
michael@0 2081 function BrowserPageInfo(doc, initialTab, imageElement) {
michael@0 2082 var args = {doc: doc, initialTab: initialTab, imageElement: imageElement};
michael@0 2083 var windows = Services.wm.getEnumerator("Browser:page-info");
michael@0 2084
michael@0 2085 var documentURL = doc ? doc.location : window.content.document.location;
michael@0 2086
michael@0 2087 // Check for windows matching the url
michael@0 2088 while (windows.hasMoreElements()) {
michael@0 2089 var currentWindow = windows.getNext();
michael@0 2090 if (currentWindow.closed) {
michael@0 2091 continue;
michael@0 2092 }
michael@0 2093 if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) {
michael@0 2094 currentWindow.focus();
michael@0 2095 currentWindow.resetPageInfo(args);
michael@0 2096 return currentWindow;
michael@0 2097 }
michael@0 2098 }
michael@0 2099
michael@0 2100 // We didn't find a matching window, so open a new one.
michael@0 2101 return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "",
michael@0 2102 "chrome,toolbar,dialog=no,resizable", args);
michael@0 2103 }
michael@0 2104
michael@0 2105 function URLBarSetURI(aURI) {
michael@0 2106 var value = gBrowser.userTypedValue;
michael@0 2107 var valid = false;
michael@0 2108
michael@0 2109 if (value == null) {
michael@0 2110 let uri = aURI || gBrowser.currentURI;
michael@0 2111 // Strip off "wyciwyg://" and passwords for the location bar
michael@0 2112 try {
michael@0 2113 uri = Services.uriFixup.createExposableURI(uri);
michael@0 2114 } catch (e) {}
michael@0 2115
michael@0 2116 // Replace initial page URIs with an empty string
michael@0 2117 // only if there's no opener (bug 370555).
michael@0 2118 // Bug 863515 - Make content.opener checks work in electrolysis.
michael@0 2119 if (gInitialPages.indexOf(uri.spec) != -1)
michael@0 2120 value = !gMultiProcessBrowser && content.opener ? uri.spec : "";
michael@0 2121 else
michael@0 2122 value = losslessDecodeURI(uri);
michael@0 2123
michael@0 2124 valid = !isBlankPageURL(uri.spec);
michael@0 2125 }
michael@0 2126
michael@0 2127 gURLBar.value = value;
michael@0 2128 gURLBar.valueIsTyped = !valid;
michael@0 2129 SetPageProxyState(valid ? "valid" : "invalid");
michael@0 2130 }
michael@0 2131
michael@0 2132 function losslessDecodeURI(aURI) {
michael@0 2133 var value = aURI.spec;
michael@0 2134 // Try to decode as UTF-8 if there's no encoding sequence that we would break.
michael@0 2135 if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value))
michael@0 2136 try {
michael@0 2137 value = decodeURI(value)
michael@0 2138 // 1. decodeURI decodes %25 to %, which creates unintended
michael@0 2139 // encoding sequences. Re-encode it, unless it's part of
michael@0 2140 // a sequence that survived decodeURI, i.e. one for:
michael@0 2141 // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
michael@0 2142 // (RFC 3987 section 3.2)
michael@0 2143 // 2. Re-encode whitespace so that it doesn't get eaten away
michael@0 2144 // by the location bar (bug 410726).
michael@0 2145 .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig,
michael@0 2146 encodeURIComponent);
michael@0 2147 } catch (e) {}
michael@0 2148
michael@0 2149 // Encode invisible characters (C0/C1 control characters, U+007F [DEL],
michael@0 2150 // U+00A0 [no-break space], line and paragraph separator,
michael@0 2151 // object replacement character) (bug 452979, bug 909264)
michael@0 2152 value = value.replace(/[\u0000-\u001f\u007f-\u00a0\u2028\u2029\ufffc]/g,
michael@0 2153 encodeURIComponent);
michael@0 2154
michael@0 2155 // Encode default ignorable characters (bug 546013)
michael@0 2156 // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186).
michael@0 2157 // This includes all bidirectional formatting characters.
michael@0 2158 // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
michael@0 2159 value = value.replace(/[\u00ad\u034f\u061c\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b\u200e-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g,
michael@0 2160 encodeURIComponent);
michael@0 2161 return value;
michael@0 2162 }
michael@0 2163
michael@0 2164 function UpdateUrlbarSearchSplitterState()
michael@0 2165 {
michael@0 2166 var splitter = document.getElementById("urlbar-search-splitter");
michael@0 2167 var urlbar = document.getElementById("urlbar-container");
michael@0 2168 var searchbar = document.getElementById("search-container");
michael@0 2169
michael@0 2170 if (document.documentElement.getAttribute("customizing") == "true") {
michael@0 2171 if (splitter) {
michael@0 2172 splitter.remove();
michael@0 2173 }
michael@0 2174 return;
michael@0 2175 }
michael@0 2176
michael@0 2177 // If the splitter is already in the right place, we don't need to do anything:
michael@0 2178 if (splitter &&
michael@0 2179 ((splitter.nextSibling == searchbar && splitter.previousSibling == urlbar) ||
michael@0 2180 (splitter.nextSibling == urlbar && splitter.previousSibling == searchbar))) {
michael@0 2181 return;
michael@0 2182 }
michael@0 2183
michael@0 2184 var ibefore = null;
michael@0 2185 if (urlbar && searchbar) {
michael@0 2186 if (urlbar.nextSibling == searchbar)
michael@0 2187 ibefore = searchbar;
michael@0 2188 else if (searchbar.nextSibling == urlbar)
michael@0 2189 ibefore = urlbar;
michael@0 2190 }
michael@0 2191
michael@0 2192 if (ibefore) {
michael@0 2193 if (!splitter) {
michael@0 2194 splitter = document.createElement("splitter");
michael@0 2195 splitter.id = "urlbar-search-splitter";
michael@0 2196 splitter.setAttribute("resizebefore", "flex");
michael@0 2197 splitter.setAttribute("resizeafter", "flex");
michael@0 2198 splitter.setAttribute("skipintoolbarset", "true");
michael@0 2199 splitter.setAttribute("overflows", "false");
michael@0 2200 splitter.className = "chromeclass-toolbar-additional";
michael@0 2201 }
michael@0 2202 urlbar.parentNode.insertBefore(splitter, ibefore);
michael@0 2203 } else if (splitter)
michael@0 2204 splitter.parentNode.removeChild(splitter);
michael@0 2205 }
michael@0 2206
michael@0 2207 function UpdatePageProxyState()
michael@0 2208 {
michael@0 2209 if (gURLBar && gURLBar.value != gLastValidURLStr)
michael@0 2210 SetPageProxyState("invalid");
michael@0 2211 }
michael@0 2212
michael@0 2213 function SetPageProxyState(aState)
michael@0 2214 {
michael@0 2215 BookmarkingUI.onPageProxyStateChanged(aState);
michael@0 2216
michael@0 2217 if (!gURLBar)
michael@0 2218 return;
michael@0 2219
michael@0 2220 if (!gProxyFavIcon)
michael@0 2221 gProxyFavIcon = document.getElementById("page-proxy-favicon");
michael@0 2222
michael@0 2223 gURLBar.setAttribute("pageproxystate", aState);
michael@0 2224 gProxyFavIcon.setAttribute("pageproxystate", aState);
michael@0 2225
michael@0 2226 // the page proxy state is set to valid via OnLocationChange, which
michael@0 2227 // gets called when we switch tabs.
michael@0 2228 if (aState == "valid") {
michael@0 2229 gLastValidURLStr = gURLBar.value;
michael@0 2230 gURLBar.addEventListener("input", UpdatePageProxyState, false);
michael@0 2231 } else if (aState == "invalid") {
michael@0 2232 gURLBar.removeEventListener("input", UpdatePageProxyState, false);
michael@0 2233 }
michael@0 2234 }
michael@0 2235
michael@0 2236 function PageProxyClickHandler(aEvent)
michael@0 2237 {
michael@0 2238 if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
michael@0 2239 middleMousePaste(aEvent);
michael@0 2240 }
michael@0 2241
michael@0 2242 /**
michael@0 2243 * Handle command events bubbling up from error page content
michael@0 2244 * or from about:newtab
michael@0 2245 */
michael@0 2246 let BrowserOnClick = {
michael@0 2247 handleEvent: function BrowserOnClick_handleEvent(aEvent) {
michael@0 2248 if (!aEvent.isTrusted || // Don't trust synthetic events
michael@0 2249 aEvent.button == 2) {
michael@0 2250 return;
michael@0 2251 }
michael@0 2252
michael@0 2253 let originalTarget = aEvent.originalTarget;
michael@0 2254 let ownerDoc = originalTarget.ownerDocument;
michael@0 2255
michael@0 2256 // If the event came from an ssl error page, it is probably either the "Add
michael@0 2257 // Exception…" or "Get me out of here!" button
michael@0 2258 if (ownerDoc.documentURI.startsWith("about:certerror")) {
michael@0 2259 this.onAboutCertError(originalTarget, ownerDoc);
michael@0 2260 }
michael@0 2261 else if (ownerDoc.documentURI.startsWith("about:blocked")) {
michael@0 2262 this.onAboutBlocked(originalTarget, ownerDoc);
michael@0 2263 }
michael@0 2264 else if (ownerDoc.documentURI.startsWith("about:neterror")) {
michael@0 2265 this.onAboutNetError(originalTarget, ownerDoc);
michael@0 2266 }
michael@0 2267 else if (gMultiProcessBrowser &&
michael@0 2268 ownerDoc.documentURI.toLowerCase() == "about:newtab") {
michael@0 2269 this.onE10sAboutNewTab(aEvent, ownerDoc);
michael@0 2270 }
michael@0 2271 else if (ownerDoc.documentURI.startsWith("about:tabcrashed")) {
michael@0 2272 this.onAboutTabCrashed(aEvent, ownerDoc);
michael@0 2273 }
michael@0 2274 },
michael@0 2275
michael@0 2276 onAboutCertError: function BrowserOnClick_onAboutCertError(aTargetElm, aOwnerDoc) {
michael@0 2277 let elmId = aTargetElm.getAttribute("id");
michael@0 2278 let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
michael@0 2279 let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
michael@0 2280
michael@0 2281 switch (elmId) {
michael@0 2282 case "exceptionDialogButton":
michael@0 2283 if (isTopFrame) {
michael@0 2284 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
michael@0 2285 }
michael@0 2286 let params = { exceptionAdded : false };
michael@0 2287
michael@0 2288 try {
michael@0 2289 switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) {
michael@0 2290 case 2 : // Pre-fetch & pre-populate
michael@0 2291 params.prefetchCert = true;
michael@0 2292 case 1 : // Pre-populate
michael@0 2293 params.location = aOwnerDoc.location.href;
michael@0 2294 }
michael@0 2295 } catch (e) {
michael@0 2296 Components.utils.reportError("Couldn't get ssl_override pref: " + e);
michael@0 2297 }
michael@0 2298
michael@0 2299 window.openDialog('chrome://pippki/content/exceptionDialog.xul',
michael@0 2300 '','chrome,centerscreen,modal', params);
michael@0 2301
michael@0 2302 // If the user added the exception cert, attempt to reload the page
michael@0 2303 if (params.exceptionAdded) {
michael@0 2304 aOwnerDoc.location.reload();
michael@0 2305 }
michael@0 2306 break;
michael@0 2307
michael@0 2308 case "getMeOutOfHereButton":
michael@0 2309 if (isTopFrame) {
michael@0 2310 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE);
michael@0 2311 }
michael@0 2312 getMeOutOfHere();
michael@0 2313 break;
michael@0 2314
michael@0 2315 case "technicalContent":
michael@0 2316 if (isTopFrame) {
michael@0 2317 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_TECHNICAL_DETAILS);
michael@0 2318 }
michael@0 2319 break;
michael@0 2320
michael@0 2321 case "expertContent":
michael@0 2322 if (isTopFrame) {
michael@0 2323 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS);
michael@0 2324 }
michael@0 2325 break;
michael@0 2326
michael@0 2327 }
michael@0 2328 },
michael@0 2329
michael@0 2330 onAboutBlocked: function BrowserOnClick_onAboutBlocked(aTargetElm, aOwnerDoc) {
michael@0 2331 let elmId = aTargetElm.getAttribute("id");
michael@0 2332 let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
michael@0 2333
michael@0 2334 // The event came from a button on a malware/phishing block page
michael@0 2335 // First check whether it's malware or phishing, so that we can
michael@0 2336 // use the right strings/links
michael@0 2337 let isMalware = /e=malwareBlocked/.test(aOwnerDoc.documentURI);
michael@0 2338 let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_";
michael@0 2339 let nsISecTel = Ci.nsISecurityUITelemetry;
michael@0 2340 let isIframe = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
michael@0 2341 bucketName += isIframe ? "TOP_" : "FRAME_";
michael@0 2342
michael@0 2343 switch (elmId) {
michael@0 2344 case "getMeOutButton":
michael@0 2345 secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
michael@0 2346 getMeOutOfHere();
michael@0 2347 break;
michael@0 2348
michael@0 2349 case "reportButton":
michael@0 2350 // This is the "Why is this site blocked" button. For malware,
michael@0 2351 // we can fetch a site-specific report, for phishing, we redirect
michael@0 2352 // to the generic page describing phishing protection.
michael@0 2353
michael@0 2354 // We log even if malware/phishing info URL couldn't be found:
michael@0 2355 // the measurement is for how many users clicked the WHY BLOCKED button
michael@0 2356 secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
michael@0 2357
michael@0 2358 if (isMalware) {
michael@0 2359 // Get the stop badware "why is this blocked" report url,
michael@0 2360 // append the current url, and go there.
michael@0 2361 try {
michael@0 2362 let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
michael@0 2363 reportURL += aOwnerDoc.location.href;
michael@0 2364 content.location = reportURL;
michael@0 2365 } catch (e) {
michael@0 2366 Components.utils.reportError("Couldn't get malware report URL: " + e);
michael@0 2367 }
michael@0 2368 }
michael@0 2369 else { // It's a phishing site, not malware
michael@0 2370 openHelpLink("phishing-malware", false, "current");
michael@0 2371 }
michael@0 2372 break;
michael@0 2373
michael@0 2374 case "ignoreWarningButton":
michael@0 2375 secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
michael@0 2376 this.ignoreWarningButton(isMalware);
michael@0 2377 break;
michael@0 2378 }
michael@0 2379 },
michael@0 2380
michael@0 2381 /**
michael@0 2382 * This functions prevents navigation from happening directly through the <a>
michael@0 2383 * link in about:newtab (which is loaded in the parent and therefore would load
michael@0 2384 * the next page also in the parent) and instructs the browser to open the url
michael@0 2385 * in the current tab which will make it update the remoteness of the tab.
michael@0 2386 */
michael@0 2387 onE10sAboutNewTab: function(aEvent, aOwnerDoc) {
michael@0 2388 let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
michael@0 2389 if (!isTopFrame || aEvent.button != 0) {
michael@0 2390 return;
michael@0 2391 }
michael@0 2392
michael@0 2393 let anchorTarget = aEvent.originalTarget.parentNode;
michael@0 2394
michael@0 2395 if (anchorTarget instanceof HTMLAnchorElement &&
michael@0 2396 anchorTarget.classList.contains("newtab-link")) {
michael@0 2397 aEvent.preventDefault();
michael@0 2398 openUILinkIn(anchorTarget.href, "current");
michael@0 2399 }
michael@0 2400 },
michael@0 2401
michael@0 2402 /**
michael@0 2403 * The about:tabcrashed can't do window.reload() because that
michael@0 2404 * would reload the page but not use a remote browser.
michael@0 2405 */
michael@0 2406 onAboutTabCrashed: function(aEvent, aOwnerDoc) {
michael@0 2407 let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
michael@0 2408 if (!isTopFrame) {
michael@0 2409 return;
michael@0 2410 }
michael@0 2411
michael@0 2412 let button = aEvent.originalTarget;
michael@0 2413 if (button.id == "tryAgain") {
michael@0 2414 #ifdef MOZ_CRASHREPORTER
michael@0 2415 if (aOwnerDoc.getElementById("checkSendReport").checked) {
michael@0 2416 let browser = gBrowser.getBrowserForDocument(aOwnerDoc);
michael@0 2417 TabCrashReporter.submitCrashReport(browser);
michael@0 2418 }
michael@0 2419 #endif
michael@0 2420 openUILinkIn(button.getAttribute("url"), "current");
michael@0 2421 }
michael@0 2422 },
michael@0 2423
michael@0 2424 ignoreWarningButton: function BrowserOnClick_ignoreWarningButton(aIsMalware) {
michael@0 2425 // Allow users to override and continue through to the site,
michael@0 2426 // but add a notify bar as a reminder, so that they don't lose
michael@0 2427 // track after, e.g., tab switching.
michael@0 2428 gBrowser.loadURIWithFlags(content.location.href,
michael@0 2429 nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
michael@0 2430 null, null, null);
michael@0 2431
michael@0 2432 Services.perms.add(makeURI(content.location.href), "safe-browsing",
michael@0 2433 Ci.nsIPermissionManager.ALLOW_ACTION,
michael@0 2434 Ci.nsIPermissionManager.EXPIRE_SESSION);
michael@0 2435
michael@0 2436 let buttons = [{
michael@0 2437 label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
michael@0 2438 accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
michael@0 2439 callback: function() { getMeOutOfHere(); }
michael@0 2440 }];
michael@0 2441
michael@0 2442 let title;
michael@0 2443 if (aIsMalware) {
michael@0 2444 title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
michael@0 2445 buttons[1] = {
michael@0 2446 label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
michael@0 2447 accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
michael@0 2448 callback: function() {
michael@0 2449 openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab');
michael@0 2450 }
michael@0 2451 };
michael@0 2452 } else {
michael@0 2453 title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery");
michael@0 2454 buttons[1] = {
michael@0 2455 label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
michael@0 2456 accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"),
michael@0 2457 callback: function() {
michael@0 2458 openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');
michael@0 2459 }
michael@0 2460 };
michael@0 2461 }
michael@0 2462
michael@0 2463 let notificationBox = gBrowser.getNotificationBox();
michael@0 2464 let value = "blocked-badware-page";
michael@0 2465
michael@0 2466 let previousNotification = notificationBox.getNotificationWithValue(value);
michael@0 2467 if (previousNotification) {
michael@0 2468 notificationBox.removeNotification(previousNotification);
michael@0 2469 }
michael@0 2470
michael@0 2471 let notification = notificationBox.appendNotification(
michael@0 2472 title,
michael@0 2473 value,
michael@0 2474 "chrome://global/skin/icons/blacklist_favicon.png",
michael@0 2475 notificationBox.PRIORITY_CRITICAL_HIGH,
michael@0 2476 buttons
michael@0 2477 );
michael@0 2478 // Persist the notification until the user removes so it
michael@0 2479 // doesn't get removed on redirects.
michael@0 2480 notification.persistence = -1;
michael@0 2481 },
michael@0 2482
michael@0 2483 onAboutNetError: function BrowserOnClick_onAboutNetError(aTargetElm, aOwnerDoc) {
michael@0 2484 let elmId = aTargetElm.getAttribute("id");
michael@0 2485 if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI))
michael@0 2486 return;
michael@0 2487 Services.io.offline = false;
michael@0 2488 },
michael@0 2489 };
michael@0 2490
michael@0 2491 /**
michael@0 2492 * Re-direct the browser to a known-safe page. This function is
michael@0 2493 * used when, for example, the user browses to a known malware page
michael@0 2494 * and is presented with about:blocked. The "Get me out of here!"
michael@0 2495 * button should take the user to the default start page so that even
michael@0 2496 * when their own homepage is infected, we can get them somewhere safe.
michael@0 2497 */
michael@0 2498 function getMeOutOfHere() {
michael@0 2499 // Get the start page from the *default* pref branch, not the user's
michael@0 2500 var prefs = Services.prefs.getDefaultBranch(null);
michael@0 2501 var url = BROWSER_NEW_TAB_URL;
michael@0 2502 try {
michael@0 2503 url = prefs.getComplexValue("browser.startup.homepage",
michael@0 2504 Ci.nsIPrefLocalizedString).data;
michael@0 2505 // If url is a pipe-delimited set of pages, just take the first one.
michael@0 2506 if (url.contains("|"))
michael@0 2507 url = url.split("|")[0];
michael@0 2508 } catch(e) {
michael@0 2509 Components.utils.reportError("Couldn't get homepage pref: " + e);
michael@0 2510 }
michael@0 2511 content.location = url;
michael@0 2512 }
michael@0 2513
michael@0 2514 function BrowserFullScreen()
michael@0 2515 {
michael@0 2516 window.fullScreen = !window.fullScreen;
michael@0 2517 }
michael@0 2518
michael@0 2519 function _checkDefaultAndSwitchToMetro() {
michael@0 2520 #ifdef HAVE_SHELL_SERVICE
michael@0 2521 #ifdef XP_WIN
michael@0 2522 #ifdef MOZ_METRO
michael@0 2523 let shell = Components.classes["@mozilla.org/browser/shell-service;1"].
michael@0 2524 getService(Components.interfaces.nsIShellService);
michael@0 2525 let isDefault = shell.isDefaultBrowser(false, false);
michael@0 2526
michael@0 2527 if (isDefault) {
michael@0 2528 let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
michael@0 2529 getService(Components.interfaces.nsIAppStartup);
michael@0 2530
michael@0 2531 Services.prefs.setBoolPref('browser.sessionstore.resume_session_once', true);
michael@0 2532
michael@0 2533 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
michael@0 2534 .createInstance(Ci.nsISupportsPRBool);
michael@0 2535 Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
michael@0 2536
michael@0 2537 if (!cancelQuit.data) {
michael@0 2538 appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
michael@0 2539 Components.interfaces.nsIAppStartup.eRestartTouchEnvironment);
michael@0 2540 }
michael@0 2541 return true;
michael@0 2542 }
michael@0 2543 return false;
michael@0 2544 #endif
michael@0 2545 #endif
michael@0 2546 #endif
michael@0 2547 }
michael@0 2548
michael@0 2549 function SwitchToMetro() {
michael@0 2550 #ifdef HAVE_SHELL_SERVICE
michael@0 2551 #ifdef XP_WIN
michael@0 2552 #ifdef MOZ_METRO
michael@0 2553 if (this._checkDefaultAndSwitchToMetro()) {
michael@0 2554 return;
michael@0 2555 }
michael@0 2556
michael@0 2557 let shell = Components.classes["@mozilla.org/browser/shell-service;1"].
michael@0 2558 getService(Components.interfaces.nsIShellService);
michael@0 2559
michael@0 2560 shell.setDefaultBrowser(false, false);
michael@0 2561
michael@0 2562 let intervalID = window.setInterval(this._checkDefaultAndSwitchToMetro, 1000);
michael@0 2563 window.setTimeout(function() { window.clearInterval(intervalID); }, 10000);
michael@0 2564 #endif
michael@0 2565 #endif
michael@0 2566 #endif
michael@0 2567 }
michael@0 2568
michael@0 2569 function onFullScreen(event) {
michael@0 2570 FullScreen.toggle(event);
michael@0 2571 }
michael@0 2572
michael@0 2573 function onMozEnteredDomFullscreen(event) {
michael@0 2574 FullScreen.enterDomFullscreen(event);
michael@0 2575 }
michael@0 2576
michael@0 2577 function getWebNavigation()
michael@0 2578 {
michael@0 2579 return gBrowser.webNavigation;
michael@0 2580 }
michael@0 2581
michael@0 2582 function BrowserReloadWithFlags(reloadFlags) {
michael@0 2583 let url = gBrowser.currentURI.spec;
michael@0 2584 if (gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, url)) {
michael@0 2585 // If the remoteness has changed, the new browser doesn't have any
michael@0 2586 // information of what was loaded before, so we need to load the previous
michael@0 2587 // URL again.
michael@0 2588 gBrowser.loadURIWithFlags(url, reloadFlags);
michael@0 2589 return;
michael@0 2590 }
michael@0 2591
michael@0 2592 /* First, we'll try to use the session history object to reload so
michael@0 2593 * that framesets are handled properly. If we're in a special
michael@0 2594 * window (such as view-source) that has no session history, fall
michael@0 2595 * back on using the web navigation's reload method.
michael@0 2596 */
michael@0 2597
michael@0 2598 var webNav = gBrowser.webNavigation;
michael@0 2599 try {
michael@0 2600 var sh = webNav.sessionHistory;
michael@0 2601 if (sh)
michael@0 2602 webNav = sh.QueryInterface(nsIWebNavigation);
michael@0 2603 } catch (e) {
michael@0 2604 }
michael@0 2605
michael@0 2606 try {
michael@0 2607 webNav.reload(reloadFlags);
michael@0 2608 } catch (e) {
michael@0 2609 }
michael@0 2610 }
michael@0 2611
michael@0 2612 var PrintPreviewListener = {
michael@0 2613 _printPreviewTab: null,
michael@0 2614 _tabBeforePrintPreview: null,
michael@0 2615
michael@0 2616 getPrintPreviewBrowser: function () {
michael@0 2617 if (!this._printPreviewTab) {
michael@0 2618 this._tabBeforePrintPreview = gBrowser.selectedTab;
michael@0 2619 this._printPreviewTab = gBrowser.loadOneTab("about:blank",
michael@0 2620 { inBackground: false });
michael@0 2621 gBrowser.selectedTab = this._printPreviewTab;
michael@0 2622 }
michael@0 2623 return gBrowser.getBrowserForTab(this._printPreviewTab);
michael@0 2624 },
michael@0 2625 getSourceBrowser: function () {
michael@0 2626 return this._tabBeforePrintPreview ?
michael@0 2627 this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
michael@0 2628 },
michael@0 2629 getNavToolbox: function () {
michael@0 2630 return gNavToolbox;
michael@0 2631 },
michael@0 2632 onEnter: function () {
michael@0 2633 gInPrintPreviewMode = true;
michael@0 2634 this._toggleAffectedChrome();
michael@0 2635 },
michael@0 2636 onExit: function () {
michael@0 2637 gBrowser.selectedTab = this._tabBeforePrintPreview;
michael@0 2638 this._tabBeforePrintPreview = null;
michael@0 2639 gInPrintPreviewMode = false;
michael@0 2640 this._toggleAffectedChrome();
michael@0 2641 gBrowser.removeTab(this._printPreviewTab);
michael@0 2642 this._printPreviewTab = null;
michael@0 2643 },
michael@0 2644 _toggleAffectedChrome: function () {
michael@0 2645 gNavToolbox.collapsed = gInPrintPreviewMode;
michael@0 2646
michael@0 2647 if (gInPrintPreviewMode)
michael@0 2648 this._hideChrome();
michael@0 2649 else
michael@0 2650 this._showChrome();
michael@0 2651
michael@0 2652 if (this._chromeState.sidebarOpen)
michael@0 2653 toggleSidebar(this._sidebarCommand);
michael@0 2654
michael@0 2655 TabsInTitlebar.allowedBy("print-preview", !gInPrintPreviewMode);
michael@0 2656 },
michael@0 2657 _hideChrome: function () {
michael@0 2658 this._chromeState = {};
michael@0 2659
michael@0 2660 var sidebar = document.getElementById("sidebar-box");
michael@0 2661 this._chromeState.sidebarOpen = !sidebar.hidden;
michael@0 2662 this._sidebarCommand = sidebar.getAttribute("sidebarcommand");
michael@0 2663
michael@0 2664 var notificationBox = gBrowser.getNotificationBox();
michael@0 2665 this._chromeState.notificationsOpen = !notificationBox.notificationsHidden;
michael@0 2666 notificationBox.notificationsHidden = true;
michael@0 2667
michael@0 2668 document.getElementById("sidebar").setAttribute("src", "about:blank");
michael@0 2669 gBrowser.updateWindowResizers();
michael@0 2670
michael@0 2671 this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden;
michael@0 2672 if (gFindBarInitialized)
michael@0 2673 gFindBar.close();
michael@0 2674
michael@0 2675 var globalNotificationBox = document.getElementById("global-notificationbox");
michael@0 2676 this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden;
michael@0 2677 globalNotificationBox.notificationsHidden = true;
michael@0 2678
michael@0 2679 this._chromeState.syncNotificationsOpen = false;
michael@0 2680 var syncNotifications = document.getElementById("sync-notifications");
michael@0 2681 if (syncNotifications) {
michael@0 2682 this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden;
michael@0 2683 syncNotifications.notificationsHidden = true;
michael@0 2684 }
michael@0 2685 },
michael@0 2686 _showChrome: function () {
michael@0 2687 if (this._chromeState.notificationsOpen)
michael@0 2688 gBrowser.getNotificationBox().notificationsHidden = false;
michael@0 2689
michael@0 2690 if (this._chromeState.findOpen)
michael@0 2691 gFindBar.open();
michael@0 2692
michael@0 2693 if (this._chromeState.globalNotificationsOpen)
michael@0 2694 document.getElementById("global-notificationbox").notificationsHidden = false;
michael@0 2695
michael@0 2696 if (this._chromeState.syncNotificationsOpen)
michael@0 2697 document.getElementById("sync-notifications").notificationsHidden = false;
michael@0 2698 }
michael@0 2699 }
michael@0 2700
michael@0 2701 function getMarkupDocumentViewer()
michael@0 2702 {
michael@0 2703 return gBrowser.markupDocumentViewer;
michael@0 2704 }
michael@0 2705
michael@0 2706 // This function is obsolete. Newer code should use <tooltip page="true"/> instead.
michael@0 2707 function FillInHTMLTooltip(tipElement)
michael@0 2708 {
michael@0 2709 document.getElementById("aHTMLTooltip").fillInPageTooltip(tipElement);
michael@0 2710 }
michael@0 2711
michael@0 2712 var browserDragAndDrop = {
michael@0 2713 canDropLink: function (aEvent) Services.droppedLinkHandler.canDropLink(aEvent, true),
michael@0 2714
michael@0 2715 dragOver: function (aEvent)
michael@0 2716 {
michael@0 2717 if (this.canDropLink(aEvent)) {
michael@0 2718 aEvent.preventDefault();
michael@0 2719 }
michael@0 2720 },
michael@0 2721
michael@0 2722 drop: function (aEvent, aName, aDisallowInherit) {
michael@0 2723 return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit);
michael@0 2724 }
michael@0 2725 };
michael@0 2726
michael@0 2727 var homeButtonObserver = {
michael@0 2728 onDrop: function (aEvent)
michael@0 2729 {
michael@0 2730 // disallow setting home pages that inherit the principal
michael@0 2731 let url = browserDragAndDrop.drop(aEvent, {}, true);
michael@0 2732 setTimeout(openHomeDialog, 0, url);
michael@0 2733 },
michael@0 2734
michael@0 2735 onDragOver: function (aEvent)
michael@0 2736 {
michael@0 2737 browserDragAndDrop.dragOver(aEvent);
michael@0 2738 aEvent.dropEffect = "link";
michael@0 2739 },
michael@0 2740 onDragExit: function (aEvent)
michael@0 2741 {
michael@0 2742 }
michael@0 2743 }
michael@0 2744
michael@0 2745 function openHomeDialog(aURL)
michael@0 2746 {
michael@0 2747 var promptTitle = gNavigatorBundle.getString("droponhometitle");
michael@0 2748 var promptMsg = gNavigatorBundle.getString("droponhomemsg");
michael@0 2749 var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg,
michael@0 2750 Services.prompt.STD_YES_NO_BUTTONS,
michael@0 2751 null, null, null, null, {value:0});
michael@0 2752
michael@0 2753 if (pressedVal == 0) {
michael@0 2754 try {
michael@0 2755 var str = Components.classes["@mozilla.org/supports-string;1"]
michael@0 2756 .createInstance(Components.interfaces.nsISupportsString);
michael@0 2757 str.data = aURL;
michael@0 2758 gPrefService.setComplexValue("browser.startup.homepage",
michael@0 2759 Components.interfaces.nsISupportsString, str);
michael@0 2760 } catch (ex) {
michael@0 2761 dump("Failed to set the home page.\n"+ex+"\n");
michael@0 2762 }
michael@0 2763 }
michael@0 2764 }
michael@0 2765
michael@0 2766 var newTabButtonObserver = {
michael@0 2767 onDragOver: function (aEvent)
michael@0 2768 {
michael@0 2769 browserDragAndDrop.dragOver(aEvent);
michael@0 2770 },
michael@0 2771
michael@0 2772 onDragExit: function (aEvent)
michael@0 2773 {
michael@0 2774 },
michael@0 2775
michael@0 2776 onDrop: function (aEvent)
michael@0 2777 {
michael@0 2778 let url = browserDragAndDrop.drop(aEvent, { });
michael@0 2779 getShortcutOrURIAndPostData(url, data => {
michael@0 2780 if (data.url) {
michael@0 2781 // allow third-party services to fixup this URL
michael@0 2782 openNewTabWith(data.url, null, data.postData, aEvent, true);
michael@0 2783 }
michael@0 2784 });
michael@0 2785 }
michael@0 2786 }
michael@0 2787
michael@0 2788 var newWindowButtonObserver = {
michael@0 2789 onDragOver: function (aEvent)
michael@0 2790 {
michael@0 2791 browserDragAndDrop.dragOver(aEvent);
michael@0 2792 },
michael@0 2793 onDragExit: function (aEvent)
michael@0 2794 {
michael@0 2795 },
michael@0 2796 onDrop: function (aEvent)
michael@0 2797 {
michael@0 2798 let url = browserDragAndDrop.drop(aEvent, { });
michael@0 2799 getShortcutOrURIAndPostData(url, data => {
michael@0 2800 if (data.url) {
michael@0 2801 // allow third-party services to fixup this URL
michael@0 2802 openNewWindowWith(data.url, null, data.postData, true);
michael@0 2803 }
michael@0 2804 });
michael@0 2805 }
michael@0 2806 }
michael@0 2807
michael@0 2808 const DOMLinkHandler = {
michael@0 2809 init: function() {
michael@0 2810 let mm = window.messageManager;
michael@0 2811 mm.addMessageListener("Link:AddFeed", this);
michael@0 2812 mm.addMessageListener("Link:AddIcon", this);
michael@0 2813 mm.addMessageListener("Link:AddSearch", this);
michael@0 2814 },
michael@0 2815
michael@0 2816 receiveMessage: function (aMsg) {
michael@0 2817 switch (aMsg.name) {
michael@0 2818 case "Link:AddFeed":
michael@0 2819 let link = {type: aMsg.data.type, href: aMsg.data.href, title: aMsg.data.title};
michael@0 2820 FeedHandler.addFeed(link, aMsg.target);
michael@0 2821 break;
michael@0 2822
michael@0 2823 case "Link:AddIcon":
michael@0 2824 return this.addIcon(aMsg.target, aMsg.data.url);
michael@0 2825 break;
michael@0 2826
michael@0 2827 case "Link:AddSearch":
michael@0 2828 this.addSearch(aMsg.target, aMsg.data.engine, aMsg.data.url);
michael@0 2829 break;
michael@0 2830 }
michael@0 2831 },
michael@0 2832
michael@0 2833 addIcon: function(aBrowser, aURL) {
michael@0 2834 if (gBrowser.isFailedIcon(aURL))
michael@0 2835 return false;
michael@0 2836
michael@0 2837 let tab = gBrowser._getTabForBrowser(aBrowser);
michael@0 2838 if (!tab)
michael@0 2839 return false;
michael@0 2840
michael@0 2841 gBrowser.setIcon(tab, aURL);
michael@0 2842 return true;
michael@0 2843 },
michael@0 2844
michael@0 2845 addSearch: function(aBrowser, aEngine, aURL) {
michael@0 2846 let tab = gBrowser._getTabForBrowser(aBrowser);
michael@0 2847 if (!tab)
michael@0 2848 return false;
michael@0 2849
michael@0 2850 BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL));
michael@0 2851 },
michael@0 2852 }
michael@0 2853
michael@0 2854 const BrowserSearch = {
michael@0 2855 addEngine: function(browser, engine, uri) {
michael@0 2856 if (!this.searchBar)
michael@0 2857 return;
michael@0 2858
michael@0 2859 // Check to see whether we've already added an engine with this title
michael@0 2860 if (browser.engines) {
michael@0 2861 if (browser.engines.some(function (e) e.title == engine.title))
michael@0 2862 return;
michael@0 2863 }
michael@0 2864
michael@0 2865 // Append the URI and an appropriate title to the browser data.
michael@0 2866 // Use documentURIObject in the check for shouldLoadFavIcon so that we
michael@0 2867 // do the right thing with about:-style error pages. Bug 453442
michael@0 2868 var iconURL = null;
michael@0 2869 if (gBrowser.shouldLoadFavIcon(uri))
michael@0 2870 iconURL = uri.prePath + "/favicon.ico";
michael@0 2871
michael@0 2872 var hidden = false;
michael@0 2873 // If this engine (identified by title) is already in the list, add it
michael@0 2874 // to the list of hidden engines rather than to the main list.
michael@0 2875 // XXX This will need to be changed when engines are identified by URL;
michael@0 2876 // see bug 335102.
michael@0 2877 if (Services.search.getEngineByName(engine.title))
michael@0 2878 hidden = true;
michael@0 2879
michael@0 2880 var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
michael@0 2881
michael@0 2882 engines.push({ uri: engine.href,
michael@0 2883 title: engine.title,
michael@0 2884 icon: iconURL });
michael@0 2885
michael@0 2886 if (hidden)
michael@0 2887 browser.hiddenEngines = engines;
michael@0 2888 else
michael@0 2889 browser.engines = engines;
michael@0 2890 },
michael@0 2891
michael@0 2892 /**
michael@0 2893 * Gives focus to the search bar, if it is present on the toolbar, or loads
michael@0 2894 * the default engine's search form otherwise. For Mac, opens a new window
michael@0 2895 * or focuses an existing window, if necessary.
michael@0 2896 */
michael@0 2897 webSearch: function BrowserSearch_webSearch() {
michael@0 2898 #ifdef XP_MACOSX
michael@0 2899 if (window.location.href != getBrowserURL()) {
michael@0 2900 var win = getTopWin();
michael@0 2901 if (win) {
michael@0 2902 // If there's an open browser window, it should handle this command
michael@0 2903 win.focus();
michael@0 2904 win.BrowserSearch.webSearch();
michael@0 2905 } else {
michael@0 2906 // If there are no open browser windows, open a new one
michael@0 2907 var observer = function observer(subject, topic, data) {
michael@0 2908 if (subject == win) {
michael@0 2909 BrowserSearch.webSearch();
michael@0 2910 Services.obs.removeObserver(observer, "browser-delayed-startup-finished");
michael@0 2911 }
michael@0 2912 }
michael@0 2913 win = window.openDialog(getBrowserURL(), "_blank",
michael@0 2914 "chrome,all,dialog=no", "about:blank");
michael@0 2915 Services.obs.addObserver(observer, "browser-delayed-startup-finished", false);
michael@0 2916 }
michael@0 2917 return;
michael@0 2918 }
michael@0 2919 #endif
michael@0 2920 let openSearchPageIfFieldIsNotActive = function(aSearchBar) {
michael@0 2921 if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField)
michael@0 2922 openUILinkIn("about:home", "current");
michael@0 2923 };
michael@0 2924
michael@0 2925 let searchBar = this.searchBar;
michael@0 2926 let placement = CustomizableUI.getPlacementOfWidget("search-container");
michael@0 2927 let focusSearchBar = () => {
michael@0 2928 searchBar = this.searchBar;
michael@0 2929 searchBar.select();
michael@0 2930 openSearchPageIfFieldIsNotActive(searchBar);
michael@0 2931 };
michael@0 2932 if (placement && placement.area == CustomizableUI.AREA_PANEL) {
michael@0 2933 // The panel is not constructed until the first time it is shown.
michael@0 2934 PanelUI.show().then(focusSearchBar);
michael@0 2935 return;
michael@0 2936 }
michael@0 2937 if (placement && placement.area == CustomizableUI.AREA_NAVBAR && searchBar &&
michael@0 2938 searchBar.parentNode.getAttribute("overflowedItem") == "true") {
michael@0 2939 let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
michael@0 2940 navBar.overflowable.show().then(() => {
michael@0 2941 focusSearchBar();
michael@0 2942 });
michael@0 2943 return;
michael@0 2944 }
michael@0 2945 if (searchBar) {
michael@0 2946 if (window.fullScreen)
michael@0 2947 FullScreen.mouseoverToggle(true);
michael@0 2948 searchBar.select();
michael@0 2949 }
michael@0 2950 openSearchPageIfFieldIsNotActive(searchBar);
michael@0 2951 },
michael@0 2952
michael@0 2953 /**
michael@0 2954 * Loads a search results page, given a set of search terms. Uses the current
michael@0 2955 * engine if the search bar is visible, or the default engine otherwise.
michael@0 2956 *
michael@0 2957 * @param searchText
michael@0 2958 * The search terms to use for the search.
michael@0 2959 *
michael@0 2960 * @param useNewTab
michael@0 2961 * Boolean indicating whether or not the search should load in a new
michael@0 2962 * tab.
michael@0 2963 *
michael@0 2964 * @param purpose [optional]
michael@0 2965 * A string meant to indicate the context of the search request. This
michael@0 2966 * allows the search service to provide a different nsISearchSubmission
michael@0 2967 * depending on e.g. where the search is triggered in the UI.
michael@0 2968 *
michael@0 2969 * @return engine The search engine used to perform a search, or null if no
michael@0 2970 * search was performed.
michael@0 2971 */
michael@0 2972 _loadSearch: function (searchText, useNewTab, purpose) {
michael@0 2973 let engine;
michael@0 2974
michael@0 2975 // If the search bar is visible, use the current engine, otherwise, fall
michael@0 2976 // back to the default engine.
michael@0 2977 if (isElementVisible(this.searchBar))
michael@0 2978 engine = Services.search.currentEngine;
michael@0 2979 else
michael@0 2980 engine = Services.search.defaultEngine;
michael@0 2981
michael@0 2982 let submission = engine.getSubmission(searchText, null, purpose); // HTML response
michael@0 2983
michael@0 2984 // getSubmission can return null if the engine doesn't have a URL
michael@0 2985 // with a text/html response type. This is unlikely (since
michael@0 2986 // SearchService._addEngineToStore() should fail for such an engine),
michael@0 2987 // but let's be on the safe side.
michael@0 2988 if (!submission) {
michael@0 2989 return null;
michael@0 2990 }
michael@0 2991
michael@0 2992 let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground");
michael@0 2993 openLinkIn(submission.uri.spec,
michael@0 2994 useNewTab ? "tab" : "current",
michael@0 2995 { postData: submission.postData,
michael@0 2996 inBackground: inBackground,
michael@0 2997 relatedToCurrent: true });
michael@0 2998
michael@0 2999 return engine;
michael@0 3000 },
michael@0 3001
michael@0 3002 /**
michael@0 3003 * Just like _loadSearch, but preserving an old API.
michael@0 3004 *
michael@0 3005 * @return string Name of the search engine used to perform a search or null
michael@0 3006 * if a search was not performed.
michael@0 3007 */
michael@0 3008 loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
michael@0 3009 let engine = BrowserSearch._loadSearch(searchText, useNewTab, purpose);
michael@0 3010 if (!engine) {
michael@0 3011 return null;
michael@0 3012 }
michael@0 3013 return engine.name;
michael@0 3014 },
michael@0 3015
michael@0 3016 /**
michael@0 3017 * Perform a search initiated from the context menu.
michael@0 3018 *
michael@0 3019 * This should only be called from the context menu. See
michael@0 3020 * BrowserSearch.loadSearch for the preferred API.
michael@0 3021 */
michael@0 3022 loadSearchFromContext: function (terms) {
michael@0 3023 let engine = BrowserSearch._loadSearch(terms, true, "contextmenu");
michael@0 3024 if (engine) {
michael@0 3025 BrowserSearch.recordSearchInHealthReport(engine, "contextmenu");
michael@0 3026 }
michael@0 3027 },
michael@0 3028
michael@0 3029 /**
michael@0 3030 * Returns the search bar element if it is present in the toolbar, null otherwise.
michael@0 3031 */
michael@0 3032 get searchBar() {
michael@0 3033 return document.getElementById("searchbar");
michael@0 3034 },
michael@0 3035
michael@0 3036 loadAddEngines: function BrowserSearch_loadAddEngines() {
michael@0 3037 var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
michael@0 3038 var where = newWindowPref == 3 ? "tab" : "window";
michael@0 3039 var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true);
michael@0 3040 openUILinkIn(searchEnginesURL, where);
michael@0 3041 },
michael@0 3042
michael@0 3043 /**
michael@0 3044 * Helper to record a search with Firefox Health Report.
michael@0 3045 *
michael@0 3046 * FHR records only search counts and nothing pertaining to the search itself.
michael@0 3047 *
michael@0 3048 * @param engine
michael@0 3049 * (nsISearchEngine) The engine handling the search.
michael@0 3050 * @param source
michael@0 3051 * (string) Where the search originated from. See the FHR
michael@0 3052 * SearchesProvider for allowed values.
michael@0 3053 */
michael@0 3054 recordSearchInHealthReport: function (engine, source) {
michael@0 3055 #ifdef MOZ_SERVICES_HEALTHREPORT
michael@0 3056 let reporter = Cc["@mozilla.org/datareporting/service;1"]
michael@0 3057 .getService()
michael@0 3058 .wrappedJSObject
michael@0 3059 .healthReporter;
michael@0 3060
michael@0 3061 // This can happen if the FHR component of the data reporting service is
michael@0 3062 // disabled. This is controlled by a pref that most will never use.
michael@0 3063 if (!reporter) {
michael@0 3064 return;
michael@0 3065 }
michael@0 3066
michael@0 3067 reporter.onInit().then(function record() {
michael@0 3068 try {
michael@0 3069 reporter.getProvider("org.mozilla.searches").recordSearch(engine, source);
michael@0 3070 } catch (ex) {
michael@0 3071 Cu.reportError(ex);
michael@0 3072 }
michael@0 3073 });
michael@0 3074 #endif
michael@0 3075 },
michael@0 3076 };
michael@0 3077
michael@0 3078 function FillHistoryMenu(aParent) {
michael@0 3079 // Lazily add the hover listeners on first showing and never remove them
michael@0 3080 if (!aParent.hasStatusListener) {
michael@0 3081 // Show history item's uri in the status bar when hovering, and clear on exit
michael@0 3082 aParent.addEventListener("DOMMenuItemActive", function(aEvent) {
michael@0 3083 // Only the current page should have the checked attribute, so skip it
michael@0 3084 if (!aEvent.target.hasAttribute("checked"))
michael@0 3085 XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
michael@0 3086 }, false);
michael@0 3087 aParent.addEventListener("DOMMenuItemInactive", function() {
michael@0 3088 XULBrowserWindow.setOverLink("");
michael@0 3089 }, false);
michael@0 3090
michael@0 3091 aParent.hasStatusListener = true;
michael@0 3092 }
michael@0 3093
michael@0 3094 // Remove old entries if any
michael@0 3095 var children = aParent.childNodes;
michael@0 3096 for (var i = children.length - 1; i >= 0; --i) {
michael@0 3097 if (children[i].hasAttribute("index"))
michael@0 3098 aParent.removeChild(children[i]);
michael@0 3099 }
michael@0 3100
michael@0 3101 var webNav = gBrowser.webNavigation;
michael@0 3102 var sessionHistory = webNav.sessionHistory;
michael@0 3103
michael@0 3104 var count = sessionHistory.count;
michael@0 3105 if (count <= 1) // don't display the popup for a single item
michael@0 3106 return false;
michael@0 3107
michael@0 3108 const MAX_HISTORY_MENU_ITEMS = 15;
michael@0 3109 var index = sessionHistory.index;
michael@0 3110 var half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
michael@0 3111 var start = Math.max(index - half_length, 0);
michael@0 3112 var end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count);
michael@0 3113 if (end == count)
michael@0 3114 start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
michael@0 3115
michael@0 3116 var tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
michael@0 3117 var tooltipCurrent = gNavigatorBundle.getString("tabHistory.current");
michael@0 3118 var tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
michael@0 3119
michael@0 3120 for (var j = end - 1; j >= start; j--) {
michael@0 3121 let item = document.createElement("menuitem");
michael@0 3122 let entry = sessionHistory.getEntryAtIndex(j, false);
michael@0 3123 let uri = entry.URI.spec;
michael@0 3124
michael@0 3125 item.setAttribute("uri", uri);
michael@0 3126 item.setAttribute("label", entry.title || uri);
michael@0 3127 item.setAttribute("index", j);
michael@0 3128
michael@0 3129 if (j != index) {
michael@0 3130 PlacesUtils.favicons.getFaviconURLForPage(entry.URI, function (aURI) {
michael@0 3131 if (aURI) {
michael@0 3132 let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec;
michael@0 3133 item.style.listStyleImage = "url(" + iconURL + ")";
michael@0 3134 }
michael@0 3135 });
michael@0 3136 }
michael@0 3137
michael@0 3138 if (j < index) {
michael@0 3139 item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
michael@0 3140 item.setAttribute("tooltiptext", tooltipBack);
michael@0 3141 } else if (j == index) {
michael@0 3142 item.setAttribute("type", "radio");
michael@0 3143 item.setAttribute("checked", "true");
michael@0 3144 item.className = "unified-nav-current";
michael@0 3145 item.setAttribute("tooltiptext", tooltipCurrent);
michael@0 3146 } else {
michael@0 3147 item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon";
michael@0 3148 item.setAttribute("tooltiptext", tooltipForward);
michael@0 3149 }
michael@0 3150
michael@0 3151 aParent.appendChild(item);
michael@0 3152 }
michael@0 3153 return true;
michael@0 3154 }
michael@0 3155
michael@0 3156 function addToUrlbarHistory(aUrlToAdd) {
michael@0 3157 if (!PrivateBrowsingUtils.isWindowPrivate(window) &&
michael@0 3158 aUrlToAdd &&
michael@0 3159 !aUrlToAdd.contains(" ") &&
michael@0 3160 !/[\x00-\x1F]/.test(aUrlToAdd))
michael@0 3161 PlacesUIUtils.markPageAsTyped(aUrlToAdd);
michael@0 3162 }
michael@0 3163
michael@0 3164 function toJavaScriptConsole()
michael@0 3165 {
michael@0 3166 toOpenWindowByType("global:console", "chrome://global/content/console.xul");
michael@0 3167 }
michael@0 3168
michael@0 3169 function BrowserDownloadsUI()
michael@0 3170 {
michael@0 3171 Cc["@mozilla.org/download-manager-ui;1"].
michael@0 3172 getService(Ci.nsIDownloadManagerUI).show(window);
michael@0 3173 }
michael@0 3174
michael@0 3175 function toOpenWindowByType(inType, uri, features)
michael@0 3176 {
michael@0 3177 var topWindow = Services.wm.getMostRecentWindow(inType);
michael@0 3178
michael@0 3179 if (topWindow)
michael@0 3180 topWindow.focus();
michael@0 3181 else if (features)
michael@0 3182 window.open(uri, "_blank", features);
michael@0 3183 else
michael@0 3184 window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
michael@0 3185 }
michael@0 3186
michael@0 3187 function OpenBrowserWindow(options)
michael@0 3188 {
michael@0 3189 var telemetryObj = {};
michael@0 3190 TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj);
michael@0 3191
michael@0 3192 function newDocumentShown(doc, topic, data) {
michael@0 3193 if (topic == "document-shown" &&
michael@0 3194 doc != document &&
michael@0 3195 doc.defaultView == win) {
michael@0 3196 Services.obs.removeObserver(newDocumentShown, "document-shown");
michael@0 3197 Services.obs.removeObserver(windowClosed, "domwindowclosed");
michael@0 3198 TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
michael@0 3199 }
michael@0 3200 }
michael@0 3201
michael@0 3202 function windowClosed(subject) {
michael@0 3203 if (subject == win) {
michael@0 3204 Services.obs.removeObserver(newDocumentShown, "document-shown");
michael@0 3205 Services.obs.removeObserver(windowClosed, "domwindowclosed");
michael@0 3206 }
michael@0 3207 }
michael@0 3208
michael@0 3209 // Make sure to remove the 'document-shown' observer in case the window
michael@0 3210 // is being closed right after it was opened to avoid leaking.
michael@0 3211 Services.obs.addObserver(newDocumentShown, "document-shown", false);
michael@0 3212 Services.obs.addObserver(windowClosed, "domwindowclosed", false);
michael@0 3213
michael@0 3214 var charsetArg = new String();
michael@0 3215 var handler = Components.classes["@mozilla.org/browser/clh;1"]
michael@0 3216 .getService(Components.interfaces.nsIBrowserHandler);
michael@0 3217 var defaultArgs = handler.defaultArgs;
michael@0 3218 var wintype = document.documentElement.getAttribute('windowtype');
michael@0 3219
michael@0 3220 var extraFeatures = "";
michael@0 3221 if (options && options.private) {
michael@0 3222 extraFeatures = ",private";
michael@0 3223 if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
michael@0 3224 // Force the new window to load about:privatebrowsing instead of the default home page
michael@0 3225 defaultArgs = "about:privatebrowsing";
michael@0 3226 }
michael@0 3227 } else {
michael@0 3228 extraFeatures = ",non-private";
michael@0 3229 }
michael@0 3230
michael@0 3231 if (options && options.remote) {
michael@0 3232 let omtcEnabled = gPrefService.getBoolPref("layers.offmainthreadcomposition.enabled");
michael@0 3233 if (!omtcEnabled) {
michael@0 3234 alert("To use out-of-process tabs, you must set the layers.offmainthreadcomposition.enabled preference and restart. Opening a normal window instead.");
michael@0 3235 } else {
michael@0 3236 extraFeatures += ",remote";
michael@0 3237 }
michael@0 3238 } else if (options && options.remote === false) {
michael@0 3239 extraFeatures += ",non-remote";
michael@0 3240 }
michael@0 3241
michael@0 3242 // if and only if the current window is a browser window and it has a document with a character
michael@0 3243 // set, then extract the current charset menu setting from the current document and use it to
michael@0 3244 // initialize the new browser window...
michael@0 3245 var win;
michael@0 3246 if (window && (wintype == "navigator:browser") && window.content && window.content.document)
michael@0 3247 {
michael@0 3248 var DocCharset = window.content.document.characterSet;
michael@0 3249 charsetArg = "charset="+DocCharset;
michael@0 3250
michael@0 3251 //we should "inherit" the charset menu setting in a new window
michael@0 3252 win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg);
michael@0 3253 }
michael@0 3254 else // forget about the charset information.
michael@0 3255 {
michael@0 3256 win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs);
michael@0 3257 }
michael@0 3258
michael@0 3259 return win;
michael@0 3260 }
michael@0 3261
michael@0 3262 // Only here for backwards compat, we should remove this soon
michael@0 3263 function BrowserCustomizeToolbar() {
michael@0 3264 gCustomizeMode.enter();
michael@0 3265 }
michael@0 3266
michael@0 3267 /**
michael@0 3268 * Update the global flag that tracks whether or not any edit UI (the Edit menu,
michael@0 3269 * edit-related items in the context menu, and edit-related toolbar buttons
michael@0 3270 * is visible, then update the edit commands' enabled state accordingly. We use
michael@0 3271 * this flag to skip updating the edit commands on focus or selection changes
michael@0 3272 * when no UI is visible to improve performance (including pageload performance,
michael@0 3273 * since focus changes when you load a new page).
michael@0 3274 *
michael@0 3275 * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
michael@0 3276 * enabled state so the UI will reflect it appropriately.
michael@0 3277 *
michael@0 3278 * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
michael@0 3279 * still work and just lazily disable them as needed when the user presses a
michael@0 3280 * shortcut.
michael@0 3281 *
michael@0 3282 * This doesn't work on Mac, since Mac menus flash when users press their
michael@0 3283 * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
michael@0 3284 * and we need to always update the edit commands. Thus on Mac this function
michael@0 3285 * is a no op.
michael@0 3286 */
michael@0 3287 function updateEditUIVisibility()
michael@0 3288 {
michael@0 3289 #ifndef XP_MACOSX
michael@0 3290 let editMenuPopupState = document.getElementById("menu_EditPopup").state;
michael@0 3291 let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state;
michael@0 3292 let placesContextMenuPopupState = document.getElementById("placesContext").state;
michael@0 3293
michael@0 3294 // The UI is visible if the Edit menu is opening or open, if the context menu
michael@0 3295 // is open, or if the toolbar has been customized to include the Cut, Copy,
michael@0 3296 // or Paste toolbar buttons.
michael@0 3297 gEditUIVisible = editMenuPopupState == "showing" ||
michael@0 3298 editMenuPopupState == "open" ||
michael@0 3299 contextMenuPopupState == "showing" ||
michael@0 3300 contextMenuPopupState == "open" ||
michael@0 3301 placesContextMenuPopupState == "showing" ||
michael@0 3302 placesContextMenuPopupState == "open" ||
michael@0 3303 document.getElementById("edit-controls") ? true : false;
michael@0 3304
michael@0 3305 // If UI is visible, update the edit commands' enabled state to reflect
michael@0 3306 // whether or not they are actually enabled for the current focus/selection.
michael@0 3307 if (gEditUIVisible)
michael@0 3308 goUpdateGlobalEditMenuItems();
michael@0 3309
michael@0 3310 // Otherwise, enable all commands, so that keyboard shortcuts still work,
michael@0 3311 // then lazily determine their actual enabled state when the user presses
michael@0 3312 // a keyboard shortcut.
michael@0 3313 else {
michael@0 3314 goSetCommandEnabled("cmd_undo", true);
michael@0 3315 goSetCommandEnabled("cmd_redo", true);
michael@0 3316 goSetCommandEnabled("cmd_cut", true);
michael@0 3317 goSetCommandEnabled("cmd_copy", true);
michael@0 3318 goSetCommandEnabled("cmd_paste", true);
michael@0 3319 goSetCommandEnabled("cmd_selectAll", true);
michael@0 3320 goSetCommandEnabled("cmd_delete", true);
michael@0 3321 goSetCommandEnabled("cmd_switchTextDirection", true);
michael@0 3322 }
michael@0 3323 #endif
michael@0 3324 }
michael@0 3325
michael@0 3326 /**
michael@0 3327 * Makes the Character Encoding menu enabled or disabled as appropriate.
michael@0 3328 * To be called when the View menu or the app menu is opened.
michael@0 3329 */
michael@0 3330 function updateCharacterEncodingMenuState()
michael@0 3331 {
michael@0 3332 let charsetMenu = document.getElementById("charsetMenu");
michael@0 3333 // gBrowser is null on Mac when the menubar shows in the context of
michael@0 3334 // non-browser windows. The above elements may be null depending on
michael@0 3335 // what parts of the menubar are present. E.g. no app menu on Mac.
michael@0 3336 if (gBrowser &&
michael@0 3337 gBrowser.docShell &&
michael@0 3338 gBrowser.docShell.mayEnableCharacterEncodingMenu) {
michael@0 3339 if (charsetMenu) {
michael@0 3340 charsetMenu.removeAttribute("disabled");
michael@0 3341 }
michael@0 3342 } else {
michael@0 3343 if (charsetMenu) {
michael@0 3344 charsetMenu.setAttribute("disabled", "true");
michael@0 3345 }
michael@0 3346 }
michael@0 3347 }
michael@0 3348
michael@0 3349 /**
michael@0 3350 * Returns true if |aMimeType| is text-based, false otherwise.
michael@0 3351 *
michael@0 3352 * @param aMimeType
michael@0 3353 * The MIME type to check.
michael@0 3354 *
michael@0 3355 * If adding types to this function, please also check the similar
michael@0 3356 * function in findbar.xml
michael@0 3357 */
michael@0 3358 function mimeTypeIsTextBased(aMimeType)
michael@0 3359 {
michael@0 3360 return aMimeType.startsWith("text/") ||
michael@0 3361 aMimeType.endsWith("+xml") ||
michael@0 3362 aMimeType == "application/x-javascript" ||
michael@0 3363 aMimeType == "application/javascript" ||
michael@0 3364 aMimeType == "application/json" ||
michael@0 3365 aMimeType == "application/xml" ||
michael@0 3366 aMimeType == "mozilla.application/cached-xul";
michael@0 3367 }
michael@0 3368
michael@0 3369 var XULBrowserWindow = {
michael@0 3370 // Stored Status, Link and Loading values
michael@0 3371 status: "",
michael@0 3372 defaultStatus: "",
michael@0 3373 overLink: "",
michael@0 3374 startTime: 0,
michael@0 3375 statusText: "",
michael@0 3376 isBusy: false,
michael@0 3377 // Left here for add-on compatibility, see bug 752434
michael@0 3378 inContentWhitelist: [],
michael@0 3379
michael@0 3380 QueryInterface: function (aIID) {
michael@0 3381 if (aIID.equals(Ci.nsIWebProgressListener) ||
michael@0 3382 aIID.equals(Ci.nsIWebProgressListener2) ||
michael@0 3383 aIID.equals(Ci.nsISupportsWeakReference) ||
michael@0 3384 aIID.equals(Ci.nsIXULBrowserWindow) ||
michael@0 3385 aIID.equals(Ci.nsISupports))
michael@0 3386 return this;
michael@0 3387 throw Cr.NS_NOINTERFACE;
michael@0 3388 },
michael@0 3389
michael@0 3390 get stopCommand () {
michael@0 3391 delete this.stopCommand;
michael@0 3392 return this.stopCommand = document.getElementById("Browser:Stop");
michael@0 3393 },
michael@0 3394 get reloadCommand () {
michael@0 3395 delete this.reloadCommand;
michael@0 3396 return this.reloadCommand = document.getElementById("Browser:Reload");
michael@0 3397 },
michael@0 3398 get statusTextField () {
michael@0 3399 return gBrowser.getStatusPanel();
michael@0 3400 },
michael@0 3401 get isImage () {
michael@0 3402 delete this.isImage;
michael@0 3403 return this.isImage = document.getElementById("isImage");
michael@0 3404 },
michael@0 3405
michael@0 3406 init: function () {
michael@0 3407 // Initialize the security button's state and tooltip text.
michael@0 3408 var securityUI = gBrowser.securityUI;
michael@0 3409 this.onSecurityChange(null, null, securityUI.state);
michael@0 3410 },
michael@0 3411
michael@0 3412 setJSStatus: function () {
michael@0 3413 // unsupported
michael@0 3414 },
michael@0 3415
michael@0 3416 setDefaultStatus: function (status) {
michael@0 3417 this.defaultStatus = status;
michael@0 3418 this.updateStatusField();
michael@0 3419 },
michael@0 3420
michael@0 3421 setOverLink: function (url, anchorElt) {
michael@0 3422 // Encode bidirectional formatting characters.
michael@0 3423 // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
michael@0 3424 url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
michael@0 3425 encodeURIComponent);
michael@0 3426
michael@0 3427 if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
michael@0 3428 url = trimURL(url);
michael@0 3429
michael@0 3430 this.overLink = url;
michael@0 3431 LinkTargetDisplay.update();
michael@0 3432 },
michael@0 3433
michael@0 3434 showTooltip: function (x, y, tooltip) {
michael@0 3435 // The x,y coordinates are relative to the <browser> element using
michael@0 3436 // the chrome zoom level.
michael@0 3437 let elt = document.getElementById("remoteBrowserTooltip");
michael@0 3438 elt.label = tooltip;
michael@0 3439
michael@0 3440 let anchor = gBrowser.selectedBrowser;
michael@0 3441 elt.openPopupAtScreen(anchor.boxObject.screenX + x, anchor.boxObject.screenY + y, false, null);
michael@0 3442 },
michael@0 3443
michael@0 3444 hideTooltip: function () {
michael@0 3445 let elt = document.getElementById("remoteBrowserTooltip");
michael@0 3446 elt.hidePopup();
michael@0 3447 },
michael@0 3448
michael@0 3449 updateStatusField: function () {
michael@0 3450 var text, type, types = ["overLink"];
michael@0 3451 if (this._busyUI)
michael@0 3452 types.push("status");
michael@0 3453 types.push("defaultStatus");
michael@0 3454 for (type of types) {
michael@0 3455 text = this[type];
michael@0 3456 if (text)
michael@0 3457 break;
michael@0 3458 }
michael@0 3459
michael@0 3460 // check the current value so we don't trigger an attribute change
michael@0 3461 // and cause needless (slow!) UI updates
michael@0 3462 if (this.statusText != text) {
michael@0 3463 let field = this.statusTextField;
michael@0 3464 field.setAttribute("previoustype", field.getAttribute("type"));
michael@0 3465 field.setAttribute("type", type);
michael@0 3466 field.label = text;
michael@0 3467 field.setAttribute("crop", type == "overLink" ? "center" : "end");
michael@0 3468 this.statusText = text;
michael@0 3469 }
michael@0 3470 },
michael@0 3471
michael@0 3472 // Called before links are navigated to to allow us to retarget them if needed.
michael@0 3473 onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
michael@0 3474 let target = this._onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
michael@0 3475 SocialUI.closeSocialPanelForLinkTraversal(target, linkNode);
michael@0 3476 return target;
michael@0 3477 },
michael@0 3478
michael@0 3479 _onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
michael@0 3480 // Don't modify non-default targets or targets that aren't in top-level app
michael@0 3481 // tab docshells (isAppTab will be false for app tab subframes).
michael@0 3482 if (originalTarget != "" || !isAppTab)
michael@0 3483 return originalTarget;
michael@0 3484
michael@0 3485 // External links from within app tabs should always open in new tabs
michael@0 3486 // instead of replacing the app tab's page (Bug 575561)
michael@0 3487 let linkHost;
michael@0 3488 let docHost;
michael@0 3489 try {
michael@0 3490 linkHost = linkURI.host;
michael@0 3491 docHost = linkNode.ownerDocument.documentURIObject.host;
michael@0 3492 } catch(e) {
michael@0 3493 // nsIURI.host can throw for non-nsStandardURL nsIURIs.
michael@0 3494 // If we fail to get either host, just return originalTarget.
michael@0 3495 return originalTarget;
michael@0 3496 }
michael@0 3497
michael@0 3498 if (docHost == linkHost)
michael@0 3499 return originalTarget;
michael@0 3500
michael@0 3501 // Special case: ignore "www" prefix if it is part of host string
michael@0 3502 let [longHost, shortHost] =
michael@0 3503 linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost];
michael@0 3504 if (longHost == "www." + shortHost)
michael@0 3505 return originalTarget;
michael@0 3506
michael@0 3507 return "_blank";
michael@0 3508 },
michael@0 3509
michael@0 3510 onProgressChange: function (aWebProgress, aRequest,
michael@0 3511 aCurSelfProgress, aMaxSelfProgress,
michael@0 3512 aCurTotalProgress, aMaxTotalProgress) {
michael@0 3513 // Do nothing.
michael@0 3514 },
michael@0 3515
michael@0 3516 onProgressChange64: function (aWebProgress, aRequest,
michael@0 3517 aCurSelfProgress, aMaxSelfProgress,
michael@0 3518 aCurTotalProgress, aMaxTotalProgress) {
michael@0 3519 return this.onProgressChange(aWebProgress, aRequest,
michael@0 3520 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
michael@0 3521 aMaxTotalProgress);
michael@0 3522 },
michael@0 3523
michael@0 3524 // This function fires only for the currently selected tab.
michael@0 3525 onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
michael@0 3526 const nsIWebProgressListener = Ci.nsIWebProgressListener;
michael@0 3527 const nsIChannel = Ci.nsIChannel;
michael@0 3528
michael@0 3529 let browser = gBrowser.selectedBrowser;
michael@0 3530
michael@0 3531 if (aStateFlags & nsIWebProgressListener.STATE_START &&
michael@0 3532 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
michael@0 3533
michael@0 3534 if (aRequest && aWebProgress.isTopLevel) {
michael@0 3535 // clear out feed data
michael@0 3536 browser.feeds = null;
michael@0 3537
michael@0 3538 // clear out search-engine data
michael@0 3539 browser.engines = null;
michael@0 3540 }
michael@0 3541
michael@0 3542 this.isBusy = true;
michael@0 3543
michael@0 3544 if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
michael@0 3545 this._busyUI = true;
michael@0 3546
michael@0 3547 // XXX: This needs to be based on window activity...
michael@0 3548 this.stopCommand.removeAttribute("disabled");
michael@0 3549 CombinedStopReload.switchToStop();
michael@0 3550 }
michael@0 3551 }
michael@0 3552 else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
michael@0 3553 // This (thanks to the filter) is a network stop or the last
michael@0 3554 // request stop outside of loading the document, stop throbbers
michael@0 3555 // and progress bars and such
michael@0 3556 if (aRequest) {
michael@0 3557 let msg = "";
michael@0 3558 let location;
michael@0 3559 // Get the URI either from a channel or a pseudo-object
michael@0 3560 if (aRequest instanceof nsIChannel || "URI" in aRequest) {
michael@0 3561 location = aRequest.URI;
michael@0 3562
michael@0 3563 // For keyword URIs clear the user typed value since they will be changed into real URIs
michael@0 3564 if (location.scheme == "keyword" && aWebProgress.isTopLevel)
michael@0 3565 gBrowser.userTypedValue = null;
michael@0 3566
michael@0 3567 if (location.spec != "about:blank") {
michael@0 3568 switch (aStatus) {
michael@0 3569 case Components.results.NS_ERROR_NET_TIMEOUT:
michael@0 3570 msg = gNavigatorBundle.getString("nv_timeout");
michael@0 3571 break;
michael@0 3572 }
michael@0 3573 }
michael@0 3574 }
michael@0 3575
michael@0 3576 this.status = "";
michael@0 3577 this.setDefaultStatus(msg);
michael@0 3578
michael@0 3579 // Disable menu entries for images, enable otherwise
michael@0 3580 if (browser.documentContentType && mimeTypeIsTextBased(browser.documentContentType))
michael@0 3581 this.isImage.removeAttribute('disabled');
michael@0 3582 else
michael@0 3583 this.isImage.setAttribute('disabled', 'true');
michael@0 3584 }
michael@0 3585
michael@0 3586 this.isBusy = false;
michael@0 3587
michael@0 3588 if (this._busyUI) {
michael@0 3589 this._busyUI = false;
michael@0 3590
michael@0 3591 this.stopCommand.setAttribute("disabled", "true");
michael@0 3592 CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest);
michael@0 3593 }
michael@0 3594 }
michael@0 3595 },
michael@0 3596
michael@0 3597 onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
michael@0 3598 var location = aLocationURI ? aLocationURI.spec : "";
michael@0 3599
michael@0 3600 // Hide the form invalid popup.
michael@0 3601 if (gFormSubmitObserver.panel) {
michael@0 3602 gFormSubmitObserver.panel.hidePopup();
michael@0 3603 }
michael@0 3604
michael@0 3605 let pageTooltip = document.getElementById("aHTMLTooltip");
michael@0 3606 let tooltipNode = pageTooltip.triggerNode;
michael@0 3607 if (tooltipNode) {
michael@0 3608 // Optimise for the common case
michael@0 3609 if (aWebProgress.isTopLevel) {
michael@0 3610 pageTooltip.hidePopup();
michael@0 3611 }
michael@0 3612 else {
michael@0 3613 for (let tooltipWindow = tooltipNode.ownerDocument.defaultView;
michael@0 3614 tooltipWindow != tooltipWindow.parent;
michael@0 3615 tooltipWindow = tooltipWindow.parent) {
michael@0 3616 if (tooltipWindow == aWebProgress.DOMWindow) {
michael@0 3617 pageTooltip.hidePopup();
michael@0 3618 break;
michael@0 3619 }
michael@0 3620 }
michael@0 3621 }
michael@0 3622 }
michael@0 3623
michael@0 3624 let browser = gBrowser.selectedBrowser;
michael@0 3625
michael@0 3626 // Disable menu entries for images, enable otherwise
michael@0 3627 if (browser.documentContentType && mimeTypeIsTextBased(browser.documentContentType))
michael@0 3628 this.isImage.removeAttribute('disabled');
michael@0 3629 else
michael@0 3630 this.isImage.setAttribute('disabled', 'true');
michael@0 3631
michael@0 3632 this.hideOverLinkImmediately = true;
michael@0 3633 this.setOverLink("", null);
michael@0 3634 this.hideOverLinkImmediately = false;
michael@0 3635
michael@0 3636 // We should probably not do this if the value has changed since the user
michael@0 3637 // searched
michael@0 3638 // Update urlbar only if a new page was loaded on the primary content area
michael@0 3639 // Do not update urlbar if there was a subframe navigation
michael@0 3640
michael@0 3641 if (aWebProgress.isTopLevel) {
michael@0 3642 if ((location == "about:blank" && (gMultiProcessBrowser || !content.opener)) ||
michael@0 3643 location == "") { // Second condition is for new tabs, otherwise
michael@0 3644 // reload function is enabled until tab is refreshed.
michael@0 3645 this.reloadCommand.setAttribute("disabled", "true");
michael@0 3646 } else {
michael@0 3647 this.reloadCommand.removeAttribute("disabled");
michael@0 3648 }
michael@0 3649
michael@0 3650 if (gURLBar) {
michael@0 3651 URLBarSetURI(aLocationURI);
michael@0 3652
michael@0 3653 BookmarkingUI.onLocationChange();
michael@0 3654 SocialUI.updateState();
michael@0 3655 }
michael@0 3656
michael@0 3657 // Utility functions for disabling find
michael@0 3658 var shouldDisableFind = function shouldDisableFind(aDocument) {
michael@0 3659 let docElt = aDocument.documentElement;
michael@0 3660 return docElt && docElt.getAttribute("disablefastfind") == "true";
michael@0 3661 }
michael@0 3662
michael@0 3663 var disableFindCommands = function disableFindCommands(aDisable) {
michael@0 3664 let findCommands = [document.getElementById("cmd_find"),
michael@0 3665 document.getElementById("cmd_findAgain"),
michael@0 3666 document.getElementById("cmd_findPrevious")];
michael@0 3667 for (let elt of findCommands) {
michael@0 3668 if (aDisable)
michael@0 3669 elt.setAttribute("disabled", "true");
michael@0 3670 else
michael@0 3671 elt.removeAttribute("disabled");
michael@0 3672 }
michael@0 3673 }
michael@0 3674
michael@0 3675 var onContentRSChange = function onContentRSChange(e) {
michael@0 3676 if (e.target.readyState != "interactive" && e.target.readyState != "complete")
michael@0 3677 return;
michael@0 3678
michael@0 3679 e.target.removeEventListener("readystatechange", onContentRSChange);
michael@0 3680 disableFindCommands(shouldDisableFind(e.target));
michael@0 3681 }
michael@0 3682
michael@0 3683 // Disable find commands in documents that ask for them to be disabled.
michael@0 3684 if (!gMultiProcessBrowser && aLocationURI &&
michael@0 3685 (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) {
michael@0 3686 // Don't need to re-enable/disable find commands for same-document location changes
michael@0 3687 // (e.g. the replaceStates in about:addons)
michael@0 3688 if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
michael@0 3689 if (content.document.readyState == "interactive" || content.document.readyState == "complete")
michael@0 3690 disableFindCommands(shouldDisableFind(content.document));
michael@0 3691 else {
michael@0 3692 content.document.addEventListener("readystatechange", onContentRSChange);
michael@0 3693 }
michael@0 3694 }
michael@0 3695 } else
michael@0 3696 disableFindCommands(false);
michael@0 3697
michael@0 3698 // Try not to instantiate gCustomizeMode as much as possible,
michael@0 3699 // so don't use CustomizeMode.jsm to check for URI or customizing.
michael@0 3700 let customizingURI = "about:customizing";
michael@0 3701 if (location == customizingURI) {
michael@0 3702 gCustomizeMode.enter();
michael@0 3703 } else if (location != customizingURI &&
michael@0 3704 (CustomizationHandler.isEnteringCustomizeMode ||
michael@0 3705 CustomizationHandler.isCustomizing())) {
michael@0 3706 gCustomizeMode.exit();
michael@0 3707 }
michael@0 3708 }
michael@0 3709 UpdateBackForwardCommands(gBrowser.webNavigation);
michael@0 3710
michael@0 3711 gGestureSupport.restoreRotationState();
michael@0 3712
michael@0 3713 // See bug 358202, when tabs are switched during a drag operation,
michael@0 3714 // timers don't fire on windows (bug 203573)
michael@0 3715 if (aRequest)
michael@0 3716 setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0);
michael@0 3717 else
michael@0 3718 this.asyncUpdateUI();
michael@0 3719
michael@0 3720 #ifdef MOZ_CRASHREPORTER
michael@0 3721 if (aLocationURI) {
michael@0 3722 let uri = aLocationURI.clone();
michael@0 3723 try {
michael@0 3724 // If the current URI contains a username/password, remove it.
michael@0 3725 uri.userPass = "";
michael@0 3726 } catch (ex) { /* Ignore failures on about: URIs. */ }
michael@0 3727
michael@0 3728 try {
michael@0 3729 gCrashReporter.annotateCrashReport("URL", uri.spec);
michael@0 3730 } catch (ex if ex.result == Components.results.NS_ERROR_NOT_INITIALIZED) {
michael@0 3731 // Don't make noise when the crash reporter is built but not enabled.
michael@0 3732 }
michael@0 3733 }
michael@0 3734 #endif
michael@0 3735 },
michael@0 3736
michael@0 3737 asyncUpdateUI: function () {
michael@0 3738 FeedHandler.updateFeeds();
michael@0 3739 },
michael@0 3740
michael@0 3741 // Left here for add-on compatibility, see bug 752434
michael@0 3742 hideChromeForLocation: function() {},
michael@0 3743
michael@0 3744 onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
michael@0 3745 this.status = aMessage;
michael@0 3746 this.updateStatusField();
michael@0 3747 },
michael@0 3748
michael@0 3749 // Properties used to cache security state used to update the UI
michael@0 3750 _state: null,
michael@0 3751 _lastLocation: null,
michael@0 3752
michael@0 3753 onSecurityChange: function (aWebProgress, aRequest, aState) {
michael@0 3754 // Don't need to do anything if the data we use to update the UI hasn't
michael@0 3755 // changed
michael@0 3756 let uri = gBrowser.currentURI;
michael@0 3757 let spec = uri.spec;
michael@0 3758 if (this._state == aState &&
michael@0 3759 this._lastLocation == spec)
michael@0 3760 return;
michael@0 3761 this._state = aState;
michael@0 3762 this._lastLocation = spec;
michael@0 3763
michael@0 3764 // aState is defined as a bitmask that may be extended in the future.
michael@0 3765 // We filter out any unknown bits before testing for known values.
michael@0 3766 const wpl = Components.interfaces.nsIWebProgressListener;
michael@0 3767 const wpl_security_bits = wpl.STATE_IS_SECURE |
michael@0 3768 wpl.STATE_IS_BROKEN |
michael@0 3769 wpl.STATE_IS_INSECURE;
michael@0 3770 var level;
michael@0 3771
michael@0 3772 switch (this._state & wpl_security_bits) {
michael@0 3773 case wpl.STATE_IS_SECURE:
michael@0 3774 level = "high";
michael@0 3775 break;
michael@0 3776 case wpl.STATE_IS_BROKEN:
michael@0 3777 level = "broken";
michael@0 3778 break;
michael@0 3779 }
michael@0 3780
michael@0 3781 if (level) {
michael@0 3782 // We don't style the Location Bar based on the the 'level' attribute
michael@0 3783 // anymore, but still set it for third-party themes.
michael@0 3784 if (gURLBar)
michael@0 3785 gURLBar.setAttribute("level", level);
michael@0 3786 } else {
michael@0 3787 if (gURLBar)
michael@0 3788 gURLBar.removeAttribute("level");
michael@0 3789 }
michael@0 3790
michael@0 3791 try {
michael@0 3792 uri = Services.uriFixup.createExposableURI(uri);
michael@0 3793 } catch (e) {}
michael@0 3794 gIdentityHandler.checkIdentity(this._state, uri);
michael@0 3795 },
michael@0 3796
michael@0 3797 // simulate all change notifications after switching tabs
michael@0 3798 onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
michael@0 3799 if (FullZoom.updateBackgroundTabs)
michael@0 3800 FullZoom.onLocationChange(gBrowser.currentURI, true);
michael@0 3801 var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
michael@0 3802 var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
michael@0 3803 // use a pseudo-object instead of a (potentially nonexistent) channel for getting
michael@0 3804 // a correct error message - and make sure that the UI is always either in
michael@0 3805 // loading (STATE_START) or done (STATE_STOP) mode
michael@0 3806 this.onStateChange(
michael@0 3807 gBrowser.webProgress,
michael@0 3808 { URI: gBrowser.currentURI },
michael@0 3809 loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START,
michael@0 3810 aStatus
michael@0 3811 );
michael@0 3812 // status message and progress value are undefined if we're done with loading
michael@0 3813 if (loadingDone)
michael@0 3814 return;
michael@0 3815 this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
michael@0 3816 }
michael@0 3817 };
michael@0 3818
michael@0 3819 var LinkTargetDisplay = {
michael@0 3820 get DELAY_SHOW() {
michael@0 3821 delete this.DELAY_SHOW;
michael@0 3822 return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay");
michael@0 3823 },
michael@0 3824
michael@0 3825 DELAY_HIDE: 250,
michael@0 3826 _timer: 0,
michael@0 3827
michael@0 3828 get _isVisible () XULBrowserWindow.statusTextField.label != "",
michael@0 3829
michael@0 3830 update: function () {
michael@0 3831 clearTimeout(this._timer);
michael@0 3832 window.removeEventListener("mousemove", this, true);
michael@0 3833
michael@0 3834 if (!XULBrowserWindow.overLink) {
michael@0 3835 if (XULBrowserWindow.hideOverLinkImmediately)
michael@0 3836 this._hide();
michael@0 3837 else
michael@0 3838 this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
michael@0 3839 return;
michael@0 3840 }
michael@0 3841
michael@0 3842 if (this._isVisible) {
michael@0 3843 XULBrowserWindow.updateStatusField();
michael@0 3844 } else {
michael@0 3845 // Let the display appear when the mouse doesn't move within the delay
michael@0 3846 this._showDelayed();
michael@0 3847 window.addEventListener("mousemove", this, true);
michael@0 3848 }
michael@0 3849 },
michael@0 3850
michael@0 3851 handleEvent: function (event) {
michael@0 3852 switch (event.type) {
michael@0 3853 case "mousemove":
michael@0 3854 // Restart the delay since the mouse was moved
michael@0 3855 clearTimeout(this._timer);
michael@0 3856 this._showDelayed();
michael@0 3857 break;
michael@0 3858 }
michael@0 3859 },
michael@0 3860
michael@0 3861 _showDelayed: function () {
michael@0 3862 this._timer = setTimeout(function (self) {
michael@0 3863 XULBrowserWindow.updateStatusField();
michael@0 3864 window.removeEventListener("mousemove", self, true);
michael@0 3865 }, this.DELAY_SHOW, this);
michael@0 3866 },
michael@0 3867
michael@0 3868 _hide: function () {
michael@0 3869 clearTimeout(this._timer);
michael@0 3870
michael@0 3871 XULBrowserWindow.updateStatusField();
michael@0 3872 }
michael@0 3873 };
michael@0 3874
michael@0 3875 var CombinedStopReload = {
michael@0 3876 init: function () {
michael@0 3877 if (this._initialized)
michael@0 3878 return;
michael@0 3879
michael@0 3880 let reload = document.getElementById("urlbar-reload-button");
michael@0 3881 let stop = document.getElementById("urlbar-stop-button");
michael@0 3882 if (!stop || !reload || reload.nextSibling != stop)
michael@0 3883 return;
michael@0 3884
michael@0 3885 this._initialized = true;
michael@0 3886 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
michael@0 3887 reload.setAttribute("displaystop", "true");
michael@0 3888 stop.addEventListener("click", this, false);
michael@0 3889 this.reload = reload;
michael@0 3890 this.stop = stop;
michael@0 3891 },
michael@0 3892
michael@0 3893 uninit: function () {
michael@0 3894 if (!this._initialized)
michael@0 3895 return;
michael@0 3896
michael@0 3897 this._cancelTransition();
michael@0 3898 this._initialized = false;
michael@0 3899 this.stop.removeEventListener("click", this, false);
michael@0 3900 this.reload = null;
michael@0 3901 this.stop = null;
michael@0 3902 },
michael@0 3903
michael@0 3904 handleEvent: function (event) {
michael@0 3905 // the only event we listen to is "click" on the stop button
michael@0 3906 if (event.button == 0 &&
michael@0 3907 !this.stop.disabled)
michael@0 3908 this._stopClicked = true;
michael@0 3909 },
michael@0 3910
michael@0 3911 switchToStop: function () {
michael@0 3912 if (!this._initialized)
michael@0 3913 return;
michael@0 3914
michael@0 3915 this._cancelTransition();
michael@0 3916 this.reload.setAttribute("displaystop", "true");
michael@0 3917 },
michael@0 3918
michael@0 3919 switchToReload: function (aDelay) {
michael@0 3920 if (!this._initialized)
michael@0 3921 return;
michael@0 3922
michael@0 3923 this.reload.removeAttribute("displaystop");
michael@0 3924
michael@0 3925 if (!aDelay || this._stopClicked) {
michael@0 3926 this._stopClicked = false;
michael@0 3927 this._cancelTransition();
michael@0 3928 this.reload.disabled = XULBrowserWindow.reloadCommand
michael@0 3929 .getAttribute("disabled") == "true";
michael@0 3930 return;
michael@0 3931 }
michael@0 3932
michael@0 3933 if (this._timer)
michael@0 3934 return;
michael@0 3935
michael@0 3936 // Temporarily disable the reload button to prevent the user from
michael@0 3937 // accidentally reloading the page when intending to click the stop button
michael@0 3938 this.reload.disabled = true;
michael@0 3939 this._timer = setTimeout(function (self) {
michael@0 3940 self._timer = 0;
michael@0 3941 self.reload.disabled = XULBrowserWindow.reloadCommand
michael@0 3942 .getAttribute("disabled") == "true";
michael@0 3943 }, 650, this);
michael@0 3944 },
michael@0 3945
michael@0 3946 _cancelTransition: function () {
michael@0 3947 if (this._timer) {
michael@0 3948 clearTimeout(this._timer);
michael@0 3949 this._timer = 0;
michael@0 3950 }
michael@0 3951 }
michael@0 3952 };
michael@0 3953
michael@0 3954 var TabsProgressListener = {
michael@0 3955 onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
michael@0 3956 // Collect telemetry data about tab load times.
michael@0 3957 if (aWebProgress.isTopLevel) {
michael@0 3958 if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
michael@0 3959 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
michael@0 3960 TelemetryStopwatch.start("FX_PAGE_LOAD_MS", aBrowser);
michael@0 3961 Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true);
michael@0 3962 } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
michael@0 3963 TelemetryStopwatch.finish("FX_PAGE_LOAD_MS", aBrowser);
michael@0 3964 }
michael@0 3965 } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
michael@0 3966 aStatus == Cr.NS_BINDING_ABORTED) {
michael@0 3967 TelemetryStopwatch.cancel("FX_PAGE_LOAD_MS", aBrowser);
michael@0 3968 }
michael@0 3969 }
michael@0 3970
michael@0 3971 // Attach a listener to watch for "click" events bubbling up from error
michael@0 3972 // pages and other similar pages (like about:newtab). This lets us fix bugs
michael@0 3973 // like 401575 which require error page UI to do privileged things, without
michael@0 3974 // letting error pages have any privilege themselves.
michael@0 3975 // We can't look for this during onLocationChange since at that point the
michael@0 3976 // document URI is not yet the about:-uri of the error page.
michael@0 3977
michael@0 3978 let isRemoteBrowser = aBrowser.isRemoteBrowser;
michael@0 3979 // We check isRemoteBrowser here to avoid requesting the doc CPOW
michael@0 3980 let doc = isRemoteBrowser ? null : aWebProgress.DOMWindow.document;
michael@0 3981
michael@0 3982 if (!isRemoteBrowser &&
michael@0 3983 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
michael@0 3984 Components.isSuccessCode(aStatus) &&
michael@0 3985 doc.documentURI.startsWith("about:") &&
michael@0 3986 !doc.documentURI.toLowerCase().startsWith("about:blank") &&
michael@0 3987 !doc.documentURI.toLowerCase().startsWith("about:home") &&
michael@0 3988 !doc.documentElement.hasAttribute("hasBrowserHandlers")) {
michael@0 3989 // STATE_STOP may be received twice for documents, thus store an
michael@0 3990 // attribute to ensure handling it just once.
michael@0 3991 doc.documentElement.setAttribute("hasBrowserHandlers", "true");
michael@0 3992 aBrowser.addEventListener("click", BrowserOnClick, true);
michael@0 3993 aBrowser.addEventListener("pagehide", function onPageHide(event) {
michael@0 3994 if (event.target.defaultView.frameElement)
michael@0 3995 return;
michael@0 3996 aBrowser.removeEventListener("click", BrowserOnClick, true);
michael@0 3997 aBrowser.removeEventListener("pagehide", onPageHide, true);
michael@0 3998 if (event.target.documentElement)
michael@0 3999 event.target.documentElement.removeAttribute("hasBrowserHandlers");
michael@0 4000 }, true);
michael@0 4001
michael@0 4002 #ifdef MOZ_CRASHREPORTER
michael@0 4003 if (doc.documentURI.startsWith("about:tabcrashed"))
michael@0 4004 TabCrashReporter.onAboutTabCrashedLoad(aBrowser);
michael@0 4005 #endif
michael@0 4006 }
michael@0 4007 },
michael@0 4008
michael@0 4009 onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI,
michael@0 4010 aFlags) {
michael@0 4011 // Filter out location changes caused by anchor navigation
michael@0 4012 // or history.push/pop/replaceState.
michael@0 4013 if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
michael@0 4014 return;
michael@0 4015
michael@0 4016 // Filter out location changes in sub documents.
michael@0 4017 if (!aWebProgress.isTopLevel)
michael@0 4018 return;
michael@0 4019
michael@0 4020 // Only need to call locationChange if the PopupNotifications object
michael@0 4021 // for this window has already been initialized (i.e. its getter no
michael@0 4022 // longer exists)
michael@0 4023 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get)
michael@0 4024 PopupNotifications.locationChange(aBrowser);
michael@0 4025
michael@0 4026 gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
michael@0 4027
michael@0 4028 FullZoom.onLocationChange(aLocationURI, false, aBrowser);
michael@0 4029 },
michael@0 4030
michael@0 4031 onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) {
michael@0 4032 if (gPrefService.getBoolPref("accessibility.blockautorefresh")) {
michael@0 4033 let brandBundle = document.getElementById("bundle_brand");
michael@0 4034 let brandShortName = brandBundle.getString("brandShortName");
michael@0 4035 let refreshButtonText =
michael@0 4036 gNavigatorBundle.getString("refreshBlocked.goButton");
michael@0 4037 let refreshButtonAccesskey =
michael@0 4038 gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
michael@0 4039 let message =
michael@0 4040 gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel"
michael@0 4041 : "refreshBlocked.redirectLabel",
michael@0 4042 [brandShortName]);
michael@0 4043 let docShell = aWebProgress.DOMWindow
michael@0 4044 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 4045 .getInterface(Ci.nsIWebNavigation)
michael@0 4046 .QueryInterface(Ci.nsIDocShell);
michael@0 4047 let notificationBox = gBrowser.getNotificationBox(aBrowser);
michael@0 4048 let notification = notificationBox.getNotificationWithValue("refresh-blocked");
michael@0 4049 if (notification) {
michael@0 4050 notification.label = message;
michael@0 4051 notification.refreshURI = aURI;
michael@0 4052 notification.delay = aDelay;
michael@0 4053 notification.docShell = docShell;
michael@0 4054 } else {
michael@0 4055 let buttons = [{
michael@0 4056 label: refreshButtonText,
michael@0 4057 accessKey: refreshButtonAccesskey,
michael@0 4058 callback: function (aNotification, aButton) {
michael@0 4059 var refreshURI = aNotification.docShell
michael@0 4060 .QueryInterface(Ci.nsIRefreshURI);
michael@0 4061 refreshURI.forceRefreshURI(aNotification.refreshURI,
michael@0 4062 aNotification.delay, true);
michael@0 4063 }
michael@0 4064 }];
michael@0 4065 notification =
michael@0 4066 notificationBox.appendNotification(message, "refresh-blocked",
michael@0 4067 "chrome://browser/skin/Info.png",
michael@0 4068 notificationBox.PRIORITY_INFO_MEDIUM,
michael@0 4069 buttons);
michael@0 4070 notification.refreshURI = aURI;
michael@0 4071 notification.delay = aDelay;
michael@0 4072 notification.docShell = docShell;
michael@0 4073 }
michael@0 4074 return false;
michael@0 4075 }
michael@0 4076 return true;
michael@0 4077 }
michael@0 4078 }
michael@0 4079
michael@0 4080 function nsBrowserAccess() { }
michael@0 4081
michael@0 4082 nsBrowserAccess.prototype = {
michael@0 4083 QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
michael@0 4084
michael@0 4085 _openURIInNewTab: function(aURI, aOpener, aIsExternal) {
michael@0 4086 let win, needToFocusWin;
michael@0 4087
michael@0 4088 // try the current window. if we're in a popup, fall back on the most recent browser window
michael@0 4089 if (window.toolbar.visible)
michael@0 4090 win = window;
michael@0 4091 else {
michael@0 4092 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);
michael@0 4093 win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate});
michael@0 4094 needToFocusWin = true;
michael@0 4095 }
michael@0 4096
michael@0 4097 if (!win) {
michael@0 4098 // we couldn't find a suitable window, a new one needs to be opened.
michael@0 4099 return null;
michael@0 4100 }
michael@0 4101
michael@0 4102 if (aIsExternal && (!aURI || aURI.spec == "about:blank")) {
michael@0 4103 win.BrowserOpenTab(); // this also focuses the location bar
michael@0 4104 win.focus();
michael@0 4105 return win.gBrowser.selectedBrowser;
michael@0 4106 }
michael@0 4107
michael@0 4108 let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
michael@0 4109 let referrer = aOpener ? makeURI(aOpener.location.href) : null;
michael@0 4110
michael@0 4111 let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
michael@0 4112 referrerURI: referrer,
michael@0 4113 fromExternal: aIsExternal,
michael@0 4114 inBackground: loadInBackground});
michael@0 4115 let browser = win.gBrowser.getBrowserForTab(tab);
michael@0 4116
michael@0 4117 if (needToFocusWin || (!loadInBackground && aIsExternal))
michael@0 4118 win.focus();
michael@0 4119
michael@0 4120 return browser;
michael@0 4121 },
michael@0 4122
michael@0 4123 openURI: function (aURI, aOpener, aWhere, aContext) {
michael@0 4124 var newWindow = null;
michael@0 4125 var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
michael@0 4126
michael@0 4127 if (isExternal && aURI && aURI.schemeIs("chrome")) {
michael@0 4128 dump("use -chrome command-line option to load external chrome urls\n");
michael@0 4129 return null;
michael@0 4130 }
michael@0 4131
michael@0 4132 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
michael@0 4133 if (isExternal &&
michael@0 4134 gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external"))
michael@0 4135 aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external");
michael@0 4136 else
michael@0 4137 aWhere = gPrefService.getIntPref("browser.link.open_newwindow");
michael@0 4138 }
michael@0 4139 switch (aWhere) {
michael@0 4140 case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
michael@0 4141 // FIXME: Bug 408379. So how come this doesn't send the
michael@0 4142 // referrer like the other loads do?
michael@0 4143 var url = aURI ? aURI.spec : "about:blank";
michael@0 4144 // Pass all params to openDialog to ensure that "url" isn't passed through
michael@0 4145 // loadOneOrMoreURIs, which splits based on "|"
michael@0 4146 newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
michael@0 4147 break;
michael@0 4148 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
michael@0 4149 let browser = this._openURIInNewTab(aURI, aOpener, isExternal);
michael@0 4150 if (browser)
michael@0 4151 newWindow = browser.contentWindow;
michael@0 4152 break;
michael@0 4153 default : // OPEN_CURRENTWINDOW or an illegal value
michael@0 4154 newWindow = content;
michael@0 4155 if (aURI) {
michael@0 4156 let referrer = aOpener ? makeURI(aOpener.location.href) : null;
michael@0 4157 let loadflags = isExternal ?
michael@0 4158 Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
michael@0 4159 Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
michael@0 4160 gBrowser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
michael@0 4161 }
michael@0 4162 if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
michael@0 4163 window.focus();
michael@0 4164 }
michael@0 4165 return newWindow;
michael@0 4166 },
michael@0 4167
michael@0 4168 openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) {
michael@0 4169 if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
michael@0 4170 dump("Error: openURIInFrame can only open in new tabs");
michael@0 4171 return null;
michael@0 4172 }
michael@0 4173
michael@0 4174 var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
michael@0 4175 let browser = this._openURIInNewTab(aURI, aOpener, isExternal);
michael@0 4176 if (browser)
michael@0 4177 return browser.QueryInterface(Ci.nsIFrameLoaderOwner);
michael@0 4178
michael@0 4179 return null;
michael@0 4180 },
michael@0 4181
michael@0 4182 isTabContentWindow: function (aWindow) {
michael@0 4183 return gBrowser.browsers.some(function (browser) browser.contentWindow == aWindow);
michael@0 4184 },
michael@0 4185
michael@0 4186 get contentWindow() {
michael@0 4187 return gBrowser.contentWindow;
michael@0 4188 }
michael@0 4189 }
michael@0 4190
michael@0 4191 function getTogglableToolbars() {
michael@0 4192 let toolbarNodes = Array.slice(gNavToolbox.childNodes);
michael@0 4193 toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars);
michael@0 4194 toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname"));
michael@0 4195 return toolbarNodes;
michael@0 4196 }
michael@0 4197
michael@0 4198 function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
michael@0 4199 var popup = aEvent.target;
michael@0 4200 if (popup != aEvent.currentTarget)
michael@0 4201 return;
michael@0 4202
michael@0 4203 // Empty the menu
michael@0 4204 for (var i = popup.childNodes.length-1; i >= 0; --i) {
michael@0 4205 var deadItem = popup.childNodes[i];
michael@0 4206 if (deadItem.hasAttribute("toolbarId"))
michael@0 4207 popup.removeChild(deadItem);
michael@0 4208 }
michael@0 4209
michael@0 4210 var firstMenuItem = aInsertPoint || popup.firstChild;
michael@0 4211
michael@0 4212 let toolbarNodes = getTogglableToolbars();
michael@0 4213
michael@0 4214 for (let toolbar of toolbarNodes) {
michael@0 4215 let menuItem = document.createElement("menuitem");
michael@0 4216 let hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
michael@0 4217 "autohide" : "collapsed";
michael@0 4218 menuItem.setAttribute("id", "toggle_" + toolbar.id);
michael@0 4219 menuItem.setAttribute("toolbarId", toolbar.id);
michael@0 4220 menuItem.setAttribute("type", "checkbox");
michael@0 4221 menuItem.setAttribute("label", toolbar.getAttribute("toolbarname"));
michael@0 4222 menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true");
michael@0 4223 menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
michael@0 4224 if (popup.id != "toolbar-context-menu")
michael@0 4225 menuItem.setAttribute("key", toolbar.getAttribute("key"));
michael@0 4226
michael@0 4227 popup.insertBefore(menuItem, firstMenuItem);
michael@0 4228
michael@0 4229 menuItem.addEventListener("command", onViewToolbarCommand, false);
michael@0 4230 }
michael@0 4231
michael@0 4232
michael@0 4233 let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
michael@0 4234 let removeFromToolbar = popup.querySelector(".customize-context-removeFromToolbar");
michael@0 4235 // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
michael@0 4236 if (!moveToPanel || !removeFromToolbar) {
michael@0 4237 return;
michael@0 4238 }
michael@0 4239
michael@0 4240 // triggerNode can be a nested child element of a toolbaritem.
michael@0 4241 let toolbarItem = popup.triggerNode;
michael@0 4242
michael@0 4243 if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") {
michael@0 4244 toolbarItem = toolbarItem.firstChild;
michael@0 4245 } else if (toolbarItem && toolbarItem.localName != "toolbar") {
michael@0 4246 while (toolbarItem && toolbarItem.parentNode) {
michael@0 4247 let parent = toolbarItem.parentNode;
michael@0 4248 if ((parent.classList && parent.classList.contains("customization-target")) ||
michael@0 4249 parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
michael@0 4250 parent.localName == "toolbarpaletteitem" ||
michael@0 4251 parent.localName == "toolbar")
michael@0 4252 break;
michael@0 4253 toolbarItem = parent;
michael@0 4254 }
michael@0 4255 } else {
michael@0 4256 toolbarItem = null;
michael@0 4257 }
michael@0 4258
michael@0 4259 // Right-clicking on an empty part of the tabstrip will exit
michael@0 4260 // the above loop with toolbarItem being the xul:document.
michael@0 4261 // That has no parentNode, and we should disable the items in
michael@0 4262 // this case.
michael@0 4263 let movable = toolbarItem && toolbarItem.parentNode &&
michael@0 4264 CustomizableUI.isWidgetRemovable(toolbarItem);
michael@0 4265 if (movable) {
michael@0 4266 moveToPanel.removeAttribute("disabled");
michael@0 4267 removeFromToolbar.removeAttribute("disabled");
michael@0 4268 } else {
michael@0 4269 moveToPanel.setAttribute("disabled", true);
michael@0 4270 removeFromToolbar.setAttribute("disabled", true);
michael@0 4271 }
michael@0 4272 }
michael@0 4273
michael@0 4274 function onViewToolbarCommand(aEvent) {
michael@0 4275 var toolbarId = aEvent.originalTarget.getAttribute("toolbarId");
michael@0 4276 var isVisible = aEvent.originalTarget.getAttribute("checked") == "true";
michael@0 4277 CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
michael@0 4278 }
michael@0 4279
michael@0 4280 function setToolbarVisibility(toolbar, isVisible, persist=true) {
michael@0 4281 let hidingAttribute;
michael@0 4282 if (toolbar.getAttribute("type") == "menubar") {
michael@0 4283 hidingAttribute = "autohide";
michael@0 4284 #ifdef MOZ_WIDGET_GTK
michael@0 4285 Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible);
michael@0 4286 #endif
michael@0 4287 } else {
michael@0 4288 hidingAttribute = "collapsed";
michael@0 4289 }
michael@0 4290
michael@0 4291 toolbar.setAttribute(hidingAttribute, !isVisible);
michael@0 4292 if (persist) {
michael@0 4293 document.persist(toolbar.id, hidingAttribute);
michael@0 4294 }
michael@0 4295
michael@0 4296 let eventParams = {
michael@0 4297 detail: {
michael@0 4298 visible: isVisible
michael@0 4299 },
michael@0 4300 bubbles: true
michael@0 4301 };
michael@0 4302 let event = new CustomEvent("toolbarvisibilitychange", eventParams);
michael@0 4303 toolbar.dispatchEvent(event);
michael@0 4304
michael@0 4305 PlacesToolbarHelper.init();
michael@0 4306 BookmarkingUI.onToolbarVisibilityChange();
michael@0 4307 gBrowser.updateWindowResizers();
michael@0 4308 if (isVisible)
michael@0 4309 ToolbarIconColor.inferFromText();
michael@0 4310 }
michael@0 4311
michael@0 4312 var TabsInTitlebar = {
michael@0 4313 init: function () {
michael@0 4314 #ifdef CAN_DRAW_IN_TITLEBAR
michael@0 4315 this._readPref();
michael@0 4316 Services.prefs.addObserver(this._prefName, this, false);
michael@0 4317
michael@0 4318 // We need to update the appearance of the titlebar when the menu changes
michael@0 4319 // from the active to the inactive state. We can't, however, rely on
michael@0 4320 // DOMMenuBarInactive, because the menu fires this event and then removes
michael@0 4321 // the inactive attribute after an event-loop spin.
michael@0 4322 //
michael@0 4323 // Because updating the appearance involves sampling the heights and margins
michael@0 4324 // of various elements, it's important that the layout be more or less
michael@0 4325 // settled before updating the titlebar. So instead of listening to
michael@0 4326 // DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to
michael@0 4327 // watch the "invalid" attribute directly.
michael@0 4328 let menu = document.getElementById("toolbar-menubar");
michael@0 4329 this._menuObserver = new MutationObserver(this._onMenuMutate);
michael@0 4330 this._menuObserver.observe(menu, {attributes: true});
michael@0 4331
michael@0 4332 this.onAreaReset = function(aArea) {
michael@0 4333 if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
michael@0 4334 this._update(true);
michael@0 4335 };
michael@0 4336 this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) {
michael@0 4337 if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
michael@0 4338 this._update(true);
michael@0 4339 };
michael@0 4340 CustomizableUI.addListener(this);
michael@0 4341
michael@0 4342 this._initialized = true;
michael@0 4343 #endif
michael@0 4344 },
michael@0 4345
michael@0 4346 allowedBy: function (condition, allow) {
michael@0 4347 #ifdef CAN_DRAW_IN_TITLEBAR
michael@0 4348 if (allow) {
michael@0 4349 if (condition in this._disallowed) {
michael@0 4350 delete this._disallowed[condition];
michael@0 4351 this._update(true);
michael@0 4352 }
michael@0 4353 } else {
michael@0 4354 if (!(condition in this._disallowed)) {
michael@0 4355 this._disallowed[condition] = null;
michael@0 4356 this._update(true);
michael@0 4357 }
michael@0 4358 }
michael@0 4359 #endif
michael@0 4360 },
michael@0 4361
michael@0 4362 updateAppearance: function updateAppearance(aForce) {
michael@0 4363 #ifdef CAN_DRAW_IN_TITLEBAR
michael@0 4364 this._update(aForce);
michael@0 4365 #endif
michael@0 4366 },
michael@0 4367
michael@0 4368 get enabled() {
michael@0 4369 return document.documentElement.getAttribute("tabsintitlebar") == "true";
michael@0 4370 },
michael@0 4371
michael@0 4372 #ifdef CAN_DRAW_IN_TITLEBAR
michael@0 4373 observe: function (subject, topic, data) {
michael@0 4374 if (topic == "nsPref:changed")
michael@0 4375 this._readPref();
michael@0 4376 },
michael@0 4377
michael@0 4378 _onMenuMutate: function (aMutations) {
michael@0 4379 for (let mutation of aMutations) {
michael@0 4380 if (mutation.attributeName == "inactive" ||
michael@0 4381 mutation.attributeName == "autohide") {
michael@0 4382 TabsInTitlebar._update(true);
michael@0 4383 return;
michael@0 4384 }
michael@0 4385 }
michael@0 4386 },
michael@0 4387
michael@0 4388 _initialized: false,
michael@0 4389 _disallowed: {},
michael@0 4390 _prefName: "browser.tabs.drawInTitlebar",
michael@0 4391 _lastSizeMode: null,
michael@0 4392
michael@0 4393 _readPref: function () {
michael@0 4394 this.allowedBy("pref",
michael@0 4395 Services.prefs.getBoolPref(this._prefName));
michael@0 4396 },
michael@0 4397
michael@0 4398 _update: function (aForce=false) {
michael@0 4399 function $(id) document.getElementById(id);
michael@0 4400 function rect(ele) ele.getBoundingClientRect();
michael@0 4401 function verticalMargins(cstyle) parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop);
michael@0 4402
michael@0 4403 if (!this._initialized || window.fullScreen)
michael@0 4404 return;
michael@0 4405
michael@0 4406 let allowed = true;
michael@0 4407
michael@0 4408 if (!aForce) {
michael@0 4409 // _update is called on resize events, because the window is not ready
michael@0 4410 // after sizemode events. However, we only care about the event when the
michael@0 4411 // sizemode is different from the last time we updated the appearance of
michael@0 4412 // the tabs in the titlebar.
michael@0 4413 let sizemode = document.documentElement.getAttribute("sizemode");
michael@0 4414 if (this._lastSizeMode == sizemode) {
michael@0 4415 return;
michael@0 4416 }
michael@0 4417 this._lastSizeMode = sizemode;
michael@0 4418 }
michael@0 4419
michael@0 4420 for (let something in this._disallowed) {
michael@0 4421 allowed = false;
michael@0 4422 break;
michael@0 4423 }
michael@0 4424
michael@0 4425 let titlebar = $("titlebar");
michael@0 4426 let titlebarContent = $("titlebar-content");
michael@0 4427 let menubar = $("toolbar-menubar");
michael@0 4428
michael@0 4429 if (allowed) {
michael@0 4430 // We set the tabsintitlebar attribute first so that our CSS for
michael@0 4431 // tabsintitlebar manifests before we do our measurements.
michael@0 4432 document.documentElement.setAttribute("tabsintitlebar", "true");
michael@0 4433 updateTitlebarDisplay();
michael@0 4434
michael@0 4435 // Try to avoid reflows in this code by calculating dimensions first and
michael@0 4436 // then later set the properties affecting layout together in a batch.
michael@0 4437
michael@0 4438 // Get the full height of the tabs toolbar:
michael@0 4439 let tabsToolbar = $("TabsToolbar");
michael@0 4440 let fullTabsHeight = rect(tabsToolbar).height;
michael@0 4441 // Buttons first:
michael@0 4442 let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width;
michael@0 4443
michael@0 4444 #ifdef XP_MACOSX
michael@0 4445 let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
michael@0 4446 // No need to look up the menubar stuff on OS X:
michael@0 4447 let menuHeight = 0;
michael@0 4448 let fullMenuHeight = 0;
michael@0 4449 // Instead, look up the titlebar padding:
michael@0 4450 let titlebarPadding = parseInt(window.getComputedStyle(titlebar).paddingTop, 10);
michael@0 4451 #else
michael@0 4452 // Otherwise, get the height and margins separately for the menubar
michael@0 4453 let menuHeight = rect(menubar).height;
michael@0 4454 let menuStyles = window.getComputedStyle(menubar);
michael@0 4455 let fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
michael@0 4456 let tabsStyles = window.getComputedStyle(tabsToolbar);
michael@0 4457 fullTabsHeight += verticalMargins(tabsStyles);
michael@0 4458 #endif
michael@0 4459
michael@0 4460 // If the navbar overlaps the tabbar using negative margins, we need to take those into
michael@0 4461 // account so we don't overlap it
michael@0 4462 let navbarMarginTop = parseFloat(window.getComputedStyle($("nav-bar")).marginTop);
michael@0 4463 navbarMarginTop = Math.min(navbarMarginTop, 0);
michael@0 4464
michael@0 4465 // And get the height of what's in the titlebar:
michael@0 4466 let titlebarContentHeight = rect(titlebarContent).height;
michael@0 4467
michael@0 4468 // Begin setting CSS properties which will cause a reflow
michael@0 4469
michael@0 4470 // If the menubar is around (menuHeight is non-zero), try to adjust
michael@0 4471 // its full height (i.e. including margins) to match the titlebar,
michael@0 4472 // by changing the menubar's bottom padding
michael@0 4473 if (menuHeight) {
michael@0 4474 // Calculate the difference between the titlebar's height and that of the menubar
michael@0 4475 let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight;
michael@0 4476 let paddingBottom;
michael@0 4477 // The titlebar is bigger:
michael@0 4478 if (menuTitlebarDelta > 0) {
michael@0 4479 fullMenuHeight += menuTitlebarDelta;
michael@0 4480 // If there is already padding on the menubar, we need to add that
michael@0 4481 // to the difference so the total padding is correct:
michael@0 4482 if ((paddingBottom = menuStyles.paddingBottom)) {
michael@0 4483 menuTitlebarDelta += parseFloat(paddingBottom);
michael@0 4484 }
michael@0 4485 menubar.style.paddingBottom = menuTitlebarDelta + "px";
michael@0 4486 // The menubar is bigger, but has bottom padding we can remove:
michael@0 4487 } else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) {
michael@0 4488 let existingPadding = parseFloat(paddingBottom);
michael@0 4489 // menuTitlebarDelta is negative; work out what's left, but don't set negative padding:
michael@0 4490 let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta);
michael@0 4491 menubar.style.paddingBottom = desiredPadding + "px";
michael@0 4492 // We've changed the menu height now:
michael@0 4493 fullMenuHeight += desiredPadding - existingPadding;
michael@0 4494 }
michael@0 4495 }
michael@0 4496
michael@0 4497 // Next, we calculate how much we need to stretch the titlebar down to
michael@0 4498 // go all the way to the bottom of the tab strip, if necessary.
michael@0 4499 let tabAndMenuHeight = fullTabsHeight + fullMenuHeight;
michael@0 4500
michael@0 4501 if (tabAndMenuHeight > titlebarContentHeight) {
michael@0 4502 // We need to increase the titlebar content's outer height (ie including margins)
michael@0 4503 // to match the tab and menu height:
michael@0 4504 let extraMargin = tabAndMenuHeight - titlebarContentHeight;
michael@0 4505 // We need to reduce the height by the amount of navbar overlap
michael@0 4506 // (this value is 0 or negative):
michael@0 4507 extraMargin += navbarMarginTop;
michael@0 4508 // On non-OSX, we can just use bottom margin:
michael@0 4509 #ifndef XP_MACOSX
michael@0 4510 titlebarContent.style.marginBottom = extraMargin + "px";
michael@0 4511 #endif
michael@0 4512 titlebarContentHeight += extraMargin;
michael@0 4513 }
michael@0 4514
michael@0 4515 // Then we bring up the titlebar by the same amount, but we add any negative margin:
michael@0 4516 titlebar.style.marginBottom = "-" + titlebarContentHeight + "px";
michael@0 4517
michael@0 4518
michael@0 4519 // Finally, size the placeholders:
michael@0 4520 #ifdef XP_MACOSX
michael@0 4521 this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
michael@0 4522 #endif
michael@0 4523 this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);
michael@0 4524
michael@0 4525 if (!this._draghandles) {
michael@0 4526 this._draghandles = {};
michael@0 4527 let tmp = {};
michael@0 4528 Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
michael@0 4529
michael@0 4530 let mouseDownCheck = function () {
michael@0 4531 return !this._dragBindingAlive && TabsInTitlebar.enabled;
michael@0 4532 };
michael@0 4533
michael@0 4534 this._draghandles.tabsToolbar = new tmp.WindowDraggingElement(tabsToolbar);
michael@0 4535 this._draghandles.tabsToolbar.mouseDownCheck = mouseDownCheck;
michael@0 4536
michael@0 4537 this._draghandles.navToolbox = new tmp.WindowDraggingElement(gNavToolbox);
michael@0 4538 this._draghandles.navToolbox.mouseDownCheck = mouseDownCheck;
michael@0 4539 }
michael@0 4540 } else {
michael@0 4541 document.documentElement.removeAttribute("tabsintitlebar");
michael@0 4542 updateTitlebarDisplay();
michael@0 4543
michael@0 4544 #ifdef XP_MACOSX
michael@0 4545 let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
michael@0 4546 this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
michael@0 4547 #endif
michael@0 4548 // Reset the margins and padding that might have been modified:
michael@0 4549 titlebarContent.style.marginTop = "";
michael@0 4550 titlebarContent.style.marginBottom = "";
michael@0 4551 titlebar.style.marginBottom = "";
michael@0 4552 menubar.style.paddingBottom = "";
michael@0 4553 }
michael@0 4554
michael@0 4555 ToolbarIconColor.inferFromText();
michael@0 4556 },
michael@0 4557
michael@0 4558 _sizePlaceholder: function (type, width) {
michael@0 4559 Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"),
michael@0 4560 function (node) { node.width = width; });
michael@0 4561 },
michael@0 4562 #endif
michael@0 4563
michael@0 4564 uninit: function () {
michael@0 4565 #ifdef CAN_DRAW_IN_TITLEBAR
michael@0 4566 this._initialized = false;
michael@0 4567 Services.prefs.removeObserver(this._prefName, this);
michael@0 4568 this._menuObserver.disconnect();
michael@0 4569 CustomizableUI.removeListener(this);
michael@0 4570 #endif
michael@0 4571 }
michael@0 4572 };
michael@0 4573
michael@0 4574 #ifdef CAN_DRAW_IN_TITLEBAR
michael@0 4575 function updateTitlebarDisplay() {
michael@0 4576
michael@0 4577 #ifdef XP_MACOSX
michael@0 4578 // OS X and the other platforms differ enough to necessitate this kind of
michael@0 4579 // special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR,
michael@0 4580 // we draw in the OS X titlebar when putting the tabs up there. However, OS X
michael@0 4581 // also draws in the titlebar when a lightweight theme is applied, regardless
michael@0 4582 // of whether or not the tabs are drawn in the titlebar.
michael@0 4583 if (TabsInTitlebar.enabled) {
michael@0 4584 document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1");
michael@0 4585 document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
michael@0 4586 document.documentElement.removeAttribute("drawtitle");
michael@0 4587 } else {
michael@0 4588 // We set chromemargin-nonlwtheme to "" instead of removing it as a way of
michael@0 4589 // making sure that LightweightThemeConsumer doesn't take it upon itself to
michael@0 4590 // detect this value again if and when we do a lwtheme state change.
michael@0 4591 document.documentElement.setAttribute("chromemargin-nonlwtheme", "");
michael@0 4592 let isCustomizing = document.documentElement.hasAttribute("customizing");
michael@0 4593 let hasLWTheme = document.documentElement.hasAttribute("lwtheme");
michael@0 4594 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
michael@0 4595 if ((!hasLWTheme || isCustomizing) && !isPrivate) {
michael@0 4596 document.documentElement.removeAttribute("chromemargin");
michael@0 4597 }
michael@0 4598 document.documentElement.setAttribute("drawtitle", "true");
michael@0 4599 }
michael@0 4600
michael@0 4601 #else
michael@0 4602
michael@0 4603 if (TabsInTitlebar.enabled)
michael@0 4604 document.documentElement.setAttribute("chromemargin", "0,2,2,2");
michael@0 4605 else
michael@0 4606 document.documentElement.removeAttribute("chromemargin");
michael@0 4607 #endif
michael@0 4608 }
michael@0 4609 #endif
michael@0 4610
michael@0 4611 #ifdef CAN_DRAW_IN_TITLEBAR
michael@0 4612 function onTitlebarMaxClick() {
michael@0 4613 if (window.windowState == window.STATE_MAXIMIZED)
michael@0 4614 window.restore();
michael@0 4615 else
michael@0 4616 window.maximize();
michael@0 4617 }
michael@0 4618 #endif
michael@0 4619
michael@0 4620 function displaySecurityInfo()
michael@0 4621 {
michael@0 4622 BrowserPageInfo(null, "securityTab");
michael@0 4623 }
michael@0 4624
michael@0 4625 /**
michael@0 4626 * Opens or closes the sidebar identified by commandID.
michael@0 4627 *
michael@0 4628 * @param commandID a string identifying the sidebar to toggle; see the
michael@0 4629 * note below. (Optional if a sidebar is already open.)
michael@0 4630 * @param forceOpen boolean indicating whether the sidebar should be
michael@0 4631 * opened regardless of its current state (optional).
michael@0 4632 * @note
michael@0 4633 * We expect to find a xul:broadcaster element with the specified ID.
michael@0 4634 * The following attributes on that element may be used and/or modified:
michael@0 4635 * - id (required) the string to match commandID. The convention
michael@0 4636 * is to use this naming scheme: 'view<sidebar-name>Sidebar'.
michael@0 4637 * - sidebarurl (required) specifies the URL to load in this sidebar.
michael@0 4638 * - sidebartitle or label (in that order) specify the title to
michael@0 4639 * display on the sidebar.
michael@0 4640 * - checked indicates whether the sidebar is currently displayed.
michael@0 4641 * Note that toggleSidebar updates this attribute when
michael@0 4642 * it changes the sidebar's visibility.
michael@0 4643 * - group this attribute must be set to "sidebar".
michael@0 4644 */
michael@0 4645 function toggleSidebar(commandID, forceOpen) {
michael@0 4646
michael@0 4647 var sidebarBox = document.getElementById("sidebar-box");
michael@0 4648 if (!commandID)
michael@0 4649 commandID = sidebarBox.getAttribute("sidebarcommand");
michael@0 4650
michael@0 4651 var sidebarBroadcaster = document.getElementById(commandID);
michael@0 4652 var sidebar = document.getElementById("sidebar"); // xul:browser
michael@0 4653 var sidebarTitle = document.getElementById("sidebar-title");
michael@0 4654 var sidebarSplitter = document.getElementById("sidebar-splitter");
michael@0 4655
michael@0 4656 if (sidebarBroadcaster.getAttribute("checked") == "true") {
michael@0 4657 if (!forceOpen) {
michael@0 4658 // Replace the document currently displayed in the sidebar with about:blank
michael@0 4659 // so that we can free memory by unloading the page. We need to explicitly
michael@0 4660 // create a new content viewer because the old one doesn't get destroyed
michael@0 4661 // until about:blank has loaded (which does not happen as long as the
michael@0 4662 // element is hidden).
michael@0 4663 sidebar.setAttribute("src", "about:blank");
michael@0 4664 sidebar.docShell.createAboutBlankContentViewer(null);
michael@0 4665
michael@0 4666 sidebarBroadcaster.removeAttribute("checked");
michael@0 4667 sidebarBox.setAttribute("sidebarcommand", "");
michael@0 4668 sidebarTitle.value = "";
michael@0 4669 sidebarBox.hidden = true;
michael@0 4670 sidebarSplitter.hidden = true;
michael@0 4671 gBrowser.selectedBrowser.focus();
michael@0 4672 } else {
michael@0 4673 fireSidebarFocusedEvent();
michael@0 4674 }
michael@0 4675 return;
michael@0 4676 }
michael@0 4677
michael@0 4678 // now we need to show the specified sidebar
michael@0 4679
michael@0 4680 // ..but first update the 'checked' state of all sidebar broadcasters
michael@0 4681 var broadcasters = document.getElementsByAttribute("group", "sidebar");
michael@0 4682 for (let broadcaster of broadcasters) {
michael@0 4683 // skip elements that observe sidebar broadcasters and random
michael@0 4684 // other elements
michael@0 4685 if (broadcaster.localName != "broadcaster")
michael@0 4686 continue;
michael@0 4687
michael@0 4688 if (broadcaster != sidebarBroadcaster)
michael@0 4689 broadcaster.removeAttribute("checked");
michael@0 4690 else
michael@0 4691 sidebarBroadcaster.setAttribute("checked", "true");
michael@0 4692 }
michael@0 4693
michael@0 4694 sidebarBox.hidden = false;
michael@0 4695 sidebarSplitter.hidden = false;
michael@0 4696
michael@0 4697 var url = sidebarBroadcaster.getAttribute("sidebarurl");
michael@0 4698 var title = sidebarBroadcaster.getAttribute("sidebartitle");
michael@0 4699 if (!title)
michael@0 4700 title = sidebarBroadcaster.getAttribute("label");
michael@0 4701 sidebar.setAttribute("src", url); // kick off async load
michael@0 4702 sidebarBox.setAttribute("sidebarcommand", sidebarBroadcaster.id);
michael@0 4703 sidebarTitle.value = title;
michael@0 4704
michael@0 4705 // We set this attribute here in addition to setting it on the <browser>
michael@0 4706 // element itself, because the code in gBrowserInit.onUnload persists this
michael@0 4707 // attribute, not the "src" of the <browser id="sidebar">. The reason it
michael@0 4708 // does that is that we want to delay sidebar load a bit when a browser
michael@0 4709 // window opens. See delayedStartup().
michael@0 4710 sidebarBox.setAttribute("src", url);
michael@0 4711
michael@0 4712 if (sidebar.contentDocument.location.href != url)
michael@0 4713 sidebar.addEventListener("load", sidebarOnLoad, true);
michael@0 4714 else // older code handled this case, so we do it too
michael@0 4715 fireSidebarFocusedEvent();
michael@0 4716 }
michael@0 4717
michael@0 4718 function sidebarOnLoad(event) {
michael@0 4719 var sidebar = document.getElementById("sidebar");
michael@0 4720 sidebar.removeEventListener("load", sidebarOnLoad, true);
michael@0 4721 // We're handling the 'load' event before it bubbles up to the usual
michael@0 4722 // (non-capturing) event handlers. Let it bubble up before firing the
michael@0 4723 // SidebarFocused event.
michael@0 4724 setTimeout(fireSidebarFocusedEvent, 0);
michael@0 4725 }
michael@0 4726
michael@0 4727 /**
michael@0 4728 * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
michael@0 4729 * a chance to adjust focus as needed. An additional event is needed, because
michael@0 4730 * we don't want to focus the sidebar when it's opened on startup or in a new
michael@0 4731 * window, only when the user opens the sidebar.
michael@0 4732 */
michael@0 4733 function fireSidebarFocusedEvent() {
michael@0 4734 var sidebar = document.getElementById("sidebar");
michael@0 4735 var event = document.createEvent("Events");
michael@0 4736 event.initEvent("SidebarFocused", true, false);
michael@0 4737 sidebar.contentWindow.dispatchEvent(event);
michael@0 4738 }
michael@0 4739
michael@0 4740
michael@0 4741 var gHomeButton = {
michael@0 4742 prefDomain: "browser.startup.homepage",
michael@0 4743 observe: function (aSubject, aTopic, aPrefName)
michael@0 4744 {
michael@0 4745 if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
michael@0 4746 return;
michael@0 4747
michael@0 4748 this.updateTooltip();
michael@0 4749 },
michael@0 4750
michael@0 4751 updateTooltip: function (homeButton)
michael@0 4752 {
michael@0 4753 if (!homeButton)
michael@0 4754 homeButton = document.getElementById("home-button");
michael@0 4755 if (homeButton) {
michael@0 4756 var homePage = this.getHomePage();
michael@0 4757 homePage = homePage.replace(/\|/g,', ');
michael@0 4758 if (homePage.toLowerCase() == "about:home")
michael@0 4759 homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip"));
michael@0 4760 else
michael@0 4761 homeButton.setAttribute("tooltiptext", homePage);
michael@0 4762 }
michael@0 4763 },
michael@0 4764
michael@0 4765 getHomePage: function ()
michael@0 4766 {
michael@0 4767 var url;
michael@0 4768 try {
michael@0 4769 url = gPrefService.getComplexValue(this.prefDomain,
michael@0 4770 Components.interfaces.nsIPrefLocalizedString).data;
michael@0 4771 } catch (e) {
michael@0 4772 }
michael@0 4773
michael@0 4774 // use this if we can't find the pref
michael@0 4775 if (!url) {
michael@0 4776 var configBundle = Services.strings
michael@0 4777 .createBundle("chrome://branding/locale/browserconfig.properties");
michael@0 4778 url = configBundle.GetStringFromName(this.prefDomain);
michael@0 4779 }
michael@0 4780
michael@0 4781 return url;
michael@0 4782 },
michael@0 4783
michael@0 4784 updatePersonalToolbarStyle: function (homeButton)
michael@0 4785 {
michael@0 4786 if (!homeButton)
michael@0 4787 homeButton = document.getElementById("home-button");
michael@0 4788 if (homeButton)
michael@0 4789 homeButton.className = homeButton.parentNode.id == "PersonalToolbar"
michael@0 4790 || homeButton.parentNode.parentNode.id == "PersonalToolbar" ?
michael@0 4791 homeButton.className.replace("toolbarbutton-1", "bookmark-item") :
michael@0 4792 homeButton.className.replace("bookmark-item", "toolbarbutton-1");
michael@0 4793 },
michael@0 4794 };
michael@0 4795
michael@0 4796 const nodeToTooltipMap = {
michael@0 4797 "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
michael@0 4798 #ifdef XP_MACOSX
michael@0 4799 "print-button": "printButton.tooltip",
michael@0 4800 #endif
michael@0 4801 "new-window-button": "newWindowButton.tooltip",
michael@0 4802 "fullscreen-button": "fullscreenButton.tooltip",
michael@0 4803 "tabview-button": "tabviewButton.tooltip",
michael@0 4804 };
michael@0 4805 const nodeToShortcutMap = {
michael@0 4806 "bookmarks-menu-button": "manBookmarkKb",
michael@0 4807 #ifdef XP_MACOSX
michael@0 4808 "print-button": "printKb",
michael@0 4809 #endif
michael@0 4810 "new-window-button": "key_newNavigator",
michael@0 4811 "fullscreen-button": "key_fullScreen",
michael@0 4812 "tabview-button": "key_tabview",
michael@0 4813 };
michael@0 4814 const gDynamicTooltipCache = new Map();
michael@0 4815 function UpdateDynamicShortcutTooltipText(aTooltip) {
michael@0 4816 let nodeId = aTooltip.triggerNode.id;
michael@0 4817 if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
michael@0 4818 let strId = nodeToTooltipMap[nodeId];
michael@0 4819 let args = [];
michael@0 4820 if (nodeId in nodeToShortcutMap) {
michael@0 4821 let shortcutId = nodeToShortcutMap[nodeId];
michael@0 4822 let shortcut = document.getElementById(shortcutId);
michael@0 4823 if (shortcut) {
michael@0 4824 args.push(ShortcutUtils.prettifyShortcut(shortcut));
michael@0 4825 }
michael@0 4826 }
michael@0 4827 gDynamicTooltipCache.set(nodeId, gNavigatorBundle.getFormattedString(strId, args));
michael@0 4828 }
michael@0 4829 aTooltip.setAttribute("label", gDynamicTooltipCache.get(nodeId));
michael@0 4830 }
michael@0 4831
michael@0 4832 /**
michael@0 4833 * Gets the selected text in the active browser. Leading and trailing
michael@0 4834 * whitespace is removed, and consecutive whitespace is replaced by a single
michael@0 4835 * space. A maximum of 150 characters will be returned, regardless of the value
michael@0 4836 * of aCharLen.
michael@0 4837 *
michael@0 4838 * @param aCharLen
michael@0 4839 * The maximum number of characters to return.
michael@0 4840 */
michael@0 4841 function getBrowserSelection(aCharLen) {
michael@0 4842 // selections of more than 150 characters aren't useful
michael@0 4843 const kMaxSelectionLen = 150;
michael@0 4844 const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);
michael@0 4845
michael@0 4846 let [element, focusedWindow] = BrowserUtils.getFocusSync(document);
michael@0 4847 var selection = focusedWindow.getSelection().toString();
michael@0 4848 // try getting a selected text in text input.
michael@0 4849 if (!selection) {
michael@0 4850 var isOnTextInput = function isOnTextInput(elem) {
michael@0 4851 // we avoid to return a value if a selection is in password field.
michael@0 4852 // ref. bug 565717
michael@0 4853 return elem instanceof HTMLTextAreaElement ||
michael@0 4854 (elem instanceof HTMLInputElement && elem.mozIsTextField(true));
michael@0 4855 };
michael@0 4856
michael@0 4857 if (isOnTextInput(element)) {
michael@0 4858 selection = element.QueryInterface(Ci.nsIDOMNSEditableElement)
michael@0 4859 .editor.selection.toString();
michael@0 4860 }
michael@0 4861 }
michael@0 4862
michael@0 4863 if (selection) {
michael@0 4864 if (selection.length > charLen) {
michael@0 4865 // only use the first charLen important chars. see bug 221361
michael@0 4866 var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
michael@0 4867 pattern.test(selection);
michael@0 4868 selection = RegExp.lastMatch;
michael@0 4869 }
michael@0 4870
michael@0 4871 selection = selection.trim().replace(/\s+/g, " ");
michael@0 4872
michael@0 4873 if (selection.length > charLen)
michael@0 4874 selection = selection.substr(0, charLen);
michael@0 4875 }
michael@0 4876 return selection;
michael@0 4877 }
michael@0 4878
michael@0 4879 var gWebPanelURI;
michael@0 4880 function openWebPanel(aTitle, aURI)
michael@0 4881 {
michael@0 4882 // Ensure that the web panels sidebar is open.
michael@0 4883 toggleSidebar('viewWebPanelsSidebar', true);
michael@0 4884
michael@0 4885 // Set the title of the panel.
michael@0 4886 document.getElementById("sidebar-title").value = aTitle;
michael@0 4887
michael@0 4888 // Tell the Web Panels sidebar to load the bookmark.
michael@0 4889 var sidebar = document.getElementById("sidebar");
michael@0 4890 if (sidebar.docShell && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) {
michael@0 4891 sidebar.contentWindow.loadWebPanel(aURI);
michael@0 4892 if (gWebPanelURI) {
michael@0 4893 gWebPanelURI = "";
michael@0 4894 sidebar.removeEventListener("load", asyncOpenWebPanel, true);
michael@0 4895 }
michael@0 4896 }
michael@0 4897 else {
michael@0 4898 // The panel is still being constructed. Attach an onload handler.
michael@0 4899 if (!gWebPanelURI)
michael@0 4900 sidebar.addEventListener("load", asyncOpenWebPanel, true);
michael@0 4901 gWebPanelURI = aURI;
michael@0 4902 }
michael@0 4903 }
michael@0 4904
michael@0 4905 function asyncOpenWebPanel(event)
michael@0 4906 {
michael@0 4907 var sidebar = document.getElementById("sidebar");
michael@0 4908 if (gWebPanelURI && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser'))
michael@0 4909 sidebar.contentWindow.loadWebPanel(gWebPanelURI);
michael@0 4910 gWebPanelURI = "";
michael@0 4911 sidebar.removeEventListener("load", asyncOpenWebPanel, true);
michael@0 4912 }
michael@0 4913
michael@0 4914 /*
michael@0 4915 * - [ Dependencies ] ---------------------------------------------------------
michael@0 4916 * utilityOverlay.js:
michael@0 4917 * - gatherTextUnder
michael@0 4918 */
michael@0 4919
michael@0 4920 /**
michael@0 4921 * Extracts linkNode and href for the current click target.
michael@0 4922 *
michael@0 4923 * @param event
michael@0 4924 * The click event.
michael@0 4925 * @return [href, linkNode].
michael@0 4926 *
michael@0 4927 * @note linkNode will be null if the click wasn't on an anchor
michael@0 4928 * element (or XLink).
michael@0 4929 */
michael@0 4930 function hrefAndLinkNodeForClickEvent(event)
michael@0 4931 {
michael@0 4932 function isHTMLLink(aNode)
michael@0 4933 {
michael@0 4934 // Be consistent with what nsContextMenu.js does.
michael@0 4935 return ((aNode instanceof HTMLAnchorElement && aNode.href) ||
michael@0 4936 (aNode instanceof HTMLAreaElement && aNode.href) ||
michael@0 4937 aNode instanceof HTMLLinkElement);
michael@0 4938 }
michael@0 4939
michael@0 4940 let node = event.target;
michael@0 4941 while (node && !isHTMLLink(node)) {
michael@0 4942 node = node.parentNode;
michael@0 4943 }
michael@0 4944
michael@0 4945 if (node)
michael@0 4946 return [node.href, node];
michael@0 4947
michael@0 4948 // If there is no linkNode, try simple XLink.
michael@0 4949 let href, baseURI;
michael@0 4950 node = event.target;
michael@0 4951 while (node && !href) {
michael@0 4952 if (node.nodeType == Node.ELEMENT_NODE) {
michael@0 4953 href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
michael@0 4954 if (href)
michael@0 4955 baseURI = node.baseURI;
michael@0 4956 }
michael@0 4957 node = node.parentNode;
michael@0 4958 }
michael@0 4959
michael@0 4960 // In case of XLink, we don't return the node we got href from since
michael@0 4961 // callers expect <a>-like elements.
michael@0 4962 return [href ? makeURLAbsolute(baseURI, href) : null, null];
michael@0 4963 }
michael@0 4964
michael@0 4965 /**
michael@0 4966 * Called whenever the user clicks in the content area.
michael@0 4967 *
michael@0 4968 * @param event
michael@0 4969 * The click event.
michael@0 4970 * @param isPanelClick
michael@0 4971 * Whether the event comes from a web panel.
michael@0 4972 * @note default event is prevented if the click is handled.
michael@0 4973 */
michael@0 4974 function contentAreaClick(event, isPanelClick)
michael@0 4975 {
michael@0 4976 if (!event.isTrusted || event.defaultPrevented || event.button == 2)
michael@0 4977 return;
michael@0 4978
michael@0 4979 let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
michael@0 4980 if (!href) {
michael@0 4981 // Not a link, handle middle mouse navigation.
michael@0 4982 if (event.button == 1 &&
michael@0 4983 gPrefService.getBoolPref("middlemouse.contentLoadURL") &&
michael@0 4984 !gPrefService.getBoolPref("general.autoScroll")) {
michael@0 4985 middleMousePaste(event);
michael@0 4986 event.preventDefault();
michael@0 4987 }
michael@0 4988 return;
michael@0 4989 }
michael@0 4990
michael@0 4991 // This code only applies if we have a linkNode (i.e. clicks on real anchor
michael@0 4992 // elements, as opposed to XLink).
michael@0 4993 if (linkNode && event.button == 0 &&
michael@0 4994 !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
michael@0 4995 // A Web panel's links should target the main content area. Do this
michael@0 4996 // if no modifier keys are down and if there's no target or the target
michael@0 4997 // equals _main (the IE convention) or _content (the Mozilla convention).
michael@0 4998 let target = linkNode.target;
michael@0 4999 let mainTarget = !target || target == "_content" || target == "_main";
michael@0 5000 if (isPanelClick && mainTarget) {
michael@0 5001 // javascript and data links should be executed in the current browser.
michael@0 5002 if (linkNode.getAttribute("onclick") ||
michael@0 5003 href.startsWith("javascript:") ||
michael@0 5004 href.startsWith("data:"))
michael@0 5005 return;
michael@0 5006
michael@0 5007 try {
michael@0 5008 urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
michael@0 5009 }
michael@0 5010 catch(ex) {
michael@0 5011 // Prevent loading unsecure destinations.
michael@0 5012 event.preventDefault();
michael@0 5013 return;
michael@0 5014 }
michael@0 5015
michael@0 5016 loadURI(href, null, null, false);
michael@0 5017 event.preventDefault();
michael@0 5018 return;
michael@0 5019 }
michael@0 5020
michael@0 5021 if (linkNode.getAttribute("rel") == "sidebar") {
michael@0 5022 // This is the Opera convention for a special link that, when clicked,
michael@0 5023 // allows to add a sidebar panel. The link's title attribute contains
michael@0 5024 // the title that should be used for the sidebar panel.
michael@0 5025 PlacesUIUtils.showBookmarkDialog({ action: "add"
michael@0 5026 , type: "bookmark"
michael@0 5027 , uri: makeURI(href)
michael@0 5028 , title: linkNode.getAttribute("title")
michael@0 5029 , loadBookmarkInSidebar: true
michael@0 5030 , hiddenRows: [ "description"
michael@0 5031 , "location"
michael@0 5032 , "keyword" ]
michael@0 5033 }, window);
michael@0 5034 event.preventDefault();
michael@0 5035 return;
michael@0 5036 }
michael@0 5037 }
michael@0 5038
michael@0 5039 handleLinkClick(event, href, linkNode);
michael@0 5040
michael@0 5041 // Mark the page as a user followed link. This is done so that history can
michael@0 5042 // distinguish automatic embed visits from user activated ones. For example
michael@0 5043 // pages loaded in frames are embed visits and lost with the session, while
michael@0 5044 // visits across frames should be preserved.
michael@0 5045 try {
michael@0 5046 if (!PrivateBrowsingUtils.isWindowPrivate(window))
michael@0 5047 PlacesUIUtils.markPageAsFollowedLink(href);
michael@0 5048 } catch (ex) { /* Skip invalid URIs. */ }
michael@0 5049 }
michael@0 5050
michael@0 5051 /**
michael@0 5052 * Handles clicks on links.
michael@0 5053 *
michael@0 5054 * @return true if the click event was handled, false otherwise.
michael@0 5055 */
michael@0 5056 function handleLinkClick(event, href, linkNode) {
michael@0 5057 if (event.button == 2) // right click
michael@0 5058 return false;
michael@0 5059
michael@0 5060 var where = whereToOpenLink(event);
michael@0 5061 if (where == "current")
michael@0 5062 return false;
michael@0 5063
michael@0 5064 var doc = event.target.ownerDocument;
michael@0 5065
michael@0 5066 if (where == "save") {
michael@0 5067 saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true,
michael@0 5068 true, doc.documentURIObject, doc);
michael@0 5069 event.preventDefault();
michael@0 5070 return true;
michael@0 5071 }
michael@0 5072
michael@0 5073 var referrerURI = doc.documentURIObject;
michael@0 5074 // if the mixedContentChannel is present and the referring URI passes
michael@0 5075 // a same origin check with the target URI, we can preserve the users
michael@0 5076 // decision of disabling MCB on a page for it's child tabs.
michael@0 5077 var persistDisableMCBInChildTab = false;
michael@0 5078
michael@0 5079 if (where == "tab" && gBrowser.docShell.mixedContentChannel) {
michael@0 5080 const sm = Services.scriptSecurityManager;
michael@0 5081 try {
michael@0 5082 var targetURI = makeURI(href);
michael@0 5083 sm.checkSameOriginURI(referrerURI, targetURI, false);
michael@0 5084 persistDisableMCBInChildTab = true;
michael@0 5085 }
michael@0 5086 catch (e) { }
michael@0 5087 }
michael@0 5088
michael@0 5089 urlSecurityCheck(href, doc.nodePrincipal);
michael@0 5090 openLinkIn(href, where, { referrerURI: referrerURI,
michael@0 5091 charset: doc.characterSet,
michael@0 5092 disableMCB: persistDisableMCBInChildTab});
michael@0 5093 event.preventDefault();
michael@0 5094 return true;
michael@0 5095 }
michael@0 5096
michael@0 5097 function middleMousePaste(event) {
michael@0 5098 let clipboard = readFromClipboard();
michael@0 5099 if (!clipboard)
michael@0 5100 return;
michael@0 5101
michael@0 5102 // Strip embedded newlines and surrounding whitespace, to match the URL
michael@0 5103 // bar's behavior (stripsurroundingwhitespace)
michael@0 5104 clipboard = clipboard.replace(/\s*\n\s*/g, "");
michael@0 5105
michael@0 5106 // if it's not the current tab, we don't need to do anything because the
michael@0 5107 // browser doesn't exist.
michael@0 5108 let where = whereToOpenLink(event, true, false);
michael@0 5109 let lastLocationChange;
michael@0 5110 if (where == "current") {
michael@0 5111 lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
michael@0 5112 }
michael@0 5113
michael@0 5114 getShortcutOrURIAndPostData(clipboard, data => {
michael@0 5115 try {
michael@0 5116 makeURI(data.url);
michael@0 5117 } catch (ex) {
michael@0 5118 // Not a valid URI.
michael@0 5119 return;
michael@0 5120 }
michael@0 5121
michael@0 5122 try {
michael@0 5123 addToUrlbarHistory(data.url);
michael@0 5124 } catch (ex) {
michael@0 5125 // Things may go wrong when adding url to session history,
michael@0 5126 // but don't let that interfere with the loading of the url.
michael@0 5127 Cu.reportError(ex);
michael@0 5128 }
michael@0 5129
michael@0 5130 if (where != "current" ||
michael@0 5131 lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
michael@0 5132 openUILink(data.url, event,
michael@0 5133 { ignoreButton: true,
michael@0 5134 disallowInheritPrincipal: !data.mayInheritPrincipal });
michael@0 5135 }
michael@0 5136 });
michael@0 5137
michael@0 5138 event.stopPropagation();
michael@0 5139 }
michael@0 5140
michael@0 5141 function handleDroppedLink(event, url, name)
michael@0 5142 {
michael@0 5143 let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
michael@0 5144
michael@0 5145 getShortcutOrURIAndPostData(url, data => {
michael@0 5146 if (data.url &&
michael@0 5147 lastLocationChange == gBrowser.selectedBrowser.lastLocationChange)
michael@0 5148 loadURI(data.url, null, data.postData, false);
michael@0 5149 });
michael@0 5150
michael@0 5151 // Keep the event from being handled by the dragDrop listeners
michael@0 5152 // built-in to gecko if they happen to be above us.
michael@0 5153 event.preventDefault();
michael@0 5154 };
michael@0 5155
michael@0 5156 function BrowserSetForcedCharacterSet(aCharset)
michael@0 5157 {
michael@0 5158 if (aCharset) {
michael@0 5159 gBrowser.docShell.gatherCharsetMenuTelemetry();
michael@0 5160 gBrowser.docShell.charset = aCharset;
michael@0 5161 // Save the forced character-set
michael@0 5162 if (!PrivateBrowsingUtils.isWindowPrivate(window))
michael@0 5163 PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset);
michael@0 5164 }
michael@0 5165 BrowserCharsetReload();
michael@0 5166 }
michael@0 5167
michael@0 5168 function BrowserCharsetReload()
michael@0 5169 {
michael@0 5170 BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
michael@0 5171 }
michael@0 5172
michael@0 5173 function charsetMenuGetElement(parent, charset) {
michael@0 5174 return parent.getElementsByAttribute("charset", charset)[0];
michael@0 5175 }
michael@0 5176
michael@0 5177 function UpdateCurrentCharset(target) {
michael@0 5178 // extract the charset from DOM
michael@0 5179 var wnd = document.commandDispatcher.focusedWindow;
michael@0 5180 if ((window == wnd) || (wnd == null)) wnd = window.content;
michael@0 5181
michael@0 5182 // Uncheck previous item
michael@0 5183 if (gPrevCharset) {
michael@0 5184 var pref_item = charsetMenuGetElement(target, gPrevCharset);
michael@0 5185 if (pref_item)
michael@0 5186 pref_item.setAttribute('checked', 'false');
michael@0 5187 }
michael@0 5188
michael@0 5189 var menuitem = charsetMenuGetElement(target, CharsetMenu.foldCharset(wnd.document.characterSet));
michael@0 5190 if (menuitem) {
michael@0 5191 menuitem.setAttribute('checked', 'true');
michael@0 5192 }
michael@0 5193 }
michael@0 5194
michael@0 5195 function charsetLoadListener() {
michael@0 5196 var charset = CharsetMenu.foldCharset(window.content.document.characterSet);
michael@0 5197
michael@0 5198 if (charset.length > 0 && (charset != gLastBrowserCharset)) {
michael@0 5199 gPrevCharset = gLastBrowserCharset;
michael@0 5200 gLastBrowserCharset = charset;
michael@0 5201 }
michael@0 5202 }
michael@0 5203
michael@0 5204 var gPageStyleMenu = {
michael@0 5205
michael@0 5206 // This maps from a <browser> element (or, more specifically, a
michael@0 5207 // browser's permanentKey) to a CPOW that gives synchronous access
michael@0 5208 // to the list of style sheets in a content window. The use of the
michael@0 5209 // permanentKey is to avoid issues with docshell swapping.
michael@0 5210 _pageStyleSyncHandlers: new WeakMap(),
michael@0 5211
michael@0 5212 init: function() {
michael@0 5213 let mm = window.messageManager;
michael@0 5214 mm.addMessageListener("PageStyle:SetSyncHandler", (msg) => {
michael@0 5215 this._pageStyleSyncHandlers.set(msg.target.permanentKey, msg.objects.syncHandler);
michael@0 5216 });
michael@0 5217 },
michael@0 5218
michael@0 5219 getAllStyleSheets: function () {
michael@0 5220 let handler = this._pageStyleSyncHandlers.get(gBrowser.selectedBrowser.permanentKey);
michael@0 5221 try {
michael@0 5222 return handler.getAllStyleSheets();
michael@0 5223 } catch (ex) {
michael@0 5224 // In case the child died or timed out.
michael@0 5225 return [];
michael@0 5226 }
michael@0 5227 },
michael@0 5228
michael@0 5229 _getStyleSheetInfo: function (browser) {
michael@0 5230 let handler = this._pageStyleSyncHandlers.get(gBrowser.selectedBrowser.permanentKey);
michael@0 5231 try {
michael@0 5232 return handler.getStyleSheetInfo();
michael@0 5233 } catch (ex) {
michael@0 5234 // In case the child died or timed out.
michael@0 5235 return {styleSheets: [], authorStyleDisabled: false, preferredStyleSheetSet: true};
michael@0 5236 }
michael@0 5237 },
michael@0 5238
michael@0 5239 fillPopup: function (menuPopup) {
michael@0 5240 let styleSheetInfo = this._getStyleSheetInfo(gBrowser.selectedBrowser);
michael@0 5241 var noStyle = menuPopup.firstChild;
michael@0 5242 var persistentOnly = noStyle.nextSibling;
michael@0 5243 var sep = persistentOnly.nextSibling;
michael@0 5244 while (sep.nextSibling)
michael@0 5245 menuPopup.removeChild(sep.nextSibling);
michael@0 5246
michael@0 5247 let styleSheets = styleSheetInfo.styleSheets;
michael@0 5248 var currentStyleSheets = {};
michael@0 5249 var styleDisabled = styleSheetInfo.authorStyleDisabled;
michael@0 5250 var haveAltSheets = false;
michael@0 5251 var altStyleSelected = false;
michael@0 5252
michael@0 5253 for (let currentStyleSheet of styleSheets) {
michael@0 5254 if (!currentStyleSheet.disabled)
michael@0 5255 altStyleSelected = true;
michael@0 5256
michael@0 5257 haveAltSheets = true;
michael@0 5258
michael@0 5259 let lastWithSameTitle = null;
michael@0 5260 if (currentStyleSheet.title in currentStyleSheets)
michael@0 5261 lastWithSameTitle = currentStyleSheets[currentStyleSheet.title];
michael@0 5262
michael@0 5263 if (!lastWithSameTitle) {
michael@0 5264 let menuItem = document.createElement("menuitem");
michael@0 5265 menuItem.setAttribute("type", "radio");
michael@0 5266 menuItem.setAttribute("label", currentStyleSheet.title);
michael@0 5267 menuItem.setAttribute("data", currentStyleSheet.title);
michael@0 5268 menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled);
michael@0 5269 menuItem.setAttribute("oncommand", "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));");
michael@0 5270 menuPopup.appendChild(menuItem);
michael@0 5271 currentStyleSheets[currentStyleSheet.title] = menuItem;
michael@0 5272 } else if (currentStyleSheet.disabled) {
michael@0 5273 lastWithSameTitle.removeAttribute("checked");
michael@0 5274 }
michael@0 5275 }
michael@0 5276
michael@0 5277 noStyle.setAttribute("checked", styleDisabled);
michael@0 5278 persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled);
michael@0 5279 persistentOnly.hidden = styleSheetInfo.preferredStyleSheetSet ? haveAltSheets : false;
michael@0 5280 sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
michael@0 5281 },
michael@0 5282
michael@0 5283 switchStyleSheet: function (title) {
michael@0 5284 let mm = gBrowser.selectedBrowser.messageManager;
michael@0 5285 mm.sendAsyncMessage("PageStyle:Switch", {title: title});
michael@0 5286 },
michael@0 5287
michael@0 5288 disableStyle: function () {
michael@0 5289 let mm = gBrowser.selectedBrowser.messageManager;
michael@0 5290 mm.sendAsyncMessage("PageStyle:Disable");
michael@0 5291 },
michael@0 5292 };
michael@0 5293
michael@0 5294 /* Legacy global page-style functions */
michael@0 5295 var getAllStyleSheets = gPageStyleMenu.getAllStyleSheets.bind(gPageStyleMenu);
michael@0 5296 var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu);
michael@0 5297 function stylesheetSwitchAll(contentWindow, title) {
michael@0 5298 // We ignore the contentWindow param. Add-ons don't appear to use
michael@0 5299 // it, and it's difficult to support in e10s (where it will be a
michael@0 5300 // CPOW).
michael@0 5301 gPageStyleMenu.switchStyleSheet(title);
michael@0 5302 }
michael@0 5303 function setStyleDisabled(disabled) {
michael@0 5304 if (disabled)
michael@0 5305 gPageStyleMenu.disableStyle();
michael@0 5306 }
michael@0 5307
michael@0 5308
michael@0 5309 var LanguageDetectionListener = {
michael@0 5310 init: function() {
michael@0 5311 window.messageManager.addMessageListener("LanguageDetection:Result", msg => {
michael@0 5312 Translation.languageDetected(msg.target, msg.data);
michael@0 5313 });
michael@0 5314 }
michael@0 5315 };
michael@0 5316
michael@0 5317
michael@0 5318 var BrowserOffline = {
michael@0 5319 _inited: false,
michael@0 5320
michael@0 5321 /////////////////////////////////////////////////////////////////////////////
michael@0 5322 // BrowserOffline Public Methods
michael@0 5323 init: function ()
michael@0 5324 {
michael@0 5325 if (!this._uiElement)
michael@0 5326 this._uiElement = document.getElementById("workOfflineMenuitemState");
michael@0 5327
michael@0 5328 Services.obs.addObserver(this, "network:offline-status-changed", false);
michael@0 5329
michael@0 5330 this._updateOfflineUI(Services.io.offline);
michael@0 5331
michael@0 5332 this._inited = true;
michael@0 5333 },
michael@0 5334
michael@0 5335 uninit: function ()
michael@0 5336 {
michael@0 5337 if (this._inited) {
michael@0 5338 Services.obs.removeObserver(this, "network:offline-status-changed");
michael@0 5339 }
michael@0 5340 },
michael@0 5341
michael@0 5342 toggleOfflineStatus: function ()
michael@0 5343 {
michael@0 5344 var ioService = Services.io;
michael@0 5345
michael@0 5346 // Stop automatic management of the offline status
michael@0 5347 try {
michael@0 5348 ioService.manageOfflineStatus = false;
michael@0 5349 } catch (ex) {
michael@0 5350 }
michael@0 5351
michael@0 5352 if (!ioService.offline && !this._canGoOffline()) {
michael@0 5353 this._updateOfflineUI(false);
michael@0 5354 return;
michael@0 5355 }
michael@0 5356
michael@0 5357 ioService.offline = !ioService.offline;
michael@0 5358 },
michael@0 5359
michael@0 5360 /////////////////////////////////////////////////////////////////////////////
michael@0 5361 // nsIObserver
michael@0 5362 observe: function (aSubject, aTopic, aState)
michael@0 5363 {
michael@0 5364 if (aTopic != "network:offline-status-changed")
michael@0 5365 return;
michael@0 5366
michael@0 5367 this._updateOfflineUI(aState == "offline");
michael@0 5368 },
michael@0 5369
michael@0 5370 /////////////////////////////////////////////////////////////////////////////
michael@0 5371 // BrowserOffline Implementation Methods
michael@0 5372 _canGoOffline: function ()
michael@0 5373 {
michael@0 5374 try {
michael@0 5375 var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
michael@0 5376 Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null);
michael@0 5377
michael@0 5378 // Something aborted the quit process.
michael@0 5379 if (cancelGoOffline.data)
michael@0 5380 return false;
michael@0 5381 }
michael@0 5382 catch (ex) {
michael@0 5383 }
michael@0 5384
michael@0 5385 return true;
michael@0 5386 },
michael@0 5387
michael@0 5388 _uiElement: null,
michael@0 5389 _updateOfflineUI: function (aOffline)
michael@0 5390 {
michael@0 5391 var offlineLocked = gPrefService.prefIsLocked("network.online");
michael@0 5392 if (offlineLocked)
michael@0 5393 this._uiElement.setAttribute("disabled", "true");
michael@0 5394
michael@0 5395 this._uiElement.setAttribute("checked", aOffline);
michael@0 5396 }
michael@0 5397 };
michael@0 5398
michael@0 5399 var OfflineApps = {
michael@0 5400 /////////////////////////////////////////////////////////////////////////////
michael@0 5401 // OfflineApps Public Methods
michael@0 5402 init: function ()
michael@0 5403 {
michael@0 5404 Services.obs.addObserver(this, "offline-cache-update-completed", false);
michael@0 5405 },
michael@0 5406
michael@0 5407 uninit: function ()
michael@0 5408 {
michael@0 5409 Services.obs.removeObserver(this, "offline-cache-update-completed");
michael@0 5410 },
michael@0 5411
michael@0 5412 handleEvent: function(event) {
michael@0 5413 if (event.type == "MozApplicationManifest") {
michael@0 5414 this.offlineAppRequested(event.originalTarget.defaultView);
michael@0 5415 }
michael@0 5416 },
michael@0 5417
michael@0 5418 /////////////////////////////////////////////////////////////////////////////
michael@0 5419 // OfflineApps Implementation Methods
michael@0 5420
michael@0 5421 // XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow
michael@0 5422 // were taken from browser/components/feeds/src/WebContentConverter.
michael@0 5423 _getBrowserWindowForContentWindow: function(aContentWindow) {
michael@0 5424 return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 5425 .getInterface(Ci.nsIWebNavigation)
michael@0 5426 .QueryInterface(Ci.nsIDocShellTreeItem)
michael@0 5427 .rootTreeItem
michael@0 5428 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 5429 .getInterface(Ci.nsIDOMWindow)
michael@0 5430 .wrappedJSObject;
michael@0 5431 },
michael@0 5432
michael@0 5433 _getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) {
michael@0 5434 // This depends on pseudo APIs of browser.js and tabbrowser.xml
michael@0 5435 aContentWindow = aContentWindow.top;
michael@0 5436 var browsers = aBrowserWindow.gBrowser.browsers;
michael@0 5437 for (let browser of browsers) {
michael@0 5438 if (browser.contentWindow == aContentWindow)
michael@0 5439 return browser;
michael@0 5440 }
michael@0 5441 // handle other browser/iframe elements that may need popupnotifications
michael@0 5442 let browser = aContentWindow
michael@0 5443 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 5444 .getInterface(Ci.nsIWebNavigation)
michael@0 5445 .QueryInterface(Ci.nsIDocShell)
michael@0 5446 .chromeEventHandler;
michael@0 5447 if (browser.getAttribute("popupnotificationanchor"))
michael@0 5448 return browser;
michael@0 5449 return null;
michael@0 5450 },
michael@0 5451
michael@0 5452 _getManifestURI: function(aWindow) {
michael@0 5453 if (!aWindow.document.documentElement)
michael@0 5454 return null;
michael@0 5455
michael@0 5456 var attr = aWindow.document.documentElement.getAttribute("manifest");
michael@0 5457 if (!attr)
michael@0 5458 return null;
michael@0 5459
michael@0 5460 try {
michael@0 5461 var contentURI = makeURI(aWindow.location.href, null, null);
michael@0 5462 return makeURI(attr, aWindow.document.characterSet, contentURI);
michael@0 5463 } catch (e) {
michael@0 5464 return null;
michael@0 5465 }
michael@0 5466 },
michael@0 5467
michael@0 5468 // A cache update isn't tied to a specific window. Try to find
michael@0 5469 // the best browser in which to warn the user about space usage
michael@0 5470 _getBrowserForCacheUpdate: function(aCacheUpdate) {
michael@0 5471 // Prefer the current browser
michael@0 5472 var uri = this._getManifestURI(content);
michael@0 5473 if (uri && uri.equals(aCacheUpdate.manifestURI)) {
michael@0 5474 return gBrowser.selectedBrowser;
michael@0 5475 }
michael@0 5476
michael@0 5477 var browsers = gBrowser.browsers;
michael@0 5478 for (let browser of browsers) {
michael@0 5479 uri = this._getManifestURI(browser.contentWindow);
michael@0 5480 if (uri && uri.equals(aCacheUpdate.manifestURI)) {
michael@0 5481 return browser;
michael@0 5482 }
michael@0 5483 }
michael@0 5484
michael@0 5485 // is this from a non-tab browser/iframe?
michael@0 5486 browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]");
michael@0 5487 for (let browser of browsers) {
michael@0 5488 uri = this._getManifestURI(browser.contentWindow);
michael@0 5489 if (uri && uri.equals(aCacheUpdate.manifestURI)) {
michael@0 5490 return browser;
michael@0 5491 }
michael@0 5492 }
michael@0 5493
michael@0 5494 return null;
michael@0 5495 },
michael@0 5496
michael@0 5497 _warnUsage: function(aBrowser, aURI) {
michael@0 5498 if (!aBrowser)
michael@0 5499 return;
michael@0 5500
michael@0 5501 let mainAction = {
michael@0 5502 label: gNavigatorBundle.getString("offlineApps.manageUsage"),
michael@0 5503 accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
michael@0 5504 callback: OfflineApps.manage
michael@0 5505 };
michael@0 5506
michael@0 5507 let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
michael@0 5508 let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
michael@0 5509 [ aURI.host,
michael@0 5510 warnQuota / 1024 ]);
michael@0 5511
michael@0 5512 let anchorID = "indexedDB-notification-icon";
michael@0 5513 PopupNotifications.show(aBrowser, "offline-app-usage", message,
michael@0 5514 anchorID, mainAction);
michael@0 5515
michael@0 5516 // Now that we've warned once, prevent the warning from showing up
michael@0 5517 // again.
michael@0 5518 Services.perms.add(aURI, "offline-app",
michael@0 5519 Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
michael@0 5520 },
michael@0 5521
michael@0 5522 // XXX: duplicated in preferences/advanced.js
michael@0 5523 _getOfflineAppUsage: function (host, groups)
michael@0 5524 {
michael@0 5525 var cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
michael@0 5526 getService(Ci.nsIApplicationCacheService);
michael@0 5527 if (!groups)
michael@0 5528 groups = cacheService.getGroups();
michael@0 5529
michael@0 5530 var usage = 0;
michael@0 5531 for (let group of groups) {
michael@0 5532 var uri = Services.io.newURI(group, null, null);
michael@0 5533 if (uri.asciiHost == host) {
michael@0 5534 var cache = cacheService.getActiveCache(group);
michael@0 5535 usage += cache.usage;
michael@0 5536 }
michael@0 5537 }
michael@0 5538
michael@0 5539 return usage;
michael@0 5540 },
michael@0 5541
michael@0 5542 _checkUsage: function(aURI) {
michael@0 5543 // if the user has already allowed excessive usage, don't bother checking
michael@0 5544 if (Services.perms.testExactPermission(aURI, "offline-app") !=
michael@0 5545 Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
michael@0 5546 var usage = this._getOfflineAppUsage(aURI.asciiHost);
michael@0 5547 var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
michael@0 5548 if (usage >= warnQuota * 1024) {
michael@0 5549 return true;
michael@0 5550 }
michael@0 5551 }
michael@0 5552
michael@0 5553 return false;
michael@0 5554 },
michael@0 5555
michael@0 5556 offlineAppRequested: function(aContentWindow) {
michael@0 5557 if (!gPrefService.getBoolPref("browser.offline-apps.notify")) {
michael@0 5558 return;
michael@0 5559 }
michael@0 5560
michael@0 5561 let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
michael@0 5562 let browser = this._getBrowserForContentWindow(browserWindow,
michael@0 5563 aContentWindow);
michael@0 5564
michael@0 5565 let currentURI = aContentWindow.document.documentURIObject;
michael@0 5566
michael@0 5567 // don't bother showing UI if the user has already made a decision
michael@0 5568 if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
michael@0 5569 return;
michael@0 5570
michael@0 5571 try {
michael@0 5572 if (gPrefService.getBoolPref("offline-apps.allow_by_default")) {
michael@0 5573 // all pages can use offline capabilities, no need to ask the user
michael@0 5574 return;
michael@0 5575 }
michael@0 5576 } catch(e) {
michael@0 5577 // this pref isn't set by default, ignore failures
michael@0 5578 }
michael@0 5579
michael@0 5580 let host = currentURI.asciiHost;
michael@0 5581 let notificationID = "offline-app-requested-" + host;
michael@0 5582 let notification = PopupNotifications.getNotification(notificationID, browser);
michael@0 5583
michael@0 5584 if (notification) {
michael@0 5585 notification.options.documents.push(aContentWindow.document);
michael@0 5586 } else {
michael@0 5587 let mainAction = {
michael@0 5588 label: gNavigatorBundle.getString("offlineApps.allow"),
michael@0 5589 accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
michael@0 5590 callback: function() {
michael@0 5591 for (let document of notification.options.documents) {
michael@0 5592 OfflineApps.allowSite(document);
michael@0 5593 }
michael@0 5594 }
michael@0 5595 };
michael@0 5596 let secondaryActions = [{
michael@0 5597 label: gNavigatorBundle.getString("offlineApps.never"),
michael@0 5598 accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
michael@0 5599 callback: function() {
michael@0 5600 for (let document of notification.options.documents) {
michael@0 5601 OfflineApps.disallowSite(document);
michael@0 5602 }
michael@0 5603 }
michael@0 5604 }];
michael@0 5605 let message = gNavigatorBundle.getFormattedString("offlineApps.available",
michael@0 5606 [ host ]);
michael@0 5607 let anchorID = "indexedDB-notification-icon";
michael@0 5608 let options= {
michael@0 5609 documents : [ aContentWindow.document ]
michael@0 5610 };
michael@0 5611 notification = PopupNotifications.show(browser, notificationID, message,
michael@0 5612 anchorID, mainAction,
michael@0 5613 secondaryActions, options);
michael@0 5614 }
michael@0 5615 },
michael@0 5616
michael@0 5617 allowSite: function(aDocument) {
michael@0 5618 Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION);
michael@0 5619
michael@0 5620 // When a site is enabled while loading, manifest resources will
michael@0 5621 // start fetching immediately. This one time we need to do it
michael@0 5622 // ourselves.
michael@0 5623 this._startFetching(aDocument);
michael@0 5624 },
michael@0 5625
michael@0 5626 disallowSite: function(aDocument) {
michael@0 5627 Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION);
michael@0 5628 },
michael@0 5629
michael@0 5630 manage: function() {
michael@0 5631 openAdvancedPreferences("networkTab");
michael@0 5632 },
michael@0 5633
michael@0 5634 _startFetching: function(aDocument) {
michael@0 5635 if (!aDocument.documentElement)
michael@0 5636 return;
michael@0 5637
michael@0 5638 var manifest = aDocument.documentElement.getAttribute("manifest");
michael@0 5639 if (!manifest)
michael@0 5640 return;
michael@0 5641
michael@0 5642 var manifestURI = makeURI(manifest, aDocument.characterSet,
michael@0 5643 aDocument.documentURIObject);
michael@0 5644
michael@0 5645 var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
michael@0 5646 getService(Ci.nsIOfflineCacheUpdateService);
michael@0 5647 updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, window);
michael@0 5648 },
michael@0 5649
michael@0 5650 /////////////////////////////////////////////////////////////////////////////
michael@0 5651 // nsIObserver
michael@0 5652 observe: function (aSubject, aTopic, aState)
michael@0 5653 {
michael@0 5654 if (aTopic == "offline-cache-update-completed") {
michael@0 5655 var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
michael@0 5656
michael@0 5657 var uri = cacheUpdate.manifestURI;
michael@0 5658 if (OfflineApps._checkUsage(uri)) {
michael@0 5659 var browser = this._getBrowserForCacheUpdate(cacheUpdate);
michael@0 5660 if (browser) {
michael@0 5661 OfflineApps._warnUsage(browser, cacheUpdate.manifestURI);
michael@0 5662 }
michael@0 5663 }
michael@0 5664 }
michael@0 5665 }
michael@0 5666 };
michael@0 5667
michael@0 5668 var IndexedDBPromptHelper = {
michael@0 5669 _permissionsPrompt: "indexedDB-permissions-prompt",
michael@0 5670 _permissionsResponse: "indexedDB-permissions-response",
michael@0 5671
michael@0 5672 _quotaPrompt: "indexedDB-quota-prompt",
michael@0 5673 _quotaResponse: "indexedDB-quota-response",
michael@0 5674 _quotaCancel: "indexedDB-quota-cancel",
michael@0 5675
michael@0 5676 _notificationIcon: "indexedDB-notification-icon",
michael@0 5677
michael@0 5678 init:
michael@0 5679 function IndexedDBPromptHelper_init() {
michael@0 5680 Services.obs.addObserver(this, this._permissionsPrompt, false);
michael@0 5681 Services.obs.addObserver(this, this._quotaPrompt, false);
michael@0 5682 Services.obs.addObserver(this, this._quotaCancel, false);
michael@0 5683 },
michael@0 5684
michael@0 5685 uninit:
michael@0 5686 function IndexedDBPromptHelper_uninit() {
michael@0 5687 Services.obs.removeObserver(this, this._permissionsPrompt);
michael@0 5688 Services.obs.removeObserver(this, this._quotaPrompt);
michael@0 5689 Services.obs.removeObserver(this, this._quotaCancel);
michael@0 5690 },
michael@0 5691
michael@0 5692 observe:
michael@0 5693 function IndexedDBPromptHelper_observe(subject, topic, data) {
michael@0 5694 if (topic != this._permissionsPrompt &&
michael@0 5695 topic != this._quotaPrompt &&
michael@0 5696 topic != this._quotaCancel) {
michael@0 5697 throw new Error("Unexpected topic!");
michael@0 5698 }
michael@0 5699
michael@0 5700 var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
michael@0 5701
michael@0 5702 var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
michael@0 5703 var contentDocument = contentWindow.document;
michael@0 5704 var browserWindow =
michael@0 5705 OfflineApps._getBrowserWindowForContentWindow(contentWindow);
michael@0 5706
michael@0 5707 if (browserWindow != window) {
michael@0 5708 // Must belong to some other window.
michael@0 5709 return;
michael@0 5710 }
michael@0 5711
michael@0 5712 var browser =
michael@0 5713 OfflineApps._getBrowserForContentWindow(browserWindow, contentWindow);
michael@0 5714
michael@0 5715 var host = contentDocument.documentURIObject.asciiHost;
michael@0 5716
michael@0 5717 var message;
michael@0 5718 var responseTopic;
michael@0 5719 if (topic == this._permissionsPrompt) {
michael@0 5720 message = gNavigatorBundle.getFormattedString("offlineApps.available",
michael@0 5721 [ host ]);
michael@0 5722 responseTopic = this._permissionsResponse;
michael@0 5723 }
michael@0 5724 else if (topic == this._quotaPrompt) {
michael@0 5725 message = gNavigatorBundle.getFormattedString("indexedDB.usage",
michael@0 5726 [ host, data ]);
michael@0 5727 responseTopic = this._quotaResponse;
michael@0 5728 }
michael@0 5729 else if (topic == this._quotaCancel) {
michael@0 5730 responseTopic = this._quotaResponse;
michael@0 5731 }
michael@0 5732
michael@0 5733 const hiddenTimeoutDuration = 30000; // 30 seconds
michael@0 5734 const firstTimeoutDuration = 300000; // 5 minutes
michael@0 5735
michael@0 5736 var timeoutId;
michael@0 5737
michael@0 5738 var observer = requestor.getInterface(Ci.nsIObserver);
michael@0 5739
michael@0 5740 var mainAction = {
michael@0 5741 label: gNavigatorBundle.getString("offlineApps.allow"),
michael@0 5742 accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
michael@0 5743 callback: function() {
michael@0 5744 clearTimeout(timeoutId);
michael@0 5745 observer.observe(null, responseTopic,
michael@0 5746 Ci.nsIPermissionManager.ALLOW_ACTION);
michael@0 5747 }
michael@0 5748 };
michael@0 5749
michael@0 5750 var secondaryActions = [
michael@0 5751 {
michael@0 5752 label: gNavigatorBundle.getString("offlineApps.never"),
michael@0 5753 accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
michael@0 5754 callback: function() {
michael@0 5755 clearTimeout(timeoutId);
michael@0 5756 observer.observe(null, responseTopic,
michael@0 5757 Ci.nsIPermissionManager.DENY_ACTION);
michael@0 5758 }
michael@0 5759 }
michael@0 5760 ];
michael@0 5761
michael@0 5762 // This will be set to the result of PopupNotifications.show() below, or to
michael@0 5763 // the result of PopupNotifications.getNotification() if this is a
michael@0 5764 // quotaCancel notification.
michael@0 5765 var notification;
michael@0 5766
michael@0 5767 function timeoutNotification() {
michael@0 5768 // Remove the notification.
michael@0 5769 if (notification) {
michael@0 5770 notification.remove();
michael@0 5771 }
michael@0 5772
michael@0 5773 // Clear all of our timeout stuff. We may be called directly, not just
michael@0 5774 // when the timeout actually elapses.
michael@0 5775 clearTimeout(timeoutId);
michael@0 5776
michael@0 5777 // And tell the page that the popup timed out.
michael@0 5778 observer.observe(null, responseTopic,
michael@0 5779 Ci.nsIPermissionManager.UNKNOWN_ACTION);
michael@0 5780 }
michael@0 5781
michael@0 5782 var options = {
michael@0 5783 eventCallback: function(state) {
michael@0 5784 // Don't do anything if the timeout has not been set yet.
michael@0 5785 if (!timeoutId) {
michael@0 5786 return;
michael@0 5787 }
michael@0 5788
michael@0 5789 // If the popup is being dismissed start the short timeout.
michael@0 5790 if (state == "dismissed") {
michael@0 5791 clearTimeout(timeoutId);
michael@0 5792 timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration);
michael@0 5793 return;
michael@0 5794 }
michael@0 5795
michael@0 5796 // If the popup is being re-shown then clear the timeout allowing
michael@0 5797 // unlimited waiting.
michael@0 5798 if (state == "shown") {
michael@0 5799 clearTimeout(timeoutId);
michael@0 5800 }
michael@0 5801 }
michael@0 5802 };
michael@0 5803
michael@0 5804 if (topic == this._quotaCancel) {
michael@0 5805 notification = PopupNotifications.getNotification(this._quotaPrompt,
michael@0 5806 browser);
michael@0 5807 timeoutNotification();
michael@0 5808 return;
michael@0 5809 }
michael@0 5810
michael@0 5811 notification = PopupNotifications.show(browser, topic, message,
michael@0 5812 this._notificationIcon, mainAction,
michael@0 5813 secondaryActions, options);
michael@0 5814
michael@0 5815 // Set the timeoutId after the popup has been created, and use the long
michael@0 5816 // timeout value. If the user doesn't notice the popup after this amount of
michael@0 5817 // time then it is most likely not visible and we want to alert the page.
michael@0 5818 timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
michael@0 5819 }
michael@0 5820 };
michael@0 5821
michael@0 5822 var CanvasPermissionPromptHelper = {
michael@0 5823 _permissionsPrompt: "canvas-permissions-prompt",
michael@0 5824 _notificationIcon: "canvas-notification-icon",
michael@0 5825
michael@0 5826 init:
michael@0 5827 function CanvasPermissionPromptHelper_init() {
michael@0 5828 if (document.styleSheets && (document.styleSheets.length > 0)) try {
michael@0 5829 let ruleText = "panel[popupid=canvas-permissions-prompt] description { white-space: pre-wrap; }";
michael@0 5830 let sheet = document.styleSheets[0];
michael@0 5831 sheet.insertRule(ruleText, sheet.cssRules.length);
michael@0 5832 } catch (e) {};
michael@0 5833
michael@0 5834 Services.obs.addObserver(this, this._permissionsPrompt, false);
michael@0 5835 },
michael@0 5836
michael@0 5837 uninit:
michael@0 5838 function CanvasPermissionPromptHelper_uninit() {
michael@0 5839 Services.obs.removeObserver(this, this._permissionsPrompt, false);
michael@0 5840 },
michael@0 5841
michael@0 5842 // aSubject is an nsIDOMWindow.
michael@0 5843 // aData is an URL string.
michael@0 5844 observe:
michael@0 5845 function CanvasPermissionPromptHelper_observe(aSubject, aTopic, aData) {
michael@0 5846 if ((aTopic != this._permissionsPrompt) || !aData)
michael@0 5847 throw new Error("Unexpected topic or missing URL");
michael@0 5848
michael@0 5849 var uri = makeURI(aData);
michael@0 5850 var contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
michael@0 5851 var contentDocument = contentWindow.document;
michael@0 5852 var browserWindow =
michael@0 5853 OfflineApps._getBrowserWindowForContentWindow(contentWindow);
michael@0 5854
michael@0 5855 if (browserWindow != window) {
michael@0 5856 // Must belong to some other window.
michael@0 5857 return;
michael@0 5858 }
michael@0 5859
michael@0 5860 // If canvas prompt is already displayed, just return. This is OK (and
michael@0 5861 // more efficient) since this permission is associated with the top
michael@0 5862 // browser's URL.
michael@0 5863 if (PopupNotifications.getNotification(aTopic, browser))
michael@0 5864 return;
michael@0 5865
michael@0 5866 var bundleSvc = Cc["@mozilla.org/intl/stringbundle;1"].
michael@0 5867 getService(Ci.nsIStringBundleService);
michael@0 5868 var torBtnBundle;
michael@0 5869 try {
michael@0 5870 torBtnBundle = bundleSvc.createBundle(
michael@0 5871 "chrome://torbutton/locale/torbutton.properties");
michael@0 5872 } catch (e) {}
michael@0 5873
michael@0 5874 var message = getLocalizedString("canvas.siteprompt", [ uri.asciiHost ]);
michael@0 5875
michael@0 5876 var mainAction = {
michael@0 5877 label: getLocalizedString("canvas.notNow"),
michael@0 5878 accessKey: getLocalizedString("canvas.notNowAccessKey"),
michael@0 5879 callback: function() {
michael@0 5880 return null;
michael@0 5881 }
michael@0 5882 };
michael@0 5883
michael@0 5884 var secondaryActions = [
michael@0 5885 {
michael@0 5886 label: getLocalizedString("canvas.never"),
michael@0 5887 accessKey: getLocalizedString("canvas.neverAccessKey"),
michael@0 5888 callback: function() {
michael@0 5889 setCanvasPermission(uri, Ci.nsIPermissionManager.DENY_ACTION);
michael@0 5890 }
michael@0 5891 },
michael@0 5892 {
michael@0 5893 label: getLocalizedString("canvas.allow"),
michael@0 5894 accessKey: getLocalizedString("canvas.allowAccessKey"),
michael@0 5895 callback: function() {
michael@0 5896 setCanvasPermission(uri, Ci.nsIPermissionManager.ALLOW_ACTION);
michael@0 5897 }
michael@0 5898 }
michael@0 5899 ];
michael@0 5900
michael@0 5901 // Since we have a process in place to perform localization for the
michael@0 5902 // Torbutton extension, get our strings from the extension if possible.
michael@0 5903 function getLocalizedString(aID, aParams) {
michael@0 5904 var s;
michael@0 5905 if (torBtnBundle) try {
michael@0 5906 if (aParams)
michael@0 5907 s = torBtnBundle.formatStringFromName(aID, aParams, aParams.length);
michael@0 5908 else
michael@0 5909 s = torBtnBundle.GetStringFromName(aID);
michael@0 5910 } catch (e) {}
michael@0 5911
michael@0 5912 if (!s) {
michael@0 5913 if (aParams)
michael@0 5914 s = gNavigatorBundle.getFormattedString(aID, aParams);
michael@0 5915 else
michael@0 5916 s = gNavigatorBundle.getString(aID);
michael@0 5917 }
michael@0 5918
michael@0 5919 return s;
michael@0 5920 }
michael@0 5921
michael@0 5922 function setCanvasPermission(aURI, aPerm) {
michael@0 5923 Services.perms.add(aURI, "canvas/extractData", aPerm,
michael@0 5924 Ci.nsIPermissionManager.EXPIRE_NEVER);
michael@0 5925 }
michael@0 5926
michael@0 5927 var browser = OfflineApps._getBrowserForContentWindow(browserWindow,
michael@0 5928 contentWindow);
michael@0 5929 notification = PopupNotifications.show(browser, aTopic, message,
michael@0 5930 this._notificationIcon, mainAction,
michael@0 5931 secondaryActions, null);
michael@0 5932 }
michael@0 5933 };
michael@0 5934
michael@0 5935 function WindowIsClosing()
michael@0 5936 {
michael@0 5937 if (TabView.isVisible()) {
michael@0 5938 TabView.hide();
michael@0 5939 return false;
michael@0 5940 }
michael@0 5941
michael@0 5942 if (!closeWindow(false, warnAboutClosingWindow))
michael@0 5943 return false;
michael@0 5944
michael@0 5945 // Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process
michael@0 5946 if (gMultiProcessBrowser)
michael@0 5947 return true;
michael@0 5948
michael@0 5949 for (let browser of gBrowser.browsers) {
michael@0 5950 let ds = browser.docShell;
michael@0 5951 if (ds.contentViewer && !ds.contentViewer.permitUnload())
michael@0 5952 return false;
michael@0 5953 }
michael@0 5954
michael@0 5955 return true;
michael@0 5956 }
michael@0 5957
michael@0 5958 /**
michael@0 5959 * Checks if this is the last full *browser* window around. If it is, this will
michael@0 5960 * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
michael@0 5961 * @returns true if closing can proceed, false if it got cancelled.
michael@0 5962 */
michael@0 5963 function warnAboutClosingWindow() {
michael@0 5964 // Popups aren't considered full browser windows; we also ignore private windows.
michael@0 5965 let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window) &&
michael@0 5966 !PrivateBrowsingUtils.permanentPrivateBrowsing;
michael@0 5967 if (!isPBWindow && !toolbar.visible)
michael@0 5968 return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
michael@0 5969
michael@0 5970 // Figure out if there's at least one other browser window around.
michael@0 5971 let e = Services.wm.getEnumerator("navigator:browser");
michael@0 5972 let otherPBWindowExists = false;
michael@0 5973 let nonPopupPresent = false;
michael@0 5974 while (e.hasMoreElements()) {
michael@0 5975 let win = e.getNext();
michael@0 5976 if (!win.closed && win != window) {
michael@0 5977 if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win))
michael@0 5978 otherPBWindowExists = true;
michael@0 5979 if (win.toolbar.visible)
michael@0 5980 nonPopupPresent = true;
michael@0 5981 // If the current window is not in private browsing mode we don't need to
michael@0 5982 // look for other pb windows, we can leave the loop when finding the
michael@0 5983 // first non-popup window. If however the current window is in private
michael@0 5984 // browsing mode then we need at least one other pb and one non-popup
michael@0 5985 // window to break out early.
michael@0 5986 if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent)
michael@0 5987 break;
michael@0 5988 }
michael@0 5989 }
michael@0 5990
michael@0 5991 if (isPBWindow && !otherPBWindowExists) {
michael@0 5992 let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
michael@0 5993 createInstance(Ci.nsISupportsPRBool);
michael@0 5994 exitingCanceled.data = false;
michael@0 5995 Services.obs.notifyObservers(exitingCanceled,
michael@0 5996 "last-pb-context-exiting",
michael@0 5997 null);
michael@0 5998 if (exitingCanceled.data)
michael@0 5999 return false;
michael@0 6000 }
michael@0 6001
michael@0 6002 if (nonPopupPresent) {
michael@0 6003 return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
michael@0 6004 }
michael@0 6005
michael@0 6006 let os = Services.obs;
michael@0 6007
michael@0 6008 let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
michael@0 6009 createInstance(Ci.nsISupportsPRBool);
michael@0 6010 os.notifyObservers(closingCanceled,
michael@0 6011 "browser-lastwindow-close-requested", null);
michael@0 6012 if (closingCanceled.data)
michael@0 6013 return false;
michael@0 6014
michael@0 6015 os.notifyObservers(null, "browser-lastwindow-close-granted", null);
michael@0 6016
michael@0 6017 #ifdef XP_MACOSX
michael@0 6018 // OS X doesn't quit the application when the last window is closed, but keeps
michael@0 6019 // the session alive. Hence don't prompt users to save tabs, but warn about
michael@0 6020 // closing multiple tabs.
michael@0 6021 return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
michael@0 6022 #else
michael@0 6023 return true;
michael@0 6024 #endif
michael@0 6025 }
michael@0 6026
michael@0 6027 var MailIntegration = {
michael@0 6028 sendLinkForWindow: function (aWindow) {
michael@0 6029 this.sendMessage(aWindow.location.href,
michael@0 6030 aWindow.document.title);
michael@0 6031 },
michael@0 6032
michael@0 6033 sendMessage: function (aBody, aSubject) {
michael@0 6034 // generate a mailto url based on the url and the url's title
michael@0 6035 var mailtoUrl = "mailto:";
michael@0 6036 if (aBody) {
michael@0 6037 mailtoUrl += "?body=" + encodeURIComponent(aBody);
michael@0 6038 mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
michael@0 6039 }
michael@0 6040
michael@0 6041 var uri = makeURI(mailtoUrl);
michael@0 6042
michael@0 6043 // now pass this uri to the operating system
michael@0 6044 this._launchExternalUrl(uri);
michael@0 6045 },
michael@0 6046
michael@0 6047 // a generic method which can be used to pass arbitrary urls to the operating
michael@0 6048 // system.
michael@0 6049 // aURL --> a nsIURI which represents the url to launch
michael@0 6050 _launchExternalUrl: function (aURL) {
michael@0 6051 var extProtocolSvc =
michael@0 6052 Cc["@mozilla.org/uriloader/external-protocol-service;1"]
michael@0 6053 .getService(Ci.nsIExternalProtocolService);
michael@0 6054 if (extProtocolSvc)
michael@0 6055 extProtocolSvc.loadUrl(aURL);
michael@0 6056 }
michael@0 6057 };
michael@0 6058
michael@0 6059 function BrowserOpenAddonsMgr(aView) {
michael@0 6060 if (aView) {
michael@0 6061 let emWindow;
michael@0 6062 let browserWindow;
michael@0 6063
michael@0 6064 var receivePong = function receivePong(aSubject, aTopic, aData) {
michael@0 6065 let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 6066 .getInterface(Ci.nsIWebNavigation)
michael@0 6067 .QueryInterface(Ci.nsIDocShellTreeItem)
michael@0 6068 .rootTreeItem
michael@0 6069 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 6070 .getInterface(Ci.nsIDOMWindow);
michael@0 6071 if (!emWindow || browserWin == window /* favor the current window */) {
michael@0 6072 emWindow = aSubject;
michael@0 6073 browserWindow = browserWin;
michael@0 6074 }
michael@0 6075 }
michael@0 6076 Services.obs.addObserver(receivePong, "EM-pong", false);
michael@0 6077 Services.obs.notifyObservers(null, "EM-ping", "");
michael@0 6078 Services.obs.removeObserver(receivePong, "EM-pong");
michael@0 6079
michael@0 6080 if (emWindow) {
michael@0 6081 emWindow.loadView(aView);
michael@0 6082 browserWindow.gBrowser.selectedTab =
michael@0 6083 browserWindow.gBrowser._getTabForContentWindow(emWindow);
michael@0 6084 emWindow.focus();
michael@0 6085 return;
michael@0 6086 }
michael@0 6087 }
michael@0 6088
michael@0 6089 var newLoad = !switchToTabHavingURI("about:addons", true);
michael@0 6090
michael@0 6091 if (aView) {
michael@0 6092 // This must be a new load, else the ping/pong would have
michael@0 6093 // found the window above.
michael@0 6094 Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
michael@0 6095 Services.obs.removeObserver(observer, aTopic);
michael@0 6096 aSubject.loadView(aView);
michael@0 6097 }, "EM-loaded", false);
michael@0 6098 }
michael@0 6099 }
michael@0 6100
michael@0 6101 function GetSearchFieldBookmarkData(node) {
michael@0 6102 var charset = node.ownerDocument.characterSet;
michael@0 6103
michael@0 6104 var formBaseURI = makeURI(node.form.baseURI,
michael@0 6105 charset);
michael@0 6106
michael@0 6107 var formURI = makeURI(node.form.getAttribute("action"),
michael@0 6108 charset,
michael@0 6109 formBaseURI);
michael@0 6110
michael@0 6111 var spec = formURI.spec;
michael@0 6112
michael@0 6113 var isURLEncoded =
michael@0 6114 (node.form.method.toUpperCase() == "POST"
michael@0 6115 && (node.form.enctype == "application/x-www-form-urlencoded" ||
michael@0 6116 node.form.enctype == ""));
michael@0 6117
michael@0 6118 var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
michael@0 6119 [node.ownerDocument.title]);
michael@0 6120 var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument);
michael@0 6121
michael@0 6122 var formData = [];
michael@0 6123
michael@0 6124 function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
michael@0 6125 if (aIsFormUrlEncoded)
michael@0 6126 return escape(aName + "=" + aValue);
michael@0 6127 else
michael@0 6128 return escape(aName) + "=" + escape(aValue);
michael@0 6129 }
michael@0 6130
michael@0 6131 for (let el of node.form.elements) {
michael@0 6132 if (!el.type) // happens with fieldsets
michael@0 6133 continue;
michael@0 6134
michael@0 6135 if (el == node) {
michael@0 6136 formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
michael@0 6137 // Don't escape "%s", just append
michael@0 6138 escapeNameValuePair(el.name, "", false) + "%s");
michael@0 6139 continue;
michael@0 6140 }
michael@0 6141
michael@0 6142 let type = el.type.toLowerCase();
michael@0 6143
michael@0 6144 if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) ||
michael@0 6145 type == "hidden" || type == "textarea") ||
michael@0 6146 ((type == "checkbox" || type == "radio") && el.checked)) {
michael@0 6147 formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
michael@0 6148 } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) {
michael@0 6149 for (var j=0; j < el.options.length; j++) {
michael@0 6150 if (el.options[j].selected)
michael@0 6151 formData.push(escapeNameValuePair(el.name, el.options[j].value,
michael@0 6152 isURLEncoded));
michael@0 6153 }
michael@0 6154 }
michael@0 6155 }
michael@0 6156
michael@0 6157 var postData;
michael@0 6158
michael@0 6159 if (isURLEncoded)
michael@0 6160 postData = formData.join("&");
michael@0 6161 else
michael@0 6162 spec += "?" + formData.join("&");
michael@0 6163
michael@0 6164 return {
michael@0 6165 spec: spec,
michael@0 6166 title: title,
michael@0 6167 description: description,
michael@0 6168 postData: postData,
michael@0 6169 charSet: charset
michael@0 6170 };
michael@0 6171 }
michael@0 6172
michael@0 6173
michael@0 6174 function AddKeywordForSearchField() {
michael@0 6175 bookmarkData = GetSearchFieldBookmarkData(document.popupNode);
michael@0 6176
michael@0 6177 PlacesUIUtils.showBookmarkDialog({ action: "add"
michael@0 6178 , type: "bookmark"
michael@0 6179 , uri: makeURI(bookmarkData.spec)
michael@0 6180 , title: bookmarkData.title
michael@0 6181 , description: bookmarkData.description
michael@0 6182 , keyword: ""
michael@0 6183 , postData: bookmarkData.postData
michael@0 6184 , charSet: bookmarkData.charset
michael@0 6185 , hiddenRows: [ "location"
michael@0 6186 , "description"
michael@0 6187 , "tags"
michael@0 6188 , "loadInSidebar" ]
michael@0 6189 }, window);
michael@0 6190 }
michael@0 6191
michael@0 6192 function SwitchDocumentDirection(aWindow) {
michael@0 6193 // document.dir can also be "auto", in which case it won't change
michael@0 6194 if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") {
michael@0 6195 aWindow.document.dir = "rtl";
michael@0 6196 } else if (aWindow.document.dir == "rtl") {
michael@0 6197 aWindow.document.dir = "ltr";
michael@0 6198 }
michael@0 6199 for (var run = 0; run < aWindow.frames.length; run++)
michael@0 6200 SwitchDocumentDirection(aWindow.frames[run]);
michael@0 6201 }
michael@0 6202
michael@0 6203 function convertFromUnicode(charset, str)
michael@0 6204 {
michael@0 6205 try {
michael@0 6206 var unicodeConverter = Components
michael@0 6207 .classes["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 6208 .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
michael@0 6209 unicodeConverter.charset = charset;
michael@0 6210 str = unicodeConverter.ConvertFromUnicode(str);
michael@0 6211 return str + unicodeConverter.Finish();
michael@0 6212 } catch(ex) {
michael@0 6213 return null;
michael@0 6214 }
michael@0 6215 }
michael@0 6216
michael@0 6217 /**
michael@0 6218 * Re-open a closed tab.
michael@0 6219 * @param aIndex
michael@0 6220 * The index of the tab (via SessionStore.getClosedTabData)
michael@0 6221 * @returns a reference to the reopened tab.
michael@0 6222 */
michael@0 6223 function undoCloseTab(aIndex) {
michael@0 6224 // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
michael@0 6225 var blankTabToRemove = null;
michael@0 6226 if (gBrowser.tabs.length == 1 && isTabEmpty(gBrowser.selectedTab))
michael@0 6227 blankTabToRemove = gBrowser.selectedTab;
michael@0 6228
michael@0 6229 var tab = null;
michael@0 6230 if (SessionStore.getClosedTabCount(window) > (aIndex || 0)) {
michael@0 6231 TabView.prepareUndoCloseTab(blankTabToRemove);
michael@0 6232 tab = SessionStore.undoCloseTab(window, aIndex || 0);
michael@0 6233 TabView.afterUndoCloseTab();
michael@0 6234
michael@0 6235 if (blankTabToRemove)
michael@0 6236 gBrowser.removeTab(blankTabToRemove);
michael@0 6237 }
michael@0 6238
michael@0 6239 return tab;
michael@0 6240 }
michael@0 6241
michael@0 6242 /**
michael@0 6243 * Re-open a closed window.
michael@0 6244 * @param aIndex
michael@0 6245 * The index of the window (via SessionStore.getClosedWindowData)
michael@0 6246 * @returns a reference to the reopened window.
michael@0 6247 */
michael@0 6248 function undoCloseWindow(aIndex) {
michael@0 6249 let window = null;
michael@0 6250 if (SessionStore.getClosedWindowCount() > (aIndex || 0))
michael@0 6251 window = SessionStore.undoCloseWindow(aIndex || 0);
michael@0 6252
michael@0 6253 return window;
michael@0 6254 }
michael@0 6255
michael@0 6256 /*
michael@0 6257 * Determines if a tab is "empty", usually used in the context of determining
michael@0 6258 * if it's ok to close the tab.
michael@0 6259 */
michael@0 6260 function isTabEmpty(aTab) {
michael@0 6261 if (aTab.hasAttribute("busy"))
michael@0 6262 return false;
michael@0 6263
michael@0 6264 let browser = aTab.linkedBrowser;
michael@0 6265 if (!isBlankPageURL(browser.currentURI.spec))
michael@0 6266 return false;
michael@0 6267
michael@0 6268 // Bug 863515 - Make content.opener checks work in electrolysis.
michael@0 6269 if (!gMultiProcessBrowser && browser.contentWindow.opener)
michael@0 6270 return false;
michael@0 6271
michael@0 6272 if (browser.sessionHistory && browser.sessionHistory.count >= 2)
michael@0 6273 return false;
michael@0 6274
michael@0 6275 return true;
michael@0 6276 }
michael@0 6277
michael@0 6278 #ifdef MOZ_SERVICES_SYNC
michael@0 6279 function BrowserOpenSyncTabs() {
michael@0 6280 switchToTabHavingURI("about:sync-tabs", true);
michael@0 6281 }
michael@0 6282 #endif
michael@0 6283
michael@0 6284 /**
michael@0 6285 * Format a URL
michael@0 6286 * eg:
michael@0 6287 * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/");
michael@0 6288 * > https://addons.mozilla.org/en-US/firefox/3.0a1/
michael@0 6289 *
michael@0 6290 * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased.
michael@0 6291 */
michael@0 6292 function formatURL(aFormat, aIsPref) {
michael@0 6293 var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
michael@0 6294 return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat);
michael@0 6295 }
michael@0 6296
michael@0 6297 /**
michael@0 6298 * Utility object to handle manipulations of the identity indicators in the UI
michael@0 6299 */
michael@0 6300 var gIdentityHandler = {
michael@0 6301 // Mode strings used to control CSS display
michael@0 6302 IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information
michael@0 6303 IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification
michael@0 6304 IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information
michael@0 6305 IDENTITY_MODE_MIXED_DISPLAY_LOADED : "unknownIdentity mixedContent mixedDisplayContent", // SSL with unauthenticated display content
michael@0 6306 IDENTITY_MODE_MIXED_ACTIVE_LOADED : "unknownIdentity mixedContent mixedActiveContent", // SSL with unauthenticated active (and perhaps also display) content
michael@0 6307 IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED : "unknownIdentity mixedContent mixedDisplayContentLoadedActiveBlocked", // SSL with unauthenticated display content; unauthenticated active content is blocked.
michael@0 6308 IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI
michael@0 6309
michael@0 6310 // Cache the most recent SSLStatus and Location seen in checkIdentity
michael@0 6311 _lastStatus : null,
michael@0 6312 _lastUri : null,
michael@0 6313 _mode : "unknownIdentity",
michael@0 6314
michael@0 6315 // smart getters
michael@0 6316 get _encryptionLabel () {
michael@0 6317 delete this._encryptionLabel;
michael@0 6318 this._encryptionLabel = {};
michael@0 6319 this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] =
michael@0 6320 gNavigatorBundle.getString("identity.encrypted2");
michael@0 6321 this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] =
michael@0 6322 gNavigatorBundle.getString("identity.encrypted2");
michael@0 6323 this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] =
michael@0 6324 gNavigatorBundle.getString("identity.unencrypted");
michael@0 6325 this._encryptionLabel[this.IDENTITY_MODE_MIXED_DISPLAY_LOADED] =
michael@0 6326 gNavigatorBundle.getString("identity.mixed_display_loaded");
michael@0 6327 this._encryptionLabel[this.IDENTITY_MODE_MIXED_ACTIVE_LOADED] =
michael@0 6328 gNavigatorBundle.getString("identity.mixed_active_loaded2");
michael@0 6329 this._encryptionLabel[this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED] =
michael@0 6330 gNavigatorBundle.getString("identity.mixed_display_loaded");
michael@0 6331 return this._encryptionLabel;
michael@0 6332 },
michael@0 6333 get _identityPopup () {
michael@0 6334 delete this._identityPopup;
michael@0 6335 return this._identityPopup = document.getElementById("identity-popup");
michael@0 6336 },
michael@0 6337 get _identityBox () {
michael@0 6338 delete this._identityBox;
michael@0 6339 return this._identityBox = document.getElementById("identity-box");
michael@0 6340 },
michael@0 6341 get _identityPopupContentBox () {
michael@0 6342 delete this._identityPopupContentBox;
michael@0 6343 return this._identityPopupContentBox =
michael@0 6344 document.getElementById("identity-popup-content-box");
michael@0 6345 },
michael@0 6346 get _identityPopupChromeLabel () {
michael@0 6347 delete this._identityPopupChromeLabel;
michael@0 6348 return this._identityPopupChromeLabel =
michael@0 6349 document.getElementById("identity-popup-chromeLabel");
michael@0 6350 },
michael@0 6351 get _identityPopupContentHost () {
michael@0 6352 delete this._identityPopupContentHost;
michael@0 6353 return this._identityPopupContentHost =
michael@0 6354 document.getElementById("identity-popup-content-host");
michael@0 6355 },
michael@0 6356 get _identityPopupContentOwner () {
michael@0 6357 delete this._identityPopupContentOwner;
michael@0 6358 return this._identityPopupContentOwner =
michael@0 6359 document.getElementById("identity-popup-content-owner");
michael@0 6360 },
michael@0 6361 get _identityPopupContentSupp () {
michael@0 6362 delete this._identityPopupContentSupp;
michael@0 6363 return this._identityPopupContentSupp =
michael@0 6364 document.getElementById("identity-popup-content-supplemental");
michael@0 6365 },
michael@0 6366 get _identityPopupContentVerif () {
michael@0 6367 delete this._identityPopupContentVerif;
michael@0 6368 return this._identityPopupContentVerif =
michael@0 6369 document.getElementById("identity-popup-content-verifier");
michael@0 6370 },
michael@0 6371 get _identityPopupEncLabel () {
michael@0 6372 delete this._identityPopupEncLabel;
michael@0 6373 return this._identityPopupEncLabel =
michael@0 6374 document.getElementById("identity-popup-encryption-label");
michael@0 6375 },
michael@0 6376 get _identityIconLabel () {
michael@0 6377 delete this._identityIconLabel;
michael@0 6378 return this._identityIconLabel = document.getElementById("identity-icon-label");
michael@0 6379 },
michael@0 6380 get _overrideService () {
michael@0 6381 delete this._overrideService;
michael@0 6382 return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
michael@0 6383 .getService(Ci.nsICertOverrideService);
michael@0 6384 },
michael@0 6385 get _identityIconCountryLabel () {
michael@0 6386 delete this._identityIconCountryLabel;
michael@0 6387 return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
michael@0 6388 },
michael@0 6389 get _identityIcon () {
michael@0 6390 delete this._identityIcon;
michael@0 6391 return this._identityIcon = document.getElementById("page-proxy-favicon");
michael@0 6392 },
michael@0 6393 get _permissionsContainer () {
michael@0 6394 delete this._permissionsContainer;
michael@0 6395 return this._permissionsContainer = document.getElementById("identity-popup-permissions");
michael@0 6396 },
michael@0 6397 get _permissionList () {
michael@0 6398 delete this._permissionList;
michael@0 6399 return this._permissionList = document.getElementById("identity-popup-permission-list");
michael@0 6400 },
michael@0 6401
michael@0 6402 /**
michael@0 6403 * Rebuild cache of the elements that may or may not exist depending
michael@0 6404 * on whether there's a location bar.
michael@0 6405 */
michael@0 6406 _cacheElements : function() {
michael@0 6407 delete this._identityBox;
michael@0 6408 delete this._identityIconLabel;
michael@0 6409 delete this._identityIconCountryLabel;
michael@0 6410 delete this._identityIcon;
michael@0 6411 delete this._permissionsContainer;
michael@0 6412 delete this._permissionList;
michael@0 6413 this._identityBox = document.getElementById("identity-box");
michael@0 6414 this._identityIconLabel = document.getElementById("identity-icon-label");
michael@0 6415 this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
michael@0 6416 this._identityIcon = document.getElementById("page-proxy-favicon");
michael@0 6417 this._permissionsContainer = document.getElementById("identity-popup-permissions");
michael@0 6418 this._permissionList = document.getElementById("identity-popup-permission-list");
michael@0 6419 },
michael@0 6420
michael@0 6421 /**
michael@0 6422 * Handler for commands on the help button in the "identity-popup" panel.
michael@0 6423 */
michael@0 6424 handleHelpCommand : function(event) {
michael@0 6425 openHelpLink("secure-connection");
michael@0 6426 this._identityPopup.hidePopup();
michael@0 6427 },
michael@0 6428
michael@0 6429 /**
michael@0 6430 * Handler for mouseclicks on the "More Information" button in the
michael@0 6431 * "identity-popup" panel.
michael@0 6432 */
michael@0 6433 handleMoreInfoClick : function(event) {
michael@0 6434 displaySecurityInfo();
michael@0 6435 event.stopPropagation();
michael@0 6436 this._identityPopup.hidePopup();
michael@0 6437 },
michael@0 6438
michael@0 6439 /**
michael@0 6440 * Helper to parse out the important parts of _lastStatus (of the SSL cert in
michael@0 6441 * particular) for use in constructing identity UI strings
michael@0 6442 */
michael@0 6443 getIdentityData : function() {
michael@0 6444 var result = {};
michael@0 6445 var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
michael@0 6446 var cert = status.serverCert;
michael@0 6447
michael@0 6448 // Human readable name of Subject
michael@0 6449 result.subjectOrg = cert.organization;
michael@0 6450
michael@0 6451 // SubjectName fields, broken up for individual access
michael@0 6452 if (cert.subjectName) {
michael@0 6453 result.subjectNameFields = {};
michael@0 6454 cert.subjectName.split(",").forEach(function(v) {
michael@0 6455 var field = v.split("=");
michael@0 6456 this[field[0]] = field[1];
michael@0 6457 }, result.subjectNameFields);
michael@0 6458
michael@0 6459 // Call out city, state, and country specifically
michael@0 6460 result.city = result.subjectNameFields.L;
michael@0 6461 result.state = result.subjectNameFields.ST;
michael@0 6462 result.country = result.subjectNameFields.C;
michael@0 6463 }
michael@0 6464
michael@0 6465 // Human readable name of Certificate Authority
michael@0 6466 result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
michael@0 6467 result.cert = cert;
michael@0 6468
michael@0 6469 return result;
michael@0 6470 },
michael@0 6471
michael@0 6472 /**
michael@0 6473 * Determine the identity of the page being displayed by examining its SSL cert
michael@0 6474 * (if available) and, if necessary, update the UI to reflect this. Intended to
michael@0 6475 * be called by onSecurityChange
michael@0 6476 *
michael@0 6477 * @param PRUint32 state
michael@0 6478 * @param nsIURI uri The address for which the UI should be updated.
michael@0 6479 */
michael@0 6480 checkIdentity : function(state, uri) {
michael@0 6481 var currentStatus = gBrowser.securityUI
michael@0 6482 .QueryInterface(Components.interfaces.nsISSLStatusProvider)
michael@0 6483 .SSLStatus;
michael@0 6484 this._lastStatus = currentStatus;
michael@0 6485 this._lastUri = uri;
michael@0 6486
michael@0 6487 let nsIWebProgressListener = Ci.nsIWebProgressListener;
michael@0 6488
michael@0 6489 // For some URIs like data: we can't get a host and so can't do
michael@0 6490 // anything useful here.
michael@0 6491 let unknown = false;
michael@0 6492 try {
michael@0 6493 uri.host;
michael@0 6494 } catch (e) { unknown = true; }
michael@0 6495
michael@0 6496 // Chrome URIs however get special treatment. Some chrome URIs are
michael@0 6497 // whitelisted to provide a positive security signal to the user.
michael@0 6498 let whitelist = /^about:(accounts|addons|app-manager|config|crashes|customizing|healthreport|home|newaddon|permissions|preferences|privatebrowsing|sessionrestore|support|welcomeback)/i;
michael@0 6499 let isChromeUI = uri.schemeIs("about") && whitelist.test(uri.spec);
michael@0 6500 if (isChromeUI) {
michael@0 6501 this.setMode(this.IDENTITY_MODE_CHROMEUI);
michael@0 6502 } else if (unknown) {
michael@0 6503 this.setMode(this.IDENTITY_MODE_UNKNOWN);
michael@0 6504 } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
michael@0 6505 this.setMode(this.IDENTITY_MODE_IDENTIFIED);
michael@0 6506 } else if (state & nsIWebProgressListener.STATE_IS_SECURE) {
michael@0 6507 this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
michael@0 6508 } else if (state & nsIWebProgressListener.STATE_IS_BROKEN) {
michael@0 6509 if ((state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) &&
michael@0 6510 gPrefService.getBoolPref("security.mixed_content.block_active_content")) {
michael@0 6511 this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_LOADED);
michael@0 6512 } else if ((state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) &&
michael@0 6513 gPrefService.getBoolPref("security.mixed_content.block_active_content")) {
michael@0 6514 this.setMode(this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED);
michael@0 6515 } else {
michael@0 6516 this.setMode(this.IDENTITY_MODE_MIXED_DISPLAY_LOADED);
michael@0 6517 }
michael@0 6518 } else {
michael@0 6519 this.setMode(this.IDENTITY_MODE_UNKNOWN);
michael@0 6520 }
michael@0 6521
michael@0 6522 // Ensure the doorhanger is shown when mixed active content is blocked.
michael@0 6523 if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT)
michael@0 6524 this.showMixedContentDoorhanger();
michael@0 6525 },
michael@0 6526
michael@0 6527 /**
michael@0 6528 * Display the Mixed Content Blocker doohanger, providing an option
michael@0 6529 * to the user to override mixed content blocking
michael@0 6530 */
michael@0 6531 showMixedContentDoorhanger : function() {
michael@0 6532 // If we've already got an active notification, bail out to avoid showing it repeatedly.
michael@0 6533 if (PopupNotifications.getNotification("mixed-content-blocked", gBrowser.selectedBrowser))
michael@0 6534 return;
michael@0 6535
michael@0 6536 let brandBundle = document.getElementById("bundle_brand");
michael@0 6537 let brandShortName = brandBundle.getString("brandShortName");
michael@0 6538 let messageString = gNavigatorBundle.getFormattedString("mixedContentBlocked.message", [brandShortName]);
michael@0 6539 let action = {
michael@0 6540 label: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.label"),
michael@0 6541 accessKey: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.accesskey"),
michael@0 6542 callback: function() { /* NOP */ }
michael@0 6543 };
michael@0 6544 let secondaryActions = [
michael@0 6545 {
michael@0 6546 label: gNavigatorBundle.getString("mixedContentBlocked.unblock.label"),
michael@0 6547 accessKey: gNavigatorBundle.getString("mixedContentBlocked.unblock.accesskey"),
michael@0 6548 callback: function() {
michael@0 6549 // Use telemetry to measure how often unblocking happens
michael@0 6550 const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
michael@0 6551 let histogram =
michael@0 6552 Services.telemetry.getHistogramById("MIXED_CONTENT_UNBLOCK_COUNTER");
michael@0 6553 histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT);
michael@0 6554 // Reload the page with the content unblocked
michael@0 6555 BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
michael@0 6556 }
michael@0 6557 }
michael@0 6558 ];
michael@0 6559 let options = {
michael@0 6560 dismissed: true,
michael@0 6561 learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "mixed-content",
michael@0 6562 };
michael@0 6563 PopupNotifications.show(gBrowser.selectedBrowser, "mixed-content-blocked",
michael@0 6564 messageString, "mixed-content-blocked-notification-icon",
michael@0 6565 action, secondaryActions, options);
michael@0 6566 },
michael@0 6567
michael@0 6568 /**
michael@0 6569 * Return the eTLD+1 version of the current hostname
michael@0 6570 */
michael@0 6571 getEffectiveHost : function() {
michael@0 6572 if (!this._IDNService)
michael@0 6573 this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
michael@0 6574 .getService(Ci.nsIIDNService);
michael@0 6575 try {
michael@0 6576 let baseDomain =
michael@0 6577 Services.eTLD.getBaseDomainFromHost(this._lastUri.host);
michael@0 6578 return this._IDNService.convertToDisplayIDN(baseDomain, {});
michael@0 6579 } catch (e) {
michael@0 6580 // If something goes wrong (e.g. host is an IP address) just fail back
michael@0 6581 // to the full domain.
michael@0 6582 return this._lastUri.host;
michael@0 6583 }
michael@0 6584 },
michael@0 6585
michael@0 6586 /**
michael@0 6587 * Update the UI to reflect the specified mode, which should be one of the
michael@0 6588 * IDENTITY_MODE_* constants.
michael@0 6589 */
michael@0 6590 setMode : function(newMode) {
michael@0 6591 if (!this._identityBox) {
michael@0 6592 // No identity box means the identity box is not visible, in which
michael@0 6593 // case there's nothing to do.
michael@0 6594 return;
michael@0 6595 }
michael@0 6596
michael@0 6597 this._identityPopup.className = newMode;
michael@0 6598 this._identityBox.className = newMode;
michael@0 6599 this.setIdentityMessages(newMode);
michael@0 6600
michael@0 6601 // Update the popup too, if it's open
michael@0 6602 if (this._identityPopup.state == "open")
michael@0 6603 this.setPopupMessages(newMode);
michael@0 6604
michael@0 6605 this._mode = newMode;
michael@0 6606 },
michael@0 6607
michael@0 6608 /**
michael@0 6609 * Set up the messages for the primary identity UI based on the specified mode,
michael@0 6610 * and the details of the SSL cert, where applicable
michael@0 6611 *
michael@0 6612 * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
michael@0 6613 */
michael@0 6614 setIdentityMessages : function(newMode) {
michael@0 6615 let icon_label = "";
michael@0 6616 let tooltip = "";
michael@0 6617 let icon_country_label = "";
michael@0 6618 let icon_labels_dir = "ltr";
michael@0 6619
michael@0 6620 switch (newMode) {
michael@0 6621 case this.IDENTITY_MODE_DOMAIN_VERIFIED: {
michael@0 6622 let iData = this.getIdentityData();
michael@0 6623
michael@0 6624 // Verifier is either the CA Org, for a normal cert, or a special string
michael@0 6625 // for certs that are trusted because of a security exception.
michael@0 6626 tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
michael@0 6627 [iData.caOrg]);
michael@0 6628
michael@0 6629 // This can't throw, because URI's with a host that throw don't end up in this case.
michael@0 6630 let host = this._lastUri.host;
michael@0 6631 let port = 443;
michael@0 6632 try {
michael@0 6633 if (this._lastUri.port > 0)
michael@0 6634 port = this._lastUri.port;
michael@0 6635 } catch (e) {}
michael@0 6636
michael@0 6637 if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {}))
michael@0 6638 tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
michael@0 6639
michael@0 6640 break; }
michael@0 6641 case this.IDENTITY_MODE_IDENTIFIED: {
michael@0 6642 // If it's identified, then we can populate the dialog with credentials
michael@0 6643 let iData = this.getIdentityData();
michael@0 6644 tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
michael@0 6645 [iData.caOrg]);
michael@0 6646 icon_label = iData.subjectOrg;
michael@0 6647 if (iData.country)
michael@0 6648 icon_country_label = "(" + iData.country + ")";
michael@0 6649
michael@0 6650 // If the organization name starts with an RTL character, then
michael@0 6651 // swap the positions of the organization and country code labels.
michael@0 6652 // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
michael@0 6653 // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
michael@0 6654 // fixed, this test should be replaced by one adhering to the
michael@0 6655 // Unicode Bidirectional Algorithm proper (at the paragraph level).
michael@0 6656 icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
michael@0 6657 "rtl" : "ltr";
michael@0 6658 break; }
michael@0 6659 case this.IDENTITY_MODE_CHROMEUI:
michael@0 6660 let brandBundle = document.getElementById("bundle_brand");
michael@0 6661 icon_label = brandBundle.getString("brandShortName");
michael@0 6662 break;
michael@0 6663 default:
michael@0 6664 tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
michael@0 6665 }
michael@0 6666
michael@0 6667 // Push the appropriate strings out to the UI
michael@0 6668 this._identityBox.tooltipText = tooltip;
michael@0 6669 this._identityIconLabel.value = icon_label;
michael@0 6670 this._identityIconCountryLabel.value = icon_country_label;
michael@0 6671 // Set cropping and direction
michael@0 6672 this._identityIconLabel.crop = icon_country_label ? "end" : "center";
michael@0 6673 this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
michael@0 6674 // Hide completely if the organization label is empty
michael@0 6675 this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
michael@0 6676 },
michael@0 6677
michael@0 6678 /**
michael@0 6679 * Set up the title and content messages for the identity message popup,
michael@0 6680 * based on the specified mode, and the details of the SSL cert, where
michael@0 6681 * applicable
michael@0 6682 *
michael@0 6683 * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
michael@0 6684 */
michael@0 6685 setPopupMessages : function(newMode) {
michael@0 6686
michael@0 6687 this._identityPopup.className = newMode;
michael@0 6688 this._identityPopupContentBox.className = newMode;
michael@0 6689
michael@0 6690 // Set the static strings up front
michael@0 6691 this._identityPopupEncLabel.textContent = this._encryptionLabel[newMode];
michael@0 6692
michael@0 6693 // Initialize the optional strings to empty values
michael@0 6694 let supplemental = "";
michael@0 6695 let verifier = "";
michael@0 6696 let host = "";
michael@0 6697 let owner = "";
michael@0 6698
michael@0 6699 switch (newMode) {
michael@0 6700 case this.IDENTITY_MODE_DOMAIN_VERIFIED:
michael@0 6701 host = this.getEffectiveHost();
michael@0 6702 owner = gNavigatorBundle.getString("identity.ownerUnknown2");
michael@0 6703 verifier = this._identityBox.tooltipText;
michael@0 6704 break;
michael@0 6705 case this.IDENTITY_MODE_IDENTIFIED: {
michael@0 6706 // If it's identified, then we can populate the dialog with credentials
michael@0 6707 let iData = this.getIdentityData();
michael@0 6708 host = this.getEffectiveHost();
michael@0 6709 owner = iData.subjectOrg;
michael@0 6710 verifier = this._identityBox.tooltipText;
michael@0 6711
michael@0 6712 // Build an appropriate supplemental block out of whatever location data we have
michael@0 6713 if (iData.city)
michael@0 6714 supplemental += iData.city + "\n";
michael@0 6715 if (iData.state && iData.country)
michael@0 6716 supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country",
michael@0 6717 [iData.state, iData.country]);
michael@0 6718 else if (iData.state) // State only
michael@0 6719 supplemental += iData.state;
michael@0 6720 else if (iData.country) // Country only
michael@0 6721 supplemental += iData.country;
michael@0 6722 break; }
michael@0 6723 case this.IDENTITY_MODE_CHROMEUI: {
michael@0 6724 let brandBundle = document.getElementById("bundle_brand");
michael@0 6725 let brandShortName = brandBundle.getString("brandShortName");
michael@0 6726 this._identityPopupChromeLabel.textContent = gNavigatorBundle.getFormattedString("identity.chrome",
michael@0 6727 [brandShortName]);
michael@0 6728 break; }
michael@0 6729 }
michael@0 6730
michael@0 6731 // Push the appropriate strings out to the UI
michael@0 6732 this._identityPopupContentHost.textContent = host;
michael@0 6733 this._identityPopupContentOwner.textContent = owner;
michael@0 6734 this._identityPopupContentSupp.textContent = supplemental;
michael@0 6735 this._identityPopupContentVerif.textContent = verifier;
michael@0 6736 },
michael@0 6737
michael@0 6738 /**
michael@0 6739 * Click handler for the identity-box element in primary chrome.
michael@0 6740 */
michael@0 6741 handleIdentityButtonEvent : function(event) {
michael@0 6742 event.stopPropagation();
michael@0 6743
michael@0 6744 if ((event.type == "click" && event.button != 0) ||
michael@0 6745 (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
michael@0 6746 event.keyCode != KeyEvent.DOM_VK_RETURN)) {
michael@0 6747 return; // Left click, space or enter only
michael@0 6748 }
michael@0 6749
michael@0 6750 // Don't allow left click, space or enter if the location has been modified.
michael@0 6751 if (gURLBar.getAttribute("pageproxystate") != "valid") {
michael@0 6752 return;
michael@0 6753 }
michael@0 6754
michael@0 6755 // Make sure that the display:none style we set in xul is removed now that
michael@0 6756 // the popup is actually needed
michael@0 6757 this._identityPopup.hidden = false;
michael@0 6758
michael@0 6759 // Update the popup strings
michael@0 6760 this.setPopupMessages(this._identityBox.className);
michael@0 6761
michael@0 6762 this.updateSitePermissions();
michael@0 6763
michael@0 6764 // Add the "open" attribute to the identity box for styling
michael@0 6765 this._identityBox.setAttribute("open", "true");
michael@0 6766 var self = this;
michael@0 6767 this._identityPopup.addEventListener("popuphidden", function onPopupHidden(e) {
michael@0 6768 e.currentTarget.removeEventListener("popuphidden", onPopupHidden, false);
michael@0 6769 self._identityBox.removeAttribute("open");
michael@0 6770 }, false);
michael@0 6771
michael@0 6772 // Now open the popup, anchored off the primary chrome element
michael@0 6773 this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
michael@0 6774 },
michael@0 6775
michael@0 6776 onPopupShown : function(event) {
michael@0 6777 document.getElementById('identity-popup-more-info-button').focus();
michael@0 6778
michael@0 6779 this._identityPopup.addEventListener("blur", this, true);
michael@0 6780 this._identityPopup.addEventListener("popuphidden", this);
michael@0 6781 },
michael@0 6782
michael@0 6783 onDragStart: function (event) {
michael@0 6784 if (gURLBar.getAttribute("pageproxystate") != "valid")
michael@0 6785 return;
michael@0 6786
michael@0 6787 var value = content.location.href;
michael@0 6788 var urlString = value + "\n" + content.document.title;
michael@0 6789 var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
michael@0 6790
michael@0 6791 var dt = event.dataTransfer;
michael@0 6792 dt.setData("text/x-moz-url", urlString);
michael@0 6793 dt.setData("text/uri-list", value);
michael@0 6794 dt.setData("text/plain", value);
michael@0 6795 dt.setData("text/html", htmlString);
michael@0 6796 dt.setDragImage(gProxyFavIcon, 16, 16);
michael@0 6797 },
michael@0 6798
michael@0 6799 handleEvent: function (event) {
michael@0 6800 switch (event.type) {
michael@0 6801 case "blur":
michael@0 6802 // Focus hasn't moved yet, need to wait until after the blur event.
michael@0 6803 setTimeout(() => {
michael@0 6804 if (document.activeElement &&
michael@0 6805 document.activeElement.compareDocumentPosition(this._identityPopup) &
michael@0 6806 Node.DOCUMENT_POSITION_CONTAINS)
michael@0 6807 return;
michael@0 6808
michael@0 6809 this._identityPopup.hidePopup();
michael@0 6810 }, 0);
michael@0 6811 break;
michael@0 6812 case "popuphidden":
michael@0 6813 this._identityPopup.removeEventListener("blur", this, true);
michael@0 6814 this._identityPopup.removeEventListener("popuphidden", this);
michael@0 6815 break;
michael@0 6816 }
michael@0 6817 },
michael@0 6818
michael@0 6819 updateSitePermissions: function () {
michael@0 6820 while (this._permissionList.hasChildNodes())
michael@0 6821 this._permissionList.removeChild(this._permissionList.lastChild);
michael@0 6822
michael@0 6823 let uri = gBrowser.currentURI;
michael@0 6824
michael@0 6825 for (let permission of SitePermissions.listPermissions()) {
michael@0 6826 let state = SitePermissions.get(uri, permission);
michael@0 6827
michael@0 6828 if (state == SitePermissions.UNKNOWN)
michael@0 6829 continue;
michael@0 6830
michael@0 6831 let item = this._createPermissionItem(permission, state);
michael@0 6832 this._permissionList.appendChild(item);
michael@0 6833 }
michael@0 6834
michael@0 6835 this._permissionsContainer.hidden = !this._permissionList.hasChildNodes();
michael@0 6836 },
michael@0 6837
michael@0 6838 setPermission: function (aPermission, aState) {
michael@0 6839 if (aState == SitePermissions.getDefault(aPermission))
michael@0 6840 SitePermissions.remove(gBrowser.currentURI, aPermission);
michael@0 6841 else
michael@0 6842 SitePermissions.set(gBrowser.currentURI, aPermission, aState);
michael@0 6843 },
michael@0 6844
michael@0 6845 _createPermissionItem: function (aPermission, aState) {
michael@0 6846 let menulist = document.createElement("menulist");
michael@0 6847 let menupopup = document.createElement("menupopup");
michael@0 6848 for (let state of SitePermissions.getAvailableStates(aPermission)) {
michael@0 6849 let menuitem = document.createElement("menuitem");
michael@0 6850 menuitem.setAttribute("value", state);
michael@0 6851 menuitem.setAttribute("label", SitePermissions.getStateLabel(aPermission, state));
michael@0 6852 menupopup.appendChild(menuitem);
michael@0 6853 }
michael@0 6854 menulist.appendChild(menupopup);
michael@0 6855 menulist.setAttribute("value", aState);
michael@0 6856 menulist.setAttribute("oncommand", "gIdentityHandler.setPermission('" +
michael@0 6857 aPermission + "', this.value)");
michael@0 6858 menulist.setAttribute("id", "identity-popup-permission:" + aPermission);
michael@0 6859
michael@0 6860 let label = document.createElement("label");
michael@0 6861 label.setAttribute("flex", "1");
michael@0 6862 label.setAttribute("control", menulist.getAttribute("id"));
michael@0 6863 label.setAttribute("value", SitePermissions.getPermissionLabel(aPermission));
michael@0 6864
michael@0 6865 let container = document.createElement("hbox");
michael@0 6866 container.setAttribute("align", "center");
michael@0 6867 container.appendChild(label);
michael@0 6868 container.appendChild(menulist);
michael@0 6869 return container;
michael@0 6870 }
michael@0 6871 };
michael@0 6872
michael@0 6873 function getNotificationBox(aWindow) {
michael@0 6874 var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
michael@0 6875 if (foundBrowser)
michael@0 6876 return gBrowser.getNotificationBox(foundBrowser)
michael@0 6877 return null;
michael@0 6878 };
michael@0 6879
michael@0 6880 function getTabModalPromptBox(aWindow) {
michael@0 6881 var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
michael@0 6882 if (foundBrowser)
michael@0 6883 return gBrowser.getTabModalPromptBox(foundBrowser);
michael@0 6884 return null;
michael@0 6885 };
michael@0 6886
michael@0 6887 /* DEPRECATED */
michael@0 6888 function getBrowser() gBrowser;
michael@0 6889 function getNavToolbox() gNavToolbox;
michael@0 6890
michael@0 6891 let gPrivateBrowsingUI = {
michael@0 6892 init: function PBUI_init() {
michael@0 6893 // Do nothing for normal windows
michael@0 6894 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
michael@0 6895 return;
michael@0 6896 }
michael@0 6897
michael@0 6898 // Disable the Clear Recent History... menu item when in PB mode
michael@0 6899 // temporary fix until bug 463607 is fixed
michael@0 6900 document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
michael@0 6901
michael@0 6902 if (window.location.href == getBrowserURL()) {
michael@0 6903 // Adjust the window's title
michael@0 6904 let docElement = document.documentElement;
michael@0 6905 if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
michael@0 6906 docElement.setAttribute("title",
michael@0 6907 docElement.getAttribute("title_privatebrowsing"));
michael@0 6908 docElement.setAttribute("titlemodifier",
michael@0 6909 docElement.getAttribute("titlemodifier_privatebrowsing"));
michael@0 6910 }
michael@0 6911 docElement.setAttribute("privatebrowsingmode",
michael@0 6912 PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary");
michael@0 6913 gBrowser.updateTitlebar();
michael@0 6914
michael@0 6915 if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
michael@0 6916 // Adjust the New Window menu entries
michael@0 6917 [
michael@0 6918 { normal: "menu_newNavigator", private: "menu_newPrivateWindow" },
michael@0 6919 ].forEach(function(menu) {
michael@0 6920 let newWindow = document.getElementById(menu.normal);
michael@0 6921 let newPrivateWindow = document.getElementById(menu.private);
michael@0 6922 if (newWindow && newPrivateWindow) {
michael@0 6923 newPrivateWindow.hidden = true;
michael@0 6924 newWindow.label = newPrivateWindow.label;
michael@0 6925 newWindow.accessKey = newPrivateWindow.accessKey;
michael@0 6926 newWindow.command = newPrivateWindow.command;
michael@0 6927 }
michael@0 6928 });
michael@0 6929 }
michael@0 6930 }
michael@0 6931
michael@0 6932 if (gURLBar &&
michael@0 6933 !PrivateBrowsingUtils.permanentPrivateBrowsing) {
michael@0 6934 // Disable switch to tab autocompletion for private windows
michael@0 6935 // (not for "Always use private browsing" mode)
michael@0 6936 gURLBar.setAttribute("autocompletesearchparam", "");
michael@0 6937 }
michael@0 6938 }
michael@0 6939 };
michael@0 6940
michael@0 6941 let gRemoteTabsUI = {
michael@0 6942 init: function() {
michael@0 6943 if (window.location.href != getBrowserURL()) {
michael@0 6944 return;
michael@0 6945 }
michael@0 6946
michael@0 6947 let remoteTabs = gPrefService.getBoolPref("browser.tabs.remote");
michael@0 6948 let autostart = gPrefService.getBoolPref("browser.tabs.remote.autostart");
michael@0 6949
michael@0 6950 let newRemoteWindow = document.getElementById("menu_newRemoteWindow");
michael@0 6951 let newNonRemoteWindow = document.getElementById("menu_newNonRemoteWindow");
michael@0 6952
michael@0 6953 if (!remoteTabs) {
michael@0 6954 newRemoteWindow.hidden = true;
michael@0 6955 newNonRemoteWindow.hidden = true;
michael@0 6956 return;
michael@0 6957 }
michael@0 6958
michael@0 6959 newRemoteWindow.hidden = autostart;
michael@0 6960 newNonRemoteWindow.hidden = !autostart;
michael@0 6961 }
michael@0 6962 };
michael@0 6963
michael@0 6964 /**
michael@0 6965 * Switch to a tab that has a given URI, and focusses its browser window.
michael@0 6966 * If a matching tab is in this window, it will be switched to. Otherwise, other
michael@0 6967 * windows will be searched.
michael@0 6968 *
michael@0 6969 * @param aURI
michael@0 6970 * URI to search for
michael@0 6971 * @param aOpenNew
michael@0 6972 * True to open a new tab and switch to it, if no existing tab is found.
michael@0 6973 * If no suitable window is found, a new one will be opened.
michael@0 6974 * @param aOpenParams
michael@0 6975 * If switching to this URI results in us opening a tab, aOpenParams
michael@0 6976 * will be the parameter object that gets passed to openUILinkIn. Please
michael@0 6977 * see the documentation for openUILinkIn to see what parameters can be
michael@0 6978 * passed via this object.
michael@0 6979 * @return True if an existing tab was found, false otherwise
michael@0 6980 */
michael@0 6981 function switchToTabHavingURI(aURI, aOpenNew, aOpenParams) {
michael@0 6982 // Certain URLs can be switched to irrespective of the source or destination
michael@0 6983 // window being in private browsing mode:
michael@0 6984 const kPrivateBrowsingWhitelist = new Set([
michael@0 6985 "about:customizing",
michael@0 6986 ]);
michael@0 6987 // This will switch to the tab in aWindow having aURI, if present.
michael@0 6988 function switchIfURIInWindow(aWindow) {
michael@0 6989 // Only switch to the tab if neither the source nor the destination window
michael@0 6990 // are private and they are not in permanent private browsing mode
michael@0 6991 if (!kPrivateBrowsingWhitelist.has(aURI.spec) &&
michael@0 6992 (PrivateBrowsingUtils.isWindowPrivate(window) ||
michael@0 6993 PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
michael@0 6994 !PrivateBrowsingUtils.permanentPrivateBrowsing) {
michael@0 6995 return false;
michael@0 6996 }
michael@0 6997
michael@0 6998 let browsers = aWindow.gBrowser.browsers;
michael@0 6999 for (let i = 0; i < browsers.length; i++) {
michael@0 7000 let browser = browsers[i];
michael@0 7001 if (browser.currentURI.equals(aURI)) {
michael@0 7002 // Focus the matching window & tab
michael@0 7003 aWindow.focus();
michael@0 7004 aWindow.gBrowser.tabContainer.selectedIndex = i;
michael@0 7005 return true;
michael@0 7006 }
michael@0 7007 }
michael@0 7008 return false;
michael@0 7009 }
michael@0 7010
michael@0 7011 // This can be passed either nsIURI or a string.
michael@0 7012 if (!(aURI instanceof Ci.nsIURI))
michael@0 7013 aURI = Services.io.newURI(aURI, null, null);
michael@0 7014
michael@0 7015 let isBrowserWindow = !!window.gBrowser;
michael@0 7016
michael@0 7017 // Prioritise this window.
michael@0 7018 if (isBrowserWindow && switchIfURIInWindow(window))
michael@0 7019 return true;
michael@0 7020
michael@0 7021 let winEnum = Services.wm.getEnumerator("navigator:browser");
michael@0 7022 while (winEnum.hasMoreElements()) {
michael@0 7023 let browserWin = winEnum.getNext();
michael@0 7024 // Skip closed (but not yet destroyed) windows,
michael@0 7025 // and the current window (which was checked earlier).
michael@0 7026 if (browserWin.closed || browserWin == window)
michael@0 7027 continue;
michael@0 7028 if (switchIfURIInWindow(browserWin))
michael@0 7029 return true;
michael@0 7030 }
michael@0 7031
michael@0 7032 // No opened tab has that url.
michael@0 7033 if (aOpenNew) {
michael@0 7034 if (isBrowserWindow && isTabEmpty(gBrowser.selectedTab))
michael@0 7035 openUILinkIn(aURI.spec, "current", aOpenParams);
michael@0 7036 else
michael@0 7037 openUILinkIn(aURI.spec, "tab", aOpenParams);
michael@0 7038 }
michael@0 7039
michael@0 7040 return false;
michael@0 7041 }
michael@0 7042
michael@0 7043 let RestoreLastSessionObserver = {
michael@0 7044 init: function () {
michael@0 7045 if (SessionStore.canRestoreLastSession &&
michael@0 7046 !PrivateBrowsingUtils.isWindowPrivate(window)) {
michael@0 7047 Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
michael@0 7048 goSetCommandEnabled("Browser:RestoreLastSession", true);
michael@0 7049 }
michael@0 7050 },
michael@0 7051
michael@0 7052 observe: function () {
michael@0 7053 // The last session can only be restored once so there's
michael@0 7054 // no way we need to re-enable our menu item.
michael@0 7055 Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
michael@0 7056 goSetCommandEnabled("Browser:RestoreLastSession", false);
michael@0 7057 },
michael@0 7058
michael@0 7059 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
michael@0 7060 Ci.nsISupportsWeakReference])
michael@0 7061 };
michael@0 7062
michael@0 7063 function restoreLastSession() {
michael@0 7064 SessionStore.restoreLastSession();
michael@0 7065 }
michael@0 7066
michael@0 7067 var TabContextMenu = {
michael@0 7068 contextTab: null,
michael@0 7069 updateContextMenu: function updateContextMenu(aPopupMenu) {
michael@0 7070 this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
michael@0 7071 aPopupMenu.triggerNode : gBrowser.selectedTab;
michael@0 7072 let disabled = gBrowser.tabs.length == 1;
michael@0 7073
michael@0 7074 var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
michael@0 7075 for (let menuItem of menuItems)
michael@0 7076 menuItem.disabled = disabled;
michael@0 7077
michael@0 7078 disabled = gBrowser.visibleTabs.length == 1;
michael@0 7079 menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
michael@0 7080 for (let menuItem of menuItems)
michael@0 7081 menuItem.disabled = disabled;
michael@0 7082
michael@0 7083 // Session store
michael@0 7084 document.getElementById("context_undoCloseTab").disabled =
michael@0 7085 SessionStore.getClosedTabCount(window) == 0;
michael@0 7086
michael@0 7087 // Only one of pin/unpin should be visible
michael@0 7088 document.getElementById("context_pinTab").hidden = this.contextTab.pinned;
michael@0 7089 document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned;
michael@0 7090
michael@0 7091 // Disable "Close Tabs to the Right" if there are no tabs
michael@0 7092 // following it and hide it when the user rightclicked on a pinned
michael@0 7093 // tab.
michael@0 7094 document.getElementById("context_closeTabsToTheEnd").disabled =
michael@0 7095 gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0;
michael@0 7096 document.getElementById("context_closeTabsToTheEnd").hidden = this.contextTab.pinned;
michael@0 7097
michael@0 7098 // Disable "Close other Tabs" if there is only one unpinned tab and
michael@0 7099 // hide it when the user rightclicked on a pinned tab.
michael@0 7100 let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs;
michael@0 7101 document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1;
michael@0 7102 document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned;
michael@0 7103
michael@0 7104 // Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible.
michael@0 7105 let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs");
michael@0 7106 bookmarkAllTabs.hidden = this.contextTab.pinned;
michael@0 7107 if (!bookmarkAllTabs.hidden)
michael@0 7108 PlacesCommandHook.updateBookmarkAllTabsCommand();
michael@0 7109
michael@0 7110 // Hide "Move to Group" if it's a pinned tab.
michael@0 7111 document.getElementById("context_tabViewMenu").hidden =
michael@0 7112 (this.contextTab.pinned || !TabView.firstUseExperienced);
michael@0 7113 }
michael@0 7114 };
michael@0 7115
michael@0 7116 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
michael@0 7117 "resource:///modules/devtools/gDevTools.jsm");
michael@0 7118
michael@0 7119 XPCOMUtils.defineLazyModuleGetter(this, "gDevToolsBrowser",
michael@0 7120 "resource:///modules/devtools/gDevTools.jsm");
michael@0 7121
michael@0 7122 Object.defineProperty(this, "HUDService", {
michael@0 7123 get: function HUDService_getter() {
michael@0 7124 let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
michael@0 7125 return devtools.require("devtools/webconsole/hudservice");
michael@0 7126 },
michael@0 7127 configurable: true,
michael@0 7128 enumerable: true
michael@0 7129 });
michael@0 7130
michael@0 7131 // Prompt user to restart the browser in safe mode
michael@0 7132 function safeModeRestart()
michael@0 7133 {
michael@0 7134 // prompt the user to confirm
michael@0 7135 let promptTitle = gNavigatorBundle.getString("safeModeRestartPromptTitle");
michael@0 7136 let promptMessage =
michael@0 7137 gNavigatorBundle.getString("safeModeRestartPromptMessage");
michael@0 7138 let restartText = gNavigatorBundle.getString("safeModeRestartButton");
michael@0 7139 let buttonFlags = (Services.prompt.BUTTON_POS_0 *
michael@0 7140 Services.prompt.BUTTON_TITLE_IS_STRING) +
michael@0 7141 (Services.prompt.BUTTON_POS_1 *
michael@0 7142 Services.prompt.BUTTON_TITLE_CANCEL) +
michael@0 7143 Services.prompt.BUTTON_POS_0_DEFAULT;
michael@0 7144
michael@0 7145 let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage,
michael@0 7146 buttonFlags, restartText, null, null,
michael@0 7147 null, {});
michael@0 7148 if (rv != 0)
michael@0 7149 return;
michael@0 7150
michael@0 7151 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
michael@0 7152 .createInstance(Ci.nsISupportsPRBool);
michael@0 7153 Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
michael@0 7154
michael@0 7155 if (!cancelQuit.data) {
michael@0 7156 Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
michael@0 7157 }
michael@0 7158 }
michael@0 7159
michael@0 7160 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
michael@0 7161 *
michael@0 7162 * |where| can be:
michael@0 7163 * "tab" new tab
michael@0 7164 * "tabshifted" same as "tab" but in background if default is to select new
michael@0 7165 * tabs, and vice versa
michael@0 7166 * "window" new window
michael@0 7167 *
michael@0 7168 * delta is the offset to the history entry that you want to load.
michael@0 7169 */
michael@0 7170 function duplicateTabIn(aTab, where, delta) {
michael@0 7171 let newTab = SessionStore.duplicateTab(window, aTab, delta);
michael@0 7172
michael@0 7173 switch (where) {
michael@0 7174 case "window":
michael@0 7175 gBrowser.hideTab(newTab);
michael@0 7176 gBrowser.replaceTabWithWindow(newTab);
michael@0 7177 break;
michael@0 7178 case "tabshifted":
michael@0 7179 // A background tab has been opened, nothing else to do here.
michael@0 7180 break;
michael@0 7181 case "tab":
michael@0 7182 gBrowser.selectedTab = newTab;
michael@0 7183 break;
michael@0 7184 }
michael@0 7185 }
michael@0 7186
michael@0 7187 var Scratchpad = {
michael@0 7188 openScratchpad: function SP_openScratchpad() {
michael@0 7189 return this.ScratchpadManager.openScratchpad();
michael@0 7190 }
michael@0 7191 };
michael@0 7192
michael@0 7193 XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() {
michael@0 7194 let tmp = {};
michael@0 7195 Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", tmp);
michael@0 7196 return tmp.ScratchpadManager;
michael@0 7197 });
michael@0 7198
michael@0 7199 var ResponsiveUI = {
michael@0 7200 toggle: function RUI_toggle() {
michael@0 7201 this.ResponsiveUIManager.toggle(window, gBrowser.selectedTab);
michael@0 7202 }
michael@0 7203 };
michael@0 7204
michael@0 7205 XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() {
michael@0 7206 let tmp = {};
michael@0 7207 Cu.import("resource:///modules/devtools/responsivedesign.jsm", tmp);
michael@0 7208 return tmp.ResponsiveUIManager;
michael@0 7209 });
michael@0 7210
michael@0 7211 function openEyedropper() {
michael@0 7212 var eyedropper = new this.Eyedropper(this);
michael@0 7213 eyedropper.open();
michael@0 7214 }
michael@0 7215
michael@0 7216 Object.defineProperty(this, "Eyedropper", {
michael@0 7217 get: function() {
michael@0 7218 let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
michael@0 7219 return devtools.require("devtools/eyedropper/eyedropper").Eyedropper;
michael@0 7220 },
michael@0 7221 configurable: true,
michael@0 7222 enumerable: true
michael@0 7223 });
michael@0 7224
michael@0 7225 XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
michael@0 7226 #ifdef XP_WIN
michael@0 7227 // Only show resizers on Windows 2000 and XP
michael@0 7228 return parseFloat(Services.sysinfo.getProperty("version")) < 6;
michael@0 7229 #else
michael@0 7230 return false;
michael@0 7231 #endif
michael@0 7232 });
michael@0 7233
michael@0 7234 var MousePosTracker = {
michael@0 7235 _listeners: new Set(),
michael@0 7236 _x: 0,
michael@0 7237 _y: 0,
michael@0 7238 get _windowUtils() {
michael@0 7239 delete this._windowUtils;
michael@0 7240 return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
michael@0 7241 },
michael@0 7242
michael@0 7243 addListener: function (listener) {
michael@0 7244 if (this._listeners.has(listener))
michael@0 7245 return;
michael@0 7246
michael@0 7247 listener._hover = false;
michael@0 7248 this._listeners.add(listener);
michael@0 7249
michael@0 7250 this._callListener(listener);
michael@0 7251 },
michael@0 7252
michael@0 7253 removeListener: function (listener) {
michael@0 7254 this._listeners.delete(listener);
michael@0 7255 },
michael@0 7256
michael@0 7257 handleEvent: function (event) {
michael@0 7258 var fullZoom = this._windowUtils.fullZoom;
michael@0 7259 this._x = event.screenX / fullZoom - window.mozInnerScreenX;
michael@0 7260 this._y = event.screenY / fullZoom - window.mozInnerScreenY;
michael@0 7261
michael@0 7262 this._listeners.forEach(function (listener) {
michael@0 7263 try {
michael@0 7264 this._callListener(listener);
michael@0 7265 } catch (e) {
michael@0 7266 Cu.reportError(e);
michael@0 7267 }
michael@0 7268 }, this);
michael@0 7269 },
michael@0 7270
michael@0 7271 _callListener: function (listener) {
michael@0 7272 let rect = listener.getMouseTargetRect();
michael@0 7273 let hover = this._x >= rect.left &&
michael@0 7274 this._x <= rect.right &&
michael@0 7275 this._y >= rect.top &&
michael@0 7276 this._y <= rect.bottom;
michael@0 7277
michael@0 7278 if (hover == listener._hover)
michael@0 7279 return;
michael@0 7280
michael@0 7281 listener._hover = hover;
michael@0 7282
michael@0 7283 if (hover) {
michael@0 7284 if (listener.onMouseEnter)
michael@0 7285 listener.onMouseEnter();
michael@0 7286 } else {
michael@0 7287 if (listener.onMouseLeave)
michael@0 7288 listener.onMouseLeave();
michael@0 7289 }
michael@0 7290 }
michael@0 7291 };
michael@0 7292
michael@0 7293 function focusNextFrame(event) {
michael@0 7294 let fm = Services.focus;
michael@0 7295 let dir = event.shiftKey ? fm.MOVEFOCUS_BACKWARDDOC : fm.MOVEFOCUS_FORWARDDOC;
michael@0 7296 let element = fm.moveFocus(window, null, dir, fm.FLAG_BYKEY);
michael@0 7297 if (element.ownerDocument == document)
michael@0 7298 focusAndSelectUrlBar();
michael@0 7299 }
michael@0 7300 let BrowserChromeTest = {
michael@0 7301 _cb: null,
michael@0 7302 _ready: false,
michael@0 7303 markAsReady: function () {
michael@0 7304 this._ready = true;
michael@0 7305 if (this._cb) {
michael@0 7306 this._cb();
michael@0 7307 this._cb = null;
michael@0 7308 }
michael@0 7309 },
michael@0 7310 runWhenReady: function (cb) {
michael@0 7311 if (this._ready)
michael@0 7312 cb();
michael@0 7313 else
michael@0 7314 this._cb = cb;
michael@0 7315 }
michael@0 7316 };
michael@0 7317
michael@0 7318 function BrowserOpenNewTabOrWindow(event) {
michael@0 7319 if (event.shiftKey) {
michael@0 7320 OpenBrowserWindow();
michael@0 7321 } else {
michael@0 7322 BrowserOpenTab();
michael@0 7323 }
michael@0 7324 }
michael@0 7325
michael@0 7326 let ToolbarIconColor = {
michael@0 7327 init: function () {
michael@0 7328 this._initialized = true;
michael@0 7329
michael@0 7330 window.addEventListener("activate", this);
michael@0 7331 window.addEventListener("deactivate", this);
michael@0 7332 Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
michael@0 7333
michael@0 7334 // If the window isn't active now, we assume that it has never been active
michael@0 7335 // before and will soon become active such that inferFromText will be
michael@0 7336 // called from the initial activate event.
michael@0 7337 if (Services.focus.activeWindow == window)
michael@0 7338 this.inferFromText();
michael@0 7339 },
michael@0 7340
michael@0 7341 uninit: function () {
michael@0 7342 this._initialized = false;
michael@0 7343
michael@0 7344 window.removeEventListener("activate", this);
michael@0 7345 window.removeEventListener("deactivate", this);
michael@0 7346 Services.obs.removeObserver(this, "lightweight-theme-styling-update");
michael@0 7347 },
michael@0 7348
michael@0 7349 handleEvent: function (event) {
michael@0 7350 switch (event.type) {
michael@0 7351 case "activate":
michael@0 7352 case "deactivate":
michael@0 7353 this.inferFromText();
michael@0 7354 break;
michael@0 7355 }
michael@0 7356 },
michael@0 7357
michael@0 7358 observe: function (aSubject, aTopic, aData) {
michael@0 7359 switch (aTopic) {
michael@0 7360 case "lightweight-theme-styling-update":
michael@0 7361 // inferFromText needs to run after LightweightThemeConsumer.jsm's
michael@0 7362 // lightweight-theme-styling-update observer.
michael@0 7363 setTimeout(() => { this.inferFromText(); }, 0);
michael@0 7364 break;
michael@0 7365 }
michael@0 7366 },
michael@0 7367
michael@0 7368 inferFromText: function () {
michael@0 7369 if (!this._initialized)
michael@0 7370 return;
michael@0 7371
michael@0 7372 function parseRGB(aColorString) {
michael@0 7373 let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
michael@0 7374 rgb.shift();
michael@0 7375 return rgb.map(x => parseInt(x));
michael@0 7376 }
michael@0 7377
michael@0 7378 let toolbarSelector = "#navigator-toolbox > toolbar:not([collapsed=true]):not(#addon-bar)";
michael@0 7379 #ifdef XP_MACOSX
michael@0 7380 toolbarSelector += ":not([type=menubar])";
michael@0 7381 #endif
michael@0 7382
michael@0 7383 for (let toolbar of document.querySelectorAll(toolbarSelector)) {
michael@0 7384 let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
michael@0 7385 let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
michael@0 7386 if (luminance <= 110)
michael@0 7387 toolbar.removeAttribute("brighttext");
michael@0 7388 else
michael@0 7389 toolbar.setAttribute("brighttext", "true");
michael@0 7390 }
michael@0 7391 }
michael@0 7392 }

mercurial