File: mailTabs.js

package info (click to toggle)
icedove 1%3A45.8.0-3~deb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 1,488,584 kB
  • ctags: 1,068,813
  • sloc: cpp: 4,801,496; ansic: 1,929,291; python: 379,296; java: 252,018; xml: 173,182; asm: 146,741; sh: 89,229; makefile: 23,462; perl: 16,380; objc: 4,088; yacc: 1,841; lex: 1,222; exp: 499; php: 437; lisp: 228; awk: 152; pascal: 116; sed: 51; ruby: 47; csh: 31; ada: 16
file content (795 lines) | stat: -rw-r--r-- 34,007 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource:///modules/MsgHdrSyntheticView.js");
Components.utils.import("resource:///modules/errUtils.js");
Components.utils.import("resource://gre/modules/Services.jsm");

/**
 * Displays message "folder"s, mail "message"s, and "glodaList" results.  The
 *  commonality is that they all use the "mailContent" panel's folder tree,
 *  thread tree, and message pane objects.  This happens for historical reasons,
 *  likely involving the fact that prior to the introduction of this
 *  abstraction, everything was always stored in global objects.  For the 3.0
 *  release cycle we considered avoiding this 'multiplexed' style of operation
 *  but decided against moving to making each tab be indepdendent because of
 *  presumed complexity.
 *
 * The tab info objects (as tabmail's currentTabInfo/tabInfo fields contain)
 *  have the following attributes specific to our implementation:
 *
 * @property {string} uriToOpen
 * @property {nsIMsgDBView} dbView The database view to use with the thread tree
 *     when this tab is displayed.  The value will be assigned to the global
 *     gDBView in the process.
 * @property {nsIMessenger} messenger Used to preserve "messenger" global value.
 *     The messenger object is the keeper of the 'undo' state and navigation
 *     history, which is why we do this.
 *
 * @property {nsIMsgDBHdr} hdr In "message" mode, the header of the message
 *     being displayed.
 * @property {nsIMsgSearchSession} searchSession Used to preserve gSearchSession
 *     global value.
 *
 */
var mailTabType = {
  name: "mail",
  panelId: "mailContent",
  modes: {
    /**
     * The folder view displays the contents of an nsIMsgDBFolder, with the
     *  folder pane (potentially), thread pane (always), and message pane
     *  (potentially) displayed.
     *
     * The actual nsMsgDBView can be any of the following types of things:
     *  - A single folder.
     *    - A quicksearch on a single folder.
     *  - A virtual folder potentially containing messages from multiple
     *    folders. (eShowVirtualFolderResults)
     */
    folder: {
      isDefault: true,
      type: "folder",
      /// The set of panes that are legal to be displayed in this mode
      legalPanes: {
        folder: true,
        thread: true,
        message: true
      },
      /// The set of panes that are legal when we are showing account central
      accountCentralLegalPanes: {
        folder: true,
        accountCentral: true,
        message: false
      },
      openFirstTab: function(aTab) {
        this.openTab(aTab, true, new MessagePaneDisplayWidget(), true);
        // persistence and restoreTab wants to know if we are the magic first tab
        aTab.firstTab = true;
        // Inherit the search mode from a window
        let windowToInheritFrom = null;
        if (window.opener &&
            (window.opener.document.documentElement.getAttribute("windowtype") ==
             "mail:3pane"))
          windowToInheritFrom = window.opener;
        else
          windowToInheritFrom = FindOther3PaneWindow();

        aTab.folderDisplay.makeActive();
      },
      /**
       * @param aArgs.folder The nsIMsgFolder to display.
       * @param [aArgs.msgHdr] Optional message header to display.
       * @param [aArgs.folderPaneVisible] Whether the folder pane should be
       *            visible. If this isn't specified, the current or first tab's
       *            current state is used.
       * @param [aArgs.messagePaneVisible] Whether the message pane should be
       *            visible. If this isn't specified, the current or first tab's
       *            current state is used.
       * @param [aArgs.forceSelectMessage] Whether we should consider dropping
       *            filters to select the message. This has no effect if
       *            aArgs.msgHdr isn't specified. Defaults to false.
       */
      openTab: function(aTab, aArgs) {
        // persistence and restoreTab wants to know if we are the magic first tab
        aTab.firstTab = false;

        // Get a tab that we can initialize our user preferences from.
        // (We don't want to assume that our immediate predecessor was a
        //  "folder" tab.)
        let modelTab = document.getElementById("tabmail")
                         .getTabInfoForCurrentOrFirstModeInstance(aTab.mode);

        // - figure out whether to show the folder pane
        let folderPaneShouldBeVisible;
        // explicitly told to us?
        if ("folderPaneVisible" in aArgs)
          folderPaneShouldBeVisible = aArgs.folderPaneVisible;
        // inherit from the previous tab (if we've got one)
        else if (modelTab)
          folderPaneShouldBeVisible = modelTab.folderDisplay.folderPaneVisible;
        // who doesn't love a folder pane?
        else
          folderPaneShouldBeVisible = true;

        // - figure out whether to show the message pane
        let messagePaneShouldBeVisible;
        // explicitly told to us?
        if ("messagePaneVisible" in aArgs)
          messagePaneShouldBeVisible = aArgs.messagePaneVisible;
        // inherit from the previous tab (if we've got one)
        else if (modelTab)
          messagePaneShouldBeVisible = modelTab.messageDisplay.visible;
        // who doesn't love a message pane?
        else
          messagePaneShouldBeVisible = true;

        this.openTab(aTab, false,
                     new MessagePaneDisplayWidget(messagePaneShouldBeVisible),
                     folderPaneShouldBeVisible);

        let background = ("background" in aArgs) && aArgs.background;
        let msgHdr = ("msgHdr" in aArgs) && aArgs.msgHdr;
        let forceSelectMessage = ("forceSelectMessage" in aArgs) &&
                                     aArgs.forceSelectMessage;

        if (msgHdr)
          // Tell the folder display that a selectMessage is coming up, so that
          // we don't generate double message loads
          aTab.folderDisplay.selectMessageComingUp();

        if (!background) {
          // Activate the folder display
          aTab.folderDisplay.makeActive();

          // HACK: Since we've switched away from the tab, we need to bring
          // back the real selection before selecting the folder, so do that
          RestoreSelectionWithoutContentLoad(document.getElementById(
                                                 "folderTree"));
        }

        aTab.folderDisplay.show(aArgs.folder);
        if (msgHdr)
          aTab.folderDisplay.selectMessage(msgHdr, forceSelectMessage);

        if (!background) {
          // This only makes sure the selection in the folder pane is correct --
          // the actual displaying is handled by the show() call above. This
          // also means that we don't have to bother about making
          // gFolderTreeView believe that a selection change has happened.
          gFolderTreeView.selectFolder(aArgs.folder);
        }

        aTab.mode.onTitleChanged.call(this, aTab, aTab.tabNode);
      },
      persistTab: function(aTab) {
        try {
          if (!aTab.folderDisplay.displayedFolder)
            return null;
          let retval = {
            folderURI: aTab.folderDisplay.displayedFolder.URI,
            // if the folder pane is active, then we need to look at
            // whether the box is collapsed
            folderPaneVisible: aTab.folderDisplay.folderPaneVisible,
            messagePaneVisible: aTab.messageDisplay.visible,
            firstTab: aTab.firstTab
          };
          return retval;
        } catch (e) {
          logException(e);
          return null;
        }
      },
      restoreTab: function(aTabmail, aPersistedState) {
      try {
        let rdfService = Components.classes['@mozilla.org/rdf/rdf-service;1']
                           .getService(Components.interfaces.nsIRDFService);
        let folder = rdfService.GetResource(aPersistedState.folderURI)
                       .QueryInterface(Components.interfaces.nsIMsgFolder);
        // if the folder no longer exists, we can't restore the tab
        if (folder) {
          let folderPaneVisible = ("folderPaneVisible" in aPersistedState) ?
                                    aPersistedState.folderPaneVisible :
                                    true;
          // If we are talking about the first tab, it already exists and we
          //  should poke it.  We are assuming it is the currently displayed
          //  tab because we are privvy to the implementation details and know
          //  it to be true.
          if (aPersistedState.firstTab) {
            // Poke the folder pane box and splitter
            document.getElementById("folderPaneBox").collapsed =
              !folderPaneVisible;
            document.getElementById("folderpane_splitter").setAttribute("state",
              (folderPaneVisible ? "open" : "collapsed"));

            if (gMessageDisplay.visible != aPersistedState.messagePaneVisible) {
              MsgToggleMessagePane();
              // For reasons that are not immediately obvious, sometimes the
              //  message display is not active at this time.  In that case, we
              //  need to explicitly set the _visible value because otherwise it
              //  misses out on the toggle event.
              if (!gMessageDisplay._active)
                gMessageDisplay._visible = aPersistedState.messagePaneVisible;
            }

            if (!("dontRestoreFirstTab" in aPersistedState &&
                  aPersistedState.dontRestoreFirstTab))
              gFolderTreeView.selectFolder(folder);

            // We need to manually trigger the tab monitor restore trigger
            // for this tab.  In theory this should be in tabmail, but the
            // special nature of the first tab will last exactly long as this
            // implementation right here so it does not particularly matter
            // and is a bit more honest, if ugly, to do it here.
            let tabmail = document.getElementById("tabmail");
            let restoreState = tabmail._restoringTabState;
            let tab = tabmail.tabInfo[0];
            for (let tabMonitor of tabmail.tabMonitors) {
              if (("onTabRestored" in tabMonitor) &&
                  (tabMonitor.monitorName in restoreState.ext)) {
                tabMonitor.onTabRestored(tab,
                                         restoreState.ext[tabMonitor.monitorName],
                                         true);
              }
            }
          }
          else {
            let tabArgs = {
              folder: folder,
              folderPaneVisible: folderPaneVisible,
              messagePaneVisible: aPersistedState.messagePaneVisible,
              background: true
            };
            aTabmail.openTab("folder", tabArgs);
          }
        }
      } catch (e) {
        logException(e);
      }
      },
      onTitleChanged: function(aTab, aTabNode) {
        if (!aTab.folderDisplay || !aTab.folderDisplay.displayedFolder) {
          // Don't show "undefined" as title when there is no account.
          aTab.title = " ";
          return;
        }
        // The user may have changed folders, triggering our onTitleChanged
        // callback.
        let folder = aTab.folderDisplay.displayedFolder;
        aTab.title = folder.prettyName;
        if (!folder.isServer && this._getNumberOfRealAccounts() > 1)
          aTab.title += " - " + folder.server.prettyName;

        // Update the appropriate attributes on the tab.
        let specialFolderStr = getSpecialFolderString(folder);
        aTabNode.setAttribute('SpecialFolder', specialFolderStr);
        aTabNode.setAttribute('ServerType', folder.server.type);
        aTabNode.setAttribute('IsServer', folder.isServer);
        aTabNode.setAttribute('IsSecure', folder.server.isSecure);
        let feedUrls = FeedUtils.getFeedUrlsInFolder(folder);
        aTabNode.setAttribute("IsFeedFolder", feedUrls ? true : false);

        // Set the favicon for feed folders.
        if (!aTabNode.selected) {
          // This is a new background tab.
          if (folder.server.type != "rss" || folder.isServer ||
              specialFolderStr != "none" || !feedUrls)
            return;

          let iconUrl = FeedUtils.getFavicon(folder, feedUrls[0], null, null, null);
          if (iconUrl) {
            let url = Services.io.newURI(iconUrl, null, null).prePath;
            let callback = function(iconUrl, domain, tabNode) {
              if (iconUrl)
                tabNode.setAttribute("image", iconUrl);
              else
                tabNode.removeAttribute("image");
            }
            aTabNode.addEventListener("error",
              function(event) { FeedUtils.getFaviconFromPage(url, callback, aTabNode); });
            aTabNode.setAttribute("image", iconUrl);
          }

          return;
        }

        // Changing folders in an existing tab or opening a foreground tab.
        let treeItem = gFolderTreeView.selection.count ?
          gFolderTreeView._rowMap[gFolderTreeView.selection.currentIndex] : null;
        if (!treeItem || folder != treeItem._folder)
          return;

        aTabNode.removeAttribute("image");
        if (folder.server.type != "rss" || folder.isServer ||
            specialFolderStr != "none")
          return;

        if (treeItem._favicon)
          aTabNode.setAttribute("image", treeItem._favicon);

        // First tab always exists but treeItem is not ready at startup.
        if (treeItem._favicon == null && feedUrls &&
            aTabNode.getAttribute("first-tab") == "true") {
          let iconUrl = FeedUtils.getFavicon(folder, feedUrls[0], null, null, null);
          if (iconUrl) {
            let url = Services.io.newURI(iconUrl, null, null).prePath;
            let callback = function(iconUrl, domain, tabNode) {
              if (iconUrl)
                tabNode.setAttribute("image", iconUrl);
              else
                tabNode.removeAttribute("image");
            }
            aTabNode.addEventListener("error",
              function(event) { FeedUtils.getFaviconFromPage(url, callback, aTabNode); });
            aTabNode.setAttribute("image", iconUrl);
          }
        }
      },
      getBrowser: function(aTab) {
        // If we are currently a thread summary, we want to select the multi
        // message browser rather than the message pane.
        return gMessageDisplay.singleMessageDisplay ?
               document.getElementById("messagepane") :
               document.getElementById("multimessage");
      }
    },
    /**
     * The message view displays a single message.  In this view, the folder
     *  pane and thread pane are forced hidden and only the message pane is
     *  displayed.
     */
    message: {
      type: "message",
      /// The set of panes that are legal to be displayed in this mode
      legalPanes: {
        folder: false,
        thread: false,
        message: true
      },
      openTab: function(aTab, aArgs) {
        this.openTab(aTab, false, new MessageTabDisplayWidget(), false);

        let viewWrapperToClone = ("viewWrapperToClone" in aArgs) &&
                                 aArgs.viewWrapperToClone;
        let background = ("background" in aArgs) && aArgs.background;

        if (viewWrapperToClone) {
          // The original view must have a collapsed group header thread's
          // message(s) found in expand mode before it's cloned, for any to
          // be selected.
          if (viewWrapperToClone.showGroupedBySort)
            viewWrapperToClone.dbView.findIndexOfMsgHdr(aArgs.msgHdr, true);

          aTab.folderDisplay.cloneView(viewWrapperToClone);
        }
        else {
          // Create a synthetic message view for the header
          let synView = new MsgHdrSyntheticView(aArgs.msgHdr);
          aTab.folderDisplay.show(synView);
        }

        // folderDisplay.show is going to try to set the title itself, but we
        // wouldn't have selected a message at that point, so set the title
        // here
        aTab.mode.onTitleChanged.call(this, aTab, null, aArgs.msgHdr);

        aTab.folderDisplay.selectMessage(aArgs.msgHdr);

        // Once we're brought into the foreground, the message pane should
        // get focus
        aTab._focusedElement = document.getElementById("messagepane");

        // we only want to make it active after setting up the view and the message
        //  to avoid generating bogus summarization events.
        if (!background) {
          aTab.folderDisplay.makeActive();
          this.restoreFocus(aTab);
        }
        else {
          // We don't want to null out the real tree box view, as that
          // corresponds to the _current_ tab, not the new one
          aTab.folderDisplay.hookUpFakeTreeBox(false);
        }
      },
      persistTab: function(aTab) {
        let msgHdr = aTab.folderDisplay.selectedMessage;
        return {
          messageURI: msgHdr.folder.getUriForMsg(msgHdr)
        };
      },
      restoreTab: function(aTabmail, aPersistedState) {
        let msgHdr = messenger.msgHdrFromURI(aPersistedState.messageURI);
        // if the message no longer exists, we can't restore the tab
        if (msgHdr)
          aTabmail.openTab("message", {msgHdr: msgHdr, background: true});
      },
      onTitleChanged: function(aTab, aTabNode, aMsgHdr) {
        // Try and figure out the selected message if one was not provided.
        // It is possible that the folder has yet to load, so it may still be
        //  null.
        if (aMsgHdr == null)
          aMsgHdr = aTab.folderDisplay.selectedMessage;
        aTab.title = "";
        if (aMsgHdr == null)
          return;
        if (aMsgHdr.flags & Components.interfaces.nsMsgMessageFlags.HasRe)
          aTab.title = "Re: ";
        if (aMsgHdr.mime2DecodedSubject)
          aTab.title += aMsgHdr.mime2DecodedSubject;

        aTab.title += " - " + aMsgHdr.folder.prettyName;
        if (this._getNumberOfRealAccounts() > 1)
          aTab.title += " - " + aMsgHdr.folder.server.prettyName;

        // Set the favicon for feed messages.
        if (aMsgHdr.flags & Components.interfaces.nsMsgMessageFlags.FeedMsg &&
            !aTab.tabNode.hasAttribute("IsFeedMessage")) {
          aTab.tabNode.setAttribute("IsFeedMessage", true);
          if (!Services.prefs.getBoolPref("browser.chrome.site_icons") ||
              !Services.prefs.getBoolPref("browser.chrome.favicons"))
            return;

          MsgHdrToMimeMessage(aMsgHdr, null, function(aMsgHdr, aMimeMsg, tabNode) {
            if (aMimeMsg && aMimeMsg.headers["content-base"] &&
                aMimeMsg.headers["content-base"][0]) {
              let callback = function(iconUrl, domain, tabNode) {
                iconUrl = iconUrl ? iconUrl : FeedUtils.getFavicon(null, domain);
                if (iconUrl)
                  tabNode.setAttribute("image", iconUrl);
                else
                  tabNode.removeAttribute("image");
              }
              aTab.tabNode.setAttribute("onerror", "this.removeAttribute('image')");
              let url = aMimeMsg.headers["content-base"][0];
              if (url.startsWith("http"))
                FeedUtils.getFaviconFromPage(url, callback, aTab.tabNode);
            }
          }, false, {saneBodySize: true});
        }
      },
      getBrowser: function(aTab) {
        // Message tabs always use the messagepane browser.
        return document.getElementById("messagepane");
      }
    },
    /**
     * The glodaList view displays a gloda-backed nsMsgDBView with only the
     *  thread pane and (potentially) the message pane displayed; the folder
     *  pane is forced hidden.
     */
    glodaList: {
      type: "glodaSearch",
      /// The set of panes that are legal to be displayed in this mode
      legalPanes: {
        folder: false,
        thread: true,
        message: true,
      },
      /**
       * Open a new folder-display-style tab showing the contents of a gloda
       *  query/collection.  You must pass one of 'query'/'collection'/
       *  'conversation'
       *
       * @param {GlodaQuery} [aArgs.query] An un-triggered gloda query to use.
       *     Alternatively, if you already have a collection, you can pass that
       *     instead as 'collection'.
       * @param {GlodaCollection} [aArgs.collection] A gloda collection to
       *     display.
       * @param {GlodaConversation} [aArgs.conversation] A conversation whose
       *     messages you want to display.
       * @param {GlodaMessage} [aArgs.message] The message to select in the
       *     conversation, if provided.
       * @param aArgs.title The title to give to the tab.  If this is not user
       *     content (a search string, a message subject, etc.), make sure you
       *     are using a localized string.
       *
       * XXX This needs to handle opening in the background
       */
      openTab: function(aTab, aArgs) {
        aTab.glodaSynView = new GlodaSyntheticView(aArgs);
        aTab.title = aArgs.title;

        this.openTab(aTab, false, new MessagePaneDisplayWidget(), false);
        aTab.folderDisplay.show(aTab.glodaSynView);
        // XXX persist threaded state?
        aTab.folderDisplay.view.showThreaded = true;

        let background = ("background" in aArgs) && aArgs.background;
        if (!background)
          aTab.folderDisplay.makeActive();
        if ("message" in aArgs) {
          let hdr = aArgs.message.folderMessage;
          if (hdr)
            aTab.folderDisplay.selectMessage(hdr);
        }
      },
      getBrowser: function(aTab) {
        // If we are currently a thread summary, we want to select the multi
        // message browser rather than the message pane.
        return gMessageDisplay.singleMessageDisplay ?
               document.getElementById("messagepane") :
               document.getElementById("multimessage");
      }
    },
  },

  _getNumberOfRealAccounts : function() {
    let accountCount = MailServices.accounts.accounts.length;
    // If we have an account, we also always have a "Local Folders" account.
    return accountCount > 0 ? (accountCount - 1) : 0;
  },

  /**
   * Common tab opening code shared by the various tab modes.
   */
  openTab: function(aTab, aIsFirstTab, aMessageDisplay, aFolderPaneVisible) {
    // Set the messagepane as the primary browser for content.
    document.getElementById("messagepane").setAttribute("type",
                                                        "content-primary");

    aTab.messageDisplay = aMessageDisplay;
    aTab.folderDisplay = new FolderDisplayWidget(aTab, aTab.messageDisplay);
    aTab.folderDisplay.msgWindow = msgWindow;
    aTab.folderDisplay.tree = document.getElementById("threadTree");
    aTab.folderDisplay.treeBox = aTab.folderDisplay.tree.boxObject.QueryInterface(
                                   Components.interfaces.nsITreeBoxObject);
    aTab.folderDisplay.folderPaneVisible = aFolderPaneVisible;

    if (aIsFirstTab) {
      aTab.folderDisplay.messenger = messenger;
    }
    else {
      // Each tab gets its own messenger instance; this provides each tab with
      // its own undo/redo stack and back/forward navigation history.
      // If this is a foreground tab, folderDisplay.makeActive() is going to
      // set it as the global messenger, so there's no need to do it here
      let tabMessenger = Components.classes["@mozilla.org/messenger;1"]
                                   .createInstance(Components.interfaces.nsIMessenger);
      tabMessenger.setWindow(window, msgWindow);
      aTab.folderDisplay.messenger = tabMessenger;
    }
  },

  closeTab: function(aTab) {
    aTab.folderDisplay.close();
  },

  /**
   * Save off the tab's currently focused element or window.
   * - If the message pane or summary is currently focused, save the
   *   corresponding browser element as the focused element.
   * - If the thread tree or folder tree is focused, save that as the focused
   *   element.
   */
  saveFocus: function mailTabType_saveFocus(aTab) {
    aTab._focusedElement = aTab.folderDisplay.focusedPane;
  },

  /**
   * Restore the tab's focused element or window.
   */
  restoreFocus: function mailTabType_restoreFocus(aTab) {
    // There seem to be issues with opening multiple messages at once, so allow
    // things to stabilize a bit before proceeding
    let reallyRestoreFocus = function mailTabType_reallyRestoreFocus(aTab) {
      if ("_focusedElement" in aTab && aTab._focusedElement) {
        aTab._focusedElement.focus();

        // If we were focused on the message pane, we need to focus on the
        // appropriate subnode (the single- or multi-message content window).
        if (aTab._focusedElement == document.getElementById("messagepanebox")) {
          if (aTab.messageDisplay.singleMessageDisplay)
            document.getElementById("messagepane").focus();
          else
            document.getElementById("multimessage").focus();
        }
      }
      aTab._focusedElement = null;
    };

    window.setTimeout(reallyRestoreFocus, 0, aTab);
  },

  saveTabState: function(aTab) {
    // Now let other tabs have a primary browser if they want.
    document.getElementById("messagepane").setAttribute("type",
                                                        "content-targetable");

    this.saveFocus(aTab);
    aTab.folderDisplay.makeInactive();
  },

  /**
   * Some panes simply are illegal in certain views, and some panes are legal
   *  but the user may have collapsed/hidden them.  If that was not enough, we
   *  have three different layouts that are possible, each of which requires a
   *  slightly different DOM configuration, and accordingly for us to poke at
   *  different DOM nodes.  Things are made somewhat simpler by our decision
   *  that all tabs share the same layout.
   * This method takes the legal states and current display states and attempts
   *  to apply the appropriate logic to make it all work out.  This method is
   *  not in charge of figuring out or preserving display states.
   *
   * A brief primer on splitters and friends:
   * - A collapsed splitter is not visible (and otherwise it is visible).
   * - A collapsed node is not visible (and otherwise it is visible).
   * - A splitter whose "state" is "collapsed" collapses the widget implied by
   *    the value of the "collapse" attribute.  The splitter itself will be
   *    visible unless "collapsed".
   *
   * @param aLegalStates A dictionary where each key and value indicates whether
   *     the pane in question (key) is legal to be displayed in this mode.  If
   *     the value is true, then the pane is legal.  Omitted pane keys imply
   *     that the pane is illegal.  Keys are:
   *     - folder: The folder (tree) pane.
   *     - thread: The thread pane.
   *     - accountCentral: While it's in a deck with the thread pane, this
   *        is distinct from the thread pane because some other things depend
   *        on whether it's actually the thread pane we are showing.
   *     - message: The message pane.  Required/assumed to be true for now.
   * @param aVisibleStates A dictionary where each value indicates whether the
   *     pane should be 'visible' (not collapsed).  Only panes that are governed
   *     by splitters are options here.  Keys are:
   *     - folder: The folder (tree) pane.
   *     - message: The message pane.
   */
  _setPaneStates: function mailTabType_setPaneStates(aLegalStates,
                                                     aVisibleStates) {
    // The display deck hosts both the thread pane and account central.
    let displayDeckLegal = aLegalStates.thread ||
                           aLegalStates.accountCentral;

    let layout = Services.prefs.getIntPref("mail.pane_config.dynamic");
    if (layout == kWidePaneConfig)
    {
      // in the "wide" configuration, the #messengerBox is left holding the
      //  folder pane and thread pane, and the message pane has migrated to be
      //  its sibling (under #mailContent).
      // Accordingly, if both the folder and thread panes are illegal, we
      //  want to collapse the #messengerBox and make sure the #messagepanebox
      //  fills up the screen.  (For example, when in "message" mode.)
      let collapseMessengerBox = !aLegalStates.folder && !displayDeckLegal;
      document.getElementById("messengerBox").collapsed = collapseMessengerBox;
      if (collapseMessengerBox)
        document.getElementById("messagepanebox").flex = 1;
    }

    // -- folder pane
    // collapse the splitter when not legal
    document.getElementById("folderpane_splitter").collapsed =
      !aLegalStates.folder;
    // collapse the folder pane when not visible
    document.getElementById("folderPaneBox").collapsed =
     !aLegalStates.folder || !aVisibleStates.folder;
    // let the splitter know as well
    document.getElementById("folderpane_splitter").setAttribute("state",
     (!aLegalStates.folder || !aVisibleStates.folder) ? "collapsed" : "open");
    try {
      // The folder-location-toolbar should be hidden if the folder
      // pane is illegal. Otherwise we shouldn't touch it
      document.getElementById("folder-location-container").collapsed =
        !aLegalStates.folder;
    } catch (ex) {}

    // -- display deck (thread pane / account central)
    // in a vertical view, the threadContentArea sits in the #threadPaneBox
    //  next to the message pane and its splitter.
    if (layout == kVerticalMailLayout)
      document.getElementById("threadContentArea").collapsed =
        !displayDeckLegal;
    // whereas in the default view, the displayDeck is the one next to the
    //  message pane and its splitter
    else
      document.getElementById("displayDeck").collapsed =
        !displayDeckLegal;

    // -- thread pane
    // the threadpane-splitter collapses the message pane (arguably a misnomer),
    //  but it only needs to exist when the thread-pane is legal
    document.getElementById("threadpane-splitter").collapsed =
      !aLegalStates.thread;
    if (aLegalStates.thread && aLegalStates.message)
      document.getElementById("threadpane-splitter").setAttribute("state",
        aVisibleStates.message ? "open" : "collapsed");

    // Some things do not make sense if the thread pane is not legal.
    // (This is likely an example of something that should be using the command
    //  mechanism to update the UI elements as to the state of what the user
    //  is looking at, rather than home-brewing it in here.)
    try {
      // you can't quick-search if you don't have a collection of messages
      document.getElementById("search-container").collapsed =
        !aLegalStates.thread;
    } catch (ex) {}
    try {
      // views only work on the thread pane; no thread pane, no views
      document.getElementById("mailviews-container").collapsed =
        !aLegalStates.thread;
    } catch (ex) {}

    // -- thread pane status bar helpers
    document.getElementById("unreadMessageCount").hidden = !aLegalStates.thread;
    document.getElementById("totalMessageCount").hidden = !aLegalStates.thread;

    // -- message pane
    document.getElementById("messagepaneboxwrapper").collapsed =
      !aLegalStates.message || !aVisibleStates.message;

    // we are responsible for updating the keybinding; view_init takes care of
    //  updating the menu item (on demand)
    let messagePaneToggleKey = document.getElementById("key_toggleMessagePane");
    if (aLegalStates.thread)
      messagePaneToggleKey.removeAttribute("disabled");
    else
      messagePaneToggleKey.setAttribute("disabled", "true");
  },

  showTab: function(aTab) {
    // Set the messagepane as the primary browser for content.
    document.getElementById("messagepane").setAttribute("type",
                                                        "content-primary");

    aTab.folderDisplay.makeActive();

    // - restore folder pane/tree selection
    if (aTab.folderDisplay.displayedFolder) {
      // but don't generate any events while doing so!
      gFolderTreeView.selection.selectEventsSuppressed = true;
      try {
        gFolderTreeView.selectFolder(aTab.folderDisplay.displayedFolder);
      }
      finally {
        gIgnoreSyntheticFolderPaneSelectionChange = true;
        gFolderTreeView.selection.selectEventsSuppressed = false;
      }
    }

    // restore focus
    this.restoreFocus(aTab);
  },

  // nsIController implementation

  supportsCommand: function mailTabType_supportsCommand(aCommand, aTab) {
    switch (aCommand) {
      case "cmd_viewClassicMailLayout":
      case "cmd_viewWideMailLayout":
      case "cmd_viewVerticalMailLayout":
      case "cmd_toggleFolderPane":
      case "cmd_toggleFolderPaneCols":
      case "cmd_toggleMessagePane":
        return true;

      default:
        return DefaultController.supportsCommand(aCommand);
    }
  },

  // We only depend on what's illegal
  isCommandEnabled: function mailTabType_isCommandEnabled(aCommand, aTab) {
    switch (aCommand) {
      case "cmd_viewClassicMailLayout":
      case "cmd_viewWideMailLayout":
      case "cmd_viewVerticalMailLayout":
      case "cmd_toggleFolderPane":
      case "cmd_toggleFolderPaneCols":
      case "cmd_toggleMessagePane":
        // If the thread pane is illegal, these are all disabled
        if (!aTab.mode.legalPanes.thread)
          return false;
        // else fall through

      default:
        return DefaultController.isCommandEnabled(aCommand);
    }
  },

  doCommand: function mailTabType_doCommand(aCommand, aTab) {
    if (!this.isCommandEnabled(aCommand, aTab))
      return;

    // DefaultController knows how to handle this
    DefaultController.doCommand(aCommand, aTab);
  }
};