Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
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 "use strict";
7 Cu.import("resource://gre/modules/devtools/dbg-server.jsm")
8 Cu.import("resource://gre/modules/WindowsPrefSync.jsm");
10 /**
11 * Constants
12 */
14 // Devtools Messages
15 const debugServerStateChanged = "devtools.debugger.remote-enabled";
16 const debugServerPortChanged = "devtools.debugger.remote-port";
18 // delay when showing the tab bar briefly after a new foreground tab opens
19 const kForegroundTabAnimationDelay = 1000;
20 // delay when showing the tab bar after opening a new background tab opens
21 const kBackgroundTabAnimationDelay = 3000;
22 // delay before closing tab bar after closing or selecting a tab
23 const kChangeTabAnimationDelay = 500;
25 /**
26 * Cache of commonly used elements.
27 */
29 let Elements = {};
30 [
31 ["contentShowing", "bcast_contentShowing"],
32 ["urlbarState", "bcast_urlbarState"],
33 ["loadingState", "bcast_loadingState"],
34 ["windowState", "bcast_windowState"],
35 ["chromeState", "bcast_chromeState"],
36 ["mainKeyset", "mainKeyset"],
37 ["stack", "stack"],
38 ["tabList", "tabs"],
39 ["tabs", "tabs-container"],
40 ["controls", "browser-controls"],
41 ["panelUI", "panel-container"],
42 ["tray", "tray"],
43 ["toolbar", "toolbar"],
44 ["browsers", "browsers"],
45 ["navbar", "navbar"],
46 ["autocomplete", "urlbar-autocomplete"],
47 ["contextappbar", "contextappbar"],
48 ["findbar", "findbar"],
49 ["contentViewport", "content-viewport"],
50 ["progress", "progress-control"],
51 ["progressContainer", "progress-container"],
52 ["feedbackLabel", "feedback-label"],
53 ].forEach(function (aElementGlobal) {
54 let [name, id] = aElementGlobal;
55 XPCOMUtils.defineLazyGetter(Elements, name, function() {
56 return document.getElementById(id);
57 });
58 });
60 /**
61 * Cache of commonly used string bundles.
62 */
64 var Strings = {};
65 [
66 ["browser", "chrome://browser/locale/browser.properties"],
67 ["brand", "chrome://branding/locale/brand.properties"]
68 ].forEach(function (aStringBundle) {
69 let [name, bundle] = aStringBundle;
70 XPCOMUtils.defineLazyGetter(Strings, name, function() {
71 return Services.strings.createBundle(bundle);
72 });
73 });
75 var BrowserUI = {
76 get _edit() { return document.getElementById("urlbar-edit"); },
77 get _back() { return document.getElementById("cmd_back"); },
78 get _forward() { return document.getElementById("cmd_forward"); },
80 lastKnownGoodURL: "", // used when the user wants to escape unfinished url entry
81 ready: false, // used for tests to determine when delayed initialization is done
83 init: function() {
84 // start the debugger now so we can use it on the startup code as well
85 if (Services.prefs.getBoolPref(debugServerStateChanged)) {
86 this.runDebugServer();
87 }
88 Services.prefs.addObserver(debugServerStateChanged, this, false);
89 Services.prefs.addObserver(debugServerPortChanged, this, false);
90 Services.prefs.addObserver("app.crashreporter.autosubmit", this, false);
91 Services.prefs.addObserver("metro.private_browsing.enabled", this, false);
92 this.updatePrivateBrowsingUI();
94 Services.obs.addObserver(this, "handle-xul-text-link", false);
96 // listen content messages
97 messageManager.addMessageListener("DOMTitleChanged", this);
98 messageManager.addMessageListener("DOMWillOpenModalDialog", this);
99 messageManager.addMessageListener("DOMWindowClose", this);
101 messageManager.addMessageListener("Browser:OpenURI", this);
102 messageManager.addMessageListener("Browser:SaveAs:Return", this);
103 messageManager.addMessageListener("Content:StateChange", this);
105 // listening escape to dismiss dialog on VK_ESCAPE
106 window.addEventListener("keypress", this, true);
108 window.addEventListener("MozPrecisePointer", this, true);
109 window.addEventListener("MozImprecisePointer", this, true);
111 window.addEventListener("AppCommand", this, true);
113 Services.prefs.addObserver("browser.cache.disk_cache_ssl", this, false);
115 // Init core UI modules
116 ContextUI.init();
117 PanelUI.init();
118 FlyoutPanelsUI.init();
119 PageThumbs.init();
120 NewTabUtils.init();
121 SettingsCharm.init();
122 NavButtonSlider.init();
123 SelectionHelperUI.init();
124 #ifdef NIGHTLY_BUILD
125 ShumwayUtils.init();
126 #endif
128 // We can delay some initialization until after startup. We wait until
129 // the first page is shown, then dispatch a UIReadyDelayed event.
130 messageManager.addMessageListener("pageshow", function onPageShow() {
131 if (getBrowser().currentURI.spec == "about:blank")
132 return;
134 messageManager.removeMessageListener("pageshow", onPageShow);
136 setTimeout(function() {
137 let event = document.createEvent("Events");
138 event.initEvent("UIReadyDelayed", true, false);
139 window.dispatchEvent(event);
140 BrowserUI.ready = true;
141 }, 0);
142 });
144 // Only load IndexedDB.js when we actually need it. A general fix will happen in bug 647079.
145 messageManager.addMessageListener("IndexedDB:Prompt", function(aMessage) {
146 return IndexedDB.receiveMessage(aMessage);
147 });
149 // hook up telemetry ping for UI data
150 try {
151 UITelemetry.addSimpleMeasureFunction("metro-ui",
152 BrowserUI._getMeasures.bind(BrowserUI));
153 } catch (ex) {
154 // swallow exception that occurs if metro-appbar measure is already set up
155 dump("Failed to addSimpleMeasureFunction in browser-ui: " + ex.message + "\n");
156 }
158 // Delay the panel UI and Sync initialization
159 window.addEventListener("UIReadyDelayed", function delayedInit(aEvent) {
160 Util.dumpLn("* delay load started...");
161 window.removeEventListener("UIReadyDelayed", delayedInit, false);
163 // Login Manager and Form History initialization
164 Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
165 messageManager.addMessageListener("Browser:MozApplicationManifest", OfflineApps);
167 try {
168 MetroDownloadsView.init();
169 DialogUI.init();
170 FormHelperUI.init();
171 FindHelperUI.init();
172 #ifdef NIGHTLY_BUILD
173 PdfJs.init();
174 #endif
175 } catch(ex) {
176 Util.dumpLn("Exception in delay load module:", ex.message);
177 }
179 BrowserUI._initFirstRunContent();
181 // check for left over crash reports and submit them if found.
182 BrowserUI.startupCrashCheck();
184 Util.dumpLn("* delay load complete.");
185 }, false);
187 #ifndef MOZ_OFFICIAL_BRANDING
188 setTimeout(function() {
189 let startup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup).getStartupInfo();
190 for (let name in startup) {
191 if (name != "process")
192 Services.console.logStringMessage("[timing] " + name + ": " + (startup[name] - startup.process) + "ms");
193 }
194 }, 3000);
195 #endif
196 },
198 uninit: function() {
199 messageManager.removeMessageListener("DOMTitleChanged", this);
200 messageManager.removeMessageListener("DOMWillOpenModalDialog", this);
201 messageManager.removeMessageListener("DOMWindowClose", this);
203 messageManager.removeMessageListener("Browser:OpenURI", this);
204 messageManager.removeMessageListener("Browser:SaveAs:Return", this);
205 messageManager.removeMessageListener("Content:StateChange", this);
207 messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps);
209 Services.prefs.removeObserver(debugServerStateChanged, this);
210 Services.prefs.removeObserver(debugServerPortChanged, this);
211 Services.prefs.removeObserver("app.crashreporter.autosubmit", this);
212 Services.prefs.removeObserver("metro.private_browsing.enabled", this);
214 Services.obs.removeObserver(this, "handle-xul-text-link");
216 PanelUI.uninit();
217 FlyoutPanelsUI.uninit();
218 MetroDownloadsView.uninit();
219 SettingsCharm.uninit();
220 PageThumbs.uninit();
221 if (WindowsPrefSync) {
222 WindowsPrefSync.uninit();
223 }
224 this.stopDebugServer();
225 },
227 /************************************
228 * Devtools Debugger
229 */
230 runDebugServer: function runDebugServer(aPort) {
231 let port = aPort || Services.prefs.getIntPref(debugServerPortChanged);
232 if (!DebuggerServer.initialized) {
233 DebuggerServer.init();
234 DebuggerServer.addBrowserActors();
235 DebuggerServer.addActors('chrome://browser/content/dbg-metro-actors.js');
236 }
237 DebuggerServer.openListener(port);
238 },
240 stopDebugServer: function stopDebugServer() {
241 if (DebuggerServer.initialized) {
242 DebuggerServer.destroy();
243 }
244 },
246 // If the server is not on, port changes have nothing to effect. The new value
247 // will be picked up if the server is started.
248 // To be consistent with desktop fx, if the port is changed while the server
249 // is running, restart server.
250 changeDebugPort:function changeDebugPort(aPort) {
251 if (DebuggerServer.initialized) {
252 this.stopDebugServer();
253 this.runDebugServer(aPort);
254 }
255 },
257 /*********************************
258 * Content visibility
259 */
261 get isContentShowing() {
262 return Elements.contentShowing.getAttribute("disabled") != true;
263 },
265 showContent: function showContent(aURI) {
266 ContextUI.dismissTabs();
267 ContextUI.dismissContextAppbar();
268 FlyoutPanelsUI.hide();
269 PanelUI.hide();
270 },
272 /*********************************
273 * Crash reporting
274 */
276 get CrashSubmit() {
277 delete this.CrashSubmit;
278 Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
279 return this.CrashSubmit;
280 },
282 get lastCrashID() {
283 return Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime).lastRunCrashID;
284 },
286 startupCrashCheck: function startupCrashCheck() {
287 #ifdef MOZ_CRASHREPORTER
288 if (!CrashReporter.enabled) {
289 return;
290 }
292 // Ensure that CrashReporter state matches pref
293 CrashReporter.submitReports = Services.prefs.getBoolPref("app.crashreporter.autosubmit");
295 BrowserUI.submitLastCrashReportOrShowPrompt();
296 #endif
297 },
300 /*********************************
301 * Navigation
302 */
304 // BrowserUI update bit flags
305 NO_STARTUI_VISIBILITY: 1, // don't change the start ui visibility
307 /*
308 * Updates the overall state of startui visibility and the toolbar, but not
309 * the URL bar.
310 */
311 update: function(aFlags) {
312 let flags = aFlags || 0;
313 if (!(flags & this.NO_STARTUI_VISIBILITY)) {
314 let uri = this.getDisplayURI(Browser.selectedBrowser);
315 this.updateStartURIAttributes(uri);
316 }
317 this._updateButtons();
318 this._updateToolbar();
319 },
321 /* Updates the URL bar. */
322 updateURI: function(aOptions) {
323 let uri = this.getDisplayURI(Browser.selectedBrowser);
324 let cleanURI = Util.isURLEmpty(uri) ? "" : uri;
325 this._edit.value = cleanURI;
326 },
328 get isStartTabVisible() {
329 return this.isStartURI();
330 },
332 isStartURI: function isStartURI(aURI) {
333 aURI = aURI || Browser.selectedBrowser.currentURI.spec;
334 return aURI.startsWith(kStartURI) || aURI == "about:start" || aURI == "about:home";
335 },
337 updateStartURIAttributes: function (aURI) {
338 let wasStart = Elements.windowState.hasAttribute("startpage");
339 aURI = aURI || Browser.selectedBrowser.currentURI.spec;
340 if (this.isStartURI(aURI)) {
341 ContextUI.displayNavbar();
342 Elements.windowState.setAttribute("startpage", "true");
343 } else if (aURI != "about:blank") { // about:blank is loaded briefly for new tabs; ignore it
344 Elements.windowState.removeAttribute("startpage");
345 }
347 let isStart = Elements.windowState.hasAttribute("startpage");
348 if (wasStart != isStart) {
349 let event = document.createEvent("Events");
350 event.initEvent("StartUIChange", true, true);
351 Browser.selectedBrowser.dispatchEvent(event);
352 }
353 },
355 getDisplayURI: function(browser) {
356 let uri = browser.currentURI;
357 let spec = uri.spec;
359 try {
360 spec = gURIFixup.createExposableURI(uri).spec;
361 } catch (ex) {}
363 try {
364 let charset = browser.characterSet;
365 let textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
366 getService(Ci.nsITextToSubURI);
367 spec = textToSubURI.unEscapeNonAsciiURI(charset, spec);
368 } catch (ex) {}
370 return spec;
371 },
373 goToURI: function(aURI) {
374 aURI = aURI || this._edit.value;
375 if (!aURI)
376 return;
378 this._edit.value = aURI;
380 // Make sure we're online before attempting to load
381 Util.forceOnline();
383 BrowserUI.showContent(aURI);
384 Browser.selectedBrowser.focus();
386 Task.spawn(function() {
387 let postData = {};
388 let webNav = Ci.nsIWebNavigation;
389 let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
390 webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
391 aURI = yield Browser.getShortcutOrURI(aURI, postData);
392 Browser.loadURI(aURI, { flags: flags, postData: postData });
394 // Delay doing the fixup so the raw URI is passed to loadURIWithFlags
395 // and the proper third-party fixup can be done
396 let fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP |
397 Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
398 let uri = gURIFixup.createFixupURI(aURI, fixupFlags);
399 gHistSvc.markPageAsTyped(uri);
401 BrowserUI._titleChanged(Browser.selectedBrowser);
402 });
403 },
405 doOpenSearch: function doOpenSearch(aName) {
406 // save the current value of the urlbar
407 let searchValue = this._edit.value;
408 let engine = Services.search.getEngineByName(aName);
409 let submission = engine.getSubmission(searchValue, null);
411 this._edit.value = submission.uri.spec;
413 // Make sure we're online before attempting to load
414 Util.forceOnline();
416 BrowserUI.showContent();
417 Browser.selectedBrowser.focus();
419 Task.spawn(function () {
420 Browser.loadURI(submission.uri.spec, { postData: submission.postData });
422 // loadURI may open a new tab, so get the selectedBrowser afterward.
423 Browser.selectedBrowser.userTypedValue = submission.uri.spec;
424 BrowserUI._titleChanged(Browser.selectedBrowser);
425 });
426 },
428 /*********************************
429 * Tab management
430 */
432 /**
433 * Open a new tab in the foreground in response to a user action.
434 * See Browser.addTab for more documentation.
435 */
436 addAndShowTab: function (aURI, aOwner, aParams) {
437 ContextUI.peekTabs(kForegroundTabAnimationDelay);
438 return Browser.addTab(aURI || kStartURI, true, aOwner, aParams);
439 },
441 addAndShowPrivateTab: function (aURI, aOwner) {
442 return this.addAndShowTab(aURI, aOwner, { private: true });
443 },
445 get isPrivateBrowsingEnabled() {
446 return Services.prefs.getBoolPref("metro.private_browsing.enabled");
447 },
449 updatePrivateBrowsingUI: function () {
450 let command = document.getElementById("cmd_newPrivateTab");
451 if (this.isPrivateBrowsingEnabled) {
452 command.removeAttribute("disabled");
453 } else {
454 command.setAttribute("disabled", "true");
455 }
456 },
458 /**
459 * Open a new tab in response to clicking a link in an existing tab.
460 * See Browser.addTab for more documentation.
461 */
462 openLinkInNewTab: function (aURI, aBringFront, aOwner) {
463 ContextUI.peekTabs(aBringFront ? kForegroundTabAnimationDelay
464 : kBackgroundTabAnimationDelay);
465 let params = null;
466 if (aOwner) {
467 params = {
468 referrerURI: aOwner.browser.documentURI,
469 charset: aOwner.browser.characterSet,
470 };
471 }
472 let tab = Browser.addTab(aURI, aBringFront, aOwner, params);
473 Elements.tabList.strip.ensureElementIsVisible(tab.chromeTab);
474 return tab;
475 },
477 setOnTabAnimationEnd: function setOnTabAnimationEnd(aCallback) {
478 Elements.tabs.addEventListener("animationend", function onAnimationEnd() {
479 Elements.tabs.removeEventListener("animationend", onAnimationEnd);
480 aCallback();
481 });
482 },
484 closeTab: function closeTab(aTab) {
485 // If no tab is passed in, assume the current tab
486 let tab = aTab || Browser.selectedTab;
487 Browser.closeTab(tab);
488 },
490 animateClosingTab: function animateClosingTab(tabToClose) {
491 tabToClose.chromeTab.setAttribute("closing", "true");
493 let wasCollapsed = !ContextUI.tabbarVisible;
494 if (wasCollapsed) {
495 ContextUI.displayTabs();
496 }
498 this.setOnTabAnimationEnd(function() {
499 Browser.closeTab(tabToClose, { forceClose: true } );
500 if (wasCollapsed)
501 ContextUI.dismissTabsWithDelay(kChangeTabAnimationDelay);
502 });
503 },
505 /**
506 * Re-open a closed tab.
507 * @param aIndex
508 * The index of the tab (via nsSessionStore.getClosedTabData)
509 * @returns a reference to the reopened tab.
510 */
511 undoCloseTab: function undoCloseTab(aIndex) {
512 var tab = null;
513 aIndex = aIndex || 0;
514 var ss = Cc["@mozilla.org/browser/sessionstore;1"].
515 getService(Ci.nsISessionStore);
516 if (ss.getClosedTabCount(window) > (aIndex)) {
517 tab = ss.undoCloseTab(window, aIndex);
518 }
519 return tab;
520 },
522 // Useful for when we've received an event to close a particular DOM window.
523 // Since we don't have windows, we want to close the corresponding tab.
524 closeTabForBrowser: function closeTabForBrowser(aBrowser) {
525 // Find the relevant tab, and close it.
526 let browsers = Browser.browsers;
527 for (let i = 0; i < browsers.length; i++) {
528 if (browsers[i] == aBrowser) {
529 Browser.closeTab(Browser.getTabAtIndex(i));
530 return { preventDefault: true };
531 }
532 }
534 return {};
535 },
537 selectTab: function selectTab(aTab) {
538 Browser.selectedTab = aTab;
539 },
541 selectTabAndDismiss: function selectTabAndDismiss(aTab) {
542 this.selectTab(aTab);
543 ContextUI.dismissTabsWithDelay(kChangeTabAnimationDelay);
544 },
546 selectTabAtIndex: function selectTabAtIndex(aIndex) {
547 // count backwards for aIndex < 0
548 if (aIndex < 0)
549 aIndex += Browser._tabs.length;
551 if (aIndex >= 0 && aIndex < Browser._tabs.length)
552 Browser.selectedTab = Browser._tabs[aIndex];
553 },
555 selectNextTab: function selectNextTab() {
556 if (Browser._tabs.length == 1 || !Browser.selectedTab) {
557 return;
558 }
560 let tabIndex = Browser._tabs.indexOf(Browser.selectedTab) + 1;
561 if (tabIndex >= Browser._tabs.length) {
562 tabIndex = 0;
563 }
565 Browser.selectedTab = Browser._tabs[tabIndex];
566 },
568 selectPreviousTab: function selectPreviousTab() {
569 if (Browser._tabs.length == 1 || !Browser.selectedTab) {
570 return;
571 }
573 let tabIndex = Browser._tabs.indexOf(Browser.selectedTab) - 1;
574 if (tabIndex < 0) {
575 tabIndex = Browser._tabs.length - 1;
576 }
578 Browser.selectedTab = Browser._tabs[tabIndex];
579 },
581 // Used for when we're about to open a modal dialog,
582 // and want to ensure the opening tab is in front.
583 selectTabForBrowser: function selectTabForBrowser(aBrowser) {
584 for (let i = 0; i < Browser.tabs.length; i++) {
585 if (Browser._tabs[i].browser == aBrowser) {
586 Browser.selectedTab = Browser.tabs[i];
587 break;
588 }
589 }
590 },
592 updateUIFocus: function _updateUIFocus() {
593 if (Elements.contentShowing.getAttribute("disabled") == "true" && Browser.selectedBrowser)
594 Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:Blur", { });
595 },
597 blurFocusedElement: function blurFocusedElement() {
598 let focusedElement = document.commandDispatcher.focusedElement;
599 if (focusedElement)
600 focusedElement.blur();
601 },
603 blurNavBar: function blurNavBar() {
604 if (this._edit.focused) {
605 this._edit.blur();
607 // Advanced notice to CAO, so we can shuffle the nav bar in advance
608 // of the keyboard transition.
609 ContentAreaObserver.navBarWillBlur();
611 return true;
612 }
613 return false;
614 },
616 observe: function BrowserUI_observe(aSubject, aTopic, aData) {
617 switch (aTopic) {
618 case "handle-xul-text-link":
619 let handled = aSubject.QueryInterface(Ci.nsISupportsPRBool);
620 if (!handled.data) {
621 this.addAndShowTab(aData, Browser.selectedTab);
622 handled.data = true;
623 }
624 break;
625 case "nsPref:changed":
626 switch (aData) {
627 case "browser.cache.disk_cache_ssl":
628 this._sslDiskCacheEnabled = Services.prefs.getBoolPref(aData);
629 break;
630 case debugServerStateChanged:
631 if (Services.prefs.getBoolPref(aData)) {
632 this.runDebugServer();
633 } else {
634 this.stopDebugServer();
635 }
636 break;
637 case debugServerPortChanged:
638 this.changeDebugPort(Services.prefs.getIntPref(aData));
639 break;
640 case "app.crashreporter.autosubmit":
641 #ifdef MOZ_CRASHREPORTER
642 CrashReporter.submitReports = Services.prefs.getBoolPref(aData);
644 // The user explicitly set the autosubmit option, so there is no
645 // need to prompt them about crash reporting in the future
646 Services.prefs.setBoolPref("app.crashreporter.prompted", true);
648 BrowserUI.submitLastCrashReportOrShowPrompt;
649 #endif
650 break;
651 case "metro.private_browsing.enabled":
652 this.updatePrivateBrowsingUI();
653 break;
654 }
655 break;
656 }
657 },
659 submitLastCrashReportOrShowPrompt: function() {
660 #ifdef MOZ_CRASHREPORTER
661 let lastCrashID = this.lastCrashID;
662 if (lastCrashID && lastCrashID.length) {
663 if (Services.prefs.getBoolPref("app.crashreporter.autosubmit")) {
664 Util.dumpLn("Submitting last crash id:", lastCrashID);
665 let params = {};
666 if (!Services.prefs.getBoolPref("app.crashreporter.submitURLs")) {
667 params['extraExtraKeyVals'] = { URL: '' };
668 }
669 try {
670 this.CrashSubmit.submit(lastCrashID, params);
671 } catch (ex) {
672 Util.dumpLn(ex);
673 }
674 } else if (!Services.prefs.getBoolPref("app.crashreporter.prompted")) {
675 BrowserUI.addAndShowTab("about:crashprompt", null);
676 }
677 }
678 #endif
679 },
683 /*********************************
684 * Internal utils
685 */
687 _titleChanged: function(aBrowser) {
688 let url = this.getDisplayURI(aBrowser);
690 let tabCaption;
691 if (aBrowser.contentTitle) {
692 tabCaption = aBrowser.contentTitle;
693 } else if (!Util.isURLEmpty(aBrowser.userTypedValue)) {
694 tabCaption = aBrowser.userTypedValue;
695 } else if (!Util.isURLEmpty(url)) {
696 tabCaption = url;
697 } else {
698 tabCaption = Util.getEmptyURLTabTitle();
699 }
701 let tab = Browser.getTabForBrowser(aBrowser);
702 if (tab)
703 tab.chromeTab.updateTitle(tabCaption);
704 },
706 _updateButtons: function _updateButtons() {
707 let browser = Browser.selectedBrowser;
708 if (!browser) {
709 return;
710 }
711 if (browser.canGoBack) {
712 this._back.removeAttribute("disabled");
713 } else {
714 this._back.setAttribute("disabled", true);
715 }
716 if (browser.canGoForward) {
717 this._forward.removeAttribute("disabled");
718 } else {
719 this._forward.setAttribute("disabled", true);
720 }
721 },
723 _updateToolbar: function _updateToolbar() {
724 if (Browser.selectedTab.isLoading()) {
725 Elements.loadingState.setAttribute("loading", true);
726 } else {
727 Elements.loadingState.removeAttribute("loading");
728 }
729 },
731 _closeOrQuit: function _closeOrQuit() {
732 // Close active dialog, if we have one. If not then close the application.
733 if (!BrowserUI.isContentShowing()) {
734 BrowserUI.showContent();
735 } else {
736 // Check to see if we should really close the window
737 if (Browser.closing()) {
738 window.close();
739 let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
740 appStartup.quit(Ci.nsIAppStartup.eForceQuit);
741 }
742 }
743 },
745 _onPreciseInput: function _onPreciseInput() {
746 document.getElementById("bcast_preciseInput").setAttribute("input", "precise");
747 let uri = Util.makeURI("chrome://browser/content/cursor.css");
748 if (StyleSheetSvc.sheetRegistered(uri, Ci.nsIStyleSheetService.AGENT_SHEET)) {
749 StyleSheetSvc.unregisterSheet(uri,
750 Ci.nsIStyleSheetService.AGENT_SHEET);
751 }
752 },
754 _onImpreciseInput: function _onImpreciseInput() {
755 document.getElementById("bcast_preciseInput").setAttribute("input", "imprecise");
756 let uri = Util.makeURI("chrome://browser/content/cursor.css");
757 if (!StyleSheetSvc.sheetRegistered(uri, Ci.nsIStyleSheetService.AGENT_SHEET)) {
758 StyleSheetSvc.loadAndRegisterSheet(uri,
759 Ci.nsIStyleSheetService.AGENT_SHEET);
760 }
761 },
763 _getMeasures: function() {
764 let dimensions = {
765 "window-width": ContentAreaObserver.width,
766 "window-height": ContentAreaObserver.height
767 };
768 return dimensions;
769 },
771 /*********************************
772 * Event handling
773 */
775 handleEvent: function handleEvent(aEvent) {
776 var target = aEvent.target;
777 switch (aEvent.type) {
778 // Window events
779 case "keypress":
780 if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE)
781 this.handleEscape(aEvent);
782 break;
783 case "MozPrecisePointer":
784 this._onPreciseInput();
785 break;
786 case "MozImprecisePointer":
787 this._onImpreciseInput();
788 break;
789 case "AppCommand":
790 this.handleAppCommandEvent(aEvent);
791 break;
792 }
793 },
795 // Checks if various different parts of the UI is visible and closes
796 // them one at a time.
797 handleEscape: function (aEvent) {
798 aEvent.stopPropagation();
799 aEvent.preventDefault();
801 if (this._edit.popupOpen) {
802 this._edit.endEditing(true);
803 return;
804 }
806 // Check open popups
807 if (DialogUI._popup) {
808 DialogUI._hidePopup();
809 return;
810 }
812 // Check open panel
813 if (PanelUI.isVisible) {
814 PanelUI.hide();
815 return;
816 }
818 // Check content helper
819 if (FindHelperUI.isActive) {
820 FindHelperUI.hide();
821 return;
822 }
824 if (Browser.selectedTab.isLoading()) {
825 Browser.selectedBrowser.stop();
826 return;
827 }
829 if (ContextUI.dismiss()) {
830 return;
831 }
832 },
834 handleBackspace: function handleBackspace() {
835 switch (Services.prefs.getIntPref("browser.backspace_action")) {
836 case 0:
837 CommandUpdater.doCommand("cmd_back");
838 break;
839 case 1:
840 CommandUpdater.doCommand("cmd_scrollPageUp");
841 break;
842 }
843 },
845 handleShiftBackspace: function handleShiftBackspace() {
846 switch (Services.prefs.getIntPref("browser.backspace_action")) {
847 case 0:
848 CommandUpdater.doCommand("cmd_forward");
849 break;
850 case 1:
851 CommandUpdater.doCommand("cmd_scrollPageDown");
852 break;
853 }
854 },
856 openFile: function() {
857 try {
858 const nsIFilePicker = Ci.nsIFilePicker;
859 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
860 let self = this;
861 let fpCallback = function fpCallback_done(aResult) {
862 if (aResult == nsIFilePicker.returnOK) {
863 self.goToURI(fp.fileURL.spec);
864 }
865 };
867 let windowTitle = Strings.browser.GetStringFromName("browserForOpenLocation");
868 fp.init(window, windowTitle, nsIFilePicker.modeOpen);
869 fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
870 nsIFilePicker.filterImages | nsIFilePicker.filterXML |
871 nsIFilePicker.filterHTML);
872 fp.open(fpCallback);
873 } catch (ex) {
874 dump ('BrowserUI openFile exception: ' + ex + '\n');
875 }
876 },
878 savePage: function() {
879 Browser.savePage();
880 },
882 receiveMessage: function receiveMessage(aMessage) {
883 let browser = aMessage.target;
884 let json = aMessage.json;
885 switch (aMessage.name) {
886 case "DOMTitleChanged":
887 this._titleChanged(browser);
888 break;
889 case "DOMWillOpenModalDialog":
890 this.selectTabForBrowser(browser);
891 break;
892 case "DOMWindowClose":
893 return this.closeTabForBrowser(browser);
894 break;
895 // XXX this and content's sender are a little warped
896 case "Browser:OpenURI":
897 let referrerURI = null;
898 if (json.referrer)
899 referrerURI = Services.io.newURI(json.referrer, null, null);
900 this.goToURI(json.uri);
901 break;
902 case "Content:StateChange": {
903 let tab = Browser.selectedTab;
904 if (this.shouldCaptureThumbnails(tab)) {
905 PageThumbs.captureAndStore(tab.browser);
906 let currPage = tab.browser.currentURI.spec;
907 Services.obs.notifyObservers(null, "Metro:RefreshTopsiteThumbnail", currPage);
908 }
909 break;
910 }
911 }
913 return {};
914 },
916 shouldCaptureThumbnails: function shouldCaptureThumbnails(aTab) {
917 // Capture only if it's the currently selected tab.
918 if (aTab != Browser.selectedTab) {
919 return false;
920 }
921 // Skip private tabs
922 if (aTab.isPrivate) {
923 return false;
924 }
925 // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
926 // that currently regresses Talos SVG tests.
927 let browser = aTab.browser;
928 let doc = browser.contentDocument;
929 if (doc instanceof SVGDocument || doc instanceof XMLDocument) {
930 return false;
931 }
933 // Don't capture pages in snapped mode, this produces 2/3 black
934 // thumbs or stretched out ones
935 // Ci.nsIWinMetroUtils.snapped is inaccessible on
936 // desktop/nonwindows systems
937 if(Elements.windowState.getAttribute("viewstate") == "snapped") {
938 return false;
939 }
940 // There's no point in taking screenshot of loading pages.
941 if (browser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
942 return false;
943 }
945 // Don't take screenshots of about: pages.
946 if (browser.currentURI.schemeIs("about")) {
947 return false;
948 }
950 // No valid document channel. We shouldn't take a screenshot.
951 let channel = browser.docShell.currentDocumentChannel;
952 if (!channel) {
953 return false;
954 }
956 // Don't take screenshots of internally redirecting about: pages.
957 // This includes error pages.
958 let uri = channel.originalURI;
959 if (uri.schemeIs("about")) {
960 return false;
961 }
963 // http checks
964 let httpChannel;
965 try {
966 httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
967 } catch (e) { /* Not an HTTP channel. */ }
969 if (httpChannel) {
970 // Continue only if we have a 2xx status code.
971 try {
972 if (Math.floor(httpChannel.responseStatus / 100) != 2) {
973 return false;
974 }
975 } catch (e) {
976 // Can't get response information from the httpChannel
977 // because mResponseHead is not available.
978 return false;
979 }
981 // Cache-Control: no-store.
982 if (httpChannel.isNoStoreResponse()) {
983 return false;
984 }
986 // Don't capture HTTPS pages unless the user enabled it.
987 if (uri.schemeIs("https") && !this.sslDiskCacheEnabled) {
988 return false;
989 }
990 }
992 return true;
993 },
995 _sslDiskCacheEnabled: null,
997 get sslDiskCacheEnabled() {
998 if (this._sslDiskCacheEnabled === null) {
999 this._sslDiskCacheEnabled = Services.prefs.getBoolPref("browser.cache.disk_cache_ssl");
1000 }
1001 return this._sslDiskCacheEnabled;
1002 },
1004 supportsCommand : function(cmd) {
1005 var isSupported = false;
1006 switch (cmd) {
1007 case "cmd_back":
1008 case "cmd_forward":
1009 case "cmd_reload":
1010 case "cmd_forceReload":
1011 case "cmd_stop":
1012 case "cmd_go":
1013 case "cmd_home":
1014 case "cmd_openLocation":
1015 case "cmd_addBookmark":
1016 case "cmd_bookmarks":
1017 case "cmd_history":
1018 case "cmd_remoteTabs":
1019 case "cmd_quit":
1020 case "cmd_close":
1021 case "cmd_newTab":
1022 case "cmd_newTabKey":
1023 case "cmd_closeTab":
1024 case "cmd_undoCloseTab":
1025 case "cmd_actions":
1026 case "cmd_panel":
1027 case "cmd_reportingCrashesSubmitURLs":
1028 case "cmd_flyout_back":
1029 case "cmd_sanitize":
1030 case "cmd_volumeLeft":
1031 case "cmd_volumeRight":
1032 case "cmd_openFile":
1033 case "cmd_savePage":
1034 isSupported = true;
1035 break;
1036 default:
1037 isSupported = false;
1038 break;
1039 }
1040 return isSupported;
1041 },
1043 isCommandEnabled : function(cmd) {
1044 let elem = document.getElementById(cmd);
1045 if (elem && elem.getAttribute("disabled") == "true")
1046 return false;
1047 return true;
1048 },
1050 doCommand : function(cmd) {
1051 if (!this.isCommandEnabled(cmd))
1052 return;
1053 let browser = getBrowser();
1054 switch (cmd) {
1055 case "cmd_back":
1056 browser.goBack();
1057 break;
1058 case "cmd_forward":
1059 browser.goForward();
1060 break;
1061 case "cmd_reload":
1062 browser.reload();
1063 break;
1064 case "cmd_forceReload":
1065 {
1066 // Simulate a new page
1067 browser.lastLocation = null;
1069 const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
1070 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
1071 browser.reloadWithFlags(reloadFlags);
1072 break;
1073 }
1074 case "cmd_stop":
1075 browser.stop();
1076 break;
1077 case "cmd_go":
1078 this.goToURI();
1079 break;
1080 case "cmd_home":
1081 this.goToURI(Browser.getHomePage());
1082 break;
1083 case "cmd_openLocation":
1084 ContextUI.displayNavbar();
1085 this._edit.beginEditing(true);
1086 this._edit.select();
1087 break;
1088 case "cmd_addBookmark":
1089 ContextUI.displayNavbar();
1090 Appbar.onStarButton(true);
1091 break;
1092 case "cmd_bookmarks":
1093 PanelUI.show("bookmarks-container");
1094 break;
1095 case "cmd_history":
1096 PanelUI.show("history-container");
1097 break;
1098 case "cmd_remoteTabs":
1099 #ifdef MOZ_SERVICES_SYNC
1100 if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED) {
1101 FlyoutPanelsUI.show('SyncFlyoutPanel');
1102 } else {
1103 PanelUI.show("remotetabs-container");
1104 }
1105 #endif
1106 break;
1107 case "cmd_quit":
1108 // Only close one window
1109 this._closeOrQuit();
1110 break;
1111 case "cmd_close":
1112 this._closeOrQuit();
1113 break;
1114 case "cmd_newTab":
1115 this.addAndShowTab();
1116 break;
1117 case "cmd_newTabKey":
1118 this.addAndShowTab();
1119 // Make sure navbar is displayed before setting focus on url bar. Bug 907244
1120 ContextUI.displayNavbar();
1121 this._edit.beginEditing(false);
1122 break;
1123 case "cmd_closeTab":
1124 this.closeTab();
1125 break;
1126 case "cmd_undoCloseTab":
1127 this.undoCloseTab();
1128 break;
1129 case "cmd_sanitize":
1130 this.confirmSanitizeDialog();
1131 break;
1132 case "cmd_flyout_back":
1133 FlyoutPanelsUI.onBackButton();
1134 break;
1135 case "cmd_reportingCrashesSubmitURLs":
1136 let urlCheckbox = document.getElementById("prefs-reporting-submitURLs");
1137 Services.prefs.setBoolPref('app.crashreporter.submitURLs', urlCheckbox.checked);
1138 break;
1139 case "cmd_panel":
1140 PanelUI.toggle();
1141 break;
1142 case "cmd_openFile":
1143 this.openFile();
1144 break;
1145 case "cmd_savePage":
1146 this.savePage();
1147 break;
1148 }
1149 },
1151 handleAppCommandEvent: function (aEvent) {
1152 switch (aEvent.command) {
1153 case "Back":
1154 this.doCommand("cmd_back");
1155 break;
1156 case "Forward":
1157 this.doCommand("cmd_forward");
1158 break;
1159 case "Reload":
1160 this.doCommand("cmd_reload");
1161 break;
1162 case "Stop":
1163 this.doCommand("cmd_stop");
1164 break;
1165 case "Home":
1166 this.doCommand("cmd_home");
1167 break;
1168 case "New":
1169 this.doCommand("cmd_newTab");
1170 break;
1171 case "Close":
1172 this.doCommand("cmd_closeTab");
1173 break;
1174 case "Find":
1175 FindHelperUI.show();
1176 break;
1177 case "Open":
1178 this.doCommand("cmd_openFile");
1179 break;
1180 case "Save":
1181 this.doCommand("cmd_savePage");
1182 break;
1183 case "Search":
1184 this.doCommand("cmd_openLocation");
1185 break;
1186 default:
1187 return;
1188 }
1189 aEvent.stopPropagation();
1190 aEvent.preventDefault();
1191 },
1193 confirmSanitizeDialog: function () {
1194 let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
1195 let title = bundle.GetStringFromName("clearPrivateData.title2");
1196 let message = bundle.GetStringFromName("clearPrivateData.message3");
1197 let clearbutton = bundle.GetStringFromName("clearPrivateData.clearButton");
1199 let prefsClearButton = document.getElementById("prefs-clear-data");
1200 prefsClearButton.disabled = true;
1202 let buttonPressed = Services.prompt.confirmEx(
1203 null,
1204 title,
1205 message,
1206 Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING +
1207 Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_CANCEL,
1208 clearbutton,
1209 null,
1210 null,
1211 null,
1212 { value: false });
1214 // Clicking 'Clear' will call onSanitize().
1215 if (buttonPressed === 0) {
1216 SanitizeUI.onSanitize();
1217 }
1219 prefsClearButton.disabled = false;
1220 },
1222 _initFirstRunContent: function () {
1223 let dismissed = Services.prefs.getBoolPref("browser.firstrun-content.dismissed");
1224 let firstRunCount = Services.prefs.getIntPref("browser.firstrun.count");
1226 if (!dismissed && firstRunCount > 0) {
1227 document.loadOverlay("chrome://browser/content/FirstRunContentOverlay.xul", null);
1228 }
1229 },
1231 firstRunContentDismiss: function() {
1232 let firstRunElements = Elements.stack.querySelectorAll(".firstrun-content");
1233 for (let node of firstRunElements) {
1234 node.parentNode.removeChild(node);
1235 }
1237 Services.prefs.setBoolPref("browser.firstrun-content.dismissed", true);
1238 },
1239 };
1241 var PanelUI = {
1242 get _panels() { return document.getElementById("panel-items"); },
1244 get isVisible() {
1245 return !Elements.panelUI.hidden;
1246 },
1248 views: {
1249 "console-container": "ConsolePanelView",
1250 },
1252 init: function() {
1253 // Perform core init soon
1254 setTimeout(function () {
1255 for each (let viewName in this.views) {
1256 let view = window[viewName];
1257 if (view.init)
1258 view.init();
1259 }
1260 }.bind(this), 0);
1262 // Lazily run other initialization tasks when the views are shown
1263 this._panels.addEventListener("ToolPanelShown", function(aEvent) {
1264 let viewName = this.views[this._panels.selectedPanel.id];
1265 let view = window[viewName];
1266 if (view.show)
1267 view.show();
1268 }.bind(this), true);
1269 },
1271 uninit: function() {
1272 for each (let viewName in this.views) {
1273 let view = window[viewName];
1274 if (view.uninit)
1275 view.uninit();
1276 }
1277 },
1279 switchPane: function switchPane(aPanelId) {
1280 BrowserUI.blurFocusedElement();
1282 let panel = aPanelId ? document.getElementById(aPanelId) : this._panels.selectedPanel;
1283 let oldPanel = this._panels.selectedPanel;
1285 if (oldPanel != panel) {
1286 this._panels.selectedPanel = panel;
1288 this._fire("ToolPanelHidden", oldPanel);
1289 }
1291 this._fire("ToolPanelShown", panel);
1292 },
1294 isPaneVisible: function isPaneVisible(aPanelId) {
1295 return this.isVisible && this._panels.selectedPanel.id == aPanelId;
1296 },
1298 show: function show(aPanelId) {
1299 Elements.panelUI.hidden = false;
1300 Elements.contentShowing.setAttribute("disabled", "true");
1302 this.switchPane(aPanelId);
1303 },
1305 hide: function hide() {
1306 if (!this.isVisible)
1307 return;
1309 Elements.panelUI.hidden = true;
1310 Elements.contentShowing.removeAttribute("disabled");
1311 BrowserUI.blurFocusedElement();
1313 this._fire("ToolPanelHidden", this._panels);
1314 },
1316 toggle: function toggle() {
1317 if (this.isVisible) {
1318 this.hide();
1319 } else {
1320 this.show();
1321 }
1322 },
1324 _fire: function _fire(aName, anElement) {
1325 let event = document.createEvent("Events");
1326 event.initEvent(aName, true, true);
1327 anElement.dispatchEvent(event);
1328 }
1329 };
1331 var DialogUI = {
1332 _popup: null,
1334 init: function() {
1335 window.addEventListener("mousedown", this, true);
1336 },
1338 /*******************************************
1339 * Popups
1340 */
1342 pushPopup: function pushPopup(aPanel, aElements, aParent) {
1343 this._hidePopup();
1344 this._popup = { "panel": aPanel,
1345 "elements": (aElements instanceof Array) ? aElements : [aElements] };
1346 this._dispatchPopupChanged(true, aPanel);
1347 },
1349 popPopup: function popPopup(aPanel) {
1350 if (!this._popup || aPanel != this._popup.panel)
1351 return;
1352 this._popup = null;
1353 this._dispatchPopupChanged(false, aPanel);
1354 },
1356 _hidePopup: function _hidePopup() {
1357 if (!this._popup)
1358 return;
1359 let panel = this._popup.panel;
1360 if (panel.hide)
1361 panel.hide();
1362 },
1364 /*******************************************
1365 * Events
1366 */
1368 handleEvent: function (aEvent) {
1369 switch (aEvent.type) {
1370 case "mousedown":
1371 if (!this._isEventInsidePopup(aEvent))
1372 this._hidePopup();
1373 break;
1374 default:
1375 break;
1376 }
1377 },
1379 _dispatchPopupChanged: function _dispatchPopupChanged(aVisible, aElement) {
1380 let event = document.createEvent("UIEvents");
1381 event.initUIEvent("PopupChanged", true, true, window, aVisible);
1382 aElement.dispatchEvent(event);
1383 },
1385 _isEventInsidePopup: function _isEventInsidePopup(aEvent) {
1386 if (!this._popup)
1387 return false;
1388 let elements = this._popup.elements;
1389 let targetNode = aEvent.target;
1390 while (targetNode && elements.indexOf(targetNode) == -1) {
1391 if (targetNode instanceof Element && targetNode.hasAttribute("for"))
1392 targetNode = document.getElementById(targetNode.getAttribute("for"));
1393 else
1394 targetNode = targetNode.parentNode;
1395 }
1396 return targetNode ? true : false;
1397 }
1398 };