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