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