browser/base/content/browser.js

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

mercurial