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