/* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WebInspector.DOMTreeContentView = function(representedObject) { console.assert(representedObject); WebInspector.ContentView.call(this, representedObject); this._compositingBordersButtonNavigationItem = new WebInspector.ActivateButtonNavigationItem("layer-borders", WebInspector.UIString("Show compositing borders"), WebInspector.UIString("Hide compositing borders"), "Images/LayerBorders.svg", 16, 16); this._compositingBordersButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleCompositingBorders, this); this._compositingBordersButtonNavigationItem.enabled = !!PageAgent.getCompositingBordersVisible; WebInspector.showShadowDOMSetting.addEventListener(WebInspector.Setting.Event.Changed, this._showShadowDOMSettingChanged, this); this._showsShadowDOMButtonNavigationItem = new WebInspector.ActivateButtonNavigationItem("shows-shadow-DOM", WebInspector.UIString("Show shadow DOM nodes"), WebInspector.UIString("Hide shadow DOM nodes"), "Images/ShadowDOM.svg", 16, 16); this._showsShadowDOMButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleShowsShadowDOMSetting, this); this._showShadowDOMSettingChanged(); this.element.classList.add(WebInspector.DOMTreeContentView.StyleClassName); this.element.addEventListener("click", this._mouseWasClicked.bind(this), false); this._domTreeOutline = new WebInspector.DOMTreeOutline(true, true, false); this._domTreeOutline.addEventListener(WebInspector.DOMTreeOutline.Event.SelectedNodeChanged, this._selectedNodeDidChange, this); this._domTreeOutline.wireToDomAgent(); this.element.appendChild(this._domTreeOutline.element); WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.AttributeModified, this._domNodeChanged, this); WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.AttributeRemoved, this._domNodeChanged, this); WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.CharacterDataModified, this._domNodeChanged, this); this._lastSelectedNodePathSetting = new WebInspector.Setting("last-selected-node-path", null); this._numberOfSearchResults = null; }; WebInspector.DOMTreeContentView.StyleClassName = "dom-tree"; WebInspector.DOMTreeContentView.prototype = { constructor: WebInspector.DOMTreeContentView, __proto__: WebInspector.ContentView.prototype, // Public get navigationItems() { return [this._showsShadowDOMButtonNavigationItem, this._compositingBordersButtonNavigationItem]; }, get domTreeOutline() { return this._domTreeOutline; }, get scrollableElements() { return [this.element]; }, updateLayout: function() { this._domTreeOutline.updateSelection(); }, shown: function() { this._domTreeOutline.setVisible(true, WebInspector.isConsoleFocused()); this._updateCompositingBordersButtonToMatchPageSettings(); }, hidden: function() { WebInspector.domTreeManager.hideDOMNodeHighlight(); this._domTreeOutline.setVisible(false); }, closed: function() { WebInspector.domTreeManager.removeEventListener(null, null, this); this._domTreeOutline.close(); }, get selectionPathComponents() { var treeElement = this._domTreeOutline.selectedTreeElement; var pathComponents = []; while (treeElement && !treeElement.root) { // The close tag is contained within the element it closes. So skip it since we don't want to // show the same node twice in the hierarchy. if (treeElement.isCloseTag()) { treeElement = treeElement.parent; continue; } var pathComponent = new WebInspector.DOMTreeElementPathComponent(treeElement, treeElement.representedObject); pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); pathComponents.unshift(pathComponent); treeElement = treeElement.parent; } return pathComponents; }, selectAndRevealDOMNode: function(domNode, preventFocusChange) { this._domTreeOutline.selectDOMNode(domNode, !preventFocusChange); }, handleCopyEvent: function(event) { var selectedDOMNode = this._domTreeOutline.selectedDOMNode(); if (!selectedDOMNode) return; event.clipboardData.clearData(); event.preventDefault(); selectedDOMNode.copyNode(); }, get supportsSave() { return WebInspector.canArchiveMainFrame(); }, get saveData() { function saveHandler(forceSaveAs) { WebInspector.archiveMainFrame(); } return { customSaveHandler: saveHandler }; }, get supportsSearch() { return true; }, get numberOfSearchResults() { return this._numberOfSearchResults; }, get hasPerformedSearch() { return this._numberOfSearchResults !== null; }, set automaticallyRevealFirstSearchResult(reveal) { this._automaticallyRevealFirstSearchResult = reveal; // If we haven't shown a search result yet, reveal one now. if (this._automaticallyRevealFirstSearchResult && this._numberOfSearchResults > 0) { if (this._currentSearchResultIndex === -1) this.revealNextSearchResult(); } }, performSearch: function(query) { if (this._searchQuery === query) return; if (this._searchIdentifier) DOMAgent.discardSearchResults(this._searchIdentifier); this._searchQuery = query; this._searchIdentifier = null; this._numberOfSearchResults = null; this._currentSearchResultIndex = -1; function searchResultsReady(error, searchIdentifier, resultsCount) { if (error) return; this._searchIdentifier = searchIdentifier; this._numberOfSearchResults = resultsCount; this.dispatchEventToListeners(WebInspector.ContentView.Event.NumberOfSearchResultsDidChange); if (this._automaticallyRevealFirstSearchResult) this.revealNextSearchResult(); } function contextNodesReady(nodeIds) { DOMAgent.performSearch(query, nodeIds, searchResultsReady.bind(this)); } this.getSearchContextNodes(contextNodesReady.bind(this)); }, getSearchContextNodes: function(callback) { // Overwrite this to limit the search to just a subtree. // Passing undefined will make DOMAgent.performSearch search through all the documents. callback(undefined); }, searchCleared: function() { if (this._searchIdentifier) DOMAgent.discardSearchResults(this._searchIdentifier); this._searchQuery = null; this._searchIdentifier = null; this._numberOfSearchResults = null; this._currentSearchResultIndex = -1; }, revealPreviousSearchResult: function(changeFocus) { if (!this._numberOfSearchResults) return; if (this._currentSearchResultIndex > 0) --this._currentSearchResultIndex; else this._currentSearchResultIndex = this._numberOfSearchResults - 1; this._revealSearchResult(this._currentSearchResultIndex, changeFocus); }, revealNextSearchResult: function(changeFocus) { if (!this._numberOfSearchResults) return; if (this._currentSearchResultIndex + 1 < this._numberOfSearchResults) ++this._currentSearchResultIndex; else this._currentSearchResultIndex = 0; this._revealSearchResult(this._currentSearchResultIndex, changeFocus); }, // Private _revealSearchResult: function(index, changeFocus) { console.assert(this._searchIdentifier); var searchIdentifier = this._searchIdentifier; function revealResult(error, nodeIdentifiers) { if (error) return; // Bail if the searchIdentifier changed since we started. if (this._searchIdentifier !== searchIdentifier) return; console.assert(nodeIdentifiers.length === 1); var domNode = WebInspector.domTreeManager.nodeForId(nodeIdentifiers[0]); console.assert(domNode); if (!domNode) return; this._domTreeOutline.selectDOMNode(domNode, changeFocus); } DOMAgent.getSearchResults(this._searchIdentifier, index, index + 1, revealResult.bind(this)); }, _restoreSelectedNodeAfterUpdate: function(documentURL, defaultNode) { function selectNode(lastSelectedNode) { var nodeToFocus = lastSelectedNode; if (!nodeToFocus) nodeToFocus = defaultNode; if (!nodeToFocus) return; this._dontSetLastSelectedNodePath = true; this.selectAndRevealDOMNode(nodeToFocus, WebInspector.isConsoleFocused()); this._dontSetLastSelectedNodePath = false; // If this wasn't the last selected node, then expand it. if (!lastSelectedNode && this._domTreeOutline.selectedTreeElement) this._domTreeOutline.selectedTreeElement.expand(); } function selectLastSelectedNode(nodeId) { selectNode.call(this, WebInspector.domTreeManager.nodeForId(nodeId)); } if (documentURL && this._lastSelectedNodePathSetting.value && this._lastSelectedNodePathSetting.value.path && this._lastSelectedNodePathSetting.value.url === documentURL.hash) WebInspector.domTreeManager.pushNodeByPathToFrontend(this._lastSelectedNodePathSetting.value.path, selectLastSelectedNode.bind(this)); else selectNode.call(this); }, _selectedNodeDidChange: function(event) { var selectedDOMNode = this._domTreeOutline.selectedDOMNode(); if (selectedDOMNode && !this._dontSetLastSelectedNodePath) this._lastSelectedNodePathSetting.value = {url: selectedDOMNode.ownerDocument.documentURL.hash, path: selectedDOMNode.path()}; if (selectedDOMNode) ConsoleAgent.addInspectedNode(selectedDOMNode.id); this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange); }, _pathComponentSelected: function(event) { console.assert(event.data.pathComponent instanceof WebInspector.DOMTreeElementPathComponent); console.assert(event.data.pathComponent.domTreeElement instanceof WebInspector.DOMTreeElement); this._domTreeOutline.selectDOMNode(event.data.pathComponent.domTreeElement.representedObject, true); }, _domNodeChanged: function(event) { var selectedDOMNode = this._domTreeOutline.selectedDOMNode(); if (selectedDOMNode !== event.data.node) return; this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange); }, _mouseWasClicked: function(event) { var anchorElement = event.target.enclosingNodeOrSelfWithNodeName("a"); if (!anchorElement || !anchorElement.href) return; // Prevent the link from navigating, since we don't do any navigation by following links normally. event.preventDefault(); event.stopPropagation(); if (WebInspector.isBeingEdited(anchorElement)) { // Don't follow the link when it is being edited. return; } // Cancel any pending link navigation. if (this._followLinkTimeoutIdentifier) { clearTimeout(this._followLinkTimeoutIdentifier); delete this._followLinkTimeoutIdentifier; } // If this is a double-click (or multiple-click), return early. if (event.detail > 1) return; function followLink() { // Since followLink is delayed, the call to WebInspector.openURL can't look at window.event // to see if the command key is down like it normally would. So we need to do that check // before calling WebInspector.openURL. var alwaysOpenExternally = event ? event.metaKey : false; WebInspector.openURL(anchorElement.href, this._frame, alwaysOpenExternally, anchorElement.lineNumber); } // Start a timeout since this is a single click, if the timeout is canceled before it fires, // then a double-click happened or another link was clicked. // FIXME: The duration might be longer or shorter than the user's configured double click speed. this._followLinkTimeoutIdentifier = setTimeout(followLink.bind(this), 333); }, _toggleCompositingBorders: function(event) { console.assert(PageAgent.setCompositingBordersVisible); var activated = !this._compositingBordersButtonNavigationItem.activated; this._compositingBordersButtonNavigationItem.activated = activated; PageAgent.setCompositingBordersVisible(activated); }, _updateCompositingBordersButtonToMatchPageSettings: function() { if (!PageAgent.getCompositingBordersVisible) return; var button = this._compositingBordersButtonNavigationItem; // We need to sync with the page settings since these can be controlled // in a different way than just using the navigation bar button. PageAgent.getCompositingBordersVisible(function(error, compositingBordersVisible) { button.activated = error ? false : compositingBordersVisible; }); }, _showShadowDOMSettingChanged: function(event) { this._showsShadowDOMButtonNavigationItem.activated = WebInspector.showShadowDOMSetting.value; }, _toggleShowsShadowDOMSetting: function(event) { WebInspector.showShadowDOMSetting.value = !WebInspector.showShadowDOMSetting.value; } };