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