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