Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / NavigatorView.js
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
20  * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 /**
30  * @constructor
31  * @extends {WebInspector.VBox}
32  */
33 WebInspector.NavigatorView = function()
34 {
35     WebInspector.VBox.call(this);
36     this.registerRequiredCSS("navigatorView.css");
37
38     var scriptsTreeElement = document.createElement("ol");
39     this._scriptsTree = new WebInspector.NavigatorTreeOutline(scriptsTreeElement);
40
41     var scriptsOutlineElement = document.createElement("div");
42     scriptsOutlineElement.classList.add("outline-disclosure");
43     scriptsOutlineElement.classList.add("navigator");
44     scriptsOutlineElement.appendChild(scriptsTreeElement);
45
46     this.element.classList.add("navigator-container");
47     this.element.appendChild(scriptsOutlineElement);
48     this.setDefaultFocusedElement(this._scriptsTree.element);
49
50     /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.NavigatorUISourceCodeTreeNode>} */
51     this._uiSourceCodeNodes = new Map();
52     /** @type {!Map.<!WebInspector.NavigatorTreeNode, !StringMap.<!WebInspector.NavigatorFolderTreeNode>>} */
53     this._subfolderNodes = new Map();
54
55     this._rootNode = new WebInspector.NavigatorRootTreeNode(this);
56     this._rootNode.populate();
57
58     this.element.addEventListener("contextmenu", this.handleContextMenu.bind(this), false);
59 }
60
61 WebInspector.NavigatorView.Events = {
62     ItemSelected: "ItemSelected",
63     ItemRenamed: "ItemRenamed",
64 }
65
66 WebInspector.NavigatorView.iconClassForType = function(type)
67 {
68     if (type === WebInspector.NavigatorTreeOutline.Types.Domain)
69         return "navigator-domain-tree-item";
70     if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem)
71         return "navigator-folder-tree-item";
72     return "navigator-folder-tree-item";
73 }
74
75 WebInspector.NavigatorView.prototype = {
76     setWorkspace: function(workspace)
77     {
78         this._workspace = workspace;
79         this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
80         this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
81         this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectWillReset, this._projectWillReset.bind(this), this);
82     },
83
84     wasShown: function()
85     {
86         if (this._loaded)
87             return;
88         this._loaded = true;
89         this._workspace.uiSourceCodes().forEach(this._addUISourceCode.bind(this));
90     },
91
92     /**
93      * @param {!WebInspector.UISourceCode} uiSourceCode
94      * @return {boolean}
95      */
96     accept: function(uiSourceCode)
97     {
98         return !uiSourceCode.project().isServiceProject();
99     },
100
101     /**
102      * @param {!WebInspector.UISourceCode} uiSourceCode
103      */
104     _addUISourceCode: function(uiSourceCode)
105     {
106         if (!this.accept(uiSourceCode))
107             return;
108         var projectNode = this._projectNode(uiSourceCode.project());
109         var folderNode = this._folderNode(projectNode, uiSourceCode.parentPath());
110         var uiSourceCodeNode = new WebInspector.NavigatorUISourceCodeTreeNode(this, uiSourceCode);
111         this._uiSourceCodeNodes.put(uiSourceCode, uiSourceCodeNode);
112         folderNode.appendChild(uiSourceCodeNode);
113     },
114
115     /**
116      * @param {!WebInspector.Event} event
117      */
118     _uiSourceCodeAdded: function(event)
119     {
120         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
121         this._addUISourceCode(uiSourceCode);
122     },
123
124     /**
125      * @param {!WebInspector.Event} event
126      */
127     _uiSourceCodeRemoved: function(event)
128     {
129         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
130         this._removeUISourceCode(uiSourceCode);
131     },
132
133     /**
134      * @param {!WebInspector.Event} event
135      */
136     _projectWillReset: function(event)
137     {
138         var project = /** @type {!WebInspector.Project} */ (event.data);
139         var uiSourceCodes = project.uiSourceCodes();
140         for (var i = 0; i < uiSourceCodes.length; ++i)
141             this._removeUISourceCode(uiSourceCodes[i]);
142     },
143
144     /**
145      * @param {!WebInspector.Project} project
146      * @return {!WebInspector.NavigatorTreeNode}
147      */
148     _projectNode: function(project)
149     {
150         if (!project.displayName())
151             return this._rootNode;
152
153         var projectNode = this._rootNode.child(project.id());
154         if (!projectNode) {
155             var type = project.type() === WebInspector.projectTypes.FileSystem ? WebInspector.NavigatorTreeOutline.Types.FileSystem : WebInspector.NavigatorTreeOutline.Types.Domain;
156             projectNode = new WebInspector.NavigatorFolderTreeNode(this, project, project.id(), type, "", project.displayName());
157             this._rootNode.appendChild(projectNode);
158         }
159         return projectNode;
160     },
161
162     /**
163      * @param {!WebInspector.NavigatorTreeNode} projectNode
164      * @param {string} folderPath
165      * @return {!WebInspector.NavigatorTreeNode}
166      */
167     _folderNode: function(projectNode, folderPath)
168     {
169         if (!folderPath)
170             return projectNode;
171
172         var subfolderNodes = this._subfolderNodes.get(projectNode);
173         if (!subfolderNodes) {
174             subfolderNodes = /** @type {!StringMap.<!WebInspector.NavigatorFolderTreeNode>} */ (new StringMap());
175             this._subfolderNodes.put(projectNode, subfolderNodes);
176         }
177
178         var folderNode = subfolderNodes.get(folderPath);
179         if (folderNode)
180             return folderNode;
181
182         var parentNode = projectNode;
183         var index = folderPath.lastIndexOf("/");
184         if (index !== -1)
185             parentNode = this._folderNode(projectNode, folderPath.substring(0, index));
186
187         var name = folderPath.substring(index + 1);
188         folderNode = new WebInspector.NavigatorFolderTreeNode(this, null, name, WebInspector.NavigatorTreeOutline.Types.Folder, folderPath, name);
189         subfolderNodes.put(folderPath, folderNode);
190         parentNode.appendChild(folderNode);
191         return folderNode;
192     },
193
194     /**
195      * @param {!WebInspector.UISourceCode} uiSourceCode
196      * @param {boolean=} select
197      */
198     revealUISourceCode: function(uiSourceCode, select)
199     {
200         var node = this._uiSourceCodeNodes.get(uiSourceCode);
201         if (!node)
202             return;
203         if (this._scriptsTree.selectedTreeElement)
204             this._scriptsTree.selectedTreeElement.deselect();
205         this._lastSelectedUISourceCode = uiSourceCode;
206         node.reveal(select);
207     },
208
209     /**
210      * @param {!WebInspector.UISourceCode} uiSourceCode
211      * @param {boolean} focusSource
212      */
213     _sourceSelected: function(uiSourceCode, focusSource)
214     {
215         this._lastSelectedUISourceCode = uiSourceCode;
216         var data = { uiSourceCode: uiSourceCode, focusSource: focusSource};
217         this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemSelected, data);
218     },
219
220     /**
221      * @param {!WebInspector.UISourceCode} uiSourceCode
222      */
223     sourceDeleted: function(uiSourceCode)
224     {
225     },
226
227     /**
228      * @param {!WebInspector.UISourceCode} uiSourceCode
229      */
230     _removeUISourceCode: function(uiSourceCode)
231     {
232         var node = this._uiSourceCodeNodes.get(uiSourceCode);
233         if (!node)
234             return;
235
236         var projectNode = this._projectNode(uiSourceCode.project());
237         var subfolderNodes = this._subfolderNodes.get(projectNode);
238         var parentNode = node.parent;
239         this._uiSourceCodeNodes.remove(uiSourceCode);
240         parentNode.removeChild(node);
241         node = parentNode;
242
243         while (node) {
244             parentNode = node.parent;
245             if (!parentNode || !node.isEmpty())
246                 break;
247             if (subfolderNodes)
248                 subfolderNodes.remove(node._folderPath);
249             parentNode.removeChild(node);
250             node = parentNode;
251         }
252     },
253
254     /**
255      * @param {!WebInspector.UISourceCode} uiSourceCode
256      */
257     _updateIcon: function(uiSourceCode)
258     {
259         var node = this._uiSourceCodeNodes.get(uiSourceCode);
260         node.updateIcon();
261     },
262
263     reset: function()
264     {
265         var nodes = this._uiSourceCodeNodes.values();
266         for (var i = 0; i < nodes.length; ++i)
267             nodes[i].dispose();
268
269         this._scriptsTree.removeChildren();
270         this._uiSourceCodeNodes.clear();
271         this._subfolderNodes.clear();
272         this._rootNode.reset();
273     },
274
275     /**
276      * @param {?Event} event
277      */
278     handleContextMenu: function(event)
279     {
280         var contextMenu = new WebInspector.ContextMenu(event);
281         this._appendAddFolderItem(contextMenu);
282         contextMenu.show();
283     },
284
285     /**
286      * @param {!WebInspector.ContextMenu} contextMenu
287      */
288     _appendAddFolderItem: function(contextMenu)
289     {
290         function addFolder()
291         {
292             WebInspector.isolatedFileSystemManager.addFileSystem();
293         }
294
295         var addFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add folder to workspace" : "Add Folder to Workspace");
296         contextMenu.appendItem(addFolderLabel, addFolder);
297     },
298
299     /**
300      * @param {!WebInspector.Project} project
301      * @param {string} path
302      */
303     _handleContextMenuRefresh: function(project, path)
304     {
305         project.refresh(path);
306     },
307
308     /**
309      * @param {!WebInspector.Project} project
310      * @param {string} path
311      * @param {!WebInspector.UISourceCode=} uiSourceCode
312      */
313     _handleContextMenuCreate: function(project, path, uiSourceCode)
314     {
315         this.create(project, path, uiSourceCode);
316     },
317
318     /**
319      * @param {!WebInspector.Project} project
320      * @param {string} path
321      */
322     _handleContextMenuExclude: function(project, path)
323     {
324         var shouldExclude = window.confirm(WebInspector.UIString("Are you sure you want to exclude this folder?"));
325         if (shouldExclude) {
326             WebInspector.startBatchUpdate();
327             project.excludeFolder(path);
328             WebInspector.endBatchUpdate();
329         }
330     },
331
332     /**
333      * @param {!WebInspector.UISourceCode} uiSourceCode
334      */
335     _handleContextMenuDelete: function(uiSourceCode)
336     {
337         var shouldDelete = window.confirm(WebInspector.UIString("Are you sure you want to delete this file?"));
338         if (shouldDelete)
339             uiSourceCode.project().deleteFile(uiSourceCode.path());
340     },
341
342     /**
343      * @param {!Event} event
344      * @param {!WebInspector.UISourceCode} uiSourceCode
345      */
346     handleFileContextMenu: function(event, uiSourceCode)
347     {
348         var contextMenu = new WebInspector.ContextMenu(event);
349         contextMenu.appendApplicableItems(uiSourceCode);
350         contextMenu.appendSeparator();
351
352         var project = uiSourceCode.project();
353         var path = uiSourceCode.parentPath();
354         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Refresh parent" : "Refresh Parent"), this._handleContextMenuRefresh.bind(this, project, path));
355         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Duplicate file" : "Duplicate File"), this._handleContextMenuCreate.bind(this, project, path, uiSourceCode));
356         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Exclude parent folder" : "Exclude Parent Folder"), this._handleContextMenuExclude.bind(this, project, path));
357         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete file" : "Delete File"), this._handleContextMenuDelete.bind(this, uiSourceCode));
358         contextMenu.appendSeparator();
359         this._appendAddFolderItem(contextMenu);
360         contextMenu.show();
361     },
362
363     /**
364      * @param {!Event} event
365      * @param {!WebInspector.NavigatorFolderTreeNode} node
366      */
367     handleFolderContextMenu: function(event, node)
368     {
369         var contextMenu = new WebInspector.ContextMenu(event);
370         var path = "/";
371         var projectNode = node;
372         while (projectNode.parent !== this._rootNode) {
373             path = "/" + projectNode.id + path;
374             projectNode = projectNode.parent;
375         }
376
377         var project = projectNode._project;
378
379         if (project.type() === WebInspector.projectTypes.FileSystem) {
380             contextMenu.appendItem(WebInspector.UIString("Refresh"), this._handleContextMenuRefresh.bind(this, project, path));
381             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "New file" : "New File"), this._handleContextMenuCreate.bind(this, project, path));
382             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Exclude folder" : "Exclude Folder"), this._handleContextMenuExclude.bind(this, project, path));
383         }
384         contextMenu.appendSeparator();
385         this._appendAddFolderItem(contextMenu);
386
387         function removeFolder()
388         {
389             var shouldRemove = window.confirm(WebInspector.UIString("Are you sure you want to remove this folder?"));
390             if (shouldRemove)
391                 project.remove();
392         }
393
394         if (project.type() === WebInspector.projectTypes.FileSystem && node === projectNode) {
395             var removeFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove folder from workspace" : "Remove Folder from Workspace");
396             contextMenu.appendItem(removeFolderLabel, removeFolder);
397         }
398
399         contextMenu.show();
400     },
401
402     /**
403      * @param {!WebInspector.UISourceCode} uiSourceCode
404      * @param {boolean} deleteIfCanceled
405      */
406     rename: function(uiSourceCode, deleteIfCanceled)
407     {
408         var node = this._uiSourceCodeNodes.get(uiSourceCode);
409         console.assert(node);
410         node.rename(callback.bind(this));
411
412         /**
413          * @this {WebInspector.NavigatorView}
414          * @param {boolean} committed
415          */
416         function callback(committed)
417         {
418             if (!committed) {
419                 if (deleteIfCanceled)
420                     uiSourceCode.remove();
421                 return;
422             }
423
424             var data = { uiSourceCode: uiSourceCode };
425             this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemRenamed, data);
426             this._updateIcon(uiSourceCode);
427             this._sourceSelected(uiSourceCode, true)
428         }
429     },
430
431     /**
432      * @param {!WebInspector.Project} project
433      * @param {string} path
434      * @param {!WebInspector.UISourceCode=} uiSourceCodeToCopy
435      */
436     create: function(project, path, uiSourceCodeToCopy)
437     {
438         var filePath;
439         var uiSourceCode;
440
441         /**
442          * @this {WebInspector.NavigatorView}
443          * @param {?string} content
444          */
445         function contentLoaded(content)
446         {
447             createFile.call(this, content || "");
448         }
449
450         if (uiSourceCodeToCopy)
451             uiSourceCodeToCopy.requestContent(contentLoaded.bind(this));
452         else
453             createFile.call(this);
454
455         /**
456          * @this {WebInspector.NavigatorView}
457          * @param {string=} content
458          */
459         function createFile(content)
460         {
461             project.createFile(path, null, content || "", fileCreated.bind(this));
462         }
463
464         /**
465          * @this {WebInspector.NavigatorView}
466          * @param {?string} path
467          */
468         function fileCreated(path)
469         {
470             if (!path)
471                 return;
472             filePath = path;
473             uiSourceCode = project.uiSourceCode(filePath);
474             if (!uiSourceCode) {
475                 console.assert(uiSourceCode)
476                 return;
477             }
478             this._sourceSelected(uiSourceCode, false);
479             this.rename(uiSourceCode, true);
480         }
481     },
482
483     __proto__: WebInspector.VBox.prototype
484 }
485
486 /**
487  * @constructor
488  * @extends {WebInspector.NavigatorView}
489  */
490 WebInspector.SourcesNavigatorView = function()
491 {
492     WebInspector.NavigatorView.call(this);
493     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this);
494 }
495
496 WebInspector.SourcesNavigatorView.prototype = {
497     /**
498      * @override
499      * @param {!WebInspector.UISourceCode} uiSourceCode
500      * @return {boolean}
501      */
502     accept: function(uiSourceCode)
503     {
504         if (!WebInspector.NavigatorView.prototype.accept(uiSourceCode))
505             return false;
506         return !uiSourceCode.isContentScript && uiSourceCode.project().type() !== WebInspector.projectTypes.Snippets;
507     },
508
509     /**
510      * @param {!WebInspector.Event} event
511      */
512     _inspectedURLChanged: function(event)
513     {
514        var nodes = this._uiSourceCodeNodes.values();
515        for (var i = 0; i < nodes.length; ++i) {
516            var uiSourceCode = nodes[i].uiSourceCode();
517            if (uiSourceCode.url === WebInspector.resourceTreeModel.inspectedPageURL())
518               this.revealUISourceCode(uiSourceCode, true);
519        }
520     },
521
522     /**
523      * @param {!WebInspector.UISourceCode} uiSourceCode
524      */
525     _addUISourceCode: function(uiSourceCode)
526     {
527         WebInspector.NavigatorView.prototype._addUISourceCode.call(this, uiSourceCode);
528         if (uiSourceCode.url === WebInspector.resourceTreeModel.inspectedPageURL())
529             this.revealUISourceCode(uiSourceCode, true);
530      },
531
532     __proto__: WebInspector.NavigatorView.prototype
533 }
534
535 /**
536  * @constructor
537  * @extends {WebInspector.NavigatorView}
538  */
539 WebInspector.ContentScriptsNavigatorView = function()
540 {
541     WebInspector.NavigatorView.call(this);
542 }
543
544 WebInspector.ContentScriptsNavigatorView.prototype = {
545     /**
546      * @override
547      * @param {!WebInspector.UISourceCode} uiSourceCode
548      * @return {boolean}
549      */
550     accept: function(uiSourceCode)
551     {
552         if (!WebInspector.NavigatorView.prototype.accept(uiSourceCode))
553             return false;
554         return uiSourceCode.isContentScript;
555     },
556
557     __proto__: WebInspector.NavigatorView.prototype
558 }
559
560 /**
561  * @constructor
562  * @extends {TreeOutline}
563  * @param {!Element} element
564  */
565 WebInspector.NavigatorTreeOutline = function(element)
566 {
567     TreeOutline.call(this, element);
568     this.element = element;
569
570     this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare;
571 }
572
573 WebInspector.NavigatorTreeOutline.Types = {
574     Root: "Root",
575     Domain: "Domain",
576     Folder: "Folder",
577     UISourceCode: "UISourceCode",
578     FileSystem: "FileSystem"
579 }
580
581 /**
582  * @param {!TreeElement} treeElement1
583  * @param {!TreeElement} treeElement2
584  * @return {number}
585  */
586 WebInspector.NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2)
587 {
588     // Insert in the alphabetical order, first domains, then folders, then scripts.
589     function typeWeight(treeElement)
590     {
591         var type = treeElement.type();
592         if (type === WebInspector.NavigatorTreeOutline.Types.Domain) {
593             if (treeElement.titleText === WebInspector.resourceTreeModel.inspectedPageDomain())
594                 return 1;
595             return 2;
596         }
597         if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem)
598             return 3;
599         if (type === WebInspector.NavigatorTreeOutline.Types.Folder)
600             return 4;
601         return 5;
602     }
603
604     var typeWeight1 = typeWeight(treeElement1);
605     var typeWeight2 = typeWeight(treeElement2);
606
607     var result;
608     if (typeWeight1 > typeWeight2)
609         result = 1;
610     else if (typeWeight1 < typeWeight2)
611         result = -1;
612     else {
613         var title1 = treeElement1.titleText;
614         var title2 = treeElement2.titleText;
615         result = title1.compareTo(title2);
616     }
617     return result;
618 }
619
620 WebInspector.NavigatorTreeOutline.prototype = {
621    /**
622     * @return {!Array.<!WebInspector.UISourceCode>}
623     */
624    scriptTreeElements: function()
625    {
626        var result = [];
627        if (this.children.length) {
628            for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) {
629                if (treeElement instanceof WebInspector.NavigatorSourceTreeElement)
630                    result.push(treeElement.uiSourceCode);
631            }
632        }
633        return result;
634    },
635
636     __proto__: TreeOutline.prototype
637 }
638
639 /**
640  * @constructor
641  * @extends {TreeElement}
642  * @param {string} type
643  * @param {string} title
644  * @param {!Array.<string>} iconClasses
645  * @param {boolean} hasChildren
646  * @param {boolean=} noIcon
647  */
648 WebInspector.BaseNavigatorTreeElement = function(type, title, iconClasses, hasChildren, noIcon)
649 {
650     this._type = type;
651     TreeElement.call(this, "", null, hasChildren);
652     this._titleText = title;
653     this._iconClasses = iconClasses;
654     this._noIcon = noIcon;
655 }
656
657 WebInspector.BaseNavigatorTreeElement.prototype = {
658     onattach: function()
659     {
660         this.listItemElement.removeChildren();
661         if (this._iconClasses) {
662             for (var i = 0; i < this._iconClasses.length; ++i)
663                 this.listItemElement.classList.add(this._iconClasses[i]);
664         }
665
666         var selectionElement = document.createElement("div");
667         selectionElement.className = "selection";
668         this.listItemElement.appendChild(selectionElement);
669
670         if (!this._noIcon) {
671             this.imageElement = document.createElement("img");
672             this.imageElement.className = "icon";
673             this.listItemElement.appendChild(this.imageElement);
674         }
675         
676         this.titleElement = document.createElement("div");
677         this.titleElement.className = "base-navigator-tree-element-title";
678         this._titleTextNode = document.createTextNode("");
679         this._titleTextNode.textContent = this._titleText;
680         this.titleElement.appendChild(this._titleTextNode);
681         this.listItemElement.appendChild(this.titleElement);
682     },
683
684     updateIconClasses: function(iconClasses)
685     {
686         for (var i = 0; i < this._iconClasses.length; ++i)
687             this.listItemElement.classList.remove(this._iconClasses[i]);
688         this._iconClasses = iconClasses;
689         for (var i = 0; i < this._iconClasses.length; ++i)
690             this.listItemElement.classList.add(this._iconClasses[i]);
691     },
692
693     onreveal: function()
694     {
695         if (this.listItemElement)
696             this.listItemElement.scrollIntoViewIfNeeded(true);
697     },
698
699     /**
700      * @return {string}
701      */
702     get titleText()
703     {
704         return this._titleText;
705     },
706
707     set titleText(titleText)
708     {
709         if (this._titleText === titleText)
710             return;
711         this._titleText = titleText || "";
712         if (this.titleElement)
713             this.titleElement.textContent = this._titleText;
714     },
715     
716     /**
717      * @return {string}
718      */
719     type: function()
720     {
721         return this._type;
722     },
723
724     __proto__: TreeElement.prototype
725 }
726
727 /**
728  * @constructor
729  * @extends {WebInspector.BaseNavigatorTreeElement}
730  * @param {!WebInspector.NavigatorView} navigatorView
731  * @param {string} type
732  * @param {string} title
733  */
734 WebInspector.NavigatorFolderTreeElement = function(navigatorView, type, title)
735 {
736     var iconClass = WebInspector.NavigatorView.iconClassForType(type);
737     WebInspector.BaseNavigatorTreeElement.call(this, type, title, [iconClass], true);
738     this._navigatorView = navigatorView;
739 }
740
741 WebInspector.NavigatorFolderTreeElement.prototype = {
742     onpopulate: function()
743     {
744         this._node.populate();
745     },
746
747     onattach: function()
748     {
749         WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this);
750         this.collapse();
751         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
752     },
753
754     /**
755      * @param {!WebInspector.NavigatorFolderTreeNode} node
756      */
757     setNode: function(node)
758     {
759         this._node = node;
760         var paths = [];
761         while (node && !node.isRoot()) {
762             paths.push(node._title);
763             node = node.parent;
764         }
765         paths.reverse();
766         this.tooltip = paths.join("/");
767     },
768
769     /**
770      * @param {?Event} event
771      */
772     _handleContextMenuEvent: function(event)
773     {
774         if (!this._node)
775             return;
776         this.select();
777         this._navigatorView.handleFolderContextMenu(/** @type {!Event} */ (event), this._node);
778     },
779
780     __proto__: WebInspector.BaseNavigatorTreeElement.prototype
781 }
782
783 /**
784  * @constructor
785  * @extends {WebInspector.BaseNavigatorTreeElement}
786  * @param {!WebInspector.NavigatorView} navigatorView
787  * @param {!WebInspector.UISourceCode} uiSourceCode
788  * @param {string} title
789  */
790 WebInspector.NavigatorSourceTreeElement = function(navigatorView, uiSourceCode, title)
791 {
792     this._navigatorView = navigatorView;
793     this._uiSourceCode = uiSourceCode;
794     WebInspector.BaseNavigatorTreeElement.call(this, WebInspector.NavigatorTreeOutline.Types.UISourceCode, title, this._calculateIconClasses(), false);
795     this.tooltip = uiSourceCode.originURL();
796 }
797
798 WebInspector.NavigatorSourceTreeElement.prototype = {
799     /**
800      * @return {!WebInspector.UISourceCode}
801      */
802     get uiSourceCode()
803     {
804         return this._uiSourceCode;
805     },
806
807     /**
808      * @return {!Array.<string>}
809      */
810     _calculateIconClasses: function()
811     {
812         return ["navigator-" + this._uiSourceCode.contentType().name() + "-tree-item"];
813     },
814
815     updateIcon: function()
816     {
817         this.updateIconClasses(this._calculateIconClasses());
818     },
819
820     onattach: function()
821     {
822         WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this);
823         this.listItemElement.draggable = true;
824         this.listItemElement.addEventListener("click", this._onclick.bind(this), false);
825         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
826         this.listItemElement.addEventListener("mousedown", this._onmousedown.bind(this), false);
827         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
828     },
829
830     _onmousedown: function(event)
831     {
832         if (event.which === 1) // Warm-up data for drag'n'drop
833             this._uiSourceCode.requestContent(callback.bind(this));
834         /**
835          * @param {?string} content
836          * @this {WebInspector.NavigatorSourceTreeElement}
837          */
838         function callback(content)
839         {
840             this._warmedUpContent = content;
841         }
842     },
843
844     _shouldRenameOnMouseDown: function()
845     {
846         if (!this._uiSourceCode.canRename())
847             return false;
848         var isSelected = this === this.treeOutline.selectedTreeElement;
849         var isFocused = this.treeOutline.childrenListElement.isSelfOrAncestor(document.activeElement);
850         return isSelected && isFocused && !WebInspector.isBeingEdited(this.treeOutline.element);
851     },
852
853     selectOnMouseDown: function(event)
854     {
855         if (event.which !== 1 || !this._shouldRenameOnMouseDown()) {
856             TreeElement.prototype.selectOnMouseDown.call(this, event);
857             return;
858         }
859         setTimeout(rename.bind(this), 300);
860
861         /**
862          * @this {WebInspector.NavigatorSourceTreeElement}
863          */
864         function rename()
865         {
866             if (this._shouldRenameOnMouseDown())
867                 this._navigatorView.rename(this.uiSourceCode, false);
868         }
869     },
870
871     _ondragstart: function(event)
872     {
873         event.dataTransfer.setData("text/plain", this._warmedUpContent);
874         event.dataTransfer.effectAllowed = "copy";
875         return true;
876     },
877
878     /**
879      * @return {boolean}
880      */
881     onspace: function()
882     {
883         this._navigatorView._sourceSelected(this.uiSourceCode, true);
884         return true;
885     },
886
887     /**
888      * @param {!Event} event
889      */
890     _onclick: function(event)
891     {
892         this._navigatorView._sourceSelected(this.uiSourceCode, false);
893     },
894
895     /**
896      * @override
897      * @return {boolean}
898      */
899     ondblclick: function(event)
900     {
901         var middleClick = event.button === 1;
902         this._navigatorView._sourceSelected(this.uiSourceCode, !middleClick);
903         return false;
904     },
905
906     /**
907      * @override
908      * @return {boolean}
909      */
910     onenter: function()
911     {
912         this._navigatorView._sourceSelected(this.uiSourceCode, true);
913         return true;
914     },
915
916     /**
917      * @override
918      * @return {boolean}
919      */
920     ondelete: function()
921     {
922         this._navigatorView.sourceDeleted(this.uiSourceCode);
923         return true;
924     },
925
926     /**
927      * @param {!Event} event
928      */
929     _handleContextMenuEvent: function(event)
930     {
931         this.select();
932         this._navigatorView.handleFileContextMenu(event, this._uiSourceCode);
933     },
934
935     __proto__: WebInspector.BaseNavigatorTreeElement.prototype
936 }
937
938 /**
939  * @constructor
940  * @param {string} id
941  */
942 WebInspector.NavigatorTreeNode = function(id)
943 {
944     this.id = id;
945     /** @type {!StringMap.<!WebInspector.NavigatorTreeNode>} */
946     this._children = new StringMap();
947 }
948
949 WebInspector.NavigatorTreeNode.prototype = {
950     /**
951      * @return {!TreeElement}
952      */
953     treeElement: function() { throw "Not implemented"; },
954
955     dispose: function() { },
956
957     /**
958      * @return {boolean}
959      */
960     isRoot: function()
961     {
962         return false;
963     },
964
965     /**
966      * @return {boolean}
967      */
968     hasChildren: function()
969     {
970         return true;
971     },
972
973     populate: function()
974     {
975         if (this.isPopulated())
976             return;
977         if (this.parent)
978             this.parent.populate();
979         this._populated = true;
980         this.wasPopulated();
981     },
982
983     wasPopulated: function()
984     {
985         var children = this.children();
986         for (var i = 0; i < children.length; ++i)
987             this.treeElement().appendChild(children[i].treeElement());
988     },
989
990     /**
991      * @param {!WebInspector.NavigatorTreeNode} node
992      */
993     didAddChild: function(node)
994     {
995         if (this.isPopulated())
996             this.treeElement().appendChild(node.treeElement());
997     },
998
999     /**
1000      * @param {!WebInspector.NavigatorTreeNode} node
1001      */
1002     willRemoveChild: function(node)
1003     {
1004         if (this.isPopulated())
1005             this.treeElement().removeChild(node.treeElement());
1006     },
1007
1008     /**
1009      * @return {boolean}
1010      */
1011     isPopulated: function()
1012     {
1013         return this._populated;
1014     },
1015
1016     /**
1017      * @return {boolean}
1018      */
1019     isEmpty: function()
1020     {
1021         return !this._children.size();
1022     },
1023
1024     /**
1025      * @param {string} id
1026      * @return {?WebInspector.NavigatorTreeNode}
1027      */
1028     child: function(id)
1029     {
1030         return this._children.get(id) || null;
1031     },
1032
1033     /**
1034      * @return {!Array.<!WebInspector.NavigatorTreeNode>}
1035      */
1036     children: function()
1037     {
1038         return this._children.values();
1039     },
1040
1041     /**
1042      * @param {!WebInspector.NavigatorTreeNode} node
1043      */
1044     appendChild: function(node)
1045     {
1046         this._children.put(node.id, node);
1047         node.parent = this;
1048         this.didAddChild(node);
1049     },
1050
1051     /**
1052      * @param {!WebInspector.NavigatorTreeNode} node
1053      */
1054     removeChild: function(node)
1055     {
1056         this.willRemoveChild(node);
1057         this._children.remove(node.id);
1058         delete node.parent;
1059         node.dispose();
1060     },
1061
1062     reset: function()
1063     {
1064         this._children.clear();
1065     }
1066 }
1067
1068 /**
1069  * @constructor
1070  * @extends {WebInspector.NavigatorTreeNode}
1071  * @param {!WebInspector.NavigatorView} navigatorView
1072  */
1073 WebInspector.NavigatorRootTreeNode = function(navigatorView)
1074 {
1075     WebInspector.NavigatorTreeNode.call(this, "");
1076     this._navigatorView = navigatorView;
1077 }
1078
1079 WebInspector.NavigatorRootTreeNode.prototype = {
1080     /**
1081      * @return {boolean}
1082      */
1083     isRoot: function()
1084     {
1085         return true;
1086     },
1087
1088     /**
1089      * @return {!TreeOutline}
1090      */
1091     treeElement: function()
1092     {
1093         return this._navigatorView._scriptsTree;
1094     },
1095
1096     __proto__: WebInspector.NavigatorTreeNode.prototype
1097 }
1098
1099 /**
1100  * @constructor
1101  * @extends {WebInspector.NavigatorTreeNode}
1102  * @param {!WebInspector.NavigatorView} navigatorView
1103  * @param {!WebInspector.UISourceCode} uiSourceCode
1104  */
1105 WebInspector.NavigatorUISourceCodeTreeNode = function(navigatorView, uiSourceCode)
1106 {
1107     WebInspector.NavigatorTreeNode.call(this, uiSourceCode.name());
1108     this._navigatorView = navigatorView;
1109     this._uiSourceCode = uiSourceCode;
1110     this._treeElement = null;
1111 }
1112
1113 WebInspector.NavigatorUISourceCodeTreeNode.prototype = {
1114     /**
1115      * @return {!WebInspector.UISourceCode}
1116      */
1117     uiSourceCode: function()
1118     {
1119         return this._uiSourceCode;
1120     },
1121
1122     updateIcon: function()
1123     {
1124         if (this._treeElement)
1125             this._treeElement.updateIcon();
1126     },
1127
1128     /**
1129      * @return {!TreeElement}
1130      */
1131     treeElement: function()
1132     {
1133         if (this._treeElement)
1134             return this._treeElement;
1135
1136         this._treeElement = new WebInspector.NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, "");
1137         this.updateTitle();
1138
1139         this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this);
1140         this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
1141         this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
1142
1143         return this._treeElement;
1144     },
1145
1146     /**
1147      * @param {boolean=} ignoreIsDirty
1148      */
1149     updateTitle: function(ignoreIsDirty)
1150     {
1151         if (!this._treeElement)
1152             return;
1153
1154         var titleText = this._uiSourceCode.displayName();
1155         if (!ignoreIsDirty && (this._uiSourceCode.isDirty() || this._uiSourceCode.hasUnsavedCommittedChanges()))
1156             titleText = "*" + titleText;
1157         this._treeElement.titleText = titleText;
1158     },
1159
1160     /**
1161      * @return {boolean}
1162      */
1163     hasChildren: function()
1164     {
1165         return false;
1166     },
1167
1168     dispose: function()
1169     {
1170         if (!this._treeElement)
1171             return;
1172         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this);
1173         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
1174         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
1175     },
1176
1177     _titleChanged: function(event)
1178     {
1179         this.updateTitle();
1180     },
1181
1182     _workingCopyChanged: function(event)
1183     {
1184         this.updateTitle();
1185     },
1186
1187     _workingCopyCommitted: function(event)
1188     {
1189         this.updateTitle();
1190     },
1191
1192     /**
1193      * @param {boolean=} select
1194      */
1195     reveal: function(select)
1196     {
1197         this.parent.populate();
1198         this.parent.treeElement().expand();
1199         this._treeElement.reveal();
1200         if (select)
1201             this._treeElement.select(true);
1202     },
1203
1204     /**
1205      * @param {function(boolean)=} callback
1206      */
1207     rename: function(callback)
1208     {
1209         if (!this._treeElement)
1210             return;
1211
1212         // Tree outline should be marked as edited as well as the tree element to prevent search from starting.
1213         var treeOutlineElement = this._treeElement.treeOutline.element;
1214         WebInspector.markBeingEdited(treeOutlineElement, true);
1215
1216         /**
1217          * @param {!Element} element
1218          * @param {string} newTitle
1219          * @param {string} oldTitle
1220          * @this {WebInspector.NavigatorUISourceCodeTreeNode}
1221          */
1222         function commitHandler(element, newTitle, oldTitle)
1223         {
1224             if (newTitle !== oldTitle) {
1225                 this._treeElement.titleText = newTitle;
1226                 this._uiSourceCode.rename(newTitle, renameCallback.bind(this));
1227                 return;
1228             }
1229             afterEditing.call(this, true);
1230         }
1231
1232         /**
1233          * @param {boolean} success
1234          * @this {WebInspector.NavigatorUISourceCodeTreeNode}
1235          */
1236         function renameCallback(success)
1237         {
1238             if (!success) {
1239                 WebInspector.markBeingEdited(treeOutlineElement, false);
1240                 this.updateTitle();
1241                 this.rename(callback);
1242                 return;
1243             }
1244             afterEditing.call(this, true);
1245         }
1246
1247         /**
1248          * @this {WebInspector.NavigatorUISourceCodeTreeNode}
1249          */
1250         function cancelHandler()
1251         {
1252             afterEditing.call(this, false);
1253         }
1254
1255         /**
1256          * @param {boolean} committed
1257          * @this {WebInspector.NavigatorUISourceCodeTreeNode}
1258          */
1259         function afterEditing(committed)
1260         {
1261             WebInspector.markBeingEdited(treeOutlineElement, false);
1262             this.updateTitle();
1263             this._treeElement.treeOutline.childrenListElement.focus();
1264             if (callback)
1265                 callback(committed);
1266         }
1267
1268         var editingConfig = new WebInspector.InplaceEditor.Config(commitHandler.bind(this), cancelHandler.bind(this));
1269         this.updateTitle(true);
1270         WebInspector.InplaceEditor.startEditing(this._treeElement.titleElement, editingConfig);
1271         window.getSelection().setBaseAndExtent(this._treeElement.titleElement, 0, this._treeElement.titleElement, 1);
1272     },
1273
1274     __proto__: WebInspector.NavigatorTreeNode.prototype
1275 }
1276
1277 /**
1278  * @constructor
1279  * @extends {WebInspector.NavigatorTreeNode}
1280  * @param {!WebInspector.NavigatorView} navigatorView
1281  * @param {?WebInspector.Project} project
1282  * @param {string} id
1283  * @param {string} type
1284  * @param {string} folderPath
1285  * @param {string} title
1286  */
1287 WebInspector.NavigatorFolderTreeNode = function(navigatorView, project, id, type, folderPath, title)
1288 {
1289     WebInspector.NavigatorTreeNode.call(this, id);
1290     this._navigatorView = navigatorView;
1291     this._project = project;
1292     this._type = type;
1293     this._folderPath = folderPath;
1294     this._title = title;
1295 }
1296
1297 WebInspector.NavigatorFolderTreeNode.prototype = {
1298     /**
1299      * @return {!TreeElement}
1300      */
1301     treeElement: function()
1302     {
1303         if (this._treeElement)
1304             return this._treeElement;
1305         this._treeElement = this._createTreeElement(this._title, this);
1306         return this._treeElement;
1307     },
1308
1309     /**
1310      * @return {!TreeElement}
1311      */
1312     _createTreeElement: function(title, node)
1313     {
1314         var treeElement = new WebInspector.NavigatorFolderTreeElement(this._navigatorView, this._type, title);
1315         treeElement.setNode(node);
1316         return treeElement;
1317     },
1318
1319     wasPopulated: function()
1320     {
1321         if (!this._treeElement || this._treeElement._node !== this)
1322             return;
1323         this._addChildrenRecursive();
1324     },
1325
1326     _addChildrenRecursive: function()
1327     {
1328         var children = this.children();
1329         for (var i = 0; i < children.length; ++i) {
1330             var child = children[i];
1331             this.didAddChild(child);
1332             if (child instanceof WebInspector.NavigatorFolderTreeNode)
1333                 child._addChildrenRecursive();
1334         }
1335     },
1336
1337     _shouldMerge: function(node)
1338     {
1339         return this._type !== WebInspector.NavigatorTreeOutline.Types.Domain && node instanceof WebInspector.NavigatorFolderTreeNode;
1340     },
1341
1342     didAddChild: function(node)
1343     {
1344         function titleForNode(node)
1345         {
1346             return node._title;
1347         }
1348
1349         if (!this._treeElement)
1350             return;
1351
1352         var children = this.children();
1353
1354         if (children.length === 1 && this._shouldMerge(node)) {
1355             node._isMerged = true;
1356             this._treeElement.titleText = this._treeElement.titleText + "/" + node._title;
1357             node._treeElement = this._treeElement;
1358             this._treeElement.setNode(node);
1359             return;
1360         }
1361
1362         var oldNode;
1363         if (children.length === 2)
1364             oldNode = children[0] !== node ? children[0] : children[1];
1365         if (oldNode && oldNode._isMerged) {
1366             delete oldNode._isMerged;
1367             var mergedToNodes = [];
1368             mergedToNodes.push(this);
1369             var treeNode = this;
1370             while (treeNode._isMerged) {
1371                 treeNode = treeNode.parent;
1372                 mergedToNodes.push(treeNode);
1373             }
1374             mergedToNodes.reverse();
1375             var titleText = mergedToNodes.map(titleForNode).join("/");
1376
1377             var nodes = [];
1378             treeNode = oldNode;
1379             do {
1380                 nodes.push(treeNode);
1381                 children = treeNode.children();
1382                 treeNode = children.length === 1 ? children[0] : null;
1383             } while (treeNode && treeNode._isMerged);
1384
1385             if (!this.isPopulated()) {
1386                 this._treeElement.titleText = titleText;
1387                 this._treeElement.setNode(this);
1388                 for (var i = 0; i < nodes.length; ++i) {
1389                     delete nodes[i]._treeElement;
1390                     delete nodes[i]._isMerged;
1391                 }
1392                 return;
1393             }
1394             var oldTreeElement = this._treeElement;
1395             var treeElement = this._createTreeElement(titleText, this);
1396             for (var i = 0; i < mergedToNodes.length; ++i)
1397                 mergedToNodes[i]._treeElement = treeElement;
1398             oldTreeElement.parent.appendChild(treeElement);
1399
1400             oldTreeElement.setNode(nodes[nodes.length - 1]);
1401             oldTreeElement.titleText = nodes.map(titleForNode).join("/");
1402             oldTreeElement.parent.removeChild(oldTreeElement);
1403             this._treeElement.appendChild(oldTreeElement);
1404             if (oldTreeElement.expanded)
1405                 treeElement.expand();
1406         }
1407         if (this.isPopulated())
1408             this._treeElement.appendChild(node.treeElement());
1409     },
1410
1411     willRemoveChild: function(node)
1412     {
1413         if (node._isMerged || !this.isPopulated())
1414             return;
1415         this._treeElement.removeChild(node._treeElement);
1416     },
1417
1418     __proto__: WebInspector.NavigatorTreeNode.prototype
1419 }