!import
1 //@line 39 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/mail/base/content/msgHdrViewOverlay.js"
2
3
4 /* This is where functions related to displaying the headers for a selected message in the
5 message pane live. */
6
7 ////////////////////////////////////////////////////////////////////////////////////
8 // Warning: if you go to modify any of these JS routines please get a code review from
9 // scott@scott-macgregor.org. It's critical that the code in here for displaying
10 // the message headers for a selected message remain as fast as possible. In particular,
11 // right now, we only introduce one reflow per message. i.e. if you click on a message in the thread
12 // pane, we batch up all the changes for displaying the header pane (to, cc, attachements button, etc.)
13 // and we make a single pass to display them. It's critical that we maintain this one reflow per message
14 // view in the message header pane.
15 ////////////////////////////////////////////////////////////////////////////////////
16
17 const kPersonalAddressbookUri = "moz-abmdbdirectory://abook.mab";
18
19 const kLargeIcon = 32;
20 const kSmallIcon = 16;
21
22 var gViewAllHeaders = false;
23 var gShowOrganization = false;
24 var gShowLargeAttachmentView = false;
25 var gShowUserAgent = false;
26 var gShowReferences = false;
27 var gShowMessageId = false;
28 var gExtraExpandedHeaders;
29 var gMinNumberOfHeaders = 0;
30 var gDummyHeaderIdIndex = 0;
31 var gCollapsedHeaderViewMode = false;
32 var gBuildAttachmentsForCurrentMsg = false;
33 var gBuildAttachmentPopupForCurrentMsg = true;
34 var gBuiltExpandedView = false;
35 var gBuiltCollapsedView = false;
36 var gOpenLabel;
37 var gOpenLabelAccesskey;
38 var gSaveLabel;
39 var gSaveLabelAccesskey;
40 var gDetachLabel;
41 var gDeleteLabel;
42 var gDetachLabelAccesskey;
43 var gDeleteLabelAccesskey;
44 var gMessengerBundle;
45 var gProfileDirURL;
46 var gShowCondensedEmailAddresses = true; // show the friendly display names for people I know instead of the name + email address
47 var gPersonalAddressBookDirectory; // used for determining if we want to show just the display name in email address nodes
48
49 // other components may listen to on start header & on end header notifications for each message we display
50 // to do that you need to add yourself to our gMessageListeners array with object that has two properties:
51 // onStartHeaders and onEndHeaders.
52 var gMessageListeners = new Array();
53
54 // For every possible "view" in the message pane, you need to define the header names you want to
55 // see in that view. In addition, include information describing how you want that header field to be
56 // presented. i.e. if it's an email address field, if you want a toggle inserted on the node in case
57 // of multiple email addresses, etc. We'll then use this static table to dynamically generate header view entries
58 // which manipulate the UI.
59 // When you add a header to one of these view lists you can specify the following properties:
60 // name: the name of the header. i.e. "to", "subject". This must be in lower case and the name of the
61 // header is used to help dynamically generate ids for objects in the document. (REQUIRED)
62 // useToggle: true if the values for this header are multiple email addresses and you want a
63 // a toggle icon to show a short vs. long list (DEFAULT: false)
64 // useShortView: (only works on some fields like From). If the field has a long presentation and a
65 // short presentation we'll use the short one. i.e. if you are showing the From field and you
66 // set this to true, we can show just "John Doe" instead of "John Doe <jdoe@netscape.net>".
67 // (DEFAULT: false)
68 //
69 // outputFunction: this is a method which takes a headerEntry (see the definition below) and a header value
70 // This allows you to provide your own methods for actually determining how the header value
71 // is displayed. (DEFAULT: updateHeaderValue which just sets the header value on the text node)
72
73 // Our first view is the collapsed view. This is very light weight view of the data. We only show a couple
74 // fields.
75 var gCollapsedHeaderList = [ {name:"subject", outputFunction:updateHeaderValueInTextNode},
76 {name:"from", useToggle:true, useShortView:true, outputFunction:OutputEmailAddresses},
77 {name:"date", outputFunction:updateHeaderValueInTextNode}];
78
79 // We also have an expanded header view. This shows many of your more common (and useful) headers.
80 var gExpandedHeaderList = [ {name:"subject"},
81 {name:"from", useToggle:true, outputFunction:OutputEmailAddresses},
82 {name:"sender", outputFunction:OutputEmailAddresses},
83 {name:"reply-to", useToggle:true, outputFunction:OutputEmailAddresses},
84 {name:"date"},
85 {name:"to", useToggle:true, outputFunction:OutputEmailAddresses},
86 {name:"cc", useToggle:true, outputFunction:OutputEmailAddresses},
87 {name:"bcc", useToggle:true, outputFunction:OutputEmailAddresses},
88 {name:"newsgroups", outputFunction:OutputNewsgroups},
89 {name:"references", outputFunction:OutputMessageIds},
90 {name:"followup-to", outputFunction:OutputNewsgroups},
91 {name:"content-base"},
92 {name:"tags"} ];
93
94 // Now, for each view the message pane can generate, we need a global table of headerEntries. These
95 // header entry objects are generated dynamically based on the static date in the header lists (see above)
96 // and elements we find in the DOM based on properties in the header lists.
97 var gCollapsedHeaderView = {};
98 var gExpandedHeaderView = {};
99
100 // currentHeaderData --> this is an array of header name and value pairs for the currently displayed message.
101 // it's purely a data object and has no view information. View information is contained in the view objects.
102 // for a given entry in this array you can ask for:
103 // .headerName ---> name of the header (i.e. 'to'). Always stored in lower case
104 // .headerValue --> value of the header "johndoe@netscape.net"
105 var currentHeaderData = {};
106
107 // For the currently displayed message, we store all the attachment data. When displaying a particular
108 // view, it's up to the view layer to extract this attachment data and turn it into something useful.
109 // For a given entry in the attachments list, you can ask for the following properties:
110 // .contentType --> the content type of the attachment
111 // url --> an imap, or mailbox url which can be used to fetch the message
112 // uri --> an RDF URI which refers to the message containig the attachment
113 // isExternalAttachment --> boolean flag stating whether the attachment is an attachment which is a URL that refers to the attachment location
114 var currentAttachments = new Array();
115
116 // createHeaderEntry --> our constructor method which creates a header Entry
117 // based on an entry in one of the header lists. A header entry is different from a header list.
118 // a header list just describes how you want a particular header to be presented. The header entry
119 // actually has knowledge about the DOM and the actual DOM elements associated with the header.
120 // prefix --> the name of the view (i.e. "collapsed", "expanded")
121 // headerListInfo --> entry from a header list.
122 function createHeaderEntry(prefix, headerListInfo)
123 {
124 var useShortView = false;
125 var partialIDName = prefix + headerListInfo.name;
126 this.enclosingBox = document.getElementById(partialIDName + 'Box');
127 this.textNode = document.getElementById(partialIDName + 'Value');
128 this.isValid = false;
129
130 if ("useShortView" in headerListInfo)
131 {
132 useShortView = headerListInfo.useShortView;
133 if (useShortView)
134 this.enclosingBox = this.textNode;
135 else
136 this.enclosingBox.emailAddressNode = this.textNode;
137 }
138
139 if ("useToggle" in headerListInfo)
140 {
141 this.useToggle = headerListInfo.useToggle;
142 if (this.useToggle) // find the toggle icon in the document
143 {
144 this.toggleIcon = this.enclosingBox.toggleIcon;
145 this.longTextNode = this.enclosingBox.longEmailAddresses;
146 this.textNode = this.enclosingBox.emailAddresses;
147 }
148 }
149 else
150 this.useToggle = false;
151
152 if (this.textNode)
153 this.textNode.useShortView = useShortView;
154
155 if ("outputFunction" in headerListInfo)
156 this.outputFunction = headerListInfo.outputFunction;
157 else
158 this.outputFunction = updateHeaderValue;
159 }
160
Called: Object:getElementById (33 calls, 504 v-uS)
XULElement:getAnonymousElementByAttribute (18 calls, 370 v-uS)
Object:setAttribute (2 calls, 46 v-uS)
ChromeWindow:split (1 calls, 12 v-uS)
ChromeWindow:toLowerCase (1 calls, 7 v-uS)
Object:appendChild (1 calls, 1942 v-uS)
Object:createElement (1 calls, 61 v-uS)
Called By: msgHdrViewOverlay.js:OnLoadMsgHeaderPane (1 calls, 4007 v-uS)
161 function initializeHeaderViewTables()
162 {
163 // iterate over each header in our header list arrays and create header entries
164 // for each one. These header entries are then stored in the appropriate header table
165 var index;
166 for (index = 0; index < gCollapsedHeaderList.length; index++)
167 {
168 gCollapsedHeaderView[gCollapsedHeaderList[index].name] =
169 new createHeaderEntry('collapsed', gCollapsedHeaderList[index]);
170 }
171
172 for (index = 0; index < gExpandedHeaderList.length; index++)
173 {
174 var headerName = gExpandedHeaderList[index].name;
175 gExpandedHeaderView[headerName] = new createHeaderEntry('expanded', gExpandedHeaderList[index]);
176 }
177
178 var extraHeaders = gExtraExpandedHeaders.split(' ');
179 for (index = 0; index < extraHeaders.length; index++)
180 {
181 var extraHeader = extraHeaders[index];
182 gExpandedHeaderView[extraHeader.toLowerCase()] = new createNewHeaderView(extraHeader, extraHeader + ':');
183 }
184 if (gShowOrganization)
185 {
186 var organizationEntry = {name:"organization", outputFunction:updateHeaderValue};
187 gExpandedHeaderView[organizationEntry.name] = new createHeaderEntry('expanded', organizationEntry);
188 }
189
190 if (gShowUserAgent)
191 {
192 var userAgentEntry = {name:"user-agent", outputFunction:updateHeaderValue};
193 gExpandedHeaderView[userAgentEntry.name] = new createHeaderEntry('expanded', userAgentEntry);
194 }
195
196 if (gShowMessageId)
197 {
198 var messageIdEntry = {name:"message-id", outputFunction:OutputMessageIds};
199 gExpandedHeaderView[messageIdEntry.name] = new createHeaderEntry('expanded', messageIdEntry);
200 }
201 }
202
Called: ChromeWindow:getBoolPref (6 calls, 74 v-uS)
ChromeWindow:getElementById (2 calls, 37 v-uS)
ChromeWindow:addObserver (1 calls, 25 v-uS)
ChromeWindow:createEvent (1 calls, 28 v-uS)
ChromeWindow:dispatchEvent (1 calls, 4516 v-uS)
ChromeWindow:getCharPref (1 calls, 14 v-uS)
ChromeWindow:getIntPref (1 calls, 13 v-uS)
ChromeWindow:initEvent (1 calls, 15 v-uS)
ChromeWindow:loadBindingDocument (1 calls, 117 v-uS)
XULElement:getAttribute (1 calls, 17 v-uS)
msgHdrViewOverlay.js:initializeHeaderViewTables (1 calls, 4007 v-uS)
203 function OnLoadMsgHeaderPane()
204 {
205 // HACK...force our XBL bindings file to be load before we try to create our first xbl widget....
206 // otherwise we have problems.
207 document.loadBindingDocument('chrome://messenger/content/mailWidgets.xml');
208
209 // load any preferences that at are global with regards to
210 // displaying a message...
211 gShowUserAgent = pref.getBoolPref("mailnews.headers.showUserAgent");
212 gMinNumberOfHeaders = pref.getIntPref("mailnews.headers.minNumHeaders");
213 gShowOrganization = pref.getBoolPref("mailnews.headers.showOrganization");
214 gShowLargeAttachmentView = pref.getBoolPref("mailnews.attachments.display.largeView");
215 gShowCondensedEmailAddresses = pref.getBoolPref("mail.showCondensedAddresses");
216 gShowReferences = pref.getBoolPref("mailnews.headers.showReferences");
217 gShowMessageId = pref.getBoolPref("mailnews.headers.showMessageId");
218 gExtraExpandedHeaders = pref.getCharPref("mailnews.headers.extraExpandedHeaders");
219
220 // listen to the
221 pref.addObserver("mail.showCondensedAddresses", MsgHdrViewObserver, false);
222
223 initializeHeaderViewTables();
224
225 var deckHeaderView = document.getElementById("msgHeaderViewDeck");
226 gCollapsedHeaderViewMode = deckHeaderView.selectedIndex == 0;
227
228 // dispatch an event letting any listeners know that we have loaded the message pane
229 var event = document.createEvent('Events');
230 event.initEvent('messagepane-loaded', false, true);
231 var headerViewElement = document.getElementById("msgHeaderView");
232 headerViewElement.dispatchEvent(event);
233 }
234
Called: ChromeWindow:createEvent (1 calls, 26 v-uS)
ChromeWindow:dispatchEvent (1 calls, 354 v-uS)
ChromeWindow:getElementById (1 calls, 16 v-uS)
ChromeWindow:initEvent (1 calls, 15 v-uS)
ChromeWindow:removeObserver (1 calls, 33 v-uS)
Called By: msgMail3PaneWindow.js:OnUnloadMessenger (1 calls, 522 v-uS)
235 function OnUnloadMsgHeaderPane()
236 {
237 pref.removeObserver("mail.showCondensedAddresses", MsgHdrViewObserver);
238
239 // dispatch an event letting any listeners know that we have unloaded the message pane
240 var event = document.createEvent('Events');
241 event.initEvent('messagepane-unloaded', false, true);
242 var headerViewElement = document.getElementById("msgHeaderView");
243 headerViewElement.dispatchEvent(event);
244 }
245
246 const MsgHdrViewObserver =
247 {
observe
248 observe: function(subject, topic, prefName)
249 {
250 // verify that we're changing the mail pane config pref
251 if (topic == "nsPref:changed")
252 {
253 if (prefName == "mail.showCondensedAddresses")
254 {
255 gShowCondensedEmailAddresses = pref.getBoolPref("mail.showCondensedAddresses");
256 MsgReload();
257 }
258 }
259 }
260 };
261
262 // The messageHeaderSink is the class that gets notified of a message's headers as we display the message
263 // through our mime converter.
264
265 var messageHeaderSink = {
266 onStartHeaders: function()
267 {
268 this.mSaveHdr = null;
269 // every time we start to redisplay a message, check the view all headers pref....
270 var showAllHeadersPref = pref.getIntPref("mail.show_headers");
271 if (showAllHeadersPref == 2)
272 {
273 gViewAllHeaders = true;
274 }
275 else
276 {
277 if (gViewAllHeaders) // if we currently are in view all header mode, rebuild our header view so we remove most of the header data
278 {
279 hideHeaderView(gExpandedHeaderView);
280 gExpandedHeaderView = {};
281 initializeHeaderViewTables();
282 }
283
284 gViewAllHeaders = false;
285 }
286
287 ClearCurrentHeaders();
288 gBuiltExpandedView = false;
289 gBuiltCollapsedView = false;
290 gBuildAttachmentsForCurrentMsg = false;
291 gBuildAttachmentPopupForCurrentMsg = true;
292 ClearAttachmentList();
293 ClearEditMessageButton();
294 gMessageNotificationBar.clearMsgNotifications();
295
296 for (index in gMessageListeners)
297 gMessageListeners[index].onStartHeaders();
298 },
299
300 onEndHeaders: function()
301 {
302 // WARNING: This is the ONLY routine inside of the message Header Sink that should
303 // trigger a reflow!
304 CheckNotify();
305
306 ClearHeaderView(gCollapsedHeaderView);
307 ClearHeaderView(gExpandedHeaderView);
308
309 EnsureSubjectValue(); // make sure there is a subject even if it's empty so we'll show the subject and the twisty
310
311 ShowMessageHeaderPane();
312 UpdateMessageHeaders();
313 ShowEditMessageButton();
314
315 for (index in gMessageListeners)
316 gMessageListeners[index].onEndHeaders();
317 },
318
319 processHeaders: function(headerNameEnumerator, headerValueEnumerator, dontCollectAddress)
320 {
321 this.onStartHeaders();
322
323 const kMailboxSeparator = ", ";
324 var index = 0;
325 while (headerNameEnumerator.hasMore())
326 {
327 var header = new Object;
328 header.headerValue = headerValueEnumerator.getNext();
329 header.headerName = headerNameEnumerator.getNext();
330
331 // for consistancy sake, let's force all header names to be lower case so
332 // we don't have to worry about looking for: Cc and CC, etc.
333 var lowerCaseHeaderName = header.headerName.toLowerCase();
334
335 // if we have an x-mailer or x-mimeole string, put it in the user-agent slot which we know how to handle
336 // already.
337 if (lowerCaseHeaderName == "x-mailer" || lowerCaseHeaderName == "x-mimeole")
338 lowerCaseHeaderName = "user-agent";
339
340 if (this.mDummyMsgHeader)
341 {
342 if (lowerCaseHeaderName == "from")
343 this.mDummyMsgHeader.author = header.headerValue;
344 else if (lowerCaseHeaderName == "to")
345 this.mDummyMsgHeader.recipients = header.headerValue;
346 else if (lowerCaseHeaderName == "cc")
347 this.mDummyMsgHeader.ccList = header.headerValue;
348 else if (lowerCaseHeaderName == "subject")
349 this.mDummyMsgHeader.subject = header.headerValue;
350 else if (lowerCaseHeaderName == "reply-to")
351 this.mDummyMsgHeader.replyTo = header.headerValue;
352 else if (lowerCaseHeaderName == "message-id")
353 this.mDummyMsgHeader.messageId = header.headerValue;
354
355 }
356 // according to RFC 2822, certain headers
357 // can occur "unlimited" times
358 if (lowerCaseHeaderName in currentHeaderData)
359 {
360 // sometimes, you can have multiple To or Cc lines....
361 // in this case, we want to append these headers into one.
362 if (lowerCaseHeaderName == 'to' || lowerCaseHeaderName == 'cc')
363 currentHeaderData[lowerCaseHeaderName].headerValue = currentHeaderData[lowerCaseHeaderName].headerValue + ',' + header.headerValue;
364 else {
365 // use the index to create a unique header name like:
366 // received5, received6, etc
367 currentHeaderData[lowerCaseHeaderName + index++] = header;
368 }
369 }
370 else
371 currentHeaderData[lowerCaseHeaderName] = header;
372 } // while we have more headers to parse
373
374 // process message tags as if they were headers in the message
375 SetTagHeader();
376
377 if (("from" in currentHeaderData) && ("sender" in currentHeaderData))
378 {
379 var msgHeaderParser = Components.classes["@mozilla.org/messenger/headerparser;1"]
380 .getService(Components.interfaces.nsIMsgHeaderParser);
381 var senderMailbox = kMailboxSeparator + msgHeaderParser.extractHeaderAddressMailboxes(null,
382 currentHeaderData.sender.headerValue) + kMailboxSeparator;
383 var fromMailboxes = kMailboxSeparator + msgHeaderParser.extractHeaderAddressMailboxes(null,
384 currentHeaderData.from.headerValue) + kMailboxSeparator;
385 if (fromMailboxes.indexOf(senderMailbox) >= 0)
386 delete currentHeaderData.sender;
387 }
388
389 this.onEndHeaders();
390 },
391
handleAttachment
392 handleAttachment: function(contentType, url, displayName, uri, isExternalAttachment)
393 {
394 // presentation level change....don't show vcards as external attachments in the UI.
395 // libmime already renders them inline.
396
397 try
398 {
399 if (!this.mSaveHdr)
400 this.mSaveHdr = messenger.messageServiceFromURI(uri).messageURIToMsgHdr(uri);
401 }
402 catch (ex) {}
403 if (contentType == "text/x-vcard")
404 {
405 var inlineAttachments = pref.getBoolPref("mail.inline_attachments");
406 var displayHtmlAs = pref.getIntPref("mailnews.display.html_as");
407 if (inlineAttachments && !displayHtmlAs)
408 {
409 return;
410 }
411 }
412
413 currentAttachments.push (new createNewAttachmentInfo(contentType, url, displayName, uri, isExternalAttachment));
414 // if we have an attachment, set the MSG_FLAG_ATTACH flag on the hdr
415 // this will cause the "message with attachment" icon to show up
416 // in the thread pane
417 // we only need to do this on the first attachment
418 var numAttachments = currentAttachments.length;
419 if (numAttachments == 1) {
420 // we also have to enable the File/Attachments menuitem
421 var node = document.getElementById("fileAttachmentMenu");
422 if (node)
423 node.removeAttribute("disabled");
424
425 try {
426 // convert the uri into a hdr
427 this.mSaveHdr.markHasAttachments(true);
428 }
429 catch (ex) {
430 dump("ex = " + ex + "\n");
431 }
432 }
433 },
434
onEndAllAttachments
435 onEndAllAttachments: function()
436 {
437 displayAttachmentsForExpandedView();
438 },
439
onEndMsgDownload
440 onEndMsgDownload: function(url)
441 {
442 // if we don't have any attachments, turn off the attachments flag
443 if (!this.mSaveHdr)
444 {
445 var messageUrl = url.QueryInterface(Components.interfaces.nsIMsgMessageUrl);
446 try
447 {
448 this.mSaveHdr = messenger.msgHdrFromURI(messageUrl.uri);
449 }
450 catch (ex) {}
451
452 }
453 if (!currentAttachments.length && this.mSaveHdr)
454 this.mSaveHdr.markHasAttachments(false);
455 OnMsgParsed(url);
456 },
457
458 onEndMsgHeaders: function(url)
459 {
460 OnMsgLoaded(url);
461 },
462
onMsgHasRemoteContent
463 onMsgHasRemoteContent: function(aMsgHdr)
464 {
465 gMessageNotificationBar.setRemoteContentMsg(aMsgHdr);
466 },
467
468 mSecurityInfo : null,
469 mSaveHdr: null,
get_securityInfo
470 get securityInfo()
471 {
472 return this.mSecurityInfo;
473 },
set_securityInfo
474 set securityInfo(aSecurityInfo)
475 {
476 this.mSecurityInfo = aSecurityInfo;
477 },
478
479 mDummyMsgHeader: null,
480
481 get dummyMsgHeader()
482 {
483 if (!this.mDummyMsgHeader)
484 this.mDummyMsgHeader = new nsDummyMsgHeader();
485 return this.mDummyMsgHeader;
486 },
487 mProperties: null,
get_properties
488 get properties()
489 {
490 if (!this.mProperties)
491 this.mProperties = Components.classes["@mozilla.org/hash-property-bag;1"].
492 createInstance(Components.interfaces.nsIWritablePropertyBag2);
493 return this.mProperties;
494 }
495 };
496
497 function SetTagHeader()
498 {
499 // it would be nice if we passed in the msgHdr from the back end
500 var msgHdr;
501 try
502 {
503 msgHdr = gDBView.hdrForFirstSelectedMessage;
504 }
505 catch (ex)
506 {
507 return; // no msgHdr to add our tags to
508 }
509
510 // get the list of known tags
511 var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
512 .getService(Components.interfaces.nsIMsgTagService);
513 var tagArray = tagService.getAllTags({});
514 var tagKeys = {};
515 for each (var tagInfo in tagArray)
516 if (tagInfo.tag)
517 tagKeys[tagInfo.key] = true;
518
519 // extract the tag keys from the msgHdr
520 var msgKeyArray = msgHdr.getStringProperty("keywords").split(" ");
521
522 // attach legacy label to the front if not already there
523 var label = msgHdr.label;
524 if (label)
525 {
526 var labelKey = "$label" + label;
527 if (msgKeyArray.indexOf(labelKey) < 0)
528 msgKeyArray.unshift(labelKey);
529 }
530
531 // Rebuild the keywords string with just the keys that are actual tags or
532 // legacy labels and not other keywords like Junk and NonJunk.
533 // Retain their order, though, with the label as oldest element.
534 for (var i = msgKeyArray.length - 1; i >= 0; --i)
535 if (!(msgKeyArray[i] in tagKeys))
536 msgKeyArray.splice(i, 1); // remove non-tag key
537 var msgKeys = msgKeyArray.join(" ");
538
539 if (msgKeys)
540 currentHeaderData.tags = {headerName: "tags", headerValue: msgKeys};
541 else // no more tags, so clear out the header field
542 delete currentHeaderData.tags;
543 }
544
EnsureSubjectValue
545 function EnsureSubjectValue()
546 {
547 if (!('subject' in currentHeaderData))
548 {
549 var foo = new Object;
550 foo.headerValue = "";
551 foo.headerName = 'subject';
552 currentHeaderData[foo.headerName] = foo;
553 }
554 }
555
CheckNotify
556 function CheckNotify()
557 {
558 if ("NotifyClearAddresses" in this)
559 NotifyClearAddresses();
560 }
561
OnTagsChange
562 function OnTagsChange()
563 {
564 // rebuild the tag headers
565 SetTagHeader();
566
567 // now update the expanded header view to rebuild the tags,
568 // and then show or hide the tag header box.
569 if (gBuiltExpandedView)
570 {
571 var headerEntry = gExpandedHeaderView.tags;
572 if (headerEntry)
573 {
574 headerEntry.valid = ("tags" in currentHeaderData);
575 if (headerEntry.valid)
576 headerEntry.outputFunction(headerEntry, currentHeaderData.tags.headerValue);
577
578 // if we are showing the expanded header view then we may need to collapse or
579 // show the tag header box...
580 if (!gCollapsedHeaderViewMode)
581 headerEntry.enclosingBox.collapsed = !headerEntry.valid;
582 }
583 }
584 }
585
586 // flush out any local state being held by a header entry for a given
587 // table
588 function ClearHeaderView(headerTable)
589 {
590 for (index in headerTable)
591 {
592 var headerEntry = headerTable[index];
593 if (headerEntry.useToggle)
594 {
595 headerEntry.enclosingBox.clearHeaderValues();
596 }
597
598 headerEntry.valid = false;
599 }
600 }
601
602 // make sure that any valid header entry in the table is collapsed
603 function hideHeaderView(headerTable)
604 {
605 for (index in headerTable)
606 {
607 headerTable[index].enclosingBox.collapsed = true;
608 }
609 }
610
611 // make sure that any valid header entry in the table specified is
612 // visible
613 function showHeaderView(headerTable)
614 {
615 var headerEntry;
616 for (index in headerTable)
617 {
618 headerEntry = headerTable[index];
619 if (headerEntry.valid)
620 {
621 headerEntry.enclosingBox.collapsed = false;
622 }
623 else // if the entry is invalid, always make sure it's collapsed
624 headerEntry.enclosingBox.collapsed = true;
625 }
626 }
627
628 // enumerate through the list of headers and find the number that are visible
629 // add empty entries if we don't have the minimum number of rows
630 function EnsureMinimumNumberOfHeaders (headerTable)
631 {
632 if (!gMinNumberOfHeaders) // 0 means we don't have a minimum..do nothing special
633 return;
634
635 var numVisibleHeaders = 0;
636 for (index in headerTable)
637 {
638 if (headerTable[index].valid)
639 numVisibleHeaders ++;
640 }
641
642 if (numVisibleHeaders < gMinNumberOfHeaders)
643 {
644 // how many empty headers do we need to add?
645 var numEmptyHeaders = gMinNumberOfHeaders - numVisibleHeaders;
646
647 // we may have already dynamically created our empty rows and we just need to make them visible
648 for (index in headerTable)
649 {
650 if (index.indexOf("Dummy-Header") == 0 && numEmptyHeaders)
651 {
652 headerTable[index].valid = true;
653 numEmptyHeaders--;
654 }
655 }
656
657 // ok, now if we have any extra dummy headers we need to add, create a new header widget for them
658 while (numEmptyHeaders)
659 {
660 var dummyHeaderId = "Dummy-Header" + gDummyHeaderIdIndex;
661 gExpandedHeaderView[dummyHeaderId] = new createNewHeaderView(dummyHeaderId, "");
662 gExpandedHeaderView[dummyHeaderId].valid = true;
663
664 gDummyHeaderIdIndex++;
665 numEmptyHeaders--;
666 }
667
668 }
669 }
670
671 // make sure the appropriate fields within the currently displayed view header mode
672 // are collapsed or visible...
673 function updateHeaderViews()
674 {
675 if (gCollapsedHeaderViewMode)
676 showHeaderView(gCollapsedHeaderView);
677 else
678 {
679 if (gMinNumberOfHeaders)
680 EnsureMinimumNumberOfHeaders(gExpandedHeaderView);
681 showHeaderView(gExpandedHeaderView);
682 }
683
684 displayAttachmentsForExpandedView();
685 }
686
687 function ToggleHeaderView ()
688 {
689 gCollapsedHeaderViewMode = !gCollapsedHeaderViewMode;
690 // Work around a xul deck bug where the height of the deck is determined by the tallest panel in the deck
691 // even if that panel is not selected...
692 document.getElementById('msgHeaderViewDeck').selectedPanel.collapsed = true;
693
694 UpdateMessageHeaders();
695
696 // select the new panel.
697 document.getElementById('msgHeaderViewDeck').selectedIndex = gCollapsedHeaderViewMode ? 0 : 1;
698
699 // Work around a xul deck bug where the height of the deck is determined by the tallest panel in the deck
700 // even if that panel is not selected...
701 document.getElementById('msgHeaderViewDeck').selectedPanel.collapsed = false;
702 }
703
704 // default method for updating a header value into a header entry
705 function updateHeaderValue(headerEntry, headerValue)
706 {
707 headerEntry.enclosingBox.headerValue = headerValue;
708 }
709
710 function updateHeaderValueInTextNode(headerEntry, headerValue)
711 {
712 headerEntry.textNode.value = headerValue;
713 }
714
715 function createNewHeaderView(headerName, label)
716 {
717 var idName = 'expanded' + headerName + 'Box';
718 var newHeader = document.createElement("mail-headerfield");
719
720 newHeader.setAttribute('id', idName);
721 newHeader.setAttribute('label', label);
722 newHeader.collapsed = true;
723
724 // this new element needs to be inserted into the view...
725 var topViewNode = document.getElementById('expandedHeaders');
726
727 topViewNode.appendChild(newHeader);
728
729 this.enclosingBox = newHeader;
730 this.isValid = false;
731 this.useToggle = false;
732 this.outputFunction = updateHeaderValue;
733 }
734
735 // UpdateMessageHeaders: Iterate through all the current header data we received from mime for this message
736 // for each header entry table, see if we have a corresponding entry for that header. i.e. does the particular
737 // view care about this header value. if it does then call updateHeaderEntry
738 function UpdateMessageHeaders()
739 {
740 // iterate over each header we received and see if we have a matching entry in each
741 // header view table...
742
743 var headerName;
744
745 // Remove the height attr so that it redraws correctly. Works around a problem that
746 // attachment-splitter causes if it's moved high enough to affect the header box:
747 document.getElementById('msgHeaderView').removeAttribute('height');
748
749 for (headerName in currentHeaderData)
750 {
751 var headerField = currentHeaderData[headerName];
752 var headerEntry = null;
753
754 if (headerName == "subject")
755 {
756 try {
757 if (gDBView.keyForFirstSelectedMessage == nsMsgKey_None)
758 {
759 var folder = null;
760 if (gCurrentFolderUri)
761 folder = RDF.GetResource(gCurrentFolderUri).QueryInterface(Components.interfaces.nsIMsgFolder);
762 setTitleFromFolder(folder, headerField.headerValue);
763 }
764 } catch (ex) {}
765 }
766
767 if (gCollapsedHeaderViewMode && !gBuiltCollapsedView)
768 {
769 if (headerName in gCollapsedHeaderView)
770 headerEntry = gCollapsedHeaderView[headerName];
771 }
772 else if (!gCollapsedHeaderViewMode && !gBuiltExpandedView)
773 {
774 if (headerName in gExpandedHeaderView)
775 headerEntry = gExpandedHeaderView[headerName];
776
777 if (!headerEntry && gViewAllHeaders)
778 {
779 // for view all headers, if we don't have a header field for this value....cheat and create one....then
780 // fill in a headerEntry
781 if (headerName == "message-id" || headerName == "in-reply-to")
782 {
783 var messageIdEntry = {name:headerName, outputFunction:OutputMessageIds};
784 gExpandedHeaderView[headerName] = new createHeaderEntry('expanded', messageIdEntry);
785 }
786 else
787 {
788 gExpandedHeaderView[headerName] = new createNewHeaderView(headerName,
789 currentHeaderData[headerName].headerName + ':');
790 }
791
792 headerEntry = gExpandedHeaderView[headerName];
793 }
794 } // if we are in expanded view....
795
796 if (headerEntry)
797 {
798 if (headerName == "references" &&
799 !(gViewAllHeaders || gShowReferences ||
800 (gDBView.msgFolder && gDBView.msgFolder.server.type == "nntp")))
801 {
802 // hide references header if view all headers mode isn't selected, the pref show references is
803 // deactivated and the currently displayed message isn't a newsgroup posting
804 headerEntry.valid = false;
805 }
806 else
807 {
808 headerEntry.outputFunction(headerEntry, headerField.headerValue);
809 headerEntry.valid = true;
810 }
811 }
812 }
813
814 if (gCollapsedHeaderViewMode)
815 gBuiltCollapsedView = true;
816 else
817 gBuiltExpandedView = true;
818
819 // now update the view to make sure the right elements are visible
820 updateHeaderViews();
821
822 if ("FinishEmailProcessing" in this)
823 FinishEmailProcessing();
824 }
825
826 function ClearCurrentHeaders()
827 {
828 currentHeaderData = {};
829 currentAttachments = new Array();
830 }
831
832 function ShowMessageHeaderPane()
833 {
834 document.getElementById('msgHeaderView').collapsed = false;
835
836 /* workaround for 39655 */
837 if (gFolderJustSwitched)
838 {
839 var el = document.getElementById("msgHeaderView");
840 el.setAttribute("style", el.getAttribute("style"));
841 gFolderJustSwitched = false;
842 }
843 }
844
845 function HideMessageHeaderPane()
846 {
847 document.getElementById('msgHeaderView').collapsed = true;
848
849 // disable the File/Attachments menuitem
850 document.getElementById("fileAttachmentMenu").setAttribute("disabled", "true");
851 // disable the attachment box
852 document.getElementById("attachmentView").collapsed = true;
853 document.getElementById("attachment-splitter").collapsed = true;
854 }
855
OutputNewsgroups
856 function OutputNewsgroups(headerEntry, headerValue)
857 {
858 headerValue = headerValue.replace(/,/g,", ");
859 updateHeaderValue(headerEntry, headerValue);
860 }
861
862 // take string of message-ids separated by whitespace, split it
863 // into message-ids and send them together with the index number
864 // to the corresponding mail-messageids-headerfield element
OutputMessageIds
865 function OutputMessageIds(headerEntry, headerValue)
866 {
867 var messageIdArray = headerValue.split(/\s+/);
868
869 headerEntry.enclosingBox.clearHeaderValues();
870 for (var i = 0; i < messageIdArray.length; i++)
871 headerEntry.enclosingBox.addMessageIdView(messageIdArray[i]);
872
873 headerEntry.enclosingBox.fillMessageIdNodes();
874 }
875
876 // OutputEmailAddresses --> knows how to take a comma separated list of email addresses,
877 // extracts them one by one, linkifying each email address into a mailto url.
878 // Then we add the link-ified email address to the parentDiv passed in.
879 //
880 // emailAddresses --> comma separated list of the addresses for this header field
881
OutputEmailAddresses
882 function OutputEmailAddresses(headerEntry, emailAddresses)
883 {
884 if (!emailAddresses)
885 return;
886
887 var addresses = {};
888 var fullNames = {};
889 var names = {};
890 var numAddresses = 0;
891
892 var msgHeaderParser = Components.classes["@mozilla.org/messenger/headerparser;1"]
893 .getService(Components.interfaces.nsIMsgHeaderParser);
894 numAddresses = msgHeaderParser.parseHeadersWithArray(emailAddresses, addresses, names, fullNames);
895 var index = 0;
896 while (index < numAddresses)
897 {
898 // if we want to include short/long toggle views and we have a long view, always add it.
899 // if we aren't including a short/long view OR if we are and we haven't parsed enough
900 // addresses to reach the cutoff valve yet then add it to the default (short) div.
901 var address = {};
902 address.emailAddress = addresses.value[index];
903 address.fullAddress = fullNames.value[index];
904 address.displayName = names.value[index];
905 if (headerEntry.useToggle)
906 headerEntry.enclosingBox.addAddressView(address);
907 else
908 updateEmailAddressNode(headerEntry.enclosingBox.emailAddressNode, address);
909 index++;
910 }
911
912 if (headerEntry.useToggle)
913 headerEntry.enclosingBox.buildViews();
914 }
915
updateEmailAddressNode
916 function updateEmailAddressNode(emailAddressNode, address)
917 {
918 emailAddressNode.setAttribute("label", address.fullAddress || address.displayName);
919 emailAddressNode.setAttribute("emailAddress", address.emailAddress);
920 emailAddressNode.setAttribute("fullAddress", address.fullAddress);
921 emailAddressNode.setAttribute("displayName", address.displayName);
922 emailAddressNode.removeAttribute("tooltiptext");
923
924 AddExtraAddressProcessing(address.emailAddress, emailAddressNode);
925 }
926
927 // thunderbird has smart logic for determining if we should show just the display name.
928 // mozilla already has a generic method that gets called on each email address node called
929 // AddExtraAddressProcessing which is undefined in seamonkey. Let's hijack this convient method to do what
930 // we want.
931
932 function AddExtraAddressProcessing(emailAddress, addressNode)
933 {
934 var displayName = addressNode.getAttribute("displayName");
935
936 if (gShowCondensedEmailAddresses && displayName)
937 {
938 // always show the address for the from and reply-to fields
939 var parentElementId = addressNode.parentNode.id;
940 var condenseName = true;
941
942 if (parentElementId == "expandedfromBox" || parentElementId == "expandedreply-toBox")
943 condenseName = false;
944
945 if (condenseName && useDisplayNameForAddress(emailAddress))
946 {
947 addressNode.setAttribute("label", displayName);
948 addressNode.setAttribute("tooltiptext", emailAddress);
949 }
950 }
951 }
952
953 function fillEmailAddressPopup(emailAddressNode)
954 {
955 var emailAddressPlaceHolder = document.getElementById('emailAddressPlaceHolder');
956 var emailAddress = emailAddressNode.getAttribute('emailAddress');
957
958 emailAddressPlaceHolder.setAttribute('label', emailAddress);
959 }
960
961 // returns true if we should use the display name for this address
962 // otherwise returns false
useDisplayNameForAddress
963 function useDisplayNameForAddress(emailAddress)
964 {
965 // For now, if the email address is in the personal address book, then consider this user a 'known' user
966 // and use the display name. I could eventually see our rules enlarged to include other local ABs, replicated
967 // LDAP directories, and maybe even domain matches (i.e. any email from someone in my company
968 // should use the friendly display name)
969
970 if (!gPersonalAddressBookDirectory)
971 {
972 var RDFService = Components.classes["@mozilla.org/rdf/rdf-service;1"]
973 .getService(Components.interfaces.nsIRDFService);
974 gPersonalAddressBookDirectory = RDFService.GetResource(kPersonalAddressbookUri).QueryInterface(Components.interfaces.nsIAbMDBDirectory);
975
976 if (!gPersonalAddressBookDirectory)
977 return false;
978 }
979
980 // look up the email address in the database
981 return gPersonalAddressBookDirectory.cardForEmailAddress(emailAddress);
982 }
983
AddNodeToAddressBook
984 function AddNodeToAddressBook (emailAddressNode)
985 {
986 if (emailAddressNode)
987 {
988 var primaryEmail = emailAddressNode.getAttribute("emailAddress");
989 var displayName = emailAddressNode.getAttribute("displayName");
990 window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul",
991 "",
992 "chrome,resizable=no,titlebar,modal,centerscreen",
993 {primaryEmail:primaryEmail, displayName:displayName });
994 }
995 }
996
997 // SendMailToNode takes the email address title button, extracts
998 // the email address we stored in there and opens a compose window
999 // with that address
SendMailToNode
1000 function SendMailToNode(emailAddressNode)
1001 {
1002 var fields = Components.classes["@mozilla.org/messengercompose/composefields;1"].createInstance(Components.interfaces.nsIMsgCompFields);
1003 var params = Components.classes["@mozilla.org/messengercompose/composeparams;1"].createInstance(Components.interfaces.nsIMsgComposeParams);
1004 if (emailAddressNode && fields && params)
1005 {
1006 fields.to = emailAddressNode.getAttribute("fullAddress");
1007 params.type = Components.interfaces.nsIMsgCompType.New;
1008 params.format = Components.interfaces.nsIMsgCompFormat.Default;
1009 params.identity = accountManager.getFirstIdentityForServer(GetLoadedMsgFolder().server);
1010 params.composeFields = fields;
1011 msgComposeService.OpenComposeWindowWithParams(null, params);
1012 }
1013 }
1014
1015 // CopyEmailAddress takes the email address title button, extracts
1016 // the email address we stored in there and copies it to the clipboard
CopyEmailAddress
1017 function CopyEmailAddress(emailAddressNode)
1018 {
1019 if (emailAddressNode)
1020 {
1021 var emailAddress = emailAddressNode.getAttribute("emailAddress");
1022
1023 var contractid = "@mozilla.org/widget/clipboardhelper;1";
1024 var iid = Components.interfaces.nsIClipboardHelper;
1025 var clipboard = Components.classes[contractid].getService(iid);
1026 clipboard.copyString(emailAddress);
1027 }
1028 }
1029
1030 // CreateFilter opens the Message Filters and Filter Rules dialogs.
1031 //The Filter Rules dialog has focus. The window is prefilled with filtername <email address>
1032 //Sender condition is selected and the value is prefilled <email address>
CreateFilter
1033 function CreateFilter(emailAddressNode)
1034 {
1035 if (emailAddressNode)
1036 {
1037 var emailAddress = emailAddressNode.getAttribute("emailAddress");
1038 if (emailAddress){
1039 top.MsgFilters(emailAddress, GetFirstSelectedMsgFolder());
1040 }
1041 }
1042 }
1043
1044 // createnewAttachmentInfo --> constructor method for creating new attachment object which goes into the
1045 // data attachment array.
createNewAttachmentInfo
1046 function createNewAttachmentInfo(contentType, url, displayName, uri, isExternalAttachment)
1047 {
1048 this.contentType = contentType;
1049 this.url = url;
1050 this.displayName = displayName;
1051 this.uri = uri;
1052 this.isExternalAttachment = isExternalAttachment;
1053 }
1054
saveAttachment
1055 function saveAttachment(aAttachment)
1056 {
1057 messenger.saveAttachment(aAttachment.contentType,
1058 aAttachment.url,
1059 encodeURIComponent(aAttachment.displayName),
1060 aAttachment.uri, aAttachment.isExternalAttachment);
1061 }
1062
openAttachment
1063 function openAttachment(aAttachment)
1064 {
1065 messenger.openAttachment(aAttachment.contentType,
1066 aAttachment.url,
1067 encodeURIComponent(aAttachment.displayName),
1068 aAttachment.uri, aAttachment.isExternalAttachment);
1069 }
1070
printAttachment
1071 function printAttachment(aAttachment)
1072 {
1073 /* we haven't implemented the ability to print attachments yet...
1074 messenger.printAttachment(aAttachment.contentType,
1075 aAttachment.url,
1076 encodeURIComponent(aAttachment.displayName),
1077 aAttachment.uri);
1078 */
1079 }
1080
detachAttachment
1081 function detachAttachment(aAttachment, aSaveFirst)
1082 {
1083 messenger.detachAttachment(aAttachment.contentType,
1084 aAttachment.url,
1085 encodeURIComponent(aAttachment.displayName),
1086 aAttachment.uri, aSaveFirst);
1087 }
1088
CanDetachAttachments
1089 function CanDetachAttachments()
1090 {
1091 var uri = GetLoadedMessage();
1092 var canDetach = !IsNewsMessage(uri) && (!IsImapMessage(uri) || MailOfflineMgr.isOnline());
1093 if (canDetach && ("content-type" in currentHeaderData))
1094 {
1095 var contentType = currentHeaderData["content-type"].headerValue;
1096 canDetach = contentType.indexOf("application/x-pkcs7-mime") < 0 &&
1097 contentType.indexOf("application/x-pkcs7-signature") < 0;
1098 }
1099 return canDetach;
1100 }
1101
1102 function onShowAttachmentContextMenu()
1103 {
1104 // if no attachments are selected, disable the Open and Save...
1105 var attachmentList = document.getElementById('attachmentList');
1106 var selectedAttachments = attachmentList.selectedItems;
1107 var attachmentList = document.getElementById('attachmentListContext');
1108 var openMenu = document.getElementById('context-openAttachment');
1109 var saveMenu = document.getElementById('context-saveAttachment');
1110 var detachMenu = document.getElementById('context-detachAttachment');
1111 var deleteMenu = document.getElementById('context-deleteAttachment');
1112 var menuSeparator = document.getElementById('context-menu-separator');
1113 var saveAllMenu = document.getElementById('context-saveAllAttachments');
1114 var detachAllMenu = document.getElementById('context-detachAllAttachments');
1115 var deleteAllMenu = document.getElementById('context-deleteAllAttachments');
1116 var canDetach = CanDetachAttachments();
1117 var canOpen = false;
1118 var selectNone = selectedAttachments.length == 0;
1119 for (var i = 0; i < selectedAttachments.length && !canOpen; i++)
1120 canOpen = selectedAttachments[i].attachment.contentType != 'text/x-moz-deleted';
1121 if (selectedAttachments.length == 1)
1122 {
1123 openMenu.setAttribute('hidden', !canOpen);
1124 saveMenu.setAttribute('hidden', !canOpen);
1125
1126 detachMenu.setAttribute('hidden', !canDetach || !canOpen);
1127 deleteMenu.setAttribute('hidden', !canDetach || !canOpen);
1128 menuSeparator.setAttribute('hidden', !canDetach || !canOpen);
1129
1130 saveAllMenu.setAttribute('hidden', 'true');
1131 detachAllMenu.setAttribute('hidden', 'true');
1132 deleteAllMenu.setAttribute('hidden', 'true');
1133 }
1134 else
1135 {
1136 openMenu.setAttribute('hidden', selectNone);
1137 saveMenu.setAttribute('hidden', selectNone);
1138 detachMenu.setAttribute('hidden', selectNone);
1139 deleteMenu.setAttribute('hidden', selectNone);
1140 saveAllMenu.setAttribute('hidden', !selectNone);
1141 menuSeparator.setAttribute('hidden', selectNone);
1142
1143 detachAllMenu.setAttribute('hidden', !canDetach || !selectNone);
1144 deleteAllMenu.setAttribute('hidden', !canDetach || !selectNone);
1145 }
1146 }
1147
MessageIdClick
1148 function MessageIdClick(node, event)
1149 {
1150 if (event.button == 0)
1151 {
1152 var messageId = GetMessageIdFromNode(node, true);
1153 OpenMessageForMessageId(messageId);
1154 }
1155 }
1156
1157 // this is our onclick handler for the attachment list.
1158 // A double click in a listitem simulates "opening" the attachment....
attachmentListClick
1159 function attachmentListClick(event)
1160 {
1161 // we only care about button 0 (left click) events
1162 if (event.button != 0) return;
1163
1164 if (event.detail == 2) // double click
1165 {
1166 var target = event.target;
1167 if (target.localName == "descriptionitem")
1168 {
1169 openAttachment(target.attachment);
1170 }
1171 }
1172 }
1173
cloneAttachment
1174 function cloneAttachment(aAttachment)
1175 {
1176 var obj = new Object();
1177 obj.contentType = aAttachment.contentType;
1178 obj.url = aAttachment.url;
1179 obj.displayName = aAttachment.displayName;
1180 obj.uri = aAttachment.uri;
1181 obj.isExternalAttachment = aAttachment.isExternalAttachment;
1182 return obj;
1183 }
1184
createAttachmentDisplayName
1185 function createAttachmentDisplayName(aAttachment)
1186 {
1187 // Strip any white space at the end of the display name to avoid
1188 // attachment name spoofing (especially Windows will drop trailing dots
1189 // and whitespace from filename extensions). Leading and internal
1190 // whitespace will be taken care of by the crop="center" attribute.
1191 // We must not change the actual filename, though.
1192 return aAttachment.displayName.replace(/\s+$/, "");
1193 }
1194
displayAttachmentsForExpandedView
1195 function displayAttachmentsForExpandedView()
1196 {
1197 var numAttachments = currentAttachments.length;
1198 var expandedAttachmentBox = document.getElementById('attachmentView');
1199 var attachmentSplitter = document.getElementById('attachment-splitter');
1200
1201 if (numAttachments <= 0)
1202 {
1203 expandedAttachmentBox.collapsed = true;
1204 attachmentSplitter.collapsed = true;
1205 }
1206 else if (!gBuildAttachmentsForCurrentMsg)
1207 {
1208 // IMPORTANT: make sure we uncollapse the attachment box BEFORE we start adding
1209 // our attachments to the view. Otherwise, layout doesn't calculate the correct height for
1210 // the attachment view and we end up with a box that is too tall.
1211 expandedAttachmentBox.collapsed = false;
1212 attachmentSplitter.collapsed = false;
1213
1214 if (gShowLargeAttachmentView)
1215 expandedAttachmentBox.setAttribute("largeView", "true");
1216
1217 // Remove height attribute, or the attachments box could be drawn badly:
1218 expandedAttachmentBox.removeAttribute("height");
1219
1220 var attachmentList = document.getElementById('attachmentList');
1221 for (index in currentAttachments)
1222 {
1223 var attachment = currentAttachments[index];
1224
1225 // Create a new attachment widget
1226 var displayName = createAttachmentDisplayName(attachment);
1227 var attachmentView = attachmentList.appendItem(displayName);
1228
1229 attachmentView.setAttribute("class", "descriptionitem-iconic");
1230
1231 if (gShowLargeAttachmentView)
1232 attachmentView.setAttribute("largeView", "true");
1233
1234 setApplicationIconForAttachment(attachment, attachmentView, gShowLargeAttachmentView);
1235 attachmentView.setAttribute("tooltiptext", attachment.displayName);
1236 attachmentView.setAttribute("context", "attachmentListContext");
1237
1238 attachmentView.attachment = cloneAttachment(attachment);
1239 attachmentView.setAttribute("attachmentUrl", attachment.url);
1240 attachmentView.setAttribute("attachmentContentType", attachment.contentType);
1241 attachmentView.setAttribute("attachmentUri", attachment.uri);
1242
1243 var item = attachmentList.appendChild(attachmentView);
1244 } // for each attachment
1245 gBuildAttachmentsForCurrentMsg = true;
1246
1247 // Switch overflow off (via css attribute selector) temporarily to get the preferred window height:
1248 var attachmentContainer = document.getElementById('attachmentView');
1249 attachmentContainer.setAttribute("attachmentOverflow", "false");
1250 var attachmentHeight = expandedAttachmentBox.boxObject.height;
1251 attachmentContainer.setAttribute("attachmentOverflow", "true");
1252
1253 // If the attachments box takes up too much of the message pane, downsize:
1254 var maxAttachmentHeight = document.getElementById('messagepanebox').boxObject.height / 4;
1255 if (attachmentHeight > maxAttachmentHeight)
1256 attachmentHeight = maxAttachmentHeight;
1257 expandedAttachmentBox.setAttribute("height", attachmentHeight);
1258 }
1259 }
1260
1261 // attachment --> the attachment struct containing all the information on the attachment
1262 // listitem --> the listitem currently showing the attachment.
setApplicationIconForAttachment
1263 function setApplicationIconForAttachment(attachment, listitem, largeView)
1264 {
1265 var iconSize = largeView ? kLargeIcon : kSmallIcon;
1266 // generate a moz-icon url for the attachment so we'll show a nice icon next to it.
1267 if (attachment.contentType == 'text/x-moz-deleted')
1268 listitem.setAttribute('image', 'chrome://messenger/skin/icons/message-mail-attach-del.png');
1269 else
1270 listitem.setAttribute('image', "moz-icon:" + "//" + attachment.displayName + "?size=" + iconSize + "&contentType=" + attachment.contentType);
1271 }
1272
1273 // Public method called when we create the attachments file menu
1274 function FillAttachmentListPopup(popup)
1275 {
1276 // the FE sometimes call this routine TWICE...I haven't been able to figure out why yet...
1277 // protect against it...
1278 if (!gBuildAttachmentPopupForCurrentMsg) return;
1279
1280 var attachmentIndex = 0;
1281
1282 // otherwise we need to build the attachment view...
1283 // First clear out the old view...
1284 ClearAttachmentMenu(popup);
1285
1286 for (index in currentAttachments)
1287 {
1288 ++attachmentIndex;
1289 addAttachmentToPopup(popup, currentAttachments[index], attachmentIndex);
1290 }
1291
1292 gBuildAttachmentPopupForCurrentMsg = false;
1293 var detachAllMenu = document.getElementById('file-detachAllAttachments');
1294 var deleteAllMenu = document.getElementById('file-deleteAllAttachments');
1295 if (CanDetachAttachments())
1296 {
1297 detachAllMenu.removeAttribute('disabled');
1298 deleteAllMenu.removeAttribute('disabled');
1299 }
1300 else
1301 {
1302 detachAllMenu.setAttribute('disabled', 'true');
1303 deleteAllMenu.setAttribute('disabled', 'true');
1304 }
1305
1306 }
1307
1308 // Public method used to clear the file attachment menu
1309 function ClearAttachmentMenu(popup)
1310 {
1311 if ( popup )
1312 {
1313 while ( popup.childNodes[0].localName == 'menu' )
1314 popup.removeChild(popup.childNodes[0]);
1315 }
1316 }
1317
1318 // private method used to build up a menu list of attachments
1319 function addAttachmentToPopup(popup, attachment, attachmentIndex)
1320 {
1321 if (popup)
1322 {
1323 var item = document.createElement('menu');
1324 if ( item )
1325 {
1326 if (!gMessengerBundle)
1327 gMessengerBundle = document.getElementById("bundle_messenger");
1328
1329 // insert the item just before the separator...the separator is the 2nd to last element in the popup.
1330 item.setAttribute('class', 'menu-iconic');
1331 setApplicationIconForAttachment(attachment,item, false);
1332
1333 var numItemsInPopup = popup.childNodes.length;
1334 // find the separator
1335 var indexOfSeparator = 0;
1336 while (popup.childNodes[indexOfSeparator].localName != 'menuseparator')
1337 indexOfSeparator++;
1338
1339 var displayName = createAttachmentDisplayName(attachment);
1340 var formattedDisplayNameString = gMessengerBundle.getFormattedString("attachmentDisplayNameFormat",
1341 [attachmentIndex, displayName]);
1342
1343 item.setAttribute("crop", "center");
1344 item.setAttribute('label', formattedDisplayNameString);
1345 item.setAttribute('accesskey', attachmentIndex);
1346
1347 // each attachment in the list gets its own menupopup with options for saving, deleting, detaching, etc.
1348 var openpopup = document.createElement('menupopup');
1349 openpopup = item.appendChild(openpopup);
1350
1351 // Due to Bug #314228, we must append our menupopup to the new attachment menu item
1352 // before we inserting the attachment menu into the popup. If we don't, our attachment
1353 // menu items will not show up.
1354 item = popup.insertBefore(item, popup.childNodes[indexOfSeparator]);
1355
1356 var menuitementry = document.createElement('menuitem');
1357 menuitementry.attachment = cloneAttachment(attachment);
1358 menuitementry.setAttribute('oncommand', 'openAttachment(this.attachment)');
1359
1360 if (!gSaveLabel)
1361 gSaveLabel = gMessengerBundle.getString("saveLabel");
1362 if (!gSaveLabelAccesskey)
1363 gSaveLabelAccesskey = gMessengerBundle.getString("saveLabelAccesskey");
1364 if (!gOpenLabel)
1365 gOpenLabel = gMessengerBundle.getString("openLabel");
1366 if (!gOpenLabelAccesskey)
1367 gOpenLabelAccesskey = gMessengerBundle.getString("openLabelAccesskey");
1368 if (!gDetachLabel)
1369 gDetachLabel = gMessengerBundle.getString("detachLabel");
1370 if (!gDetachLabelAccesskey)
1371 gDetachLabelAccesskey = gMessengerBundle.getString("detachLabelAccesskey");
1372 if (!gDeleteLabel)
1373 gDeleteLabel = gMessengerBundle.getString("deleteLabel");
1374 if (!gDeleteLabelAccesskey)
1375 gDeleteLabelAccesskey = gMessengerBundle.getString("deleteLabelAccesskey");
1376
1377 // we should also check if an attachment has been detached...
1378 // but that uses X-Mozilla-External-Attachment-URL, which
1379 // we'd need to check for somehow.
1380
1381 var signedOrEncrypted = false;
1382 if ("content-type" in currentHeaderData)
1383 {
1384 var contentType = currentHeaderData["content-type"].headerValue;
1385 signedOrEncrypted = contentType.indexOf("application/x-pkcs7-mime") >= 0 ||
1386 contentType.indexOf("application/x-pkcs7-signature") >= 0;
1387 }
1388 var canDetach = !(/news-message:/.test(attachment.uri)) &&
1389 !signedOrEncrypted &&
1390 (!(/imap-message/.test(attachment.uri)) || MailOfflineMgr.isOnline());
1391 menuitementry.setAttribute('label', gOpenLabel);
1392 menuitementry.setAttribute('accesskey', gOpenLabelAccesskey);
1393 menuitementry = openpopup.appendChild(menuitementry);
1394 if (attachment.contentType == 'text/x-moz-deleted')
1395 menuitementry.setAttribute('disabled', true);
1396
1397 var menuseparator = document.createElement('menuseparator');
1398 openpopup.appendChild(menuseparator);
1399
1400 menuitementry = document.createElement('menuitem');
1401 menuitementry.attachment = cloneAttachment(attachment);
1402 menuitementry.setAttribute('oncommand', 'saveAttachment(this.attachment)');
1403 menuitementry.setAttribute('label', gSaveLabel);
1404 menuitementry.setAttribute('accesskey', gSaveLabelAccesskey);
1405 if (attachment.contentType == 'text/x-moz-deleted')
1406 menuitementry.setAttribute('disabled', true);
1407 menuitementry = openpopup.appendChild(menuitementry);
1408
1409 var menuseparator = document.createElement('menuseparator');
1410 openpopup.appendChild(menuseparator);
1411
1412 menuitementry = document.createElement('menuitem');
1413 menuitementry.attachment = cloneAttachment(attachment);
1414 menuitementry.setAttribute('oncommand', 'detachAttachment(this.attachment, true)');
1415 menuitementry.setAttribute('label', gDetachLabel);
1416 menuitementry.setAttribute('accesskey', gDetachLabelAccesskey);
1417 if (attachment.contentType == 'text/x-moz-deleted' || !canDetach)
1418 menuitementry.setAttribute('disabled', true);
1419 menuitementry = openpopup.appendChild(menuitementry);
1420
1421 menuitementry = document.createElement('menuitem');
1422 menuitementry.attachment = cloneAttachment(attachment);
1423 menuitementry.setAttribute('oncommand', 'detachAttachment(this.attachment, false)');
1424 menuitementry.setAttribute('label', gDeleteLabel);
1425 menuitementry.setAttribute('accesskey', gDeleteLabelAccesskey);
1426 if (attachment.contentType == 'text/x-moz-deleted' || !canDetach)
1427 menuitementry.setAttribute('disabled', true);
1428 menuitementry = openpopup.appendChild(menuitementry);
1429 } // if we created a menu item for this attachment...
1430 } // if we have a popup
1431 }
1432
HandleAllAttachments
1433 function HandleAllAttachments(action)
1434 {
1435 HandleMultipleAttachments(currentAttachments, action);
1436 }
1437
HandleSelectedAttachments
1438 function HandleSelectedAttachments(action)
1439 {
1440 var attachmentList = document.getElementById('attachmentList');
1441 var selectedAttachments = new Array();
1442 for (var i in attachmentList.selectedItems)
1443 selectedAttachments.push(attachmentList.selectedItems[i].attachment);
1444
1445 HandleMultipleAttachments(selectedAttachments, action);
1446 }
1447
1448 // supported actions: open, save, saveAs, detach, delete
HandleMultipleAttachments
1449 function HandleMultipleAttachments(attachments, action)
1450 {
1451 try
1452 {
1453 // convert our attachment data into some c++ friendly structs
1454 var attachmentContentTypeArray = new Array();
1455 var attachmentUrlArray = new Array();
1456 var attachmentDisplayNameArray = new Array();
1457 var attachmentMessageUriArray = new Array();
1458
1459 // populate these arrays..
1460 var actionIndex = 0;
1461 for (var index in attachments)
1462 {
1463 // exclude all attachments already deleted
1464 var attachment = attachments[index];
1465 if ( attachment.contentType != 'text/x-moz-deleted' )
1466 {
1467 attachmentContentTypeArray[actionIndex] = attachment.contentType;
1468 attachmentUrlArray[actionIndex] = attachment.url;
1469 attachmentDisplayNameArray[actionIndex] = encodeURI(attachment.displayName);
1470 attachmentMessageUriArray[actionIndex] = attachment.uri;
1471 ++actionIndex;
1472 }
1473 }
1474
1475 // okay the list has been built... now call our action code...
1476 if ( action == 'save' )
1477 messenger.saveAllAttachments(attachmentContentTypeArray.length,
1478 attachmentContentTypeArray, attachmentUrlArray,
1479 attachmentDisplayNameArray, attachmentMessageUriArray);
1480 else if ( action == 'detach' )
1481 messenger.detachAllAttachments(attachmentContentTypeArray.length,
1482 attachmentContentTypeArray, attachmentUrlArray,
1483 attachmentDisplayNameArray, attachmentMessageUriArray,
1484 true); // save
1485 else if ( action == 'delete' )
1486 messenger.detachAllAttachments(attachmentContentTypeArray.length,
1487 attachmentContentTypeArray, attachmentUrlArray,
1488 attachmentDisplayNameArray, attachmentMessageUriArray,
1489 false); // don't save
1490 else if ( action == 'open'|| action == 'saveAs' ) {
1491 // XXX hack alert. If we sit in tight loop and open/save multiple attachments,
1492 // we get chrome errors in layout as we start loading the first helper app dialog
1493 // then before it loads, we kick off the next one and the next one. Subsequent helper
1494 // app dialogs were failing because we were still loading the chrome files for the
1495 // first attempt (error about the xul cache being empty). For now, work around this
1496 // by doing the first helper app dialog right away, then waiting a bit before we
1497 // launch the rest.
1498 var actionFunction = (action == 'open') ? openAttachment : saveAttachment;
1499 for (var i = 0; i < attachments.length; i++)
1500 {
1501 if (i == 0)
1502 actionFunction(attachments[i]);
1503 else
1504 setTimeout(actionFunction, 100, attachments[i]);
1505 }
1506 }
1507 else
1508 dump ("** unknown HandleMultipleAttachments action: " + action + "**\n");
1509 }
1510 catch (ex)
1511 {
1512 dump ("** failed to handle multiple attachments **\n");
1513 }
1514 }
1515
ClearAttachmentList
1516 function ClearAttachmentList()
1517 {
1518 // we also have to disable the File/Attachments menuitem
1519 var node = document.getElementById("fileAttachmentMenu");
1520 if (node)
1521 node.setAttribute("disabled", "true");
1522
1523 // clear selection
1524 var list = document.getElementById('attachmentList');
1525
1526 while (list.hasChildNodes())
1527 list.removeChild(list.lastChild);
1528 }
1529
ShowEditMessageButton
1530 function ShowEditMessageButton()
1531 {
1532 // it would be nice if we passed in the msgHdr from the back end
1533 var msgHdr;
1534 try
1535 {
1536 msgHdr = gDBView.hdrForFirstSelectedMessage;
1537 }
1538 catch (ex)
1539 {
1540 return;
1541 }
1542
1543 if (IsSpecialFolder(msgHdr.folder, MSG_FOLDER_FLAG_DRAFTS, true))
1544 document.getElementById("editMessageBox").collapsed = false;
1545 }
1546
ClearEditMessageButton
1547 function ClearEditMessageButton()
1548 {
1549 var editBox = document.getElementById("editMessageBox");
1550 if (editBox)
1551 editBox.collapsed = true;
1552 }
1553
1554 // CopyWebsiteAddress takes the website address title button, extracts
1555 // the website address we stored in there and copies it to the clipboard
CopyWebsiteAddress
1556 function CopyWebsiteAddress(websiteAddressNode)
1557 {
1558 if (websiteAddressNode)
1559 {
1560 var websiteAddress = websiteAddressNode.getAttribute("value");
1561
1562 var contractid = "@mozilla.org/widget/clipboardhelper;1";
1563 var iid = Components.interfaces.nsIClipboardHelper;
1564 var clipboard = Components.classes[contractid].getService(iid);
1565 clipboard.copyString(websiteAddress);
1566 }
1567 }
1568
1569 var attachmentAreaDNDObserver = {
onDragStart
1570 onDragStart: function (aEvent, aAttachmentData, aDragAction)
1571 {
1572 var target = aEvent.target;
1573 if (target.localName == "descriptionitem")
1574 {
1575 var attachmentUrl = target.getAttribute("attachmentUrl");
1576 var attachmentDisplayName = target.getAttribute("label");
1577 var attachmentContentType = target.getAttribute("attachmentContentType");
1578 var tmpurl = attachmentUrl;
1579 var tmpurlWithExtraInfo = tmpurl + "&type=" + attachmentContentType + "&filename=" + attachmentDisplayName;
1580 aAttachmentData.data = new TransferData();
1581 if (attachmentUrl && attachmentDisplayName)
1582 {
1583 aAttachmentData.data.addDataForFlavour("text/x-moz-url", tmpurlWithExtraInfo + "\n" + attachmentDisplayName);
1584 aAttachmentData.data.addDataForFlavour("text/x-moz-url-data", tmpurl);
1585 aAttachmentData.data.addDataForFlavour("text/x-moz-url-desc", attachmentDisplayName);
1586
1587 aAttachmentData.data.addDataForFlavour("application/x-moz-file-promise-url", tmpurl);
1588 aAttachmentData.data.addDataForFlavour("application/x-moz-file-promise", new nsFlavorDataProvider(), 0, Components.interfaces.nsISupports);
1589 }
1590 }
1591 }
1592 };
1593
nsFlavorDataProvider
1594 function nsFlavorDataProvider()
1595 {
1596 }
1597
1598 nsFlavorDataProvider.prototype =
1599 {
QueryInterface
1600 QueryInterface : function(iid)
1601 {
1602 if (iid.equals(Components.interfaces.nsIFlavorDataProvider) ||
1603 iid.equals(Components.interfaces.nsISupports))
1604 return this;
1605 throw Components.results.NS_NOINTERFACE;
1606 },
1607
getFlavorData
1608 getFlavorData : function(aTransferable, aFlavor, aData, aDataLen)
1609 {
1610 // get the url for the attachment
1611 if (aFlavor == "application/x-moz-file-promise")
1612 {
1613 var urlPrimitive = { };
1614 var dataSize = { };
1615 aTransferable.getTransferData("application/x-moz-file-promise-url", urlPrimitive, dataSize);
1616
1617 var srcUrlPrimitive = urlPrimitive.value.QueryInterface(Components.interfaces.nsISupportsString);
1618
1619 // now get the destination file location from kFilePromiseDirectoryMime
1620 var dirPrimitive = {};
1621 aTransferable.getTransferData("application/x-moz-file-promise-dir", dirPrimitive, dataSize);
1622 var destDirectory = dirPrimitive.value.QueryInterface(Components.interfaces.nsILocalFile);
1623
1624 // now save the attachment to the specified location
1625 // XXX: we need more information than just the attachment url to save it, fortunately, we have an array
1626 // of all the current attachments so we can cheat and scan through them
1627
1628 var attachment = null;
1629 for (index in currentAttachments)
1630 {
1631 attachment = currentAttachments[index];
1632 if (attachment.url == srcUrlPrimitive)
1633 break;
1634 }
1635
1636 // call our code for saving attachments
1637 if (attachment)
1638 {
1639 var destFilePath = messenger.saveAttachmentToFolder(attachment.contentType, attachment.url, encodeURIComponent(attachment.displayName), attachment.uri, destDirectory);
1640 aData.value = destFilePath.QueryInterface(Components.interfaces.nsISupports);
1641 aDataLen.value = 4;
1642 }
1643 }
1644 }
1645
1646 }
1647
1648 function nsDummyMsgHeader()
1649 {
1650 }
1651
1652 nsDummyMsgHeader.prototype =
1653 {
1654 mProperties : new Array,
getStringProperty
1655 getStringProperty : function(property) {return this.mProperties[property];},
setStringProperty
1656 setStringProperty : function(property, val) {this.mProperties[property] = val;},
1657 messageSize : 0,
1658 recipients : null,
1659 from : null,
1660 subject : null,
1661 ccList : null,
1662 messageId : null,
1663 accountKey : "",
1664 folder : null
1665 };