OSDN Git Service

import source-tree based svn r84.
[bluegriffon/BlueGriffon.git] / base / content / bluegriffon / js / activeSourceView.js
1 function ActiveSourceTree(aEditor)
2 {
3   this.mOldSourceElt = null;
4   this.mOneEltSelected = false;
5   this.mOldSelectedLi = null;
6
7   this.mEditing = false;
8   this.mEditedNode = null;
9
10   this.mMutationsEnabled = true;
11
12   this.mSourceTreeXULElement = null;
13   this.mEditor = null;
14
15   this.mRefuseNextMergeTransaction = false;
16
17   this.startup(aEditor);
18 }
19
20 ActiveSourceTree.prototype.startup =
21 function(aEditor)
22 {
23     function ActiveSourceTreeContentListener(aActiveSourceTree)
24     {
25       this.init(aActiveSourceTree);
26     }
27     
28     ActiveSourceTreeContentListener.prototype = {
29     
30       init : function(aActiveSourceTree)
31         {
32           this.mActiveSourceTree = aActiveSourceTree;
33         },
34     
35       QueryInterface : function(aIID)
36         {
37           if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
38               aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
39               aIID.equals(Components.interfaces.nsISupports))
40             return this;
41           throw Components.results.NS_NOINTERFACE;
42         },
43     
44       onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
45       {
46         const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
47     
48         if (aStateFlags & nsIWebProgressListener.STATE_IS_DOCUMENT)
49         {
50           if (aStateFlags & nsIWebProgressListener.STATE_STOP)
51           {
52             this.mActiveSourceTree.postStartup();
53           }
54         }
55       },
56     
57       onProgressChange : function(aWebProgress, aRequest,
58                                   aCurSelfProgress, aMaxSelfProgress,
59                                   aCurTotalProgress, aMaxTotalProgress)
60       {},
61     
62       onLocationChange : function(aWebProgress, aRequest, aLocation)
63       {},
64     
65       onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
66       {},
67     
68       onSecurityChange : function(aWebProgress, aRequest, aState)
69       {},
70     };
71
72   var sourceTreeDeck = gDialog.sourceTreeDeck;
73
74   var browser = document.createElement("browser");
75   browser.setAttribute("flex", "1");
76
77   sourceTreeDeck.appendChild(browser);
78   sourceTreeDeck.selectedPanel = browser;
79
80   this.mSourceTreeXULElement = browser;
81   this.mEditor = aEditor;
82   browser.setAttribute("src", "chrome://bluegriffon/content/sourceViewTemplate.html");
83   var docShell = browser.docShell;
84   var progress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);
85   var progressListener = new ActiveSourceTreeContentListener(this);
86   progress.addProgressListener(progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
87
88
89 };
90
91 ActiveSourceTree.prototype.postStartup =
92 function()
93 {
94   this.mSourceTreeXULElement.contentWindow.startup();
95   this.addEditorListeners();
96   this.addSourceTreeListeners();
97
98   this.serializeInSourceTree();
99 };
100
101 ActiveSourceTree.prototype.shutdown =
102 function()
103 {
104   this.removeEditorListeners();
105   this.removeSourceTreeListeners()
106
107 };
108
109 ActiveSourceTree.prototype.addEditorListeners =
110 function()
111 {
112   var doc = this.mEditor.contentDocument;
113   var _self = this;
114
115   doc.addEventListener("DOMAttrModified",          function(aEvent){_self.onBrowserMutateAttribute(aEvent)}, false);
116   doc.addEventListener("DOMNodeInserted",          function(aEvent){_self.onBrowserMutateNode(aEvent)}, false);
117   doc.addEventListener("DOMNodeRemoved",           function(aEvent){_self.onBrowserMutateNode(aEvent)}, false);
118   doc.addEventListener("DOMCharacterDataModified", function(aEvent){_self.onBrowserMutateText(aEvent)}, false);
119
120   NotifierUtils.addNotifierCallback("selection",   function(a,b,c){_self.onBrowserSelectionChanged(a,b,c)}, this);
121 };
122
123 ActiveSourceTree.prototype.removeEditorListeners =
124 function()
125 {
126   var doc = this.mEditor.contentDocument;
127   var _self = this;  
128
129   doc.removeEventListener("DOMAttrModified",          function(aEvent){_self.onBrowserMutateAttribute(aEvent)}, false);
130   doc.removeEventListener("DOMNodeInserted",          function(aEvent){_self.onBrowserMutateNode(aEvent)}, false);
131   doc.removeEventListener("DOMNodeRemoved",           function(aEvent){_self.onBrowserMutateNode(aEvent)}, false);
132   doc.removeEventListener("DOMCharacterDataModified", function(aEvent){_self.onBrowserMutateText(aEvent)}, false);
133
134   NotifierUtils.removeNotifierCallback("selection",   function(a,b,c){_self.onBrowserSelectionChanged(a,b,c)});
135 };
136
137 ActiveSourceTree.prototype.addSourceTreeListeners =
138 function()
139 {
140   var sourceDoc = this.mSourceTreeXULElement.contentDocument;
141   var _self = this;
142
143   sourceDoc.addEventListener("mousemove",                function(aEvent){_self.onSourceViewMouseMove(aEvent)}, true);
144   sourceDoc.addEventListener("click",                    function(aEvent){_self.onSourceViewClick(aEvent)}, true);
145   sourceDoc.addEventListener("keypress",                 function(aEvent){_self.onSourceViewKeyPress(aEvent)}, true);
146   sourceDoc.addEventListener("blur",                     function(aEvent){_self.onSourceViewBlur(aEvent)}, true);
147   sourceDoc.addEventListener("DOMCharacterDataModified", function(aEvent){_self.onSourceViewMutateText(aEvent)}, false);
148 };
149
150 ActiveSourceTree.prototype.removeSourceTreeListeners =
151 function()
152 {
153   var sourceDoc = this.mSourceTreeXULElement.contentDocument;
154   var _self = this;
155
156   sourceDoc.removeEventListener("mousemove",                function(aEvent){_self.onSourceViewMouseMove(aEvent)}, true);
157   sourceDoc.removeEventListener("click",                    function(aEvent){_self.onSourceViewClick(aEvent)}, true);
158   sourceDoc.removeEventListener("keypress",                 function(aEvent){_self.onSourceViewKeyPress(aEvent)}, true);
159   sourceDoc.removeEventListener("blur",                     function(aEvent){_self.onSourceViewBlur(aEvent)}, true);
160   sourceDoc.removeEventListener("DOMCharacterDataModified", function(aEvent){_self.onSourceViewMutateText(aEvent)}, false);
161 };
162
163 ActiveSourceTree.prototype.onSourceViewBlur =
164 function(aEvent)
165 {
166   var target = aEvent.target;
167   if (target &&
168       target.nodeType == Node.ELEMENT_NODE &&
169       target.getAttribute("contenteditable") == "true")
170     this.mRefuseNextMergeTransaction = true;
171
172   this.mOneEltSelected = false;
173   if (this.mOldSelectedLi)
174     this.mOldSelectedLi.removeAttribute("selected");
175   this.mOldSelectedLi = null;};
176
177 ActiveSourceTree.prototype.serializeInSourceTree =
178 function()
179 {
180   this.cleanupSourceTree();
181
182   this.mMutationsEnabled = false;
183
184   var sourceDoc = this.mSourceTreeXULElement.contentDocument;
185   var doc = this.mEditor.contentDocument;
186
187   // create the initial UL that will hold the root element
188   var currentUL = sourceDoc.createElement("ul");
189   sourceDoc.body.appendChild(currentUL);
190
191   this.addEltToSourceView(sourceDoc,
192                           doc.documentElement,
193                           currentUL);
194
195   this.mMutationsEnabled = true;
196 };
197
198 ActiveSourceTree.prototype.addEltToSourceView =
199 function(aDoc, aNode, aCurrentUL)
200 {
201     while (aNode)
202     {
203       var li; 
204       switch (aNode.nodeType)
205       {
206         case Node.TEXT_NODE:
207           li = this.createSourceViewForTextNode(aDoc, aNode, aCurrentUL);
208           aCurrentUL.appendChild(li);
209           if (!aNode.data.match( /\S/g ))
210             li.setAttribute("style", "display: none");
211           break;
212         case Node.ELEMENT_NODE:
213           if (aNode.nodeName.toLowerCase() != "br" ||
214               aNode.getAttribute("type") != "_moz")
215           {
216             li = this.createSourceViewForElementNode(aDoc, aNode, aCurrentUL, null);
217             aCurrentUL.appendChild(li);
218           }
219           break;
220         default:
221           break;
222       }
223       aNode = aNode.nextSibling;
224     }
225 };
226
227 ActiveSourceTree.prototype.createSourceViewForTextNode =
228 function(aDoc, aNode, aCurrentUL)
229 {
230   var span = aDoc.createElement("span");
231   span.setAttribute("class", "textNode");
232
233   var t = aDoc.createTextNode(aNode.data);
234   var li = aDoc.createElement("li");
235   var id = this.getRandomID();
236   li.setAttribute("id", id);
237   li.setAttribute("contenteditable", "true");
238   span.appendChild(t);
239   li.appendChild(span);
240   li.setUserData("originalNode", aNode, null);
241   aNode.setUserData("sourceViewID", id, null);
242   li.setAttribute("empty", "true");
243   return li
244 };
245
246 ActiveSourceTree.prototype.createSourceViewForElementNode =
247 function(aDoc, aNode, aCurrentUL, aCurrentLI)
248 {
249   var li;
250   if (aCurrentLI)
251     li = aCurrentLI;
252   else
253   {
254     var id = this.getRandomID();
255     li = aDoc.createElement("li");
256     li.setAttribute("id", id);
257     aNode.setUserData("sourceViewID", id, null);
258   }
259                     
260   var img = aDoc.createElement("img");
261   img.setAttribute("src", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAABGdBTUEAANbY1E9YMgAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADgSURBVHjalFFLDgFBFKw2RAwJiVjYOIA4gI0zuI6jOAFrC4mt2FhYij0SvxHjMxmjP1r3zCBMCJW87uq86pfqaiKlxK+I66UzGMvhZPZRVC2XUK9VCPTkRrMrvyHsB5M1hOAgJOCGkQDnTDEJRi+vNjQYoyEL3kCViNEznIP9EMfu5HrlSKXSqjL+2TQzyOYK6pIXnSyEwH6/VTtDPl/EZj3H5ezCc09R8dG24KkmD+1sV3OfC9/7m3hnLeAcbXBK0e+1YS2nflKGoSXpp1jn2BrpnJNhRXPWIP/84E2AAQCcuIfwPqAmjQAAAABJRU5ErkJggg==");
262   img.setAttribute("class", "minusTwisty");
263   li.appendChild(img);
264   
265   var img = aDoc.createElement("img");
266   img.setAttribute("src", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAABGdBTUEAANbY1E9YMgAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADHSURBVHjalJE9CsJAEIXfEsEihTfwAOIBbDyD1/EonsDewhPYWFiJ2FgIFjYRE2PIz/5k3DExQRZFH+wPzDePt7OCiPCrOrwtVjta708fodGgj8l4KMDO09mSvqmuV84sYzSEaN2qdAQli/cYLKXkC2tgJTMkt9CFy1LD93tO3uNh68LGGETRxZ4KVJJtNiiyFHl6d+E4DJDborZxiMrnw/lutHLha3BGEofQ0sJNboLnMeK3MM9xvuE5d+vlzpkl/vnBhwADALPWiKYmr0SBAAAAAElFTkSuQmCC");
267   img.setAttribute("class", "plusTwisty");
268   li.appendChild(img);
269   
270   var lt = aDoc.createTextNode("<");
271   li.appendChild(lt);
272
273   var span = aDoc.createElement("span");
274   span.setAttribute("class", "tagName");
275   var tagName = aNode.nodeName.toLowerCase();
276   var t = aDoc.createTextNode(tagName);
277   span.appendChild(t);
278   li.appendChild(span);
279
280   if (aNode.attributes.length)
281   {
282     var attributes = aNode.attributes;
283     for (var i = 0; i < attributes.length; i++)
284     {
285       var name = attributes[i].nodeName;
286       if (name.substr(0, 4) == "_moz")
287         continue;
288
289       // the @ in the line below is here because of bug 455992
290       span = aDoc.createElement("span");
291       span.setAttribute("class", "at-separator");
292       span.appendChild(aDoc.createTextNode("@"));
293       li.appendChild(span);
294
295       span = aDoc.createElement("span");
296       span.setAttribute("class", "attrName");
297       span.setAttribute("originalAttrName", name);
298       span.setAttribute("contenteditable", "true");
299       span.appendChild(aDoc.createTextNode(name));
300       li.appendChild(span);
301
302       li.appendChild(aDoc.createTextNode("=\""));
303
304       span = aDoc.createElement("span");
305       span.setAttribute("class", "attrValue");
306       span.setAttribute("originalAttrName", name);
307       span.setAttribute("contenteditable", "true");
308       span.appendChild(aDoc.createTextNode(attributes[i].nodeValue));
309       li.appendChild(span);
310
311       li.appendChild(aDoc.createTextNode("\""));
312     }
313   }
314
315   if (tagName == "style"  ||
316       tagName == "script" ||
317       tagName == "pre")
318     li.setAttribute("class", "pre-content");
319   li.appendChild(aDoc.createTextNode(">"));
320   li.setUserData("originalNode", aNode, null);
321
322   if (!aCurrentLI)
323   {
324     if (aNode.firstChild)
325     {
326       var newUL = aDoc.createElement("ul");
327       li.appendChild(newUL);
328   
329       this.addEltToSourceView(aDoc, aNode.firstChild, newUL);
330     }
331     else
332       li.setAttribute("empty", "true");
333   }
334
335   return li;
336 };
337
338 ActiveSourceTree.prototype.getRandomID =
339 function()
340 {
341   return "moz" + new Date().valueOf() +
342          "_" + Math.round(Math.random() * 100000);
343 };
344
345 ActiveSourceTree.prototype.onBrowserMutateNode =
346 function(aEvent)
347 {
348   if (!this.mMutationsEnabled)
349     return;
350
351   this.mMutationsEnabled = false;
352
353   var target = aEvent.target;
354   var parent = aEvent.relatedNode;
355   var removal = aEvent.type == "DOMNodeRemoved";
356
357   var sourceDoc = this.mSourceTreeXULElement.contentDocument;
358   if (removal)
359   {
360     var sourceViewID = target.getUserData("sourceViewID");
361     if (!sourceViewID)
362     {
363       this.mMutationsEnabled = true;
364       return;
365     }
366     var sourceTarget = sourceDoc.getElementById(sourceViewID);
367     if (!sourceTarget)
368     {
369       this.mMutationsEnabled = true;
370       return;
371     }
372     var sourceParent = sourceTarget.parentNode;
373     sourceParent.removeChild(sourceTarget);
374     if (!sourceParent.childNodes.length)
375       sourceParent.parentNode.removeChild(sourceParent);
376   }
377   else
378   {
379     var sourceViewID = parent.getUserData("sourceViewID");
380     var sourceParent = sourceDoc.getElementById(sourceViewID);
381     var currentUL = null;
382     if (sourceParent.lastChild.nodeName.toLowerCase() == "ul")
383       currentUL = sourceParent.lastChild;
384     else
385     {
386       currentUL = sourceDoc.createElement("ul");
387       sourceParent.appendChild(currentUL);
388     }
389
390     var nextSibling = target.nextSibling;
391     var sourceNextSibling = null;
392     if (nextSibling)
393     {
394       var sourceViewNextSiblingID =  nextSibling.getUserData("sourceViewID");
395       sourceNextSibling = sourceDoc.getElementById(sourceViewNextSiblingID);
396     }
397
398     var li = null;
399     switch (target.nodeType)
400     {
401       case Node.TEXT_NODE:
402         if (target.data.match( /\S/g ))
403         {
404           li = this.createSourceViewForTextNode(sourceDoc, target, currentUL);
405           currentUL.insertBefore(li, sourceNextSibling);
406         }
407           
408         break;
409       case Node.ELEMENT_NODE:
410         if (target.nodeName.toLowerCase() == "br" &&
411             target.getAttribute("type") == "_moz")
412           break;
413         li = this.createSourceViewForElementNode(sourceDoc, target, currentUL, null);
414         li.setAttribute("open", "true");
415         currentUL.insertBefore(li, sourceNextSibling);
416
417         li.setAttribute("selected", "true");
418         if (this.mOldSelectedLi && this.mOneEltSelected)
419         {
420           this.mOldSelectedLi.removeAttribute("selected");
421         }
422         this.mOneEltSelected = true;
423         this.mOldSelectedLi = li;
424         break;
425       default:
426         break;
427     }
428     if (li)
429       this.ensureElementIsVisible(this.mSourceTreeXULElement, li);
430   }
431
432   this.mMutationsEnabled = true;
433
434 };
435
436 ActiveSourceTree.prototype.cleanupSourceTree =
437 function()
438 {
439   this.mMutationsEnabled = false;
440
441   var sourceDoc = this.mSourceTreeXULElement.contentDocument;
442   sourceDoc.body.innerHTML = "";
443
444   this.mMutationsEnabled = true;
445 };
446
447 ActiveSourceTree.prototype.onBrowserMutateText =
448 function(aEvent)
449 {
450   if (!this.mMutationsEnabled)
451     return;
452
453   this.mMutationsEnabled = false;
454
455   var target = aEvent.target;
456   var sourceViewID = target.getUserData("sourceViewID");
457   var sourceDoc = this.mSourceTreeXULElement.contentDocument;
458   var sourceTarget = sourceDoc.getElementById(sourceViewID);
459   sourceTarget.firstChild.firstChild.data = target.data;
460
461   this.mMutationsEnabled = true;
462 };
463
464 ActiveSourceTree.prototype.onBrowserSelectionChanged =
465 function(aArgs, aElt, aOneElementSelected)
466 {
467   if (!this.mMutationsEnabled)
468     return;
469
470   var elt = EditorUtils.getSelectionContainer().node;
471   var sourceViewID = elt.getUserData("sourceViewID");
472   var sourceDoc = this.mSourceTreeXULElement.contentDocument;
473   var sourceElt = sourceDoc.getElementById(sourceViewID);
474   var e = sourceElt;
475   if (!e)
476     return;
477   while (e)
478   {
479     if (e.nodeType == Node.ELEMENT_NODE &&
480         e.nodeName.toLowerCase() == "li")
481       e.setAttribute("open", "true");
482     e = e.parentNode;
483   }
484   this.ensureElementIsVisible(this.mSourceTreeXULElement, sourceElt);
485
486   sourceElt.setAttribute("selected", "true");
487   if (this.mOldSelectedLi && this.mOneEltSelected)
488   {
489     this.mOldSelectedLi.removeAttribute("selected");
490   }
491   this.mOneEltSelected = true;
492   this.mOldSelectedLi = sourceElt;
493 };
494
495 ActiveSourceTree.prototype.onSourceViewMouseMove =
496 function(aEvent)
497 {
498   if (!this.mMutationsEnabled)
499     return;
500
501   if (this.mOneEltSelected)
502     return;
503   this._onSourceViewMouseMove(aEvent);
504 };
505
506
507 ActiveSourceTree.prototype._onSourceViewMouseMove =
508 function(aEvent)
509 {
510   var e = aEvent.originalTarget;
511   if (!e)
512     return;
513   if (e != this.mOldSourceElt &&
514       this.mOldSourceElt &&
515       this.mOldSourceElt.style)
516   {
517     //this.mOldSourceElt.style.outline = "";
518     if (this.mOldSelectedLi)
519       this.mOldSelectedLi.removeAttribute("selected");
520   }
521   this.mOldSourceElt = null;
522
523   var tagName = e.nodeName.toLowerCase();
524   if (tagName != "li" &&
525       tagName != "span" &&
526       tagName != "#text")
527     return;
528
529   if (tagName != "li")
530     e = e.parentNode;
531   if (this.mOneEltSelected)
532     this.mOldSelectedLi = e;
533   e = e.getUserData("originalNode");
534   if (e && e.style)
535   {
536     this.flash(EditorUtils.getCurrentEditorElement(), e);
537   }
538 };
539
540 ActiveSourceTree.prototype.ensureElementIsVisible =
541 function(aXULContainer, aHTMLElement)
542 {
543   var y = 0;
544   while (aHTMLElement)
545   {
546     y += aHTMLElement.offsetTop;
547     aHTMLElement = aHTMLElement.offsetParent;
548   }
549   aXULContainer.contentWindow.scroll(0, Math.max(0, y - 10));
550 };
551
552 ActiveSourceTree.prototype.flash =
553 function(aXULContainer, aHTMLElement)
554 {
555   this.mOldSourceElt = aHTMLElement;
556   //aHTMLElement.style.outline = "blue solid 2px";
557
558   this.ensureElementIsVisible(aXULContainer, aHTMLElement);
559 };
560
561 ActiveSourceTree.prototype.onSourceViewClick =
562 function(aEvent)
563 {
564   if (!this.mMutationsEnabled)
565     return;
566
567   var e = aEvent.explicitOriginalTarget;
568   var p = e.parentNode;
569   var currentTag = e.nodeName.toLowerCase();
570   if (e.nodeType == Node.TEXT_NODE &&
571       p.nodeType == Node.ELEMENT_NODE)
572   {
573     if (p.className == "tagName")
574     {
575       var li = p.parentNode;
576       li.setAttribute("selected", "true");
577       this.mOneEltSelected = true;
578       this._onSourceViewMouseMove(
579           { originalTarget: li }
580         );
581     }
582     else if (p.className == "textNode" ||
583              p.className == "attrName" ||
584              p.className == "attrValue")
585     {
586       this.mEditing = true;
587       this.mEditedNode = p;
588       this.mOneEltSelected = true;
589       switch (p.className)
590       {
591         case "textNode":
592           this._onSourceViewMouseMove(
593             { originalTarget: p.parentNode.parentNode.firstChild }
594           );
595           break;
596         case "attrName":
597         case "attrValue":
598           this._onSourceViewMouseMove(
599             { originalTarget: p.parentNode }
600           );
601           break;
602         default: break;
603       }
604     }
605   }
606   else if (this.mOneEltSelected &&
607            (currentTag == "li" ||
608             currentTag == "ul" ||
609             currentTag == "body"))
610   {
611     this.mOneEltSelected = false;
612     if (this.mOldSelectedLi)
613       this.mOldSelectedLi.removeAttribute("selected");
614     this.mOldSelectedLi = null;
615   }
616 };
617
618 ActiveSourceTree.prototype.getIndexOfNode =
619 function getIndexOfNode(aNode)
620 {
621   if (aNode)
622   {
623     // the following 3 lines are an excellent suggestion from Neil Rashbrook
624     var range = aNode.ownerDocument.createRange();
625     range.selectNode(aNode);
626     return range.startOffset;
627   }
628   return null;
629 };
630
631 ActiveSourceTree.prototype.onSourceViewKeyPress =
632 function(aEvent)
633 {
634   if (!this.mMutationsEnabled)
635     return;
636
637   if (aEvent.keyCode == 27 && // ESCAPE KEY
638       this.mEditing &&
639       this.mEditedNode)
640   {
641     this.mEditing = false;
642     this.mEditedNode.blur();
643     this.mEditedNode = null;
644   }
645 };
646
647 ActiveSourceTree.prototype.onSourceViewMutateText = function(aEvent)
648 {
649   if (!this.mMutationsEnabled)
650     return;
651
652   this.mMutationsEnabled = false;
653
654   var e = aEvent.originalTarget; 
655   if (e.parentNode.nodeName.toLowerCase() == "span")
656   {
657     
658     var type = e.parentNode.className;
659     if (type != "textNode" &&
660         type != "attrName" &&
661         type != "attrValue")
662       return;
663
664     
665     switch (e.parentNode.className)
666     {
667       case "textNode":
668         {
669           var textNode = e.parentNode.parentNode.getUserData("originalNode");
670           var txn = new diTextNodeChangedTxn(this, textNode, e.data);
671           EditorUtils.getCurrentEditor().transactionManager.doTransaction(txn);          
672         }
673         break;
674       case "attrValue":
675         {
676           var targetElement = e.parentNode.parentNode.getUserData("originalNode");
677           var attrName = e.parentNode.getAttribute("originalattrname");
678           var txn = new diAttrChangedTxn(this, targetElement, attrName, e.data);
679           EditorUtils.getCurrentEditor().transactionManager.doTransaction(txn);  
680         }
681         break;
682         case "attrName":
683         {
684           var targetElement = e.parentNode.parentNode.getUserData("originalNode");
685           var attrName = e.parentNode.getAttribute("originalattrname");
686           var newName = e.data;
687           var txn = new diAttrNameChangedTxn(this, targetElement, attrName, newName);
688           EditorUtils.getCurrentEditor().transactionManager.doTransaction(txn);
689           e.parentNode.setAttribute("originalattrname", newName);
690           e.parentNode.nextSibling.nextSibling.setAttribute("originalattrname", newName);
691         }
692         break;
693       default:
694         break;
695     }
696   }
697
698   this.mMutationsEnabled = true;
699
700 };
701
702 ActiveSourceTree.prototype.onBrowserMutateAttribute =
703 function(aEvent)
704 {
705   if (!this.mMutationsEnabled)
706     return;
707
708   var target = aEvent.target;
709
710   var attrChange = aEvent.attrChange;
711   var attrName = aEvent.attrName;
712   var newValue = aEvent.newValue;
713
714   // early way out in case of a scrollbar change
715   if (attrName == "curpos" ||
716       attrName.substr(0, 4) == "_moz")
717     return;
718
719   this.mMutationsEnabled = false;
720
721   var sourceViewID = target.getUserData("sourceViewID");
722   var sourceDoc = this.mSourceTreeXULElement.contentDocument;
723   var sourceElt = sourceDoc.getElementById(sourceViewID);
724
725   switch (attrChange)
726   {
727     case MutationEvent.ADDITION:
728     case MutationEvent.MODIFICATION: break;
729     case MutationEvent.REMOVAL: break;
730     default: break;
731   }
732
733   var child = sourceElt.firstChild;
734   while (child && child.nodeName.toLowerCase() != "ul")
735   {
736     var tmp = child.nextSibling;
737     sourceElt.removeChild(child);
738     child = tmp;
739   }
740
741   this.createSourceViewForElementNode(sourceDoc, target, sourceElt.parentNode, sourceElt);
742   if (sourceElt.firstChild.nodeName.toLowerCase() == "ul")
743     sourceElt.appendChild(sourceElt.firstChild);
744
745   this.mMutationsEnabled = true;
746 };
747
748
749 /********************** diTextNodeChangedTxn **********************/
750
751 function diTextNodeChangedTxn(aContext, aNode, aData)
752 {
753   this.mContext = aContext;
754   this.mNode = aNode;
755   this.mOldData = aNode.data;
756   this.mNewData = aData;
757 }
758
759 diTextNodeChangedTxn.prototype = {
760
761   getNode:    function() { return this.mNode; },
762   getOldData: function() { return this.mOldData; },
763   getNewData: function() { return this.mNewData; },
764
765   QueryInterface : function(aIID)
766   {
767     if (aIID.equals(Components.interfaces.nsITransaction) ||
768         aIID.equals(Components.interfaces.diITextNodeChangedTxn) ||
769         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
770         aIID.equals(Components.interfaces.nsISupports))
771       return this;
772     throw Components.results.NS_NOINTERFACE;
773   },
774
775   doTransaction: function()
776   {
777     this.mNode.data = this.mNewData;
778   },
779
780   undoTransaction: function()
781   {
782     this.mNode.data = this.mOldData;
783   },
784
785   redoTransaction: function()
786   {
787     this.doTransaction();
788   },
789
790   isTransient: false,
791
792   merge: function(aTransaction)
793   {
794     var txn = aTransaction.QueryInterface(Components.interfaces.diITextNodeChangedTxn);
795     if (txn)
796     {
797       if (this.getNode() == txn.getNode())
798       {
799         if (this.mContext.mRefuseNextMergeTransaction)
800           this.mContext.mRefuseNextMergeTransaction = false;
801         else
802         {
803           this.mNewData = txn.getNewData();
804           return true;
805         }
806       }
807     }
808     return false;
809   }
810 };
811
812 /********************** diAttrChangedTxn **********************/
813
814 function diAttrChangedTxn(aContext, aNode, aAttrName, aData)
815 {
816   this.mContext = aContext;
817   this.mNode = aNode;
818   this.mAttrName = aAttrName;
819   this.mAttrWasSet = this.mNode.hasAttribute(this.mAttrName);
820   this.mOldData = this.mNode.getAttribute(this.mAttrName);
821   this.mNewData = aData;
822 }
823
824 diAttrChangedTxn.prototype = {
825
826   getNode:          function() { return this.mNode; },
827   getOldData:       function() { return this.mOldData; },
828   getNewData:       function() { return this.mNewData; },
829   getAttributeName: function() { return this.mAttrName; },
830
831   QueryInterface : function(aIID)
832   {
833     if (aIID.equals(Components.interfaces.nsITransaction) ||
834         aIID.equals(Components.interfaces.diIAttrChangedTxn) ||
835         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
836         aIID.equals(Components.interfaces.nsISupports))
837       return this;
838     throw Components.results.NS_NOINTERFACE;
839   },
840
841   doTransaction: function()
842   {
843     this.mNode.setAttribute(this.mAttrName, this.mNewData);
844   },
845
846   undoTransaction: function()
847   {
848     if (this.mAttrWasSet)
849       this.mNode.setAttribute(this.mAttrName, this.mOldData);
850     else
851       this.mNode.removeAttribute(this.mAttrName);
852   },
853
854   redoTransaction: function()
855   {
856     this.doTransaction();
857   },
858
859   isTransient: false,
860
861   merge: function(aTransaction)
862   {
863     var txn = aTransaction.QueryInterface(Components.interfaces.diIAttrChangedTxn);
864     if (txn)
865     {
866       if (this.getNode() == txn.getNode() &&
867           this.getAttributeName() == txn.getAttributeName())
868       {
869         if (this.mContext.mRefuseNextMergeTransaction)
870           this.mContext.mRefuseNextMergeTransaction = false;
871         else
872         {
873           this.mNewData = txn.getNewData();
874           return true;
875         }
876       }
877     }
878     return false;
879   }
880 };
881
882 /********************** diAttrNameChangedTxn **********************/
883
884 function diAttrNameChangedTxn(aContext, aNode, aOldAttrName, aNewAttrName)
885 {
886   this.mContext = aContext;
887   this.mNode = aNode;
888   this.mOldAttrName = aOldAttrName;
889   this.mNewAttrName = aNewAttrName;
890
891   this.mValue = this.mNode.getAttribute(this.mOldAttrName);
892 }
893
894 diAttrNameChangedTxn.prototype = {
895
896   getNode:             function() { return this.mNode; },
897   getNewAttributeName: function() { return this.mNewAttrName; },
898
899
900   QueryInterface : function(aIID)
901   {
902     if (aIID.equals(Components.interfaces.nsITransaction) ||
903         aIID.equals(Components.interfaces.diIAttrNameChangedTxn) ||
904         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
905         aIID.equals(Components.interfaces.nsISupports))
906       return this;
907     throw Components.results.NS_NOINTERFACE;
908   },
909
910   doTransaction: function()
911   {
912     this.mNode.removeAttribute(this.mOldAttrName);
913     this.mNode.setAttribute(this.mNewAttrName, this.mValue);
914   },
915
916   undoTransaction: function()
917   {
918     this.mNode.removeAttribute(this.mNewAttrName);
919     this.mNode.setAttribute(this.mOldAttrName, this.mValue);
920   },
921
922   redoTransaction: function()
923   {
924     this.doTransaction();
925   },
926
927   isTransient: false,
928
929   merge: function(aTransaction)
930   {
931     var txn = aTransaction.QueryInterface(Components.interfaces.diIAttrChangedTxn);
932     if (txn)
933     {
934       if (this.getNode() == txn.getNode())
935       {
936         if (this.mContext.mRefuseNextMergeTransaction)
937           this.mContext.mRefuseNextMergeTransaction = false;
938         else
939         {
940           this.mNewAttrName = txn.getNewAttributeName();
941           return true;
942         }
943       }
944     }
945     return false;
946   }
947 };