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