Mozilla Cross-Reference mozilla
mozilla/ editor/ ui/ composer/ content/ editor.js
CVS Log
CVS Blame
CVS Graph
Diff file
Raw file
changes to
this file in
the last:
day
week
month
view using tree:
1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is mozilla.org code.
16  *
17  * The Initial Developer of the Original Code is
18  * Netscape Communications Corporation.
19  * Portions created by the Initial Developer are Copyright (C) 1998-1999
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *   Sammy Ford (sford@swbell.net)
24  *   Dan Haddix (dan6992@hotmail.com)
25  *   John Ratke (jratke@owc.net)
26  *   Ryan Cassin (rcassin@supernova.org)
27  *   Daniel Glazman (glazman@netscape.com)
28  *
29  * Alternatively, the contents of this file may be used under the terms of
30  * either of the GNU General Public License Version 2 or later (the "GPL"),
31  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32  * in which case the provisions of the GPL or the LGPL are applicable instead
33  * of those above. If you wish to allow use of your version of this file only
34  * under the terms of either the GPL or the LGPL, and not to allow others to
35  * use your version of this file under the terms of the MPL, indicate your
36  * decision by deleting the provisions above and replace them with the notice
37  * and other provisions required by the GPL or the LGPL. If you do not delete
38  * the provisions above, a recipient may use your version of this file under
39  * the terms of any one of the MPL, the GPL or the LGPL.
40  *
41  * ***** END LICENSE BLOCK ***** */
42 
43 /* Main Composer window UI control */
44 
45 var gComposerWindowControllerID = 0;
46 var prefAuthorString = "";
47 
48 const kDisplayModeNormal = 0;
49 const kDisplayModeAllTags = 1;
50 const kDisplayModeSource = 2;
51 const kDisplayModePreview = 3;
52 const kDisplayModeMenuIDs = ["viewNormalMode", "viewAllTagsMode", "viewSourceMode", "viewPreviewMode"];
53 const kDisplayModeTabIDS = ["NormalModeButton", "TagModeButton", "SourceModeButton", "PreviewModeButton"];
54 const kNormalStyleSheet = "chrome://editor/content/EditorContent.css";
55 const kAllTagsStyleSheet = "chrome://editor/content/EditorAllTags.css";
56 const kParagraphMarksStyleSheet = "chrome://editor/content/EditorParagraphMarks.css";
57 const kContentEditableStyleSheet = "resource://gre/res/contenteditable.css";
58 
59 const kTextMimeType = "text/plain";
60 const kHTMLMimeType = "text/html";
61 
62 const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
63 
64 var gPreviousNonSourceDisplayMode = 1;
65 var gEditorDisplayMode = -1;
66 var gDocWasModified = false;  // Check if clean document, if clean then unload when user "Opens"
67 var gContentWindow = 0;
68 var gSourceContentWindow = 0;
69 var gSourceTextEditor = null;
70 var gContentWindowDeck;
71 var gFormatToolbar;
72 var gFormatToolbarHidden = false;
73 var gViewFormatToolbar;
74 var gColorObj = { LastTextColor:"", LastBackgroundColor:"", LastHighlightColor:"",
75                   Type:"", SelectedType:"", NoDefault:false, Cancel:false,
76                   HighlightColor:"", BackgroundColor:"", PageColor:"",
77                   TextColor:"", TableColor:"", CellColor:""
78                 };
79 var gDefaultTextColor = "";
80 var gDefaultBackgroundColor = "";
81 var gCSSPrefListener;
82 var gEditorToolbarPrefListener;
83 var gReturnInParagraphPrefListener;
84 var gPrefs;
85 var gLocalFonts = null;
86 
87 var gLastFocusNode = null;
88 var gLastFocusNodeWasSelected = false;
89 
90 // These must be kept in synch with the XUL <options> lists
91 var gFontSizeNames = ["xx-small","x-small","small","medium","large","x-large","xx-large"];
92 
93 const nsIFilePicker = Components.interfaces.nsIFilePicker;
94 
95 const kEditorToolbarPrefs = "editor.toolbars.showbutton.";
96 const kUseCssPref         = "editor.use_css";
97 const kCRInParagraphsPref = "editor.CR_creates_new_p";
98 
99 function ShowHideToolbarSeparators(toolbar) {
100   var childNodes = toolbar.childNodes;
101   var separator = null;
102   var hideSeparator = true;
103   for (var i = 0; childNodes[i].localName != "spacer"; i++) {
104     if (childNodes[i].localName == "toolbarseparator") {
105       if (separator)
106         separator.hidden = true;
107       separator = childNodes[i];
108     } else if (!childNodes[i].hidden) {
109       if (separator)
110         separator.hidden = hideSeparator;
111       separator = null;
112       hideSeparator = false;
113     }
114   }
115 }
116 
117 function ShowHideToolbarButtons()
118 {
119   var array = gPrefs.getChildList(kEditorToolbarPrefs, {});
120   for (var i in array) {
121     var prefName = array[i];
122     var id = prefName.substr(kEditorToolbarPrefs.length) + "Button";
123     var button = document.getElementById(id);
124     if (button)
125       button.hidden = !gPrefs.getBoolPref(prefName);
126   }
127   ShowHideToolbarSeparators(document.getElementById("EditToolbar"));
128   ShowHideToolbarSeparators(document.getElementById("FormatToolbar"));
129 }
130   
131 function nsPrefListener(prefName)
132 {
133   this.startup(prefName);
134 }
135 
136 // implements nsIObserver
137 nsPrefListener.prototype =
138 {
139   domain: "",
140   startup: function(prefName)
141   {
142     this.domain = prefName;
143     try {
144       var pbi = pref.QueryInterface(Components.interfaces.nsIPrefBranch2);
145       pbi.addObserver(this.domain, this, false);
146     } catch(ex) {
147       dump("Failed to observe prefs: " + ex + "\n");
148     }
149   },
150   shutdown: function()
151   {
152     try {
153       var pbi = pref.QueryInterface(Components.interfaces.nsIPrefBranch2);
154       pbi.removeObserver(this.domain, this);
155     } catch(ex) {
156       dump("Failed to remove pref observers: " + ex + "\n");
157     }
158   },
159   observe: function(subject, topic, prefName)
160   {
161     if (!IsHTMLEditor())
162       return;
163     // verify that we're changing a button pref
164     if (topic != "nsPref:changed") return;
165     
166     var editor = GetCurrentEditor();
167     if (prefName == kUseCssPref)
168     {
169       var cmd = document.getElementById("cmd_highlight");
170       if (cmd) {
171         var useCSS = gPrefs.getBoolPref(prefName);
172 
173         if (useCSS && editor) {
174           var mixedObj = {};
175           var state = editor.getHighlightColorState(mixedObj);
176           cmd.setAttribute("state", state);
177           cmd.collapsed = false;
178         }      
179         else {
180           cmd.setAttribute("state", "transparent");
181           cmd.collapsed = true;
182         }
183 
184         if (editor)
185           editor.isCSSEnabled = useCSS;
186       }
187     }
188     else if (prefName.substr(0, kEditorToolbarPrefs.length) == kEditorToolbarPrefs)
189     {
190       var id = prefName.substr(kEditorToolbarPrefs.length) + "Button";
191       var button = document.getElementById(id);
192       if (button) {
193         button.hidden = !gPrefs.getBoolPref(prefName);
194         ShowHideToolbarSeparators(button.parentNode);
195       }
196     }
197     else if (editor && (prefName == kCRInParagraphsPref))
198       editor.returnInParagraphCreatesNewParagraph = gPrefs.getBoolPref(prefName);
199   }
200 }
201 
202 function AfterHighlightColorChange()
203 {
204   if (!IsHTMLEditor())
205     return;
206 
207   var button = document.getElementById("cmd_highlight");
208   if (button) {
209     var mixedObj = {};
210     try {
211       var state = GetCurrentEditor().getHighlightColorState(mixedObj);
212       button.setAttribute("state", state);
213       onHighlightColorChange();
214     } catch (e) {}
215   }      
216 }
217 
218 function EditorOnLoad()
219 {
220     // See if argument was passed.
221     if ( window.arguments && window.arguments[0] ) {
222         // Opened via window.openDialog with URL as argument.
223         // Put argument where EditorStartup expects it.
224         document.getElementById( "args" ).setAttribute( "value", window.arguments[0] );
225     }
226 
227     // get default character set if provided
228     if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) {
229       if (window.arguments[1].indexOf("charset=") != -1) {
230         var arrayArgComponents = window.arguments[1].split("=");
231         if (arrayArgComponents) {
232           // Put argument where EditorStartup expects it.
233           document.getElementById( "args" ).setAttribute("charset", arrayArgComponents[1]);
234         }
235       }
236     }
237 
238     window.tryToClose = EditorCanClose;
239 
240     // Continue with normal startup.
241     EditorStartup();
242 
243     // Initialize our source text <editor>
244     try {
245       gSourceContentWindow = document.getElementById("content-source");
246       gSourceContentWindow.makeEditable("text", false);
247       gSourceTextEditor = gSourceContentWindow.getEditor(gSourceContentWindow.contentWindow);
248       gSourceTextEditor.QueryInterface(Components.interfaces.nsIPlaintextEditor);
249       gSourceTextEditor.enableUndo(false);
250       gSourceTextEditor.rootElement.style.fontFamily = "-moz-fixed";
251       gSourceTextEditor.rootElement.style.whiteSpace = "pre";
252       gSourceTextEditor.rootElement.style.margin = 0;
253       var controller = Components.classes["@mozilla.org/embedcomp/base-command-controller;1"]
254                                  .createInstance(Components.interfaces.nsIControllerContext);
255       controller.init(null);
256       controller.setCommandContext(gSourceContentWindow);
257       gSourceContentWindow.contentWindow.controllers.insertControllerAt(0, controller);
258       var commandTable = controller.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
259                                    .getInterface(Components.interfaces.nsIControllerCommandTable);
260       commandTable.registerCommand("cmd_find",        nsFindCommand);
261       commandTable.registerCommand("cmd_findNext",    nsFindAgainCommand);
262       commandTable.registerCommand("cmd_findPrev",    nsFindAgainCommand);
263     } catch (e) { dump("makeEditable failed: "+e+"\n"); }
264 }
265 
266 const gSourceTextListener =
267 {
268   NotifyDocumentCreated: function NotifyDocumentCreated() {},
269   NotifyDocumentWillBeDestroyed: function NotifyDocumentWillBeDestroyed() {},
270   NotifyDocumentStateChanged: function NotifyDocumentStateChanged(isChanged)
271   {
272     window.updateCommands("save");
273   }
274 };
275 
276 const gSourceTextObserver =
277 {
278   observe: function observe(aSubject, aTopic, aData)
279   {
280     // we currently only use this to update undo
281     window.updateCommands("undo");
282   }
283 };
284 
285 function TextEditorOnLoad()
286 {
287     // See if argument was passed.
288     if ( window.arguments && window.arguments[0] ) {
289         // Opened via window.openDialog with URL as argument.
290         // Put argument where EditorStartup expects it.
291         document.getElementById( "args" ).setAttribute( "value", window.arguments[0] );
292     }
293     // Continue with normal startup.
294     EditorStartup();
295 }
296 
297 // This should be called by all editor users when they close their window
298 //  or other similar "done with editor" actions, like recycling a Mail Composer window.
299 function EditorCleanup()
300 {
301   SwitchInsertCharToAnotherEditorOrClose();
302 }
303 
304 var DocumentReloadListener =
305 {
306   NotifyDocumentCreated: function() {},
307   NotifyDocumentWillBeDestroyed: function() {},
308 
309   NotifyDocumentStateChanged:function( isNowDirty )
310   {
311     var editor = GetCurrentEditor();
312     try {
313       // unregister the listener to prevent multiple callbacks
314       editor.removeDocumentStateListener( DocumentReloadListener );
315 
316       var charset = editor.documentCharacterSet;
317 
318       // update the META charset with the current presentation charset
319       editor.documentCharacterSet = charset;
320 
321     } catch (e) {}
322   }
323 };
324 
325 function addEditorClickEventListener()
326 {
327   try {
328     var bodyelement = GetBodyElement();
329     if (bodyelement)
330       bodyelement.addEventListener("click", EditorClick, false);
331   } catch (e) {}
332 }
333 
334 // implements nsIObserver
335 var gEditorDocumentObserver =
336 { 
337   observe: function(aSubject, aTopic, aData)
338   {
339     // Should we allow this even if NOT the focused editor?
340     var commandManager = GetCurrentCommandManager();
341     if (commandManager != aSubject)
342       return;
343 
344     var editor = GetCurrentEditor();
345     switch(aTopic)
346     {
347       case "obs_documentCreated":
348         // Just for convenience
349         gContentWindow = window.content;
350 
351         // Get state to see if document creation succeeded
352         var params = newCommandParams();
353         if (!params)
354           return;
355 
356         try {
357           commandManager.getCommandState(aTopic, gContentWindow, params);
358           var errorStringId = 0;
359           var editorStatus = params.getLongValue("state_data");
360           if (!editor && editorStatus == nsIEditingSession.eEditorOK)
361           {
362             dump("\n ****** NO EDITOR BUT NO EDITOR ERROR REPORTED ******* \n\n");
363             editorStatus = nsIEditingSession.eEditorErrorUnknown;
364           }
365 
366           switch (editorStatus)
367           {
368             case nsIEditingSession.eEditorErrorCantEditFramesets:
369               errorStringId = "CantEditFramesetMsg";
370               break;
371             case nsIEditingSession.eEditorErrorCantEditMimeType:
372               errorStringId = "CantEditMimeTypeMsg";
373               break;
374             case nsIEditingSession.eEditorErrorUnknown:
375               errorStringId = "CantEditDocumentMsg";
376               break;
377             // Note that for "eEditorErrorFileNotFound, 
378             // network code popped up an alert dialog, so we don't need to
379           }
380           if (errorStringId)
381             AlertWithTitle("", GetString(errorStringId));
382         } catch(e) { dump("EXCEPTION GETTING obs_documentCreated state "+e+"\n"); }
383 
384         // We have a bad editor -- nsIEditingSession will rebuild an editor
385         //   with a blank page, so simply abort here
386         if (editorStatus)
387           return; 
388 
389         if (!("InsertCharWindow" in window))
390           window.InsertCharWindow = null;
391 
392         try {
393           editor.QueryInterface(nsIEditorStyleSheets);
394 
395           //  and extra styles for showing anchors, table borders, smileys, etc
396           editor.addOverrideStyleSheet(kNormalStyleSheet);
397 
398           // remove contenteditable stylesheets if they were applied by the
399           // editingSession
400           editor.removeOverrideStyleSheet(kContentEditableStyleSheet);
401         } catch (e) {}
402 
403         // Things for just the Web Composer application
404         if (IsWebComposer())
405         {
406           InlineSpellCheckerUI.init(editor);
407           document.getElementById('menu_inlinespellcheck').setAttribute('disabled', !InlineSpellCheckerUI.canSpellCheck);
408 
409           editor.returnInParagraphCreatesNewParagraph = gPrefs.getBoolPref(kCRInParagraphsPref);
410 
411           // Set focus to content window if not a mail composer
412           // Race conditions prevent us from setting focus here
413           //   when loading a url into blank window
414           setTimeout(SetFocusOnStartup, 0);
415 
416           // Call EditorSetDefaultPrefsAndDoctype first so it gets the default author before initing toolbars
417           EditorSetDefaultPrefsAndDoctype();
418 
419           // We may load a text document into an html editor,
420           //   so be sure editortype is set correctly
421           // XXX We really should use the "real" plaintext editor for this!
422           if (editor.contentsMIMEType == "text/plain")
423           {
424             try {
425               GetCurrentEditorElement().editortype = "text";
426             } catch (e) { dump (e)+"\n"; }
427 
428             // Hide or disable UI not used for plaintext editing
429             HideItem("FormatToolbar");
430             HideItem("EditModeToolbar");
431             HideItem("formatMenu");
432             HideItem("tableMenu");
433             HideItem("menu_validate");
434             HideItem("sep_validate");
435             HideItem("previewButton");
436             HideItem("imageButton");
437             HideItem("linkButton");
438             HideItem("namedAnchorButton");
439             HideItem("hlineButton");
440             HideItem("tableButton");
441 
442             HideItem("fileExportToText");
443             HideItem("previewInBrowser");
444 
445 /* XXX When paste actually converts formatted rich text to pretty formatted plain text
446        and pasteNoFormatting is fixed to paste the text without formatting (what paste
447        currently does), then this item shouldn't be hidden: */
448             HideItem("menu_pasteNoFormatting"); 
449 
450             HideItem("cmd_viewFormatToolbar");
451             HideItem("cmd_viewEditModeToolbar");
452 
453             HideItem("viewSep1");
454             HideItem("viewNormalMode");
455             HideItem("viewAllTagsMode");
456             HideItem("viewSourceMode");
457             HideItem("viewPreviewMode");
458 
459             HideItem("structSpacer");
460 
461             // Hide everything in "Insert" except for "Symbols"
462             var menuPopup = document.getElementById("insertMenuPopup");
463             if (menuPopup)
464             {
465               var children = menuPopup.childNodes;
466               for (var i=0; i < children.length; i++) 
467               {
468                 var item = children.item(i);
469                 if (item.id != "insertChars")
470                   item.hidden = true;
471               }
472             }
473           }
474     
475           // Set window title
476           UpdateWindowTitle();
477 
478           // We must wait until document is created to get proper Url
479           // (Windows may load with local file paths)
480           SetSaveAndPublishUI(GetDocumentUrl());
481 
482           // Start in "Normal" edit mode
483           SetDisplayMode(kDisplayModeNormal);
484         }
485 
486         // Add mouse click watcher if right type of editor
487         if (IsHTMLEditor())
488         {
489           addEditorClickEventListener();
490 
491           // Force color widgets to update
492           onFontColorChange();
493           onBackgroundColorChange();
494         }
495         break;
496 
497       case "cmd_setDocumentModified":
498         window.updateCommands("save");
499         break;
500 
501       case "obs_documentWillBeDestroyed":
502         dump("obs_documentWillBeDestroyed notification\n");
503         break;
504 
505       case "obs_documentLocationChanged":
506         // Ignore this when editor doesn't exist,
507         //   which happens once when page load starts
508         if (editor)
509           try {
510             editor.updateBaseURL();
511           } catch(e) { dump (e); }
512         break;
513 
514       case "cmd_bold":
515         // Update all style items
516         // cmd_bold is a proxy; see EditorSharedStartup (above) for details
517         window.updateCommands("style");
518         window.updateCommands("undo");
519         break;
520     }
521   }
522 }
523 
524 function SetFocusOnStartup()
525 {
526   gContentWindow.focus();
527 }
528 
529 function EditorStartup()
530 {
531   var ds = GetCurrentEditorElement().docShell;
532   ds.useErrorPages = false;
533   var root = ds.QueryInterface(Components.interfaces.nsIDocShellTreeItem).
534     rootTreeItem.QueryInterface(Components.interfaces.nsIDocShell);
535 
536   root.QueryInterface(Components.interfaces.nsIDocShell).appType =
537     Components.interfaces.nsIDocShell.APP_TYPE_EDITOR;
538 
539   var is_HTMLEditor = IsHTMLEditor();
540   if (is_HTMLEditor)
541   {
542     // XUL elements we use when switching from normal editor to edit source
543     gContentWindowDeck = document.getElementById("ContentWindowDeck");
544     gFormatToolbar = document.getElementById("FormatToolbar");
545     gViewFormatToolbar = document.getElementById("viewFormatToolbar");
546   }
547 
548   // set up our global prefs object
549   GetPrefsService();
550 
551   // Startup also used by other editor users, such as Message Composer
552   EditorSharedStartup();
553 
554   // Commands specific to the Composer Application window,
555   //  (i.e., not embedded editors)
556   //  such as file-related commands, HTML Source editing, Edit Modes...
557   SetupComposerWindowCommands();
558 
559   ShowHideToolbarButtons();
560   gEditorToolbarPrefListener = new nsPrefListener(kEditorToolbarPrefs);
561 
562   gCSSPrefListener = new nsPrefListener(kUseCssPref);
563   gReturnInParagraphPrefListener = new nsPrefListener(kCRInParagraphsPref);
564 
565   // hide Highlight button if we are in an HTML editor with CSS mode off
566   // and tell the editor if a CR in a paragraph creates a new paragraph
567   var cmd = document.getElementById("cmd_highlight");
568   if (cmd) {
569     var useCSS = gPrefs.getBoolPref(kUseCssPref);
570     if (!useCSS && is_HTMLEditor) {
571       cmd.collapsed = true;
572     }
573   }
574 
575   // Get url for editor content and load it.
576   // the editor gets instantiated by the edittingSession when the URL has finished loading.
577   var url = document.getElementById("args").getAttribute("value");
578   try {
579     var charset = document.getElementById("args").getAttribute("charset");
580     var contentViewer = GetCurrentEditorElement().docShell.contentViewer;
581     contentViewer.QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
582     contentViewer.defaultCharacterSet = charset;
583     contentViewer.forceCharacterSet = charset;
584   } catch (e) {}
585   EditorLoadUrl(url);
586 }
587 
588 function EditorLoadUrl(url)
589 {
590   try {
591     if (url)
592       GetCurrentEditorElement().webNavigation.loadURI(url, // uri string
593              nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,     // load flags
594              null,                                         // referrer
595              null,                                         // post-data stream
596              null);
597   } catch (e) { dump(" EditorLoadUrl failed: "+e+"\n"); }
598 }
599 
600 // This should be called by all Composer types
601 function EditorSharedStartup()
602 {
603   // Just for convenience
604   gContentWindow = window.content;
605 
606   // Set up the mime type and register the commands.
607   if (IsHTMLEditor())
608     SetupHTMLEditorCommands();
609   else
610     SetupTextEditorCommands();
611 
612   // add observer to be called when document is really done loading 
613   // and is modified
614   // Note: We're really screwed if we fail to install this observer!
615   try {
616     var commandManager = GetCurrentCommandManager();
617     commandManager.addCommandObserver(gEditorDocumentObserver, "obs_documentCreated");
618     commandManager.addCommandObserver(gEditorDocumentObserver, "cmd_setDocumentModified");
619     commandManager.addCommandObserver(gEditorDocumentObserver, "obs_documentWillBeDestroyed");
620     commandManager.addCommandObserver(gEditorDocumentObserver, "obs_documentLocationChanged");
621 
622     // Until nsIControllerCommandGroup-based code is implemented,
623     //  we will observe just the bold command to trigger update of
624     //  all toolbar style items
625     commandManager.addCommandObserver(gEditorDocumentObserver, "cmd_bold");
626   } catch (e) { dump(e); }
627 
628   var isMac = (GetOS() == gMac);
629 
630   // Set platform-specific hints for how to select cells
631   // Mac uses "Cmd", all others use "Ctrl"
632   var tableKey = GetString(isMac ? "XulKeyMac" : "TableSelectKey");
633   var dragStr = tableKey+GetString("Drag");
634   var clickStr = tableKey+GetString("Click");
635 
636   var delStr = GetString(isMac ? "Clear" : "Del");
637 
638   SafeSetAttribute("menu_SelectCell", "acceltext", clickStr);
639   SafeSetAttribute("menu_SelectRow", "acceltext", dragStr);
640   SafeSetAttribute("menu_SelectColumn", "acceltext", dragStr);
641   SafeSetAttribute("menu_SelectAllCells", "acceltext", dragStr);
642   // And add "Del" or "Clear"
643   SafeSetAttribute("menu_DeleteCellContents", "acceltext", delStr);
644 
645   // Set text for indent, outdent keybinding
646 
647   // hide UI that we don't have components for
648   RemoveInapplicableUIElements();
649 
650   gPrefs = GetPrefs();
651 
652   // Use browser colors as initial values for editor's default colors
653   var BrowserColors = GetDefaultBrowserColors();
654   if (BrowserColors)
655   {
656     gDefaultTextColor = BrowserColors.TextColor;
657     gDefaultBackgroundColor = BrowserColors.BackgroundColor;
658   }
659 
660   // For new window, no default last-picked colors
661   gColorObj.LastTextColor = "";
662   gColorObj.LastBackgroundColor = "";
663   gColorObj.LastHighlightColor = "";
664 }
665 
666 // This method is only called by Message composer when recycling a compose window
667 function EditorResetFontAndColorAttributes()
668 {
669   try {  
670     var editor = GetCurrentEditor();
671     editor.rebuildDocumentFromSource("");
672     // Because the selection is now collapsed, the following line
673     // clears the typing state to discontinue all inline styles
674     editor.removeAllInlineProperties();
675     document.getElementById("cmd_fontFace").setAttribute("state", "");
676     gColorObj.LastTextColor = "";
677     gColorObj.LastBackgroundColor = "";
678     gColorObj.LastHighlightColor = "";
679     document.getElementById("cmd_fontColor").setAttribute("state", "");
680     document.getElementById("cmd_backgroundColor").setAttribute("state", "");
681     UpdateDefaultColors();
682   } catch (e) {}
683 }
684 
685 function EditorShutdown()
686 {
687   gEditorToolbarPrefListener.shutdown();
688   gCSSPrefListener.shutdown();
689   gReturnInParagraphPrefListener.shutdown();
690 
691   try {
692     var commandManager = GetCurrentCommandManager();
693     commandManager.removeCommandObserver(gEditorDocumentObserver, "obs_documentCreated");
694     commandManager.removeCommandObserver(gEditorDocumentObserver, "obs_documentWillBeDestroyed");
695     commandManager.removeCommandObserver(gEditorDocumentObserver, "obs_documentLocationChanged");
696   } catch (e) { dump (e); }   
697 }
698 
699 function SafeSetAttribute(nodeID, attributeName, attributeValue)
700 {
701     var theNode = document.getElementById(nodeID);
702     if (theNode)
703         theNode.setAttribute(attributeName, attributeValue);
704 }
705 
706 function DocumentHasBeenSaved()
707 {
708   var fileurl = "";
709   try {
710     fileurl = GetDocumentUrl();
711   } catch (e) {
712     return false;
713   }
714 
715   if (!fileurl || IsUrlAboutBlank(fileurl))
716     return false;
717 
718   // We have a file URL already
719   return true;
720 }
721 
722 function CheckAndSaveDocument(command, allowDontSave)
723 {
724   var document;
725   try {
726     // if we don't have an editor or an document, bail
727     var editor = GetCurrentEditor();
728     document = editor.document;
729     if (!document)
730       return true;
731   } catch (e) { return true; }
732 
733   if (!IsDocumentModified() && !IsHTMLSourceChanged())
734     return true;
735 
736   // call window.focus, since we need to pop up a dialog
737   // and therefore need to be visible (to prevent user confusion)
738   top.document.commandDispatcher.focusedWindow.focus();  
739 
740   var scheme = GetScheme(GetDocumentUrl());
741   var doPublish = (scheme && scheme != "file");
742 
743   var strID;
744   switch (command)
745   {
746     case "cmd_close":
747       strID = "BeforeClosing";
748       break;
749     case "cmd_preview":
750       strID = "BeforePreview";
751       break;
752     case "cmd_editSendPage":
753       strID = "SendPageReason";
754       break;
755     case "cmd_validate":
756       strID = "BeforeValidate";
757       break;
758   }
759     
760   var reasonToSave = strID ? GetString(strID) : "";
761 
762   var title = document.title;
763   if (!title)
764     title = GetString("untitled");
765 
766   var dialogTitle = GetString(doPublish ? "PublishPage" : "SaveDocument");
767   var dialogMsg = GetString(doPublish ? "PublishPrompt" : "SaveFilePrompt");
768   dialogMsg = (dialogMsg.replace(/%title%/,title)).replace(/%reason%/,reasonToSave);
769 
770   var promptService = GetPromptService();
771   if (!promptService)
772     return false;
773 
774   var result = {value:0};
775   var promptFlags = promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1;
776   var button1Title = null;
777   var button3Title = null;
778 
779   if (doPublish)
780   {
781     promptFlags += promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0;
782     button1Title = GetString("Publish");
783     button3Title = GetString("DontPublish");    
784   }
785   else
786   {
787     promptFlags += promptService.BUTTON_TITLE_SAVE * promptService.BUTTON_POS_0;
788   }
789 
790   // If allowing "Don't..." button, add that
791   if (allowDontSave)
792     promptFlags += doPublish ?
793         (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2)
794         : (promptService.BUTTON_TITLE_DONT_SAVE * promptService.BUTTON_POS_2);
795   
796   result = promptService.confirmEx(window, dialogTitle, dialogMsg, promptFlags,
797                           button1Title, null, button3Title, null, {value:0});
798 
799   if (result == 0)
800   {
801     // Save, but first finish HTML source mode
802     if (IsHTMLSourceChanged()) {
803       try {
804         FinishHTMLSource();
805       } catch (e) { return false;}
806     }
807 
808     if (doPublish)
809     {
810       // We save the command the user wanted to do in a global
811       // and return as if user canceled because publishing is asynchronous
812       // This command will be fired when publishing finishes
813       gCommandAfterPublishing = command;
814       goDoCommand("cmd_publish");
815       return false;
816     }
817 
818     // Save to local disk
819     var contentsMIMEType;
820     if (IsHTMLEditor())
821       contentsMIMEType = kHTMLMimeType;
822     else
823       contentsMIMEType = kTextMimeType;
824     var success = SaveDocument(false, false, contentsMIMEType);
825     return success;
826   }
827 
828   if (result == 2) // "Don't Save"
829     return true;
830 
831   // Default or result == 1 (Cancel)
832   return false;
833 }
834 
835 // --------------------------- File menu ---------------------------
836 
837 // Check for changes to document and allow saving before closing
838 // This is hooked up to the OS's window close widget (e.g., "X" for Windows)
839 function EditorCanClose()
840 {
841   // Returns FALSE only if user cancels save action
842 
843   // "true" means allow "Don't Save" button
844   var canClose = CheckAndSaveDocument("cmd_close", true);
845 
846   // This is our only hook into closing via the "X" in the caption
847   //   or "Quit" (or other paths?)
848   //   so we must shift association to another
849   //   editor or close any non-modal windows now
850   if (canClose && "InsertCharWindow" in window && window.InsertCharWindow)
851     SwitchInsertCharToAnotherEditorOrClose();
852 
853   return canClose;
854 }
855 
856 // --------------------------- View menu ---------------------------
857 
858 function EditorSetDocumentCharacterSet(aCharset)
859 {
860   try {
861     var editor = GetCurrentEditor();
862     editor.documentCharacterSet = aCharset;
863     var docUrl = GetDocumentUrl();
864     if( !IsUrlAboutBlank(docUrl))
865     {
866       // reloading the document will reverse any changes to the META charset, 
867       // we need to put them back in, which is achieved by a dedicated listener
868       editor.addDocumentStateListener( DocumentReloadListener );
869       EditorLoadUrl(docUrl);
870     }
871   } catch (e) {}
872 }
873 
874 // ------------------------------------------------------------------
875 function updateCharsetPopupMenu(menuPopup)
876 {
877   if (IsDocumentModified() && !IsDocumentEmpty())
878   {
879     for (var i = 0; i < menuPopup.childNodes.length; i++)
880     {
881       var menuItem = menuPopup.childNodes[i];
882       menuItem.setAttribute('disabled', 'true');
883     }
884   }
885 }
886 
887 // --------------------------- Text style ---------------------------
888 
889 function onParagraphFormatChange(paraMenuList, commandID)
890 {
891   if (!paraMenuList)
892     return;
893 
894   var commandNode = document.getElementById(commandID);
895   var state = commandNode.getAttribute("state");
896 
897   // force match with "normal"
898   if (state == "body")
899     state = "";
900 
901   if (state == "mixed")
902   {
903     //Selection is the "mixed" ( > 1 style) state
904     paraMenuList.selectedItem = null;
905     paraMenuList.setAttribute("label",GetString('Mixed'));
906   }
907   else
908   {
909     var menuPopup = document.getElementById("ParagraphPopup");
910     var menuItems = menuPopup.childNodes;
911     for (var i=0; i < menuItems.length; i++)
912     {
913       var menuItem = menuItems.item(i);
914       if ("value" in menuItem && menuItem.value == state)
915       {
916         paraMenuList.selectedItem = menuItem;
917         break;
918       }
919     }
920   }
921 }
922 
923 function onFontFaceChange(fontFaceMenuList, commandID)
924 {
925   var commandNode = document.getElementById(commandID);
926   var state = commandNode.getAttribute("state");
927 
928   if (state == "mixed")
929   {
930     //Selection is the "mixed" ( > 1 style) state
931     fontFaceMenuList.selectedItem = null;
932     fontFaceMenuList.setAttribute("label",GetString('Mixed'));
933   }
934   else
935   {
936     var menuPopup = document.getElementById("FontFacePopup");
937     var menuItems = menuPopup.childNodes;
938     for (var i=0; i < menuItems.length; i++)
939     {
940       var menuItem = menuItems.item(i);
941       if (menuItem.getAttribute("label") && ("value" in menuItem && menuItem.value.toLowerCase() == state.toLowerCase()))
942       {
943         fontFaceMenuList.selectedItem = menuItem;
944         break;
945       }
946     }
947   }
948 }
949 
950 function EditorSelectFontSize()
951 {
952   var select = document.getElementById("FontSizeSelect");
953   if (select)
954   {
955     if (select.selectedIndex == -1)
956       return;
957 
958     EditorSetFontSize(gFontSizeNames[select.selectedIndex]);
959   }
960 }
961 
962 function onFontSizeChange(fontSizeMenulist, commandID)
963 {
964   // If we don't match anything, set to "0 (normal)"
965   var newIndex = 2;
966   var size = fontSizeMenulist.getAttribute("size");
967   if ( size == "mixed")
968   {
969     // No single type selected
970     newIndex = -1;
971   }
972   else
973   {
974     for (var i = 0; i < gFontSizeNames.length; i++)
975     {
976       if( gFontSizeNames[i] == size )
977       {
978         newIndex = i;
979         break;
980       }
981     }
982   }
983   if (fontSizeMenulist.selectedIndex != newIndex)
984     fontSizeMenulist.selectedIndex = newIndex;
985 }
986 
987 function EditorSetFontSize(size)
988 {
989   if( size == "0" || size == "normal" ||
990       size == "medium" )
991   {
992     EditorRemoveTextProperty("font", "size");
993     // Also remove big and small,
994     //  else it will seem like size isn't changing correctly
995     EditorRemoveTextProperty("small", "");
996     EditorRemoveTextProperty("big", "");
997   } else {
998     // Temp: convert from new CSS size strings to old HTML size strings
999     switch (size)
1000     {
1001       case "xx-small":
1002       case "x-small":
1003         size = "-2";
1004         break;
1005       case "small":
1006         size = "-1";
1007         break;
1008       case "large":
1009         size = "+1";
1010         break;
1011       case "x-large":
1012         size = "+2";
1013         break;
1014       case "xx-large":
1015         size = "+3";
1016         break;
1017     }
1018     EditorSetTextProperty("font", "size", size);
1019   }
1020   gContentWindow.focus();
1021 }
1022 
1023 function initFontFaceMenu(menuPopup)
1024 {
1025   initLocalFontFaceMenu(menuPopup);
1026 
1027   if (menuPopup)
1028   {
1029     var children = menuPopup.childNodes;
1030     if (!children) return;
1031 
1032     var firstHas = { value: false };
1033     var anyHas = { value: false };
1034     var allHas = { value: false };
1035 
1036     // we need to set or clear the checkmark for each menu item since the selection
1037     // may be in a new location from where it was when the menu was previously opened
1038 
1039     // Fixed width (second menu item) is special case: old TT ("teletype") attribute
1040     EditorGetTextProperty("tt", "", "", firstHas, anyHas, allHas);
1041     children[1].setAttribute("checked", allHas.value);
1042 
1043     if (!anyHas.value)
1044       EditorGetTextProperty("font", "face", "", firstHas, anyHas, allHas);
1045 
1046     children[0].setAttribute("checked", !anyHas.value);
1047 
1048     // Skip over default, TT, and separator
1049     for (var i = 3; i < children.length; i++)
1050     {
1051       var menuItem = children[i];
1052       var faceType = menuItem.getAttribute("value");
1053 
1054       if (faceType)
1055       {
1056         EditorGetTextProperty("font", "face", faceType, firstHas, anyHas, allHas);
1057 
1058         // Check the menuitem only if all of selection has the face
1059         if (allHas.value)
1060         {
1061           menuItem.setAttribute("checked", "true");
1062           break;
1063         }
1064 
1065         // in case none match, make sure we've cleared the checkmark
1066         menuItem.removeAttribute("checked");
1067       }
1068     }
1069   }
1070 }
1071 
1072 const kFixedFontFaceMenuItems = 7; // number of fixed font face menuitems
1073 
1074 function initLocalFontFaceMenu(menuPopup)
1075 {
1076   if (!gLocalFonts)
1077   {
1078     // Build list of all local fonts once per editor
1079     try 
1080     {
1081       var enumerator = Components.classes["@mozilla.org/gfx/fontenumerator;1"]
1082                                  .getService(Components.interfaces.nsIFontEnumerator);
1083       var localFontCount = { value: 0 }
1084       gLocalFonts = enumerator.EnumerateAllFonts(localFontCount);
1085     }
1086     catch(e) { }
1087   }
1088   
1089   var useRadioMenuitems = (menuPopup.parentNode.localName == "menu"); // don't do this for menulists  
1090   if (menuPopup.childNodes.length == kFixedFontFaceMenuItems) 
1091   {
1092     if (gLocalFonts.length == 0) {
1093       menuPopup.childNodes[kFixedFontFaceMenuItems - 1].hidden = true;
1094     }
1095     for (var i = 0; i < gLocalFonts.length; ++i)
1096     {
1097       if (gLocalFonts[i] != "")
1098       {
1099         var itemNode = document.createElementNS(XUL_NS, "menuitem");
1100         itemNode.setAttribute("label", gLocalFonts[i]);
1101         itemNode.setAttribute("value", gLocalFonts[i]);
1102         if (useRadioMenuitems) {
1103           itemNode.setAttribute("type", "radio");
1104           itemNode.setAttribute("name", "2");
1105           itemNode.setAttribute("observes", "cmd_renderedHTMLEnabler");
1106         }
1107         menuPopup.appendChild(itemNode);
1108       }
1109     }
1110   }
1111 }
1112  
1113 
1114 function initFontSizeMenu(menuPopup)
1115 {
1116   if (menuPopup)
1117   {
1118     var children = menuPopup.childNodes;
1119     if (!children) return;
1120 
1121     var firstHas = { value: false };
1122     var anyHas = { value: false };
1123     var allHas = { value: false };
1124 
1125     var sizeWasFound = false;
1126 
1127     // we need to set or clear the checkmark for each menu item since the selection
1128     // may be in a new location from where it was when the menu was previously opened
1129 
1130     // First 2 items add <small> and <big> tags
1131     // While it would be better to show the number of levels,
1132     //  at least this tells user if either of them are set
1133     var menuItem = children[0];
1134     if (menuItem)
1135     {
1136       EditorGetTextProperty("small", "", "", firstHas, anyHas, allHas);
1137       menuItem.setAttribute("checked", allHas.value);
1138       sizeWasFound = anyHas.value;
1139     }
1140 
1141     menuItem = children[1];
1142     if (menuItem)
1143     {
1144       EditorGetTextProperty("big", "", "", firstHas, anyHas, allHas);
1145       menuItem.setAttribute("checked", allHas.value);
1146       sizeWasFound |= anyHas.value;
1147     }
1148 
1149     // Fixed size items start after menu separator
1150     var menuIndex = 3;
1151     // Index of the medium (default) item
1152     var mediumIndex = 5;
1153 
1154     // Scan through all supported "font size" attribute values
1155     for (var i = -2; i <= 3; i++)
1156     {
1157       menuItem = children[menuIndex];
1158 
1159       // Skip over medium since it'll be set below.
1160       // If font size=0 is actually set, we'll toggle it off below if
1161       // we enter this loop in this case.
1162       if (menuItem && (i != 0))
1163       {
1164         var sizeString = (i <= 0) ? String(i) : ("+" + String(i));
1165         EditorGetTextProperty("font", "size", sizeString, firstHas, anyHas, allHas);
1166         // Check the item only if all of selection has the size...
1167         menuItem.setAttribute("checked", allHas.value);
1168         // ...but remember if ANY of of selection had size set
1169         sizeWasFound |= anyHas.value;
1170       }
1171       menuIndex++;
1172     }
1173 
1174     // if no size was found, then check default (medium)
1175     // note that no item is checked in the case of "mixed" selection
1176     children[mediumIndex].setAttribute("checked", !sizeWasFound);
1177   }
1178 }
1179 
1180 function onHighlightColorChange()
1181 {
1182   var commandNode = document.getElementById("cmd_highlight");
1183   if (commandNode)
1184   {
1185     var color = commandNode.getAttribute("state");
1186     var button = document.getElementById("HighlightColorButton");
1187     if (button)
1188     {
1189       // No color set - get color set on page or other defaults
1190       if (!color)
1191         color = "transparent" ;
1192 
1193       button.setAttribute("style", "background-color:"+color+" !important");
1194     }
1195   }
1196 }
1197 
1198 function onFontColorChange()
1199 {
1200   var commandNode = document.getElementById("cmd_fontColor");
1201   if (commandNode)
1202   {
1203     var color = commandNode.getAttribute("state");
1204     var button = document.getElementById("TextColorButton");
1205     if (button)
1206     {
1207       // No color set - get color set on page or other defaults
1208       if (!color)
1209         color = gDefaultTextColor;
1210       button.setAttribute("style", "background-color:"+color);
1211     }
1212   }
1213 }
1214 
1215 function onBackgroundColorChange()
1216 {
1217   var commandNode = document.getElementById("cmd_backgroundColor");
1218   if (commandNode)
1219   {
1220     var color = commandNode.getAttribute("state");
1221     var button = document.getElementById("BackgroundColorButton");
1222     if (button)
1223     {
1224       if (!color)
1225         color = gDefaultBackgroundColor;
1226 
1227       button.setAttribute("style", "background-color:"+color);
1228     }
1229   }
1230 }
1231 
1232 // Call this when user changes text and/or background colors of the page
1233 function UpdateDefaultColors()
1234 {
1235   var BrowserColors = GetDefaultBrowserColors();
1236   var bodyelement = GetBodyElement();
1237   var defTextColor = gDefaultTextColor;
1238   var defBackColor = gDefaultBackgroundColor;
1239 
1240   if (bodyelement)
1241   {
1242     var color = bodyelement.getAttribute("text");
1243     if (color)
1244       gDefaultTextColor = color;
1245     else if (BrowserColors)
1246       gDefaultTextColor = BrowserColors.TextColor;
1247 
1248     color = bodyelement.getAttribute("bgcolor");
1249     if (color)
1250       gDefaultBackgroundColor = color;
1251     else if (BrowserColors)
1252       gDefaultBackgroundColor = BrowserColors.BackgroundColor;
1253   }
1254 
1255   // Trigger update on toolbar
1256   if (defTextColor != gDefaultTextColor)
1257   {
1258     goUpdateCommandState("cmd_fontColor");
1259     onFontColorChange();
1260   }
1261   if (defBackColor != gDefaultBackgroundColor)
1262   {
1263     goUpdateCommandState("cmd_backgroundColor");
1264     onBackgroundColorChange();
1265   }
1266 }
1267 
1268 function GetBackgroundElementWithColor()
1269 {
1270   var editor = GetCurrentTableEditor();
1271   if (!editor)
1272     return null;
1273 
1274   gColorObj.Type = "";
1275   gColorObj.PageColor = "";
1276   gColorObj.TableColor = "";
1277   gColorObj.CellColor = "";
1278   gColorObj.BackgroundColor = "";
1279   gColorObj.SelectedType = "";
1280 
1281   var tagNameObj = { value: "" };
1282   var element;
1283   try {
1284     element = editor.getSelectedOrParentTableElement(tagNameObj, {value:0});
1285   }
1286   catch(e) {}
1287 
1288   if (element && tagNameObj && tagNameObj.value)
1289   {
1290     gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(element, "bgcolor", "background-color");
1291     gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(gColorObj.BackgroundColor);
1292     if (tagNameObj.value.toLowerCase() == "td")
1293     {
1294       gColorObj.Type = "Cell";
1295       gColorObj.CellColor = gColorObj.BackgroundColor;
1296 
1297       // Get any color that might be on parent table
1298       var table = GetParentTable(element);
1299       gColorObj.TableColor = GetHTMLOrCSSStyleValue(table, "bgcolor", "background-color");
1300       gColorObj.TableColor = ConvertRGBColorIntoHEXColor(gColorObj.TableColor);
1301     }
1302     else
1303     {
1304       gColorObj.Type = "Table";
1305       gColorObj.TableColor = gColorObj.BackgroundColor;
1306     }
1307     gColorObj.SelectedType = gColorObj.Type;
1308   }
1309   else
1310   {
1311     var IsCSSPrefChecked = gPrefs.getBoolPref(kUseCssPref);
1312     if (IsCSSPrefChecked && IsHTMLEditor())
1313     {
1314       var selection = editor.selection;
1315       if (selection)
1316       {
1317         element = selection.focusNode;
1318         while (!editor.nodeIsBlock(element))
1319           element = element.parentNode;
1320       }
1321       else
1322       {
1323         element = GetBodyElement();
1324       }
1325     }
1326     else
1327     {
1328       element = GetBodyElement();
1329     }
1330     if (element)
1331     {
1332       gColorObj.Type = "Page";
1333       gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(element, "bgcolor", "background-color");
1334       if (gColorObj.BackgroundColor == "")
1335       {
1336         gColorObj.BackgroundColor = "transparent";
1337       }
1338       else
1339       {
1340         gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(gColorObj.BackgroundColor);
1341       }
1342       gColorObj.PageColor = gColorObj.BackgroundColor;
1343     }
1344   }
1345   return element;
1346 }
1347 
1348 function SetSmiley(smileyText)
1349 {
1350   try {
1351     GetCurrentEditor().insertText(smileyText);
1352     gContentWindow.focus();
1353   }
1354   catch(e) {}
1355 }
1356 
1357 function EditorSelectColor(colorType, mouseEvent)
1358 {
1359   var editor = GetCurrentEditor();
1360   if (!editor || !gColorObj)
1361     return;
1362 
1363   // Shift + mouse click automatically applies last color, if available
1364   var useLastColor = mouseEvent ? ( mouseEvent.button == 0 && mouseEvent.shiftKey ) : false;
1365   var element;
1366   var table;
1367   var currentColor = "";
1368   var commandNode;
1369 
1370   if (!colorType)
1371     colorType = "";
1372 
1373   if (colorType == "Text")
1374   {
1375     gColorObj.Type = colorType;
1376 
1377     // Get color from command node state
1378     commandNode = document.getElementById("cmd_fontColor");
1379     currentColor = commandNode.getAttribute("state");
1380     currentColor = ConvertRGBColorIntoHEXColor(currentColor);
1381     gColorObj.TextColor = currentColor;
1382 
1383     if (useLastColor && gColorObj.LastTextColor )
1384       gColorObj.TextColor = gColorObj.LastTextColor;
1385     else
1386       useLastColor = false;
1387   }
1388   else if (colorType == "Highlight")
1389   {
1390     gColorObj.Type = colorType;
1391 
1392     // Get color from command node state
1393     commandNode = document.getElementById("cmd_highlight");
1394     currentColor = commandNode.getAttribute("state");
1395     currentColor = ConvertRGBColorIntoHEXColor(currentColor);
1396     gColorObj.HighlightColor = currentColor;
1397 
1398     if (useLastColor && gColorObj.LastHighlightColor )
1399       gColorObj.HighlightColor = gColorObj.LastHighlightColor;
1400     else
1401       useLastColor = false;
1402   }
1403   else
1404   {
1405     element = GetBackgroundElementWithColor();
1406     if (!element)
1407       return;
1408 
1409     // Get the table if we found a cell
1410     if (gColorObj.Type == "Table")
1411       table = element;
1412     else if (gColorObj.Type == "Cell")
1413       table = GetParentTable(element);
1414 
1415     // Save to avoid resetting if not necessary
1416     currentColor = gColorObj.BackgroundColor;
1417 
1418     if (colorType == "TableOrCell" || colorType == "Cell")
1419     {
1420       if (gColorObj.Type == "Cell")
1421         gColorObj.Type = colorType;
1422       else if (gColorObj.Type != "Table")
1423         return;
1424     }
1425     else if (colorType == "Table" && gColorObj.Type == "Page")
1426       return;
1427 
1428     if (colorType == "" && gColorObj.Type == "Cell")
1429     {
1430       // Using empty string for requested type means
1431       //  we can let user select cell or table
1432       gColorObj.Type = "TableOrCell";
1433     }
1434 
1435     if (useLastColor && gColorObj.LastBackgroundColor )
1436       gColorObj.BackgroundColor = gColorObj.LastBackgroundColor;
1437     else
1438       useLastColor = false;
1439   }
1440   // Save the type we are really requesting
1441   colorType = gColorObj.Type;
1442 
1443   if (!useLastColor)
1444   {
1445     // Avoid the JS warning
1446     gColorObj.NoDefault = false;
1447 
1448     // Launch the ColorPicker dialog
1449     // TODO: Figure out how to position this under the color buttons on the toolbar
1450     window.openDialog("chrome://editor/content/EdColorPicker.xul", "_blank", "chrome,close,titlebar,modal", "", gColorObj);
1451 
1452     // User canceled the dialog
1453     if (gColorObj.Cancel)
1454       return;
1455   }
1456 
1457   if (gColorObj.Type == "Text")
1458   {
1459     if (currentColor != gColorObj.TextColor)
1460     {
1461       if (gColorObj.TextColor)
1462         EditorSetTextProperty("font", "color", gColorObj.TextColor);
1463       else
1464         EditorRemoveTextProperty("font", "color");
1465     }
1466     // Update the command state (this will trigger color button update)
1467     goUpdateCommandState("cmd_fontColor");
1468   }
1469   else if (gColorObj.Type == "Highlight")
1470   {
1471     if (currentColor != gColorObj.HighlightColor)
1472     {
1473       if (gColorObj.HighlightColor)
1474         EditorSetTextProperty("font", "bgcolor", gColorObj.HighlightColor);
1475       else
1476         EditorRemoveTextProperty("font", "bgcolor");
1477     }
1478     // Update the command state (this will trigger color button update)
1479     goUpdateCommandState("cmd_highlight");
1480   }
1481   else if (element)
1482   {
1483     if (gColorObj.Type == "Table")
1484     {
1485       // Set background on a table
1486       // Note that we shouldn't trust "currentColor" because of "TableOrCell" behavior
1487       if (table)
1488       {
1489         var bgcolor = table.getAttribute("bgcolor");
1490         if (bgcolor != gColorObj.BackgroundColor)
1491         try {
1492           if (gColorObj.BackgroundColor)
1493             editor.setAttributeOrEquivalent(table, "bgcolor", gColorObj.BackgroundColor, false);
1494           else
1495             editor.removeAttributeOrEquivalent(table, "bgcolor", false);
1496         } catch (e) {}
1497       }
1498     }
1499     else if (currentColor != gColorObj.BackgroundColor && IsHTMLEditor())
1500     {
1501       editor.beginTransaction();
1502       try
1503       {
1504         editor.setBackgroundColor(gColorObj.BackgroundColor);
1505 
1506         if (gColorObj.Type == "Page" && gColorObj.BackgroundColor)
1507         {
1508           // Set all page colors not explicitly set,
1509           //  else you can end up with unreadable pages
1510           //  because viewer's default colors may not be same as page author's
1511           var bodyelement = GetBodyElement();
1512           if (bodyelement)
1513           {
1514             var defColors = GetDefaultBrowserColors();
1515             if (defColors)
1516             {
1517               if (!bodyelement.getAttribute("text"))
1518                 editor.setAttributeOrEquivalent(bodyelement, "text", defColors.TextColor, false);
1519 
1520               // The following attributes have no individual CSS declaration counterparts
1521               // Getting rid of them in favor of CSS implies CSS rules management
1522               if (!bodyelement.getAttribute("link"))
1523                 editor.setAttribute(bodyelement, "link", defColors.LinkColor);
1524 
1525               if (!bodyelement.getAttribute("alink"))
1526                 editor.setAttribute(bodyelement, "alink", defColors.ActiveLinkColor);
1527 
1528               if (!bodyelement.getAttribute("vlink"))
1529                 editor.setAttribute(bodyelement, "vlink", defColors.VisitedLinkColor);
1530             }
1531           }
1532         }
1533       }
1534       catch(e) {}
1535 
1536       editor.endTransaction();
1537     }
1538 
1539     goUpdateCommandState("cmd_backgroundColor");
1540   }
1541   gContentWindow.focus();
1542 }
1543 
1544 function GetParentTable(element)
1545 {
1546   var node = element;
1547   while (node)
1548   {
1549     if (node.nodeName.toLowerCase() == "table")
1550       return node;
1551 
1552     node = node.parentNode;
1553   }
1554   return node;
1555 }
1556 
1557 function GetParentTableCell(element)
1558 {
1559   var node = element;
1560   while (node)
1561   {
1562     if (node.nodeName.toLowerCase() == "td" || node.nodeName.toLowerCase() == "th")
1563       return node;
1564 
1565     node = node.parentNode;
1566   }
1567   return node;
1568 }
1569 
1570 function EditorDblClick(event)
1571 {
1572   // We check event.explicitOriginalTarget here because .target will never
1573   // be a textnode (bug 193689)
1574   if (event.explicitOriginalTarget)
1575   {
1576     // Only bring up properties if clicked on an element or selected link
1577     var element;
1578     try {
1579       element = event.explicitOriginalTarget.QueryInterface(
1580                     Components.interfaces.nsIDOMElement);
1581     } catch (e) {}
1582 
1583      //  We use "href" instead of "a" to not be fooled by named anchor
1584     if (!element)
1585       try {
1586         element = GetCurrentEditor().getSelectedElement("href");
1587       } catch (e) {}
1588 
1589     if (element)
1590     {
1591       goDoCommand("cmd_objectProperties");  
1592       event.preventDefault();
1593     }
1594   }
1595 }
1596 
1597 function EditorClick(event)
1598 {
1599   if (!event)
1600     return;
1601 
1602   if (event.detail == 2)
1603   {
1604     EditorDblClick(event);
1605     return;
1606   }
1607 
1608   // For Web Composer: In Show All Tags Mode,
1609   // single click selects entire element,
1610   //  except for body and table elements
1611   if (IsWebComposer() && event.explicitOriginalTarget && IsHTMLEditor() &&
1612       gEditorDisplayMode == kDisplayModeAllTags)
1613   {
1614     try
1615     {
1616       // We check event.explicitOriginalTarget here because .target will never
1617       // be a textnode (bug 193689)
1618       var element = event.explicitOriginalTarget.QueryInterface(
1619                         Components.interfaces.nsIDOMElement);
1620       var name = element.localName.toLowerCase();
1621       if (name != "body" && name != "table" &&
1622           name != "td" && name != "th" && name != "caption" && name != "tr")
1623       {          
1624         GetCurrentEditor().selectElement(event.explicitOriginalTarget);
1625         event.preventDefault();
1626       }
1627     } catch (e) {}
1628   }
1629 }
1630 
1631 /*TODO: We need an oncreate hook to do enabling/disabling for the
1632         Format menu. There should be code like this for the
1633         object-specific "Properties" item
1634 */
1635 // For property dialogs, we want the selected element,
1636 //  but will accept a parent link, list, or table cell if inside one
1637 function GetObjectForProperties()
1638 {
1639   var editor = GetCurrentEditor();
1640   if (!editor || !IsHTMLEditor())
1641     return null;
1642 
1643   var element;
1644   try {
1645     element = editor.getSelectedElement("");
1646   } catch (e) {}
1647   if (element)
1648     return element;
1649 
1650   // Find nearest parent of selection anchor node
1651   //   that is a link, list, table cell, or table
1652 
1653   var anchorNode
1654   var node;
1655   try {
1656     anchorNode = editor.selection.anchorNode;
1657     if (anchorNode.firstChild)
1658     {
1659       // Start at actual selected node
1660       var offset = editor.selection.anchorOffset;
1661       // Note: If collapsed, offset points to element AFTER caret,
1662       //  thus node may be null
1663       node = anchorNode.childNodes.item(offset);
1664     }
1665     if (!node)
1666       node = anchorNode;
1667   } catch (e) {}
1668 
1669   while (node)
1670   {
1671     if (node.nodeName)
1672     {
1673       var nodeName = node.nodeName.toLowerCase();
1674 
1675       // Done when we hit the body
1676       if (nodeName == "body") break;
1677 
1678       if ((nodeName == "a" && node.href) ||
1679           nodeName == "ol" || nodeName == "ul" || nodeName == "dl" ||
1680           nodeName == "td" || nodeName == "th" ||
1681           nodeName == "table")
1682       {
1683         return node;
1684       }
1685     }
1686     node = node.parentNode;
1687   }
1688   return null;
1689 }
1690 
1691 function SetEditMode(mode)
1692 {
1693   if (!IsHTMLEditor())
1694     return;
1695 
1696   var bodyElement = GetBodyElement();
1697   if (!bodyElement)
1698   {
1699     dump("SetEditMode: We don't have a body node!\n");
1700     return;
1701   }
1702 
1703   // must have editor if here!
1704   var editor = GetCurrentEditor();
1705   var inlineSpellCheckItem = document.getElementById('menu_inlinespellcheck');
1706 
1707   // Switch the UI mode before inserting contents
1708   //   so user can't type in source window while new window is being filled
1709   var previousMode = gEditorDisplayMode;
1710   if (!SetDisplayMode(mode))
1711     return;
1712 
1713   if (mode == kDisplayModeSource)
1714   {
1715     // Display the DOCTYPE as a non-editable string above edit area
1716     var domdoc;
1717     try { domdoc = editor.document; } catch (e) { dump( e + "\n");}
1718     if (domdoc)
1719     {
1720       var doctypeNode = document.getElementById("doctype-text");
1721       var dt = domdoc.doctype;
1722       if (doctypeNode)
1723       {
1724         if (dt)
1725         {
1726           doctypeNode.collapsed = false;
1727           var doctypeText = "<!DOCTYPE " + domdoc.doctype.name;
1728           if (dt.publicId)
1729             doctypeText += " PUBLIC \"" + domdoc.doctype.publicId;
1730           if (dt.systemId)
1731             doctypeText += " "+"\"" + dt.systemId;
1732           doctypeText += "\">"
1733           doctypeNode.setAttribute("value", doctypeText);
1734         }
1735         else
1736           doctypeNode.collapsed = true;
1737       }
1738     }
1739     // Get the entire document's source string
1740 
1741     var flags = (editor.documentCharacterSet == "ISO-8859-1")
1742       ? kOutputEncodeLatin1Entities
1743       : kOutputEncodeBasicEntities;
1744     try { 
1745       var encodeEntity = gPrefs.getCharPref("editor.encode_entity");
1746       switch (encodeEntity) {
1747         case "basic"  : flags = kOutputEncodeBasicEntities; break;
1748         case "latin1" : flags = kOutputEncodeLatin1Entities; break;
1749         case "html"   : flags = kOutputEncodeHTMLEntities; break;
1750         case "none"   : flags = 0;     break;
1751       }
1752     } catch (e) { }
1753 
1754     try { 
1755       var prettyPrint = gPrefs.getBoolPref("editor.prettyprint");
1756       if (prettyPrint)
1757         flags |= kOutputFormatted;
1758 
1759     } catch (e) {}
1760 
1761     flags |= kOutputLFLineBreak;
1762     var source = editor.outputToString(kHTMLMimeType, flags);
1763     var start = source.search(/<html/i);
1764     if (start == -1) start = 0;
1765     gSourceTextEditor.insertText(source.slice(start));
1766     gSourceTextEditor.resetModificationCount();
1767     gSourceTextEditor.addDocumentStateListener(gSourceTextListener);
1768     gSourceTextEditor.enableUndo(true);
1769     gSourceContentWindow.commandManager.addCommandObserver(gSourceTextObserver, "cmd_undo");
1770     gSourceContentWindow.contentWindow.focus();
1771     goDoCommand("cmd_moveTop");
1772   }
1773   else if (previousMode == kDisplayModeSource)
1774   {
1775     // Only rebuild document if a change was made in source window
1776     if (IsHTMLSourceChanged())
1777     {
1778       // Disable spell checking when rebuilding source
1779       InlineSpellCheckerUI.enabled = false;
1780       inlineSpellCheckItem.removeAttribute('checked');
1781 
1782       // Reduce the undo count so we don't use too much memory
1783       //   during multiple uses of source window 
1784       //   (reinserting entire doc caches all nodes)
1785       try {
1786         editor.transactionManager.maxTransactionCount = 1;
1787       } catch (e) {}
1788 
1789       editor.beginTransaction();
1790       try {
1791         // We are coming from edit source mode,
1792         //   so transfer that back into the document
1793         source = gSourceTextEditor.outputToString(kTextMimeType, kOutputLFLineBreak);
1794         editor.rebuildDocumentFromSource(source);
1795 
1796         // Get the text for the <title> from the newly-parsed document
1797         // (must do this for proper conversion of "escaped" characters)
1798         var title = "";
1799         var titlenodelist = editor.document.getElementsByTagName("title");
1800         if (titlenodelist)
1801         {
1802           var titleNode = titlenodelist.item(0);
1803           if (titleNode && titleNode.firstChild && titleNode.firstChild.data)
1804             title = titleNode.firstChild.data;
1805         }
1806         if (editor.document.title != title)
1807           SetDocumentTitle(title);
1808 
1809       } catch (ex) {
1810         dump(ex);
1811       }
1812       editor.endTransaction();
1813 
1814       // Restore unlimited undo count
1815       try {
1816         editor.transactionManager.maxTransactionCount = -1;
1817       } catch (e) {}
1818     }
1819 
1820     // Clear out the string buffers
1821     gSourceContentWindow.commandManager.removeCommandObserver(gSourceTextObserver, "cmd_undo");
1822     gSourceTextEditor.removeDocumentStateListener(gSourceTextListener);
1823     gSourceTextEditor.enableUndo(false);
1824     gSourceTextEditor.selectAll();
1825     gSourceTextEditor.deleteSelection(gSourceTextEditor.eNone);
1826     gSourceTextEditor.resetModificationCount();
1827 
1828     gContentWindow.focus();
1829   }
1830 
1831   switch (mode) {
1832     case kDisplayModePreview:
1833       // Disable spell checking when previewing
1834       InlineSpellCheckerUI.enabled = false;
1835       inlineSpellCheckItem.removeAttribute('checked');
1836       // fall through
1837     case kDisplayModeSource:
1838       inlineSpellCheckItem.setAttribute('disabled', 'true');
1839       break;
1840     default:
1841       inlineSpellCheckItem.setAttribute('disabled', !InlineSpellCheckerUI.canSpellCheck);
1842       break;
1843   }
1844 }
1845 
1846 function CancelHTMLSource()
1847 {
1848   // Don't convert source text back into the DOM document
1849   gSourceTextEditor.resetModificationCount();
1850   SetDisplayMode(gPreviousNonSourceDisplayMode);
1851 }
1852 
1853 function FinishHTMLSource()
1854 {
1855   //Here we need to check whether the HTML source contains <head> and <body> tags
1856   //Or RebuildDocumentFromSource() will fail.
1857   if (IsInHTMLSourceMode())
1858   {
1859     var htmlSource = gSourceTextEditor.outputToString(kTextMimeType, kOutputLFLineBreak);
1860     if (htmlSource.length > 0)
1861     {
1862       var beginHead = htmlSource.indexOf("<head");
1863       if (beginHead == -1)
1864       {
1865         AlertWithTitle(GetString("Alert"), GetString("NoHeadTag"));
1866         //cheat to force back to Source Mode
1867         gEditorDisplayMode = kDisplayModePreview;
1868         SetDisplayMode(kDisplayModeSource);
1869         throw Components.results.NS_ERROR_FAILURE;
1870       }
1871 
1872       var beginBody = htmlSource.indexOf("<body");
1873       if (beginBody == -1)
1874       {
1875         AlertWithTitle(GetString("Alert"), GetString("NoBodyTag"));
1876         //cheat to force back to Source Mode
1877         gEditorDisplayMode = kDisplayModePreview;
1878         SetDisplayMode(kDisplayModeSource);
1879         throw Components.results.NS_ERROR_FAILURE;
1880       }
1881     }
1882   }
1883 
1884   // Switch edit modes -- converts source back into DOM document
1885   SetEditMode(gPreviousNonSourceDisplayMode);
1886 }
1887 
1888 function SetDisplayMode(mode)
1889 {
1890   if (!IsHTMLEditor())
1891     return false;
1892 
1893   // Already in requested mode:
1894   //  return false to indicate we didn't switch
1895   if (mode == gEditorDisplayMode)
1896     return false;
1897 
1898   var previousMode = gEditorDisplayMode;
1899   gEditorDisplayMode = mode;
1900 
1901   ResetStructToolbar();
1902   if (mode == kDisplayModeSource)
1903   {
1904     // Switch to the sourceWindow (second in the deck)
1905     gContentWindowDeck.selectedIndex = 1;
1906 
1907     //Hide the formatting toolbar if not already hidden
1908     gFormatToolbarHidden = gFormatToolbar.hidden;
1909     gFormatToolbar.hidden = true;
1910     gViewFormatToolbar.hidden = true;
1911 
1912     gSourceContentWindow.contentWindow.focus();
1913   }
1914   else
1915   {
1916     // Save the last non-source mode so we can cancel source editing easily
1917     gPreviousNonSourceDisplayMode = mode;
1918 
1919     // Load/unload appropriate override style sheet
1920     try {
1921       var editor = GetCurrentEditor();
1922       editor.QueryInterface(nsIEditorStyleSheets);
1923       editor instanceof Components.interfaces.nsIHTMLObjectResizer;
1924 
1925       switch (mode)
1926       {
1927         case kDisplayModePreview:
1928           // Disable all extra "edit mode" style sheets 
1929           editor.enableStyleSheet(kNormalStyleSheet, false);
1930           editor.enableStyleSheet(kAllTagsStyleSheet, false);
1931           editor.isImageResizingEnabled = true;
1932           break;
1933 
1934         case kDisplayModeNormal:
1935           editor.addOverrideStyleSheet(kNormalStyleSheet);
1936           // Disable ShowAllTags mode
1937           editor.enableStyleSheet(kAllTagsStyleSheet, false);
1938           editor.isImageResizingEnabled = true;
1939           break;
1940 
1941         case kDisplayModeAllTags:
1942           editor.addOverrideStyleSheet(kNormalStyleSheet);
1943           editor.addOverrideStyleSheet(kAllTagsStyleSheet);
1944           // don't allow resizing in AllTags mode because the visible tags
1945           // change the computed size of images and tables...
1946           if (editor.resizedObject) {
1947             editor.hideResizers();
1948           }
1949           editor.isImageResizingEnabled = false;
1950           break;
1951       }
1952     } catch(e) {}
1953 
1954     // Switch to the normal editor (first in the deck)
1955     gContentWindowDeck.selectedIndex = 0;
1956 
1957     // Restore menus and toolbars
1958     gFormatToolbar.hidden = gFormatToolbarHidden;
1959     gViewFormatToolbar.hidden = false;
1960 
1961     gContentWindow.focus();
1962   }
1963 
1964   // update commands to disable or re-enable stuff
1965   window.updateCommands("mode_switch");
1966 
1967   // Set the selected tab at bottom of window:
1968   // (Note: Setting "selectedIndex = mode" won't redraw tabs when menu is used.)
1969   document.getElementById("EditModeTabs").selectedItem = document.getElementById(kDisplayModeTabIDS[mode]);
1970 
1971   // Uncheck previous menuitem and set new check since toolbar may have been used
1972   if (previousMode >= 0)
1973     document.getElementById(kDisplayModeMenuIDs[previousMode]).setAttribute("checked","false");
1974   document.getElementById(kDisplayModeMenuIDs[mode]).setAttribute("checked","true");
1975   
1976 
1977   return true;
1978 }
1979 
1980 function EditorToggleParagraphMarks()
1981 {
1982   var menuItem = document.getElementById("viewParagraphMarks");
1983   if (menuItem)
1984   {
1985     // Note that the 'type="checbox"' mechanism automatically
1986     //  toggles the "checked" state before the oncommand is called,
1987     //  so if "checked" is true now, it was just switched to that mode
1988     var checked = menuItem.getAttribute("checked");
1989     try {
1990       var editor = GetCurrentEditor();
1991       editor.QueryInterface(nsIEditorStyleSheets);
1992 
1993       if (checked == "true")
1994         editor.addOverrideStyleSheet(kParagraphMarksStyleSheet);
1995       else
1996         editor.enableStyleSheet(kParagraphMarksStyleSheet, false);
1997     }
1998     catch(e) { return; }
1999   }
2000 }
2001 
2002 function InitPasteAsMenu()
2003 {
2004   var menuItem = document.getElementById("menu_pasteTable")
2005   if(menuItem)
2006   {
2007     menuItem.IsInTable  
2008     menuItem.setAttribute("label", GetString(IsInTable() ? "NestedTable" : "Table"));
2009    // menuItem.setAttribute("accesskey",GetString("ObjectPropertiesAccessKey"));
2010   }
2011   // TODO: Do enabling based on what is in the clipboard
2012 }
2013 
2014 function UpdateWindowTitle()
2015 {
2016   try {
2017     var windowTitle = GetDocumentTitle();
2018     if (!windowTitle)
2019       windowTitle = GetString("untitled");
2020 
2021     // Append just the 'leaf' filename to the Doc. Title for the window caption
2022     var docUrl = GetDocumentUrl();
2023     if (docUrl && !IsUrlAboutBlank(docUrl))
2024     {
2025       var scheme = GetScheme(docUrl);
2026       var filename = GetFilename(docUrl);
2027       if (filename)
2028         windowTitle += " [" + scheme + ":/.../" + filename + "]";
2029 
2030       // Save changed title in the recent pages data in prefs
2031       SaveRecentFilesPrefs();
2032     }
2033     // Set window title with " - Composer" appended
2034     var xulWin = document.documentElement;
2035     document.title = windowTitle + xulWin.getAttribute("titlemenuseparator") + 
2036                      xulWin.getAttribute("titlemodifier");
2037   } catch (e) { dump(e); }
2038 }
2039 
2040 function BuildRecentPagesMenu()
2041 {
2042   var editor = GetCurrentEditor();
2043   if (!editor || !gPrefs)
2044     return;
2045 
2046   var popup = document.getElementById("menupopup_RecentFiles");
2047   if (!popup || !editor.document)
2048     return;
2049 
2050   // Delete existing menu
2051   while (popup.firstChild)
2052     popup.removeChild(popup.firstChild);
2053 
2054   // Current page is the "0" item in the list we save in prefs,
2055   //  but we don't include it in the menu.
2056   var curUrl = StripPassword(GetDocumentUrl());
2057   var historyCount = 10;
2058   try {
2059     historyCount = gPrefs.getIntPref("editor.history.url_maximum");
2060   } catch(e) {}
2061   var menuIndex = 1;
2062 
2063   for (var i = 0; i < historyCount; i++)
2064   {
2065     var url = GetUnicharPref("editor.history_url_"+i);
2066 
2067     // Skip over current url
2068     if (url && url != curUrl)
2069     {
2070       // Build the menu
2071       var title = GetUnicharPref("editor.history_title_"+i);
2072       AppendRecentMenuitem(popup, title, url, menuIndex);
2073       menuIndex++;
2074     }
2075   }
2076 }
2077 
2078 function SaveRecentFilesPrefs()
2079 {
2080   // Can't do anything if no prefs
2081   if (!gPrefs) return;
2082 
2083   var curUrl = StripPassword(GetDocumentUrl());
2084   var historyCount = 10;
2085   try {
2086     historyCount = gPrefs.getIntPref("editor.history.url_maximum"); 
2087   } catch(e) {}
2088 
2089   var titleArray = [];
2090   var urlArray = [];
2091 
2092   if (historyCount && !IsUrlAboutBlank(curUrl) &&  GetScheme(curUrl) != "data")
2093   {
2094     titleArray.push(GetDocumentTitle());
2095     urlArray.push(curUrl);
2096   }
2097 
2098   for (var i = 0; i < historyCount && urlArray.length < historyCount; i++)
2099   {
2100     var url = GetUnicharPref("editor.history_url_"+i);
2101 
2102     // Continue if URL pref is missing because 
2103     //  a URL not found during loading may have been removed
2104 
2105     // Skip over current an "data" URLs
2106     if (url && url != curUrl && GetScheme(url) != "data")
2107     {
2108       var title = GetUnicharPref("editor.history_title_"+i);
2109       titleArray.push(title);
2110       urlArray.push(url);
2111     }
2112   }
2113 
2114   // Resave the list back to prefs in the new order
2115   for (i = 0; i < urlArray.length; i++)
2116   {
2117     SetUnicharPref("editor.history_title_"+i, titleArray[i]);
2118     SetUnicharPref("editor.history_url_"+i, urlArray[i]);
2119   }
2120 }
2121 
2122 function AppendRecentMenuitem(menupopup, title, url, menuIndex)
2123 {
2124   if (menupopup)
2125   {
2126     var menuItem = document.createElementNS(XUL_NS, "menuitem");
2127     if (menuItem)
2128     {
2129       var accessKey;
2130       if (menuIndex <= 9)
2131         accessKey = String(menuIndex);
2132       else if (menuIndex == 10)
2133         accessKey = "0";
2134       else
2135         accessKey = " ";
2136 
2137       var itemString = accessKey+" ";
2138 
2139       // Show "title [url]" or just the URL
2140       if (title)
2141       {
2142        itemString += title;
2143        itemString += " [";
2144       }
2145       itemString += url;
2146       if (title)
2147         itemString += "]";
2148 
2149       menuItem.setAttribute("label", itemString);
2150       menuItem.setAttribute("crop", "center");
2151       menuItem.setAttribute("value", url);
2152       if (accessKey != " ")
2153         menuItem.setAttribute("accesskey", accessKey);
2154       menupopup.appendChild(menuItem);
2155     }
2156   }
2157 }
2158 
2159 function EditorInitFileMenu()
2160 {
2161   // Disable "Save" menuitem when editing remote url. User should use "Save As"
2162   var docUrl = GetDocumentUrl();
2163   var scheme = GetScheme(docUrl);
2164   if (scheme && scheme != "file")
2165     SetElementEnabledById("saveMenuitem", false);
2166 
2167   // Enable recent pages submenu if there are any history entries in prefs
2168   var historyUrl = "";
2169 
2170   var historyCount = 10;
2171   try { historyCount = gPrefs.getIntPref("editor.history.url_maximum"); } catch(e) {}
2172   if (historyCount)
2173   {
2174     historyUrl = GetUnicharPref("editor.history_url_0");
2175     
2176     // See if there's more if current file is only entry in history list
2177     if (historyUrl && historyUrl == docUrl)
2178       historyUrl = GetUnicharPref("editor.history_url_1");
2179   }
2180   SetElementEnabledById("menu_RecentFiles", historyUrl != "");
2181 }
2182 
2183 function EditorInitFormatMenu()
2184 {
2185   try {
2186     InitObjectPropertiesMenuitem("objectProperties");
2187     InitRemoveStylesMenuitems("removeStylesMenuitem", "removeLinksMenuitem", "removeNamedAnchorsMenuitem");
2188   } catch(ex) {}
2189 }
2190 
2191 function InitObjectPropertiesMenuitem(id)
2192 {
2193   // Set strings and enable for the [Object] Properties item
2194   // Note that we directly do the enabling instead of
2195   //  using goSetCommandEnabled since we already have the menuitem
2196   var menuItem = document.getElementById(id);
2197   if (!menuItem) return null;
2198 
2199   var element;
2200   var menuStr = GetString("AdvancedProperties");
2201   var name;
2202 
2203   if (IsEditingRenderedHTML())
2204     element = GetObjectForProperties();
2205 
2206   if (element && element.nodeName)
2207   {
2208     var objStr = "";
2209     menuItem.setAttribute("disabled", "");
2210     name = element.nodeName.toLowerCase();
2211     switch (name)
2212     {
2213       case "img":
2214         // Check if img is enclosed in link
2215         //  (use "href" to not be fooled by named anchor)
2216         try
2217         {
2218           if (GetCurrentEditor().getElementOrParentByTagName("href", element))
2219             objStr = GetString("ImageAndLink");
2220         } catch(e) {}
2221         
2222         if (objStr == "")
2223           objStr = GetString("Image");
2224         break;
2225       case "hr":
2226         objStr = GetString("HLine");
2227         break;
2228       case "table":
2229         objStr = GetString("Table");
2230         break;
2231       case "th":
2232         name = "td";
2233       case "td":
2234         objStr = GetString("TableCell");
2235         break;
2236       case "ol":
2237       case "ul":
2238       case "dl":
2239         objStr = GetString("List");
2240         break;
2241       case "li":
2242         objStr = GetString("ListItem");
2243         break;
2244       case "form":
2245         objStr = GetString("Form");
2246         break;
2247       case "input":
2248         var type = element.getAttribute("type");
2249         if (type && type.toLowerCase() == "image")
2250           objStr = GetString("InputImage");
2251         else
2252           objStr = GetString("InputTag");
2253         break;
2254       case "textarea":
2255         objStr = GetString("TextArea");
2256         break;
2257       case "select":
2258         objStr = GetString("Select");
2259         break;
2260       case "button":
2261         objStr = GetString("Button");
2262         break;
2263       case "label":
2264         objStr = GetString("Label");
2265         break;
2266       case "fieldset":
2267         objStr = GetString("FieldSet");
2268         break;
2269       case "a":
2270         if (element.name)
2271         {
2272           objStr = GetString("NamedAnchor");
2273           name = "anchor";
2274         }
2275         else if(element.href)
2276         {
2277           objStr = GetString("Link");
2278           name = "href";
2279         }
2280         break;
2281     }
2282     if (objStr)
2283       menuStr = GetString("ObjectProperties").replace(/%obj%/,objStr);
2284   }
2285   else
2286   {
2287     // We show generic "Properties" string, but disable menu item
2288     menuItem.setAttribute("disabled","true");
2289   }
2290   menuItem.setAttribute("label", menuStr);
2291   menuItem.setAttribute("accesskey",GetString("ObjectPropertiesAccessKey"));
2292   return name;
2293 }
2294 
2295 function InitParagraphMenu()
2296 {
2297   var mixedObj = { value: null };
2298   var state;
2299   try {
2300     state = GetCurrentEditor().getParagraphState(mixedObj);
2301   }
2302   catch(e) {}
2303   var IDSuffix;
2304 
2305   // PROBLEM: When we get blockquote, it masks other styles contained by it
2306   // We need a separate method to get blockquote state
2307 
2308   // We use "x" as uninitialized paragraph state
2309   if (!state || state == "x")
2310     IDSuffix = "bodyText" // No paragraph container
2311   else
2312     IDSuffix = state;
2313 
2314   // Set "radio" check on one item, but...
2315   var menuItem = document.getElementById("menu_"+IDSuffix);
2316   menuItem.setAttribute("checked", "true");
2317 
2318   // ..."bodyText" is returned if mixed selection, so remove checkmark
2319   if (mixedObj.value)
2320     menuItem.setAttribute("checked", "false");
2321 }
2322 
2323 function GetListStateString()
2324 {
2325   try {
2326     var editor = GetCurrentEditor();
2327 
2328     var mixedObj = { value: null };
2329     var hasOL = { value: false };
2330     var hasUL = { value: false };
2331     var hasDL = { value: false };
2332     editor.getListState(mixedObj, hasOL, hasUL, hasDL);
2333 
2334     if (mixedObj.value)
2335       return "mixed";
2336     if (hasOL.value)
2337       return "ol";
2338     if (hasUL.value)
2339       return "ul";
2340 
2341     if (hasDL.value)
2342     {
2343       var hasLI = { value: false };
2344       var hasDT = { value: false };
2345       var hasDD = { value: false };
2346       editor.getListItemState(mixedObj, hasLI, hasDT, hasDD);
2347       if (mixedObj.value)
2348         return "mixed";
2349       if (hasLI.value)
2350         return "li";
2351       if (hasDT.value)
2352         return "dt";
2353       if (hasDD.value)
2354         return "dd";
2355     }
2356   } catch (e) {}
2357 
2358   // return "noList" if we aren't in a list at all
2359   return "noList";
2360 }
2361 
2362 function InitListMenu()
2363 {
2364   if (!IsHTMLEditor())
2365     return;
2366 
2367   var IDSuffix = GetListStateString();
2368 
2369   // Set enable state for the "None" menuitem
2370   goSetCommandEnabled("cmd_removeList", IDSuffix != "noList");
2371 
2372   // Set "radio" check on one item, but...
2373   // we won't find a match if it's "mixed"
2374   var menuItem = document.getElementById("menu_"+IDSuffix);
2375   if (menuItem)
2376     menuItem.setAttribute("checked", "true");
2377 }
2378 
2379 function GetAlignmentString()
2380 {
2381   var mixedObj = { value: null };
2382   var alignObj = { value: null };
2383   try {
2384     GetCurrentEditor().getAlignment(mixedObj, alignObj);
2385   } catch (e) {}
2386 
2387   if (mixedObj.value)
2388     return "mixed";
2389   if (alignObj.value == nsIHTMLEditor.eLeft)
2390     return "left";
2391   if (alignObj.value == nsIHTMLEditor.eCenter)
2392     return "center";
2393   if (alignObj.value == nsIHTMLEditor.eRight)
2394     return "right";
2395   if (alignObj.value == nsIHTMLEditor.eJustify)
2396     return "justify";
2397 
2398   // return "left" if we got here
2399   return "left";
2400 }
2401 
2402 function InitAlignMenu()
2403 {
2404   if (!IsHTMLEditor())
2405     return;
2406 
2407   var IDSuffix = GetAlignmentString();
2408 
2409   // we won't find a match if it's "mixed"
2410   var menuItem = document.getElementById("menu_"+IDSuffix);
2411   if (menuItem)
2412     menuItem.setAttribute("checked", "true");
2413 }
2414 
2415 function EditorSetDefaultPrefsAndDoctype()
2416 {
2417   var editor = GetCurrentEditor();
2418 
2419   var domdoc;
2420   try { 
2421     domdoc = editor.document;
2422   } catch (e) { dump( e + "\n"); }
2423   if ( !domdoc )
2424   {
2425     dump("EditorSetDefaultPrefsAndDoctype: EDITOR DOCUMENT NOT FOUND\n");
2426     return;
2427   }
2428 
2429   // Insert a doctype element 
2430   // if it is missing from existing doc
2431   if (!domdoc.doctype)
2432   {
2433     var newdoctype = domdoc.implementation.createDocumentType("HTML", "-//W3C//DTD HTML 4.01 Transitional//EN","");
2434     if (newdoctype)
2435       domdoc.insertBefore(newdoctype, domdoc.firstChild);
2436   }
2437   
2438   // search for head; we'll need this for meta tag additions
2439   var headelement = 0;
2440   var headnodelist = domdoc.getElementsByTagName("head");
2441   if (headnodelist)
2442   {
2443     var sz = headnodelist.length;
2444     if ( sz >= 1 )
2445       headelement = headnodelist.item(0);
2446   }
2447   else
2448   {
2449     headelement = domdoc.createElement("head");
2450     if (headelement)
2451       domdoc.insertAfter(headelement, domdoc.firstChild);
2452   }
2453 
2454   /* only set default prefs for new documents */
2455   if (!IsUrlAboutBlank(GetDocumentUrl()))
2456     return;
2457 
2458   // search for author meta tag.
2459   // if one is found, don't do anything.
2460   // if not, create one and make it a child of the head tag
2461   //   and set its content attribute to the value of the editor.author preference.
2462 
2463   var nodelist = domdoc.getElementsByTagName("meta");
2464   if ( nodelist )
2465   {
2466     // we should do charset first since we need to have charset before
2467     // hitting other 8-bit char in other meta tags
2468     // grab charset pref and make it the default charset
2469     var element;
2470     var prefCharsetString = 0;
2471     try
2472     {
2473       prefCharsetString = gPrefs.getComplexValue("intl.charset.default",
2474                                                  Components.interfaces.nsIPrefLocalizedString).data;
2475     }
2476     catch (ex) {}
2477     if ( prefCharsetString && prefCharsetString != 0)
2478     {
2479       editor.enableUndo(false);
2480       editor.documentCharacterSet = prefCharsetString;
2481       editor.resetModificationCount();
2482       editor.enableUndo(true);
2483     }
2484 
2485     var node = 0;
2486     var listlength = nodelist.length;
2487 
2488     // let's start by assuming we have an author in case we don't have the pref
2489     var authorFound = false;
2490     for (var i = 0; i < listlength && !authorFound; i++)
2491     {
2492       node = nodelist.item(i);
2493       if ( node )
2494       {
2495         var value = node.getAttribute("name");
2496         if (value && value.toLowerCase() == "author")
2497         {
2498           authorFound = true;
2499         }
2500       }
2501     }
2502 
2503     var prefAuthorString = 0;
2504     try
2505     {
2506       prefAuthorString = gPrefs.getComplexValue("editor.author",
2507                                                 Components.interfaces.nsISupportsString).data;
2508     }
2509     catch (ex) {}
2510     if ( prefAuthorString && prefAuthorString != 0)
2511     {
2512       if ( !authorFound && headelement)
2513       {
2514         /* create meta tag with 2 attributes */
2515         element = domdoc.createElement("meta");
2516         if ( element )
2517         {
2518           element.setAttribute("name", "author");
2519           element.setAttribute("content", prefAuthorString);
2520           headelement.appendChild( element );
2521         }
2522       }
2523     }
2524   }
2525 
2526   // add title tag if not present
2527   var titlenodelist = editor.document.getElementsByTagName("title");
2528   if (headelement && titlenodelist && titlenodelist.length == 0)
2529   {
2530      titleElement = domdoc.createElement("title");
2531      if (titleElement)
2532        headelement.appendChild(titleElement);
2533   }
2534 
2535   // Get editor color prefs
2536   var use_custom_colors = false;
2537   try {
2538     use_custom_colors = gPrefs.getBoolPref("editor.use_custom_colors");
2539   }
2540   catch (ex) {}
2541 
2542   // find body node
2543   var bodyelement = GetBodyElement();
2544   if (bodyelement)
2545   {
2546     if ( use_custom_colors )
2547     {
2548       // try to get the default color values.  ignore them if we don't have them.
2549       var text_color;
2550       var link_color;
2551       var active_link_color;
2552       var followed_link_color;
2553       var background_color;
2554 
2555       try { text_color = gPrefs.getCharPref("editor.text_color"); } catch (e) {}
2556       try { link_color = gPrefs.getCharPref("editor.link_color"); } catch (e) {}
2557       try { active_link_color = gPrefs.getCharPref("editor.active_link_color"); } catch (e) {}
2558       try { followed_link_color = gPrefs.getCharPref("editor.followed_link_color"); } catch (e) {}
2559       try { background_color = gPrefs.getCharPref("editor.background_color"); } catch(e) {}
2560 
2561       // add the color attributes to the body tag.
2562       // and use them for the default text and background colors if not empty
2563       try {
2564         if (text_color)
2565         {
2566           editor.setAttributeOrEquivalent(bodyelement, "text", text_color, true);
2567           gDefaultTextColor = text_color;
2568         }
2569         if (background_color)
2570         {
2571           editor.setAttributeOrEquivalent(bodyelement, "bgcolor", background_color, true);
2572           gDefaultBackgroundColor = background_color
2573         }
2574 
2575         if (link_color)
2576           bodyelement.setAttribute("link", link_color);
2577         if (active_link_color)
2578           bodyelement.setAttribute("alink", active_link_color);
2579         if (followed_link_color)
2580           bodyelement.setAttribute("vlink", followed_link_color);
2581       } catch (e) {}
2582     }
2583     // Default image is independent of Custom colors???
2584     try {
2585       var background_image = gPrefs.getCharPref("editor.default_background_image");
2586       if (background_image)
2587         editor.setAttributeOrEquivalent(bodyelement, "background", background_image, true);
2588     } catch (e) {dump("BACKGROUND EXCEPTION: "+e+"\n"); }
2589 
2590   }
2591   // auto-save???
2592 }
2593 
2594 function GetBodyElement()
2595 {
2596   try {
2597     return GetCurrentEditor().rootElement;
2598   }
2599   catch (ex) {
2600     dump("no body tag found?!\n");
2601     //  better have one, how can we blow things up here?
2602   }
2603   return null;
2604 }
2605 
2606 // --------------------------- Logging stuff ---------------------------
2607 
2608 function EditorGetNodeFromOffsets(offsets)
2609 {
2610   var node = null;
2611   try {
2612     node = GetCurrentEditor().document;
2613 
2614     for (var i = 0; i < offsets.length; i++)
2615       node = node.childNodes[offsets[i]];
2616   } catch (e) {}
2617   return node;
2618 }
2619 
2620 function EditorSetSelectionFromOffsets(selRanges)
2621 {
2622   try {
2623     var editor = GetCurrentEditor();
2624     var selection = editor.selection;
2625     selection.removeAllRanges();
2626 
2627     var rangeArr, start, end, node, offset;
2628     for (var i = 0; i < selRanges.length; i++)
2629     {
2630       rangeArr = selRanges[i];
2631       start    = rangeArr[0];
2632       end      = rangeArr[1];
2633 
2634       var range = editor.document.createRange();
2635 
2636       node   = EditorGetNodeFromOffsets(start[0]);
2637       offset = start[1];
2638 
2639       range.setStart(node, offset);
2640 
2641       node   = EditorGetNodeFromOffsets(end[0]);
2642       offset = end[1];
2643 
2644       range.setEnd(node, offset);
2645 
2646       selection.addRange(range);
2647     }
2648   } catch (e) {}
2649 }
2650 
2651 //--------------------------------------------------------------------
2652 function initFontStyleMenu(menuPopup)
2653 {
2654   for (var i = 0; i < menuPopup.childNodes.length; i++)
2655   {
2656     var menuItem = menuPopup.childNodes[i];
2657     var theStyle = menuItem.getAttribute("state");
2658     if (theStyle)
2659     {
2660       menuItem.setAttribute("checked", theStyle);
2661     }
2662   }
2663 }
2664 
2665 //--------------------------------------------------------------------
2666 function onButtonUpdate(button, commmandID)
2667 {
2668   var commandNode = document.getElementById(commmandID);
2669   var state = commandNode.getAttribute("state");
2670   button.checked = state == "true";
2671 }
2672 
2673 //--------------------------------------------------------------------
2674 function onStateButtonUpdate(button, commmandID, onState)
2675 {
2676   var commandNode = document.getElementById(commmandID);
2677   var state = commandNode.getAttribute("state");
2678 
2679   button.checked = state == onState;
2680 }
2681 
2682 // --------------------------- Status calls ---------------------------
2683 function getColorAndSetColorWell(ColorPickerID, ColorWellID)
2684 {
2685   var colorWell;
2686   if (ColorWellID)
2687     colorWell = document.getElementById(ColorWellID);
2688 
2689   var colorPicker = document.getElementById(ColorPickerID);
2690   if (colorPicker)
2691   {
2692     // Extract color from colorPicker and assign to colorWell.
2693     var color = colorPicker.getAttribute("color");
2694 
2695     if (colorWell && color)
2696     {
2697       // Use setAttribute so colorwell can be a XUL element, such as button
2698       colorWell.setAttribute("style", "background-color: " + color);
2699     }
2700   }
2701   return color;
2702 }
2703 
2704 //-----------------------------------------------------------------------------------
2705 function IsSpellCheckerInstalled()
2706 {
2707   return "@mozilla.org/spellchecker;1" in Components.classes;
2708 }
2709 
2710 //-----------------------------------------------------------------------------------
2711 function IsFindInstalled()
2712 {
2713   return "@mozilla.org/embedcomp/rangefind;1" in Components.classes
2714           && "@mozilla.org/find/find_service;1" in Components.classes;
2715 }
2716 
2717 //-----------------------------------------------------------------------------------
2718 function RemoveInapplicableUIElements()
2719 {
2720   // For items that are in their own menu block, remove associated separator
2721   // (we can't use "hidden" since class="hide-in-IM" CSS rule interferes)
2722 
2723    // if no find, remove find ui
2724   if (!IsFindInstalled())
2725   {
2726     HideItem("menu_find");
2727     HideItem("menu_findnext");
2728     HideItem("menu_replace");
2729     HideItem("menu_find");
2730     RemoveItem("sep_find");
2731   }
2732 
2733    // if no spell checker, remove spell checker ui
2734   if (!IsSpellCheckerInstalled())
2735   {
2736     HideItem("spellingButton");
2737     HideItem("menu_checkspelling");
2738     RemoveItem("sep_checkspelling");
2739   }
2740   else
2741   {
2742     SetElementEnabled(document.getElementById("menu_checkspelling"), true);
2743     SetElementEnabled(document.getElementById("spellingButton"), true);
2744     SetElementEnabled(document.getElementById("checkspellingkb"), true);
2745   }
2746 
2747   // Remove menu items (from overlay shared with HTML editor) in non-HTML.
2748   if (!IsHTMLEditor())
2749   {
2750     HideItem("insertAnchor");
2751     HideItem("insertImage");
2752     HideItem("insertHline");
2753     HideItem("insertTable");
2754     HideItem("insertHTML");
2755     HideItem("insertFormMenu");
2756     HideItem("fileExportToText");
2757     HideItem("viewFormatToolbar");
2758     HideItem("viewEditModeToolbar");
2759   }
2760 }
2761 
2762 function HideItem(id)
2763 {
2764   var item = document.getElementById(id);
2765   if (item)
2766     item.hidden = true;
2767 }
2768 
2769 function RemoveItem(id)
2770 {
2771   var item = document.getElementById(id);
2772   if (item)
2773     item.parentNode.removeChild(item);
2774 }
2775 
2776 // Command Updating Strategy:
2777 //   Don't update on on selection change, only when menu is displayed,
2778 //   with this "oncreate" hander:
2779 function EditorInitTableMenu()
2780 {
2781   try {
2782     InitJoinCellMenuitem("menu_JoinTableCells");
2783   } catch (ex) {}
2784 
2785   // Set enable states for all table commands
2786   goUpdateTableMenuItems(document.getElementById("composerTableMenuItems"));
2787 }
2788 
2789 function InitJoinCellMenuitem(id)
2790 {
2791   // Change text on the "Join..." item depending if we
2792   //   are joining selected cells or just cell to right
2793   // TODO: What to do about normal selection that crosses
2794   //       table border? Try to figure out all cells
2795   //       included in the selection?
2796   var menuText;
2797   var menuItem = document.getElementById(id);
2798   if (!menuItem) return;
2799 
2800   // Use "Join selected cells if there's more than 1 cell selected
2801   var numSelected;
2802   var foundElement;
2803   
2804   try {
2805     var tagNameObj = {};
2806     var countObj = {value:0}
2807     foundElement = GetCurrentTableEditor().getSelectedOrParentTableElement(tagNameObj, countObj);
2808     numSelected = countObj.value
2809   }
2810   catch(e) {}
2811   if (foundElement && numSelected > 1)
2812     menuText = GetString("JoinSelectedCells");
2813   else
2814     menuText = GetString("JoinCellToRight");
2815 
2816   menuItem.setAttribute("label",menuText);
2817   menuItem.setAttribute("accesskey",GetString("JoinCellAccesskey"));
2818 }
2819 
2820 function InitRemoveStylesMenuitems(removeStylesId, removeLinksId, removeNamedAnchorsId)
2821 {
2822   var editor = GetCurrentEditor();
2823   if (!editor)
2824     return;
2825 
2826   // Change wording of menuitems depending on selection
2827   var stylesItem = document.getElementById(removeStylesId);
2828   var linkItem = document.getElementById(removeLinksId);
2829 
2830   var isCollapsed = editor.selection.isCollapsed;
2831   if (stylesItem)
2832   {
2833     stylesItem.setAttribute("label", isCollapsed ? GetString("StopTextStyles") : GetString("RemoveTextStyles"));
2834     stylesItem.setAttribute("accesskey", GetString("RemoveTextStylesAccesskey"));
2835   }
2836   if (linkItem)
2837   {
2838     linkItem.setAttribute("label", isCollapsed ? GetString("StopLinks") : GetString("RemoveLinks"));
2839     linkItem.setAttribute("accesskey", GetString("RemoveLinksAccesskey"));
2840     // Note: disabling text style is a pain since there are so many - forget it!
2841 
2842     // Disable if not in a link, but always allow "Remove"
2843     //  if selection isn't collapsed since we only look at anchor node
2844     try {
2845       SetElementEnabled(linkItem, !isCollapsed ||
2846                       editor.getElementOrParentByTagName("href", null));
2847     } catch(e) {}      
2848   }
2849   // Disable if selection is collapsed
2850   SetElementEnabledById(removeNamedAnchorsId, !isCollapsed);
2851 }
2852 
2853 function goUpdateTableMenuItems(commandset)
2854 {
2855   var editor = GetCurrentTableEditor();
2856   if (!editor)
2857   {
2858     dump("goUpdateTableMenuItems: too early, not initialized\n");
2859     return;
2860   }
2861 
2862   var enabled = false;
2863   var enabledIfTable = false;
2864 
2865   var flags = editor.flags;
2866   if (!(flags & nsIPlaintextEditor.eEditorReadonlyMask) &&
2867       IsEditingRenderedHTML())
2868   {
2869     var tagNameObj = { value: "" };
2870     var element;
2871     try {
2872       element = editor.getSelectedOrParentTableElement(tagNameObj, {value:0});
2873     }
2874     catch(e) {}
2875 
2876     if (element)
2877     {
2878       // Value when we need to have a selected table or inside a table
2879       enabledIfTable = true;
2880 
2881       // All others require being inside a cell or selected cell
2882       enabled = (tagNameObj.value == "td");
2883     }
2884   }
2885 
2886   // Loop through command nodes
2887   for (var i = 0; i < commandset.childNodes.length; i++)
2888   {
2889     var commandID = commandset.childNodes[i].getAttribute("id");
2890     if (commandID)
2891     {
2892       if (commandID == "cmd_InsertTable" ||
2893           commandID == "cmd_JoinTableCells" ||
2894           commandID == "cmd_SplitTableCell" ||
2895           commandID == "cmd_ConvertToTable")
2896       {
2897         // Call the update method in the command class
2898         goUpdateCommand(commandID);
2899       }
2900       // Directly set with the values calculated here
2901       else if (commandID == "cmd_DeleteTable" ||
2902                commandID == "cmd_NormalizeTable" ||
2903                commandID == "cmd_editTable" ||
2904                commandID == "cmd_TableOrCellColor" ||
2905                commandID == "cmd_SelectTable")
2906       {
2907         goSetCommandEnabled(commandID, enabledIfTable);
2908       } else {
2909         goSetCommandEnabled(commandID, enabled);
2910       }
2911     }
2912   }
2913 }
2914 
2915 //-----------------------------------------------------------------------------------
2916 // Helpers for inserting and editing tables:
2917 
2918 function IsInTable()
2919 {
2920   var editor = GetCurrentEditor();
2921   try {
2922     var flags = editor.flags;
2923     return (IsHTMLEditor() &&
2924             !(flags & nsIPlaintextEditor.eEditorReadonlyMask) &&
2925             IsEditingRenderedHTML() &&
2926             null != editor.getElementOrParentByTagName("table", null));
2927   } catch (e) {}
2928   return false;
2929 }
2930 
2931 function IsInTableCell()
2932 {
2933   try {
2934     var editor = GetCurrentEditor();
2935     var flags = editor.flags;
2936     return (IsHTMLEditor() &&
2937             !(flags & nsIPlaintextEditor.eEditorReadonlyMask) && 
2938             IsEditingRenderedHTML() &&
2939             null != editor.getElementOrParentByTagName("td", null));
2940   } catch (e) {}
2941   return false;
2942 
2943 }
2944 
2945 function IsSelectionInOneCell()
2946 {
2947   try {
2948     var editor = GetCurrentEditor();
2949     var selection = editor.selection;
2950 
2951     if (selection.rangeCount == 1)
2952     {
2953       // We have a "normal" single-range selection
2954       if (!selection.isCollapsed &&
2955          selection.anchorNode != selection.focusNode)
2956       {
2957         // Check if both nodes are within the same cell
2958         var anchorCell = editor.getElementOrParentByTagName("td", selection.anchorNode);
2959         var focusCell = editor.getElementOrParentByTagName("td", selection.focusNode);
2960         return (focusCell != null && anchorCell != null && (focusCell == anchorCell));
2961       }
2962       // Collapsed selection or anchor == focus (thus must be in 1 cell)
2963       return true;
2964     }
2965   } catch (e) {}
2966   return false;
2967 }
2968 
2969 // Call this with insertAllowed = true to allow inserting if not in existing table,
2970 //   else use false to do nothing if not in a table
2971 function EditorInsertOrEditTable(insertAllowed)
2972 {
2973   if (IsInTable())
2974   {
2975     // Edit properties of existing table
2976     window.openDialog("chrome://editor/content/EdTableProps.xul", "_blank", "chrome,close,titlebar,modal", "","TablePanel");
2977     gContentWindow.focus();
2978   } 
2979   else if (insertAllowed)
2980   {
2981     try {
2982       if (GetCurrentEditor().selection.isCollapsed)
2983         // If we have a caret, insert a blank table...
2984         EditorInsertTable();
2985       else
2986         // else convert the selection into a table
2987         goDoCommand("cmd_ConvertToTable");
2988     } catch (e) {}
2989   }
2990 }
2991 
2992 function EditorInsertTable()
2993 {
2994   // Insert a new table
2995   window.openDialog("chrome://editor/content/EdInsertTable.xul", "_blank", "chrome,close,titlebar,modal", "");
2996   gContentWindow.focus();
2997 }
2998 
2999 function EditorTableCellProperties()
3000 {
3001   if (!IsHTMLEditor())
3002     return;
3003 
3004   try {
3005     var cell = GetCurrentEditor().getElementOrParentByTagName("td", null);
3006     if (cell) {
3007       // Start Table Properties dialog on the "Cell" panel
3008       window.openDialog("chrome://editor/content/EdTableProps.xul", "_blank", "chrome,close,titlebar,modal", "", "CellPanel");
3009       gContentWindow.focus();
3010     }
3011   } catch (e) {}
3012 }
3013 
3014 function GetNumberOfContiguousSelectedRows()
3015 {
3016   if (!IsHTMLEditor())
3017     return 0;
3018 
3019   var rows = 0;
3020   try {
3021     var editor = GetCurrentTableEditor();
3022     var rowObj = { value: 0 };
3023     var colObj = { value: 0 };
3024     var cell = editor.getFirstSelectedCellInTable(rowObj, colObj);
3025     if (!cell)
3026       return 0;
3027 
3028     // We have at least one row
3029     rows++;
3030 
3031     var lastIndex = rowObj.value;
3032     do {
3033       cell = editor.getNextSelectedCell({value:0});
3034       if (cell)
3035       {
3036         editor.getCellIndexes(cell, rowObj, colObj);
3037         var index = rowObj.value;
3038         if (index == lastIndex + 1)
3039         {
3040           lastIndex = index;
3041           rows++;
3042         }
3043       }
3044     }
3045     while (cell);
3046   } catch (e) {}
3047 
3048   return rows;
3049 }
3050 
3051 function GetNumberOfContiguousSelectedColumns()
3052 {
3053   if (!IsHTMLEditor())
3054     return 0;
3055 
3056   var columns = 0;
3057   try {
3058     var editor = GetCurrentTableEditor();
3059     var colObj = { value: 0 };
3060     var rowObj = { value: 0 };
3061     var cell = editor.getFirstSelectedCellInTable(rowObj, colObj);
3062     if (!cell)
3063       return 0;
3064 
3065     // We have at least one column
3066     columns++;
3067 
3068     var lastIndex = colObj.value;
3069     do {
3070       cell = editor.getNextSelectedCell({value:0});
3071       if (cell)
3072       {
3073         editor.getCellIndexes(cell, rowObj, colObj);
3074         var index = colObj.value;
3075         if (index == lastIndex +1)
3076         {
3077           lastIndex = index;
3078           columns++;
3079         }
3080       }
3081     }
3082     while (cell);
3083   } catch (e) {}
3084 
3085   return columns;
3086 }
3087 
3088 function EditorOnFocus()
3089 {
3090   // Current window already has the InsertCharWindow
3091   if ("InsertCharWindow" in window && window.InsertCharWindow) return;
3092 
3093   // Find window with an InsertCharsWindow and switch association to this one
3094   var windowWithDialog = FindEditorWithInsertCharDialog();
3095   if (windowWithDialog)
3096   {
3097     // Switch the dialog to current window
3098     // this sets focus to dialog, so bring focus back to editor window
3099     if (SwitchInsertCharToThisWindow(windowWithDialog))
3100       top.document.commandDispatcher.focusedWindow.focus();
3101   }
3102 }
3103 
3104 function SwitchInsertCharToThisWindow(windowWithDialog)
3105 {
3106   if (windowWithDialog && "InsertCharWindow" in windowWithDialog &&
3107       windowWithDialog.InsertCharWindow)
3108   {
3109     // Move dialog association to the current window
3110     window.InsertCharWindow = windowWithDialog.InsertCharWindow;
3111     windowWithDialog.InsertCharWindow = null;
3112 
3113     // Switch the dialog's opener to current window's
3114     window.InsertCharWindow.opener = window;
3115 
3116     // Bring dialog to the forground
3117     window.InsertCharWindow.focus();
3118     return true;
3119   }
3120   return false;
3121 }
3122 
3123 function FindEditorWithInsertCharDialog()
3124 {
3125   try {
3126     // Find window with an InsertCharsWindow and switch association to this one
3127     var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService();
3128     var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
3129     var enumerator = windowManagerInterface.getEnumerator( null );
3130 
3131     while ( enumerator.hasMoreElements()  )
3132     {
3133       var tempWindow = enumerator.getNext();
3134 
3135       if (tempWindow != window && "InsertCharWindow" in tempWindow &&
3136           tempWindow.InsertCharWindow)
3137       {
3138         return tempWindow;
3139       }
3140     }
3141   }
3142   catch(e) {}
3143   return null;
3144 }
3145 
3146 function EditorFindOrCreateInsertCharWindow()
3147 {
3148   if ("InsertCharWindow" in window && window.InsertCharWindow)
3149     window.InsertCharWindow.focus();
3150   else
3151   {
3152     // Since we switch the dialog during EditorOnFocus(),
3153     //   this should really never be found, but it's good to be sure
3154     var windowWithDialog = FindEditorWithInsertCharDialog();
3155     if (windowWithDialog)
3156     {
3157       SwitchInsertCharToThisWindow(windowWithDialog);
3158     }
3159     else
3160     {
3161       // The dialog will set window.InsertCharWindow to itself
3162       window.openDialog("chrome://editor/content/EdInsertChars.xul", "_blank", "chrome,close,titlebar", "");
3163     }
3164   }
3165 }
3166 
3167 // Find another HTML editor window to associate with the InsertChar dialog
3168 //   or close it if none found  (May be a mail composer)
3169 function SwitchInsertCharToAnotherEditorOrClose()
3170 {
3171   if ("InsertCharWindow" in window && window.InsertCharWindow)
3172   {
3173     var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService();
3174     var enumerator;
3175     try {
3176       var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
3177       enumerator = windowManagerInterface.getEnumerator( null );
3178     }
3179     catch(e) {}
3180     if (!enumerator) return;
3181 
3182     // TODO: Fix this to search for command controllers and look for "cmd_InsertChars"
3183     // For now, detect just Web Composer and HTML Mail Composer
3184     while ( enumerator.hasMoreElements()  )
3185     {
3186       var  tempWindow = enumerator.getNext();
3187       if (tempWindow != window && tempWindow != window.InsertCharWindow &&
3188           "GetCurrentEditor" in tempWindow && tmpWindow.GetCurrentEditor())
3189       {
3190         tempWindow.InsertCharWindow = window.InsertCharWindow;
3191         window.InsertCharWindow = null;
3192         tempWindow.InsertCharWindow.opener = tempWindow;
3193         return;
3194       }
3195     }
3196     // Didn't find another editor - close the dialog
3197     window.InsertCharWindow.close();
3198   }
3199 }
3200 
3201 function ResetStructToolbar()
3202 {
3203   gLastFocusNode = null;
3204   UpdateStructToolbar();
3205 }
3206 
3207 function newCommandListener(element)
3208 {
3209   return function() { return SelectFocusNodeAncestor(element); };
3210 }
3211 
3212 function newContextmenuListener(button, element)
3213 {
3214   return function() { return InitStructBarContextMenu(button, element); };
3215 }
3216 
3217 function UpdateStructToolbar()
3218 {
3219   var editor = GetCurrentEditor();
3220   if (!editor) return;
3221 
3222   var mixed = GetSelectionContainer();
3223   if (!mixed) return;
3224   var element = mixed.node;
3225   var oneElementSelected = mixed.oneElementSelected;
3226 
3227   if (!element) return;
3228 
3229   if (element == gLastFocusNode &&
3230       oneElementSelected == gLastFocusNodeWasSelected)
3231     return;
3232 
3233   gLastFocusNode = element;
3234   gLastFocusNodeWasSelected = mixed.oneElementSelected;
3235 
3236   var toolbar = document.getElementById("structToolbar");
3237   if (!toolbar) return;
3238   var childNodes = toolbar.childNodes;
3239   var childNodesLength = childNodes.length;
3240   // We need to leave the <label> to flex the buttons to the left
3241   // so, don't remove the last child at position length - 1
3242   while (childNodes.length > 1) {
3243     // Remove back to front so there's less moving about.
3244     toolbar.removeChild(childNodes.item(childNodes.length - 2));
3245   }
3246 
3247   toolbar.removeAttribute("label");
3248 
3249   if ( IsInHTMLSourceMode() ) {
3250     // we have destroyed the contents of the status bar and are
3251     // about to recreate it ; but we don't want to do that in
3252     // Source mode
3253     return;
3254   }
3255 
3256   var tag, button;
3257   var bodyElement = GetBodyElement();
3258   var isFocusNode = true;
3259   var tmp;
3260   do {
3261     tag = element.nodeName.toLowerCase();
3262 
3263     button = document.createElementNS(XUL_NS, "toolbarbutton");
3264     button.setAttribute("label",   "<" + tag + ">");
3265     button.setAttribute("value",   tag);
3266     button.setAttribute("context", "structToolbarContext");
3267     button.className = "struct-button";
3268 
3269     toolbar.insertBefore(button, toolbar.firstChild);
3270 
3271     button.addEventListener("command", newCommandListener(element), false);
3272 
3273     button.addEventListener("contextmenu", newContextmenuListener(button, element), false);
3274 
3275     if (isFocusNode && oneElementSelected) {
3276       button.setAttribute("checked", "true");
3277       isFocusNode = false;
3278     }
3279 
3280     tmp = element;
3281     element = element.parentNode;
3282 
3283   } while (tmp != bodyElement);
3284 }
3285 
3286 function SelectFocusNodeAncestor(element)
3287 {
3288   var editor = GetCurrentEditor();
3289   if (editor) {
3290     if (element == GetBodyElement())
3291       editor.selectAll();
3292     else
3293       editor.selectElement(element);
3294   }
3295   ResetStructToolbar();
3296 }
3297 
3298 function GetSelectionContainer()
3299 {
3300   var editor = GetCurrentEditor();
3301   if (!editor) return null;
3302 
3303   try {
3304     var selection = editor.selection;
3305     if (!selection) return null;
3306   }
3307   catch (e) { return null; }
3308 
3309   var result = { oneElementSelected:false };
3310 
3311   if (selection.isCollapsed) {
3312     result.node = selection.focusNode;
3313   }
3314   else {
3315     var rangeCount = selection.rangeCount;
3316     if (rangeCount == 1) {
3317       result.node = editor.getSelectedElement("");
3318       var range = selection.getRangeAt(0);
3319 
3320       // check for a weird case : when we select a piece of text inside
3321       // a text node and apply an inline style to it, the selection starts
3322       // at the end of the text node preceding the style and ends after the
3323       // last char of the style. Assume the style element is selected for
3324       // user's pleasure
3325       if (!result.node &&
3326           range.startContainer.nodeType == Node.TEXT_NODE &&
3327           range.startOffset == range.startContainer.length &&
3328           range.endContainer.nodeType == Node.TEXT_NODE &&
3329           range.endOffset == range.endContainer.length &&
3330           range.endContainer.nextSibling == null &&
3331           range.startContainer.nextSibling == range.endContainer.parentNode)
3332         result.node = range.endContainer.parentNode;
3333 
3334       if (!result.node) {
3335         // let's rely on the common ancestor of the selection
3336         result.node = range.commonAncestorContainer;
3337       }
3338       else {
3339         result.oneElementSelected = true;
3340       }
3341     }
3342     else {
3343       // assume table cells !
3344       var i, container = null;
3345       for (i = 0; i < rangeCount; i++) {
3346         range = selection.getRangeAt(i);
3347         if (!container) {
3348           container = range.startContainer;
3349         }
3350         else if (container != range.startContainer) {
3351           // all table cells don't belong to same row so let's
3352           // select the parent of all rows
3353           result.node = container.parentNode;
3354           break;
3355         }
3356         result.node = container;
3357       }
3358     }
3359   }
3360 
3361   // make sure we have an element here
3362   while (result.node.nodeType != Node.ELEMENT_NODE)
3363     result.node = result.node.parentNode;
3364 
3365   // and make sure the element is not a special editor node like
3366   // the <br> we insert in blank lines
3367   // and don't select anonymous content !!! (fix for bug 190279)
3368   while (result.node.hasAttribute("_moz_editor_bogus_node") ||
3369          editor.isAnonymousElement(result.node))
3370     result.node = result.node.parentNode;
3371 
3372   return result;
3373 }
3374 
3375 function FillInHTMLTooltipEditor(tooltip)
3376 {
3377   const XLinkNS = "http://www.w3.org/1999/xlink";
3378   var tooltipText = null;
3379   var node;
3380   if (gEditorDisplayMode == kDisplayModePreview) {
3381     for (node = document.tooltipNode; node; node = node.parentNode) {
3382       if (node.nodeType == Node.ELEMENT_NODE) {
3383         tooltipText = node.getAttributeNS(XLinkNS, "title");
3384         if (tooltipText && /\S/.test(tooltipText)) {
3385           tooltip.setAttribute("label", tooltipText);
3386           return true;
3387         }
3388         tooltipText = node.getAttribute("title");
3389         if (tooltipText && /\S/.test(tooltipText)) {
3390           tooltip.setAttribute("label", tooltipText);
3391           return true;
3392         }
3393       }
3394     }
3395   } else {
3396     for (node = document.tooltipNode; node; node = node.parentNode) {
3397       if (node instanceof Components.interfaces.nsIDOMHTMLImageElement ||
3398           node instanceof Components.interfaces.nsIDOMHTMLInputElement)
3399         tooltipText = node.getAttribute("src");
3400       else if (node instanceof Components.interfaces.nsIDOMHTMLAnchorElement)
3401         tooltipText = node.getAttribute("href") || node.name;
3402       if (tooltipText) {
3403         tooltip.setAttribute("label", tooltipText);
3404         return true;
3405       }
3406     }
3407   }
3408   return false;
3409 }
3410 
3411 function UpdateTOC()
3412 {
3413   window.openDialog("chrome://editor/content/EdInsertTOC.xul",
3414                     "_blank", "chrome,close,modal,titlebar");
3415   window.content.focus();
3416 }
3417 
3418 function InitTOCMenu()
3419 {
3420   var elt = GetCurrentEditor().document.getElementById("mozToc");
3421   var createMenuitem = document.getElementById("insertTOCMenuitem");
3422   var updateMenuitem = document.getElementById("updateTOCMenuitem");
3423   var removeMenuitem = document.getElementById("removeTOCMenuitem");
3424   if (removeMenuitem && createMenuitem && updateMenuitem) {
3425     if (elt) {
3426       createMenuitem.setAttribute("disabled", "true");
3427       updateMenuitem.removeAttribute("disabled");
3428       removeMenuitem.removeAttribute("disabled");
3429     }
3430     else {
3431       createMenuitem.removeAttribute("disabled");
3432       removeMenuitem.setAttribute("disabled", "true");
3433       updateMenuitem.setAttribute("disabled", "true");
3434     }
3435   }
3436 }
3437 
3438 function RemoveTOC()
3439 {
3440   var theDocument = GetCurrentEditor().document;
3441   var elt = theDocument.getElementById("mozToc");
3442   if (elt) {
3443     elt.parentNode.removeChild(elt);
3444   }
3445 
3446   function acceptNode(node)
3447   {
3448     if (node.nodeName.toLowerCase() == "a" &&
3449         node.hasAttribute("name") &&
3450         node.getAttribute("name").substr(0, 8) == "mozTocId") {
3451       return NodeFilter.FILTER_ACCEPT;
3452     }
3453     return NodeFilter.FILTER_SKIP;
3454   }
3455 
3456   var treeWalker = theDocument.createTreeWalker(theDocument.documentElement,
3457                                                 NodeFilter.SHOW_ELEMENT,
3458                                                 acceptNode,
3459                                                 true);
3460   if (treeWalker) {
3461     var anchorNode = treeWalker.nextNode();
3462     while (anchorNode) {
3463       var tmp = treeWalker.nextNode();
3464       anchorNode.parentNode.removeChild(anchorNode);
3465       anchorNode = tmp;
3466     }
3467   }
3468 }
3469