home *** CD-ROM | disk | FTP | other *** search
/ Computer Active 2010 July / CA07.iso / Multimedija / QuickTimeInstaller.exe / AppleApplicationSupport.msi / WebKit.resources_inspector_ElementsPanel.js < prev    next >
Encoding:
Text File  |  2010-03-15  |  44.5 KB  |  1,207 lines

  1. /*
  2.  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
  3.  * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  * 1.  Redistributions of source code must retain the above copyright
  10.  *     notice, this list of conditions and the following disclaimer.
  11.  * 2.  Redistributions in binary form must reproduce the above copyright
  12.  *     notice, this list of conditions and the following disclaimer in the
  13.  *     documentation and/or other materials provided with the distribution.
  14.  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  15.  *     its contributors may be used to endorse or promote products derived
  16.  *     from this software without specific prior written permission.
  17.  *
  18.  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  19.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21.  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  22.  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25.  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  27.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28.  */
  29.  
  30. WebInspector.ElementsPanel = function()
  31. {
  32.     WebInspector.Panel.call(this);
  33.  
  34.     this.element.addStyleClass("elements");
  35.  
  36.     this.contentElement = document.createElement("div");
  37.     this.contentElement.id = "elements-content";
  38.     this.contentElement.className = "outline-disclosure";
  39.  
  40.     this.treeOutline = new WebInspector.ElementsTreeOutline();
  41.     this.treeOutline.panel = this;
  42.     this.treeOutline.includeRootDOMNode = false;
  43.     this.treeOutline.selectEnabled = true;
  44.  
  45.     this.treeOutline.focusedNodeChanged = function(forceUpdate)
  46.     {
  47.         if (this.panel.visible && WebInspector.currentFocusElement !== document.getElementById("search"))
  48.             WebInspector.currentFocusElement = document.getElementById("main-panels");
  49.  
  50.         this.panel.updateBreadcrumb(forceUpdate);
  51.  
  52.         for (var pane in this.panel.sidebarPanes)
  53.            this.panel.sidebarPanes[pane].needsUpdate = true;
  54.  
  55.         this.panel.updateStyles(true);
  56.         this.panel.updateMetrics();
  57.         this.panel.updateProperties();
  58.  
  59.         if (InspectorController.searchingForNode()) {
  60.             InspectorController.toggleNodeSearch();
  61.             this.panel.nodeSearchButton.removeStyleClass("toggled-on");
  62.         }
  63.     };
  64.  
  65.     this.contentElement.appendChild(this.treeOutline.element);
  66.  
  67.     this.crumbsElement = document.createElement("div");
  68.     this.crumbsElement.className = "crumbs";
  69.     this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false);
  70.     this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false);
  71.  
  72.     this.sidebarPanes = {};
  73.     this.sidebarPanes.styles = new WebInspector.StylesSidebarPane();
  74.     this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
  75.     this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
  76.  
  77.     this.sidebarPanes.styles.onexpand = this.updateStyles.bind(this);
  78.     this.sidebarPanes.metrics.onexpand = this.updateMetrics.bind(this);
  79.     this.sidebarPanes.properties.onexpand = this.updateProperties.bind(this);
  80.  
  81.     this.sidebarPanes.styles.expanded = true;
  82.  
  83.     this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this);
  84.     this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this);
  85.     this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this);
  86.  
  87.     this.sidebarElement = document.createElement("div");
  88.     this.sidebarElement.id = "elements-sidebar";
  89.  
  90.     this.sidebarElement.appendChild(this.sidebarPanes.styles.element);
  91.     this.sidebarElement.appendChild(this.sidebarPanes.metrics.element);
  92.     this.sidebarElement.appendChild(this.sidebarPanes.properties.element);
  93.  
  94.     this.sidebarResizeElement = document.createElement("div");
  95.     this.sidebarResizeElement.className = "sidebar-resizer-vertical";
  96.     this.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarResizerDragStart.bind(this), false);
  97.  
  98.     this.nodeSearchButton = document.createElement("button");
  99.     this.nodeSearchButton.title = WebInspector.UIString("Select an element in the page to inspect it.");
  100.     this.nodeSearchButton.id = "node-search-status-bar-item";
  101.     this.nodeSearchButton.className = "status-bar-item";
  102.     this.nodeSearchButton.addEventListener("click", this._nodeSearchButtonClicked.bind(this), false);
  103.  
  104.     this.searchingForNode = false;
  105.  
  106.     this.element.appendChild(this.contentElement);
  107.     this.element.appendChild(this.sidebarElement);
  108.     this.element.appendChild(this.sidebarResizeElement);
  109.  
  110.     this._mutationMonitoredWindows = [];
  111.     this._nodeInsertedEventListener = InspectorController.wrapCallback(this._nodeInserted.bind(this));
  112.     this._nodeRemovedEventListener = InspectorController.wrapCallback(this._nodeRemoved.bind(this));
  113.     this._contentLoadedEventListener = InspectorController.wrapCallback(this._contentLoaded.bind(this));
  114.  
  115.     this.reset();
  116. }
  117.  
  118. WebInspector.ElementsPanel.prototype = {
  119.     toolbarItemClass: "elements",
  120.  
  121.     get toolbarItemLabel()
  122.     {
  123.         return WebInspector.UIString("Elements");
  124.     },
  125.  
  126.     get statusBarItems()
  127.     {
  128.         return [this.nodeSearchButton, this.crumbsElement];
  129.     },
  130.  
  131.     updateStatusBarItems: function()
  132.     {
  133.         this.updateBreadcrumbSizes();
  134.     },
  135.  
  136.     show: function()
  137.     {
  138.         WebInspector.Panel.prototype.show.call(this);
  139.         this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
  140.         this.updateBreadcrumb();
  141.         this.treeOutline.updateSelection();
  142.         if (this.recentlyModifiedNodes.length)
  143.             this._updateModifiedNodes();
  144.     },
  145.  
  146.     hide: function()
  147.     {
  148.         WebInspector.Panel.prototype.hide.call(this);
  149.  
  150.         WebInspector.hoveredDOMNode = null;
  151.  
  152.         if (InspectorController.searchingForNode()) {
  153.             InspectorController.toggleNodeSearch();
  154.             this.nodeSearchButton.removeStyleClass("toggled-on");
  155.         }
  156.     },
  157.  
  158.     resize: function()
  159.     {
  160.         this.treeOutline.updateSelection();
  161.         this.updateBreadcrumbSizes();
  162.     },
  163.  
  164.     reset: function()
  165.     {
  166.         this.rootDOMNode = null;
  167.         this.focusedDOMNode = null;
  168.  
  169.         WebInspector.hoveredDOMNode = null;
  170.  
  171.         if (InspectorController.searchingForNode()) {
  172.             InspectorController.toggleNodeSearch();
  173.             this.nodeSearchButton.removeStyleClass("toggled-on");
  174.         }
  175.  
  176.         this.recentlyModifiedNodes = [];
  177.         this.unregisterAllMutationEventListeners();
  178.  
  179.         delete this.currentQuery;
  180.         this.searchCanceled();
  181.  
  182.         var inspectedWindow = InspectorController.inspectedWindow();
  183.         if (!inspectedWindow || !inspectedWindow.document)
  184.             return;
  185.  
  186.         if (!inspectedWindow.document.firstChild) {
  187.             function contentLoaded()
  188.             {
  189.                 inspectedWindow.document.removeEventListener("DOMContentLoaded", contentLoadedCallback, false);
  190.  
  191.                 this.reset();
  192.             }
  193.  
  194.             var contentLoadedCallback = InspectorController.wrapCallback(contentLoaded.bind(this));
  195.             inspectedWindow.document.addEventListener("DOMContentLoaded", contentLoadedCallback, false);
  196.             return;
  197.         }
  198.  
  199.         // If the window isn't visible, return early so the DOM tree isn't built
  200.         // and mutation event listeners are not added.
  201.         if (!InspectorController.isWindowVisible())
  202.             return;
  203.  
  204.         this.registerMutationEventListeners(inspectedWindow);
  205.  
  206.         var inspectedRootDocument = inspectedWindow.document;
  207.         this.rootDOMNode = inspectedRootDocument;
  208.  
  209.         var canidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
  210.         if (canidateFocusNode) {
  211.             this.treeOutline.suppressSelectHighlight = true;
  212.             this.focusedDOMNode = canidateFocusNode;
  213.             this.treeOutline.suppressSelectHighlight = false;
  214.  
  215.             if (this.treeOutline.selectedTreeElement)
  216.                 this.treeOutline.selectedTreeElement.expand();
  217.         }
  218.     },
  219.  
  220.     includedInSearchResultsPropertyName: "__includedInInspectorSearchResults",
  221.  
  222.     searchCanceled: function()
  223.     {
  224.         if (this._searchResults) {
  225.             const searchResultsProperty = this.includedInSearchResultsPropertyName;
  226.             for (var i = 0; i < this._searchResults.length; ++i) {
  227.                 var node = this._searchResults[i];
  228.  
  229.                 // Remove the searchResultsProperty since there might be an unfinished search.
  230.                 delete node[searchResultsProperty];
  231.  
  232.                 var treeElement = this.treeOutline.findTreeElement(node);
  233.                 if (treeElement)
  234.                     treeElement.highlighted = false;
  235.             }
  236.         }
  237.  
  238.         WebInspector.updateSearchMatchesCount(0, this);
  239.  
  240.         if (this._currentSearchChunkIntervalIdentifier) {
  241.             clearInterval(this._currentSearchChunkIntervalIdentifier);
  242.             delete this._currentSearchChunkIntervalIdentifier;
  243.         }
  244.  
  245.         this._currentSearchResultIndex = 0;
  246.         this._searchResults = [];
  247.     },
  248.  
  249.     performSearch: function(query)
  250.     {
  251.         // Call searchCanceled since it will reset everything we need before doing a new search.
  252.         this.searchCanceled();
  253.  
  254.         const whitespaceTrimmedQuery = query.trimWhitespace();
  255.         if (!whitespaceTrimmedQuery.length)
  256.             return;
  257.  
  258.         var tagNameQuery = whitespaceTrimmedQuery;
  259.         var attributeNameQuery = whitespaceTrimmedQuery;
  260.         var startTagFound = (tagNameQuery.indexOf("<") === 0);
  261.         var endTagFound = (tagNameQuery.lastIndexOf(">") === (tagNameQuery.length - 1));
  262.  
  263.         if (startTagFound || endTagFound) {
  264.             var tagNameQueryLength = tagNameQuery.length;
  265.             tagNameQuery = tagNameQuery.substring((startTagFound ? 1 : 0), (endTagFound ? (tagNameQueryLength - 1) : tagNameQueryLength));
  266.         }
  267.  
  268.         // Check the tagNameQuery is it is a possibly valid tag name.
  269.         if (!/^[a-zA-Z0-9\-_:]+$/.test(tagNameQuery))
  270.             tagNameQuery = null;
  271.  
  272.         // Check the attributeNameQuery is it is a possibly valid tag name.
  273.         if (!/^[a-zA-Z0-9\-_:]+$/.test(attributeNameQuery))
  274.             attributeNameQuery = null;
  275.  
  276.         const escapedQuery = query.escapeCharacters("'");
  277.         const escapedTagNameQuery = (tagNameQuery ? tagNameQuery.escapeCharacters("'") : null);
  278.         const escapedWhitespaceTrimmedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
  279.         const searchResultsProperty = this.includedInSearchResultsPropertyName;
  280.  
  281.         var updatedMatchCountOnce = false;
  282.         var matchesCountUpdateTimeout = null;
  283.  
  284.         function updateMatchesCount()
  285.         {
  286.             WebInspector.updateSearchMatchesCount(this._searchResults.length, this);
  287.             matchesCountUpdateTimeout = null;
  288.             updatedMatchCountOnce = true;
  289.         }
  290.  
  291.         function updateMatchesCountSoon()
  292.         {
  293.             if (!updatedMatchCountOnce)
  294.                 return updateMatchesCount.call(this);
  295.             if (matchesCountUpdateTimeout)
  296.                 return;
  297.             // Update the matches count every half-second so it doesn't feel twitchy.
  298.             matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500);
  299.         }
  300.  
  301.         function addNodesToResults(nodes, length, getItem)
  302.         {
  303.             if (!length)
  304.                 return;
  305.  
  306.             for (var i = 0; i < length; ++i) {
  307.                 var node = getItem.call(nodes, i);
  308.                 // Skip this node if it already has the property.
  309.                 if (searchResultsProperty in node)
  310.                     continue;
  311.  
  312.                 if (!this._searchResults.length) {
  313.                     this._currentSearchResultIndex = 0;
  314.                     this.focusedDOMNode = node;
  315.                 }
  316.  
  317.                 node[searchResultsProperty] = true;
  318.                 this._searchResults.push(node);
  319.  
  320.                 // Highlight the tree element to show it matched the search.
  321.                 // FIXME: highlight the substrings in text nodes and attributes.
  322.                 var treeElement = this.treeOutline.findTreeElement(node);
  323.                 if (treeElement)
  324.                     treeElement.highlighted = true;
  325.             }
  326.  
  327.             updateMatchesCountSoon.call(this);
  328.         }
  329.  
  330.         function matchExactItems(doc)
  331.         {
  332.             matchExactId.call(this, doc);
  333.             matchExactClassNames.call(this, doc);
  334.             matchExactTagNames.call(this, doc);
  335.             matchExactAttributeNames.call(this, doc);
  336.         }
  337.  
  338.         function matchExactId(doc)
  339.         {
  340.             const result = doc.__proto__.getElementById.call(doc, whitespaceTrimmedQuery);
  341.             addNodesToResults.call(this, result, (result ? 1 : 0), function() { return this });
  342.         }
  343.  
  344.         function matchExactClassNames(doc)
  345.         {
  346.             const result = doc.__proto__.getElementsByClassName.call(doc, whitespaceTrimmedQuery);
  347.             addNodesToResults.call(this, result, result.length, result.item);
  348.         }
  349.  
  350.         function matchExactTagNames(doc)
  351.         {
  352.             if (!tagNameQuery)
  353.                 return;
  354.             const result = doc.__proto__.getElementsByTagName.call(doc, tagNameQuery);
  355.             addNodesToResults.call(this, result, result.length, result.item);
  356.         }
  357.  
  358.         function matchExactAttributeNames(doc)
  359.         {
  360.             if (!attributeNameQuery)
  361.                 return;
  362.             const result = doc.__proto__.querySelectorAll.call(doc, "[" + attributeNameQuery + "]");
  363.             addNodesToResults.call(this, result, result.length, result.item);
  364.         }
  365.  
  366.         function matchPartialTagNames(doc)
  367.         {
  368.             if (!tagNameQuery)
  369.                 return;
  370.             const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
  371.             addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
  372.         }
  373.  
  374.         function matchStartOfTagNames(doc)
  375.         {
  376.             if (!tagNameQuery)
  377.                 return;
  378.             const result = doc.__proto__.evaluate.call(doc, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
  379.             addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
  380.         }
  381.  
  382.         function matchPartialTagNamesAndAttributeValues(doc)
  383.         {
  384.             if (!tagNameQuery) {
  385.                 matchPartialAttributeValues.call(this, doc);
  386.                 return;
  387.             }
  388.  
  389.             const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "') or contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
  390.             addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
  391.         }
  392.  
  393.         function matchPartialAttributeValues(doc)
  394.         {
  395.             const result = doc.__proto__.evaluate.call(doc, "//*[contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
  396.             addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
  397.         }
  398.  
  399.         function matchStyleSelector(doc)
  400.         {
  401.             const result = doc.__proto__.querySelectorAll.call(doc, whitespaceTrimmedQuery);
  402.             addNodesToResults.call(this, result, result.length, result.item);
  403.         }
  404.  
  405.         function matchPlainText(doc)
  406.         {
  407.             const result = doc.__proto__.evaluate.call(doc, "//text()[contains(., '" + escapedQuery + "')] | //comment()[contains(., '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
  408.             addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
  409.         }
  410.  
  411.         function matchXPathQuery(doc)
  412.         {
  413.             const result = doc.__proto__.evaluate.call(doc, whitespaceTrimmedQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
  414.             addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
  415.         }
  416.  
  417.         function finishedSearching()
  418.         {
  419.             // Remove the searchResultsProperty now that the search is finished.
  420.             for (var i = 0; i < this._searchResults.length; ++i)
  421.                 delete this._searchResults[i][searchResultsProperty];
  422.         }
  423.  
  424.         const mainFrameDocument = InspectorController.inspectedWindow().document;
  425.         const searchDocuments = [mainFrameDocument];
  426.  
  427.         if (tagNameQuery && startTagFound && endTagFound)
  428.             const searchFunctions = [matchExactTagNames, matchPlainText];
  429.         else if (tagNameQuery && startTagFound)
  430.             const searchFunctions = [matchStartOfTagNames, matchPlainText];
  431.         else if (tagNameQuery && endTagFound) {
  432.             // FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound.
  433.             // This requires ends-with() support in XPath, WebKit only supports starts-with() and contains().
  434.             const searchFunctions = [matchPartialTagNames, matchPlainText];
  435.         } else if (whitespaceTrimmedQuery === "//*" || whitespaceTrimmedQuery === "*") {
  436.             // These queries will match every node. Matching everything isn't useful and can be slow for large pages,
  437.             // so limit the search functions list to plain text and attribute matching.
  438.             const searchFunctions = [matchPartialAttributeValues, matchPlainText];
  439.         } else
  440.             const searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery];
  441.  
  442.         // Find all frames, iframes and object elements to search their documents.
  443.         const querySelectorAllFunction = InspectorController.inspectedWindow().Document.prototype.querySelectorAll;
  444.         const subdocumentResult = querySelectorAllFunction.call(mainFrameDocument, "iframe, frame, object");
  445.  
  446.         for (var i = 0; i < subdocumentResult.length; ++i) {
  447.             var element = subdocumentResult.item(i);
  448.             if (element.contentDocument)
  449.                 searchDocuments.push(element.contentDocument);
  450.         }
  451.  
  452.         const panel = this;
  453.         var documentIndex = 0;
  454.         var searchFunctionIndex = 0;
  455.         var chunkIntervalIdentifier = null;
  456.  
  457.         // Split up the work into chunks so we don't block the UI thread while processing.
  458.  
  459.         function processChunk()
  460.         {
  461.             var searchDocument = searchDocuments[documentIndex];
  462.             var searchFunction = searchFunctions[searchFunctionIndex];
  463.  
  464.             if (++searchFunctionIndex > searchFunctions.length) {
  465.                 searchFunction = searchFunctions[0];
  466.                 searchFunctionIndex = 0;
  467.  
  468.                 if (++documentIndex > searchDocuments.length) {
  469.                     if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
  470.                         delete panel._currentSearchChunkIntervalIdentifier;
  471.                     clearInterval(chunkIntervalIdentifier);
  472.                     finishedSearching.call(panel);
  473.                     return;
  474.                 }
  475.  
  476.                 searchDocument = searchDocuments[documentIndex];
  477.             }
  478.  
  479.             if (!searchDocument || !searchFunction)
  480.                 return;
  481.  
  482.             try {
  483.                 searchFunction.call(panel, searchDocument);
  484.             } catch(err) {
  485.                 // ignore any exceptions. the query might be malformed, but we allow that.
  486.             }
  487.         }
  488.  
  489.         processChunk();
  490.  
  491.         chunkIntervalIdentifier = setInterval(processChunk, 25);
  492.         this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
  493.     },
  494.  
  495.     jumpToNextSearchResult: function()
  496.     {
  497.         if (!this._searchResults || !this._searchResults.length)
  498.             return;
  499.         if (++this._currentSearchResultIndex >= this._searchResults.length)
  500.             this._currentSearchResultIndex = 0;
  501.         this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex];
  502.     },
  503.  
  504.     jumpToPreviousSearchResult: function()
  505.     {
  506.         if (!this._searchResults || !this._searchResults.length)
  507.             return;
  508.         if (--this._currentSearchResultIndex < 0)
  509.             this._currentSearchResultIndex = (this._searchResults.length - 1);
  510.         this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex];
  511.     },
  512.  
  513.     inspectedWindowCleared: function(window)
  514.     {
  515.         if (InspectorController.isWindowVisible())
  516.             this.updateMutationEventListeners(window);
  517.     },
  518.  
  519.     _addMutationEventListeners: function(monitoredWindow)
  520.     {
  521.         monitoredWindow.document.addEventListener("DOMNodeInserted", this._nodeInsertedEventListener, true);
  522.         monitoredWindow.document.addEventListener("DOMNodeRemoved", this._nodeRemovedEventListener, true);
  523.         if (monitoredWindow.frameElement)
  524.             monitoredWindow.addEventListener("DOMContentLoaded", this._contentLoadedEventListener, true);
  525.     },
  526.  
  527.     _removeMutationEventListeners: function(monitoredWindow)
  528.     {
  529.         if (monitoredWindow.frameElement)
  530.             monitoredWindow.removeEventListener("DOMContentLoaded", this._contentLoadedEventListener, true);
  531.         if (!monitoredWindow.document)
  532.             return;
  533.         monitoredWindow.document.removeEventListener("DOMNodeInserted", this._nodeInsertedEventListener, true);
  534.         monitoredWindow.document.removeEventListener("DOMNodeRemoved", this._nodeRemovedEventListener, true);
  535.     },
  536.  
  537.     updateMutationEventListeners: function(monitoredWindow)
  538.     {
  539.         this._addMutationEventListeners(monitoredWindow);
  540.     },
  541.  
  542.     registerMutationEventListeners: function(monitoredWindow)
  543.     {
  544.         if (!monitoredWindow || this._mutationMonitoredWindows.indexOf(monitoredWindow) !== -1)
  545.             return;
  546.         this._mutationMonitoredWindows.push(monitoredWindow);
  547.         if (InspectorController.isWindowVisible())
  548.             this._addMutationEventListeners(monitoredWindow);
  549.     },
  550.  
  551.     unregisterMutationEventListeners: function(monitoredWindow)
  552.     {
  553.         if (!monitoredWindow || this._mutationMonitoredWindows.indexOf(monitoredWindow) === -1)
  554.             return;
  555.         this._mutationMonitoredWindows.remove(monitoredWindow);
  556.         this._removeMutationEventListeners(monitoredWindow);
  557.     },
  558.  
  559.     unregisterAllMutationEventListeners: function()
  560.     {
  561.         for (var i = 0; i < this._mutationMonitoredWindows.length; ++i)
  562.             this._removeMutationEventListeners(this._mutationMonitoredWindows[i]);
  563.         this._mutationMonitoredWindows = [];
  564.     },
  565.  
  566.     get rootDOMNode()
  567.     {
  568.         return this.treeOutline.rootDOMNode;
  569.     },
  570.  
  571.     set rootDOMNode(x)
  572.     {
  573.         this.treeOutline.rootDOMNode = x;
  574.     },
  575.  
  576.     get focusedDOMNode()
  577.     {
  578.         return this.treeOutline.focusedDOMNode;
  579.     },
  580.  
  581.     set focusedDOMNode(x)
  582.     {
  583.         this.treeOutline.focusedDOMNode = x;
  584.     },
  585.  
  586.     _contentLoaded: function(event)
  587.     {
  588.         this.recentlyModifiedNodes.push({node: event.target, parent: event.target.defaultView.frameElement, replaced: true});
  589.         if (this.visible)
  590.             this._updateModifiedNodesSoon();
  591.     },
  592.  
  593.     _nodeInserted: function(event)
  594.     {
  595.         this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, inserted: true});
  596.         if (this.visible)
  597.             this._updateModifiedNodesSoon();
  598.     },
  599.  
  600.     _nodeRemoved: function(event)
  601.     {
  602.         this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, removed: true});
  603.         if (this.visible)
  604.             this._updateModifiedNodesSoon();
  605.     },
  606.  
  607.     _updateModifiedNodesSoon: function()
  608.     {
  609.         if ("_updateModifiedNodesTimeout" in this)
  610.             return;
  611.         this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 0);
  612.     },
  613.  
  614.     _updateModifiedNodes: function()
  615.     {
  616.         if ("_updateModifiedNodesTimeout" in this) {
  617.             clearTimeout(this._updateModifiedNodesTimeout);
  618.             delete this._updateModifiedNodesTimeout;
  619.         }
  620.  
  621.         var updatedParentTreeElements = [];
  622.         var updateBreadcrumbs = false;
  623.  
  624.         for (var i = 0; i < this.recentlyModifiedNodes.length; ++i) {
  625.             var replaced = this.recentlyModifiedNodes[i].replaced;
  626.             var parent = this.recentlyModifiedNodes[i].parent;
  627.             if (!parent)
  628.                 continue;
  629.  
  630.             var parentNodeItem = this.treeOutline.findTreeElement(parent, null, null, objectsAreSame);
  631.             if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) {
  632.                 parentNodeItem.updateChildren(replaced);
  633.                 parentNodeItem.alreadyUpdatedChildren = true;
  634.                 updatedParentTreeElements.push(parentNodeItem);
  635.             }
  636.  
  637.             if (!updateBreadcrumbs && (objectsAreSame(this.focusedDOMNode, parent) || isAncestorIncludingParentFrames(this.focusedDOMNode, parent)))
  638.                 updateBreadcrumbs = true;
  639.         }
  640.  
  641.         for (var i = 0; i < updatedParentTreeElements.length; ++i)
  642.             delete updatedParentTreeElements[i].alreadyUpdatedChildren;
  643.  
  644.         this.recentlyModifiedNodes = [];
  645.  
  646.         if (updateBreadcrumbs)
  647.             this.updateBreadcrumb(true);
  648.     },
  649.  
  650.     _stylesPaneEdited: function()
  651.     {
  652.         this.sidebarPanes.metrics.needsUpdate = true;
  653.         this.updateMetrics();
  654.     },
  655.  
  656.     _metricsPaneEdited: function()
  657.     {
  658.         this.sidebarPanes.styles.needsUpdate = true;
  659.         this.updateStyles(true);
  660.     },
  661.  
  662.     _mouseMovedInCrumbs: function(event)
  663.     {
  664.         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
  665.         var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");
  666.  
  667.         WebInspector.hoveredDOMNode = (crumbElement ? crumbElement.representedObject : null);
  668.  
  669.         if ("_mouseOutOfCrumbsTimeout" in this) {
  670.             clearTimeout(this._mouseOutOfCrumbsTimeout);
  671.             delete this._mouseOutOfCrumbsTimeout;
  672.         }
  673.     },
  674.  
  675.     _mouseMovedOutOfCrumbs: function(event)
  676.     {
  677.         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
  678.         if (nodeUnderMouse.isDescendant(this.crumbsElement))
  679.             return;
  680.  
  681.         WebInspector.hoveredDOMNode = null;
  682.  
  683.         this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000);
  684.     },
  685.  
  686.     updateBreadcrumb: function(forceUpdate)
  687.     {
  688.         if (!this.visible)
  689.             return;
  690.  
  691.         var crumbs = this.crumbsElement;
  692.  
  693.         var handled = false;
  694.         var foundRoot = false;
  695.         var crumb = crumbs.firstChild;
  696.         while (crumb) {
  697.             if (objectsAreSame(crumb.representedObject, this.rootDOMNode))
  698.                 foundRoot = true;
  699.  
  700.             if (foundRoot)
  701.                 crumb.addStyleClass("dimmed");
  702.             else
  703.                 crumb.removeStyleClass("dimmed");
  704.  
  705.             if (objectsAreSame(crumb.representedObject, this.focusedDOMNode)) {
  706.                 crumb.addStyleClass("selected");
  707.                 handled = true;
  708.             } else {
  709.                 crumb.removeStyleClass("selected");
  710.             }
  711.  
  712.             crumb = crumb.nextSibling;
  713.         }
  714.  
  715.         if (handled && !forceUpdate) {
  716.             // We don't need to rebuild the crumbs, but we need to adjust sizes
  717.             // to reflect the new focused or root node.
  718.             this.updateBreadcrumbSizes();
  719.             return;
  720.         }
  721.  
  722.         crumbs.removeChildren();
  723.  
  724.         var panel = this;
  725.  
  726.         function selectCrumbFunction(event)
  727.         {
  728.             var crumb = event.currentTarget;
  729.             if (crumb.hasStyleClass("collapsed")) {
  730.                 // Clicking a collapsed crumb will expose the hidden crumbs.
  731.                 if (crumb === panel.crumbsElement.firstChild) {
  732.                     // If the focused crumb is the first child, pick the farthest crumb
  733.                     // that is still hidden. This allows the user to expose every crumb.
  734.                     var currentCrumb = crumb;
  735.                     while (currentCrumb) {
  736.                         var hidden = currentCrumb.hasStyleClass("hidden");
  737.                         var collapsed = currentCrumb.hasStyleClass("collapsed");
  738.                         if (!hidden && !collapsed)
  739.                             break;
  740.                         crumb = currentCrumb;
  741.                         currentCrumb = currentCrumb.nextSibling;
  742.                     }
  743.                 }
  744.  
  745.                 panel.updateBreadcrumbSizes(crumb);
  746.             } else {
  747.                 // Clicking a dimmed crumb or double clicking (event.detail >= 2)
  748.                 // will change the root node in addition to the focused node.
  749.                 if (event.detail >= 2 || crumb.hasStyleClass("dimmed"))
  750.                     panel.rootDOMNode = crumb.representedObject.parentNode;
  751.                 panel.focusedDOMNode = crumb.representedObject;
  752.             }
  753.  
  754.             event.preventDefault();
  755.         }
  756.  
  757.         foundRoot = false;
  758.         for (var current = this.focusedDOMNode; current; current = parentNodeOrFrameElement(current)) {
  759.             if (current.nodeType === Node.DOCUMENT_NODE)
  760.                 continue;
  761.  
  762.             if (objectsAreSame(current, this.rootDOMNode))
  763.                 foundRoot = true;
  764.  
  765.             var crumb = document.createElement("span");
  766.             crumb.className = "crumb";
  767.             crumb.representedObject = current;
  768.             crumb.addEventListener("mousedown", selectCrumbFunction, false);
  769.  
  770.             var crumbTitle;
  771.             switch (current.nodeType) {
  772.                 case Node.ELEMENT_NODE:
  773.                     crumbTitle = current.nodeName.toLowerCase();
  774.  
  775.                     var nameElement = document.createElement("span");
  776.                     nameElement.textContent = crumbTitle;
  777.                     crumb.appendChild(nameElement);
  778.  
  779.                     var idAttribute = current.getAttribute("id");
  780.                     if (idAttribute) {
  781.                         var idElement = document.createElement("span");
  782.                         crumb.appendChild(idElement);
  783.  
  784.                         var part = "#" + idAttribute;
  785.                         crumbTitle += part;
  786.                         idElement.appendChild(document.createTextNode(part));
  787.  
  788.                         // Mark the name as extra, since the ID is more important.
  789.                         nameElement.className = "extra";
  790.                     }
  791.  
  792.                     var classAttribute = current.getAttribute("class");
  793.                     if (classAttribute) {
  794.                         var classes = classAttribute.split(/\s+/);
  795.                         var foundClasses = {};
  796.  
  797.                         if (classes.length) {
  798.                             var classesElement = document.createElement("span");
  799.                             classesElement.className = "extra";
  800.                             crumb.appendChild(classesElement);
  801.  
  802.                             for (var i = 0; i < classes.length; ++i) {
  803.                                 var className = classes[i];
  804.                                 if (className && !(className in foundClasses)) {
  805.                                     var part = "." + className;
  806.                                     crumbTitle += part;
  807.                                     classesElement.appendChild(document.createTextNode(part));
  808.                                     foundClasses[className] = true;
  809.                                 }
  810.                             }
  811.                         }
  812.                     }
  813.  
  814.                     break;
  815.  
  816.                 case Node.TEXT_NODE:
  817.                     if (isNodeWhitespace.call(current))
  818.                         crumbTitle = WebInspector.UIString("(whitespace)");
  819.                     else
  820.                         crumbTitle = WebInspector.UIString("(text)");
  821.                     break
  822.  
  823.                 case Node.COMMENT_NODE:
  824.                     crumbTitle = "<!-->";
  825.                     break;
  826.  
  827.                 case Node.DOCUMENT_TYPE_NODE:
  828.                     crumbTitle = "<!DOCTYPE>";
  829.                     break;
  830.  
  831.                 default:
  832.                     crumbTitle = current.nodeName.toLowerCase();
  833.             }
  834.  
  835.             if (!crumb.childNodes.length) {
  836.                 var nameElement = document.createElement("span");
  837.                 nameElement.textContent = crumbTitle;
  838.                 crumb.appendChild(nameElement);
  839.             }
  840.  
  841.             crumb.title = crumbTitle;
  842.  
  843.             if (foundRoot)
  844.                 crumb.addStyleClass("dimmed");
  845.             if (objectsAreSame(current, this.focusedDOMNode))
  846.                 crumb.addStyleClass("selected");
  847.             if (!crumbs.childNodes.length)
  848.                 crumb.addStyleClass("end");
  849.  
  850.             crumbs.appendChild(crumb);
  851.         }
  852.  
  853.         if (crumbs.hasChildNodes())
  854.             crumbs.lastChild.addStyleClass("start");
  855.  
  856.         this.updateBreadcrumbSizes();
  857.     },
  858.  
  859.     updateBreadcrumbSizes: function(focusedCrumb)
  860.     {
  861.         if (!this.visible)
  862.             return;
  863.  
  864.         if (document.body.offsetWidth <= 0) {
  865.             // The stylesheet hasn't loaded yet or the window is closed,
  866.             // so we can't calculate what is need. Return early.
  867.             return;
  868.         }
  869.  
  870.         var crumbs = this.crumbsElement;
  871.         if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0)
  872.             return; // No crumbs, do nothing.
  873.  
  874.         // A Zero index is the right most child crumb in the breadcrumb.
  875.         var selectedIndex = 0;
  876.         var focusedIndex = 0;
  877.         var selectedCrumb;
  878.  
  879.         var i = 0;
  880.         var crumb = crumbs.firstChild;
  881.         while (crumb) {
  882.             // Find the selected crumb and index. 
  883.             if (!selectedCrumb && crumb.hasStyleClass("selected")) {
  884.                 selectedCrumb = crumb;
  885.                 selectedIndex = i;
  886.             }
  887.  
  888.             // Find the focused crumb index. 
  889.             if (crumb === focusedCrumb)
  890.                 focusedIndex = i;
  891.  
  892.             // Remove any styles that affect size before
  893.             // deciding to shorten any crumbs.
  894.             if (crumb !== crumbs.lastChild)
  895.                 crumb.removeStyleClass("start");
  896.             if (crumb !== crumbs.firstChild)
  897.                 crumb.removeStyleClass("end");
  898.  
  899.             crumb.removeStyleClass("compact");
  900.             crumb.removeStyleClass("collapsed");
  901.             crumb.removeStyleClass("hidden");
  902.  
  903.             crumb = crumb.nextSibling;
  904.             ++i;
  905.         }
  906.  
  907.         // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs().
  908.         // The order of the crumbs in the document is opposite of the visual order.
  909.         crumbs.firstChild.addStyleClass("end");
  910.         crumbs.lastChild.addStyleClass("start");
  911.  
  912.         function crumbsAreSmallerThanContainer()
  913.         {
  914.             var rightPadding = 20;
  915.             var errorWarningElement = document.getElementById("error-warning-count");
  916.             if (!WebInspector.console.visible && errorWarningElement)
  917.                 rightPadding += errorWarningElement.offsetWidth;
  918.             return ((crumbs.totalOffsetLeft + crumbs.offsetWidth + rightPadding) < window.innerWidth);
  919.         }
  920.  
  921.         if (crumbsAreSmallerThanContainer())
  922.             return; // No need to compact the crumbs, they all fit at full size.
  923.  
  924.         var BothSides = 0;
  925.         var AncestorSide = -1;
  926.         var ChildSide = 1;
  927.  
  928.         function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb)
  929.         {
  930.             if (!significantCrumb)
  931.                 significantCrumb = (focusedCrumb || selectedCrumb);
  932.  
  933.             if (significantCrumb === selectedCrumb)
  934.                 var significantIndex = selectedIndex;
  935.             else if (significantCrumb === focusedCrumb)
  936.                 var significantIndex = focusedIndex;
  937.             else {
  938.                 var significantIndex = 0;
  939.                 for (var i = 0; i < crumbs.childNodes.length; ++i) {
  940.                     if (crumbs.childNodes[i] === significantCrumb) {
  941.                         significantIndex = i;
  942.                         break;
  943.                     }
  944.                 }
  945.             }
  946.  
  947.             function shrinkCrumbAtIndex(index)
  948.             {
  949.                 var shrinkCrumb = crumbs.childNodes[index];
  950.                 if (shrinkCrumb && shrinkCrumb !== significantCrumb)
  951.                     shrinkingFunction(shrinkCrumb);
  952.                 if (crumbsAreSmallerThanContainer())
  953.                     return true; // No need to compact the crumbs more.
  954.                 return false;
  955.             }
  956.  
  957.             // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs
  958.             // fit in the container or we run out of crumbs to shrink.
  959.             if (direction) {
  960.                 // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb.
  961.                 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1);
  962.                 while (index !== significantIndex) {
  963.                     if (shrinkCrumbAtIndex(index))
  964.                         return true;
  965.                     index += (direction > 0 ? 1 : -1);
  966.                 }
  967.             } else {
  968.                 // Crumbs are shrunk in order of descending distance from the signifcant crumb,
  969.                 // with a tie going to child crumbs.
  970.                 var startIndex = 0;
  971.                 var endIndex = crumbs.childNodes.length - 1;
  972.                 while (startIndex != significantIndex || endIndex != significantIndex) {
  973.                     var startDistance = significantIndex - startIndex;
  974.                     var endDistance = endIndex - significantIndex;
  975.                     if (startDistance >= endDistance)
  976.                         var index = startIndex++;
  977.                     else
  978.                         var index = endIndex--;
  979.                     if (shrinkCrumbAtIndex(index))
  980.                         return true;
  981.                 }
  982.             }
  983.  
  984.             // We are not small enough yet, return false so the caller knows.
  985.             return false;
  986.         }
  987.  
  988.         function coalesceCollapsedCrumbs()
  989.         {
  990.             var crumb = crumbs.firstChild;
  991.             var collapsedRun = false;
  992.             var newStartNeeded = false;
  993.             var newEndNeeded = false;
  994.             while (crumb) {
  995.                 var hidden = crumb.hasStyleClass("hidden");
  996.                 if (!hidden) {
  997.                     var collapsed = crumb.hasStyleClass("collapsed"); 
  998.                     if (collapsedRun && collapsed) {
  999.                         crumb.addStyleClass("hidden");
  1000.                         crumb.removeStyleClass("compact");
  1001.                         crumb.removeStyleClass("collapsed");
  1002.  
  1003.                         if (crumb.hasStyleClass("start")) {
  1004.                             crumb.removeStyleClass("start");
  1005.                             newStartNeeded = true;
  1006.                         }
  1007.  
  1008.                         if (crumb.hasStyleClass("end")) {
  1009.                             crumb.removeStyleClass("end");
  1010.                             newEndNeeded = true;
  1011.                         }
  1012.  
  1013.                         continue;
  1014.                     }
  1015.  
  1016.                     collapsedRun = collapsed;
  1017.  
  1018.                     if (newEndNeeded) {
  1019.                         newEndNeeded = false;
  1020.                         crumb.addStyleClass("end");
  1021.                     }
  1022.                 } else
  1023.                     collapsedRun = true;
  1024.                 crumb = crumb.nextSibling;
  1025.             }
  1026.  
  1027.             if (newStartNeeded) {
  1028.                 crumb = crumbs.lastChild;
  1029.                 while (crumb) {
  1030.                     if (!crumb.hasStyleClass("hidden")) {
  1031.                         crumb.addStyleClass("start");
  1032.                         break;
  1033.                     }
  1034.                     crumb = crumb.previousSibling;
  1035.                 }
  1036.             }
  1037.         }
  1038.  
  1039.         function compact(crumb)
  1040.         {
  1041.             if (crumb.hasStyleClass("hidden"))
  1042.                 return;
  1043.             crumb.addStyleClass("compact");
  1044.         }
  1045.  
  1046.         function collapse(crumb, dontCoalesce)
  1047.         {
  1048.             if (crumb.hasStyleClass("hidden"))
  1049.                 return;
  1050.             crumb.addStyleClass("collapsed");
  1051.             crumb.removeStyleClass("compact");
  1052.             if (!dontCoalesce)
  1053.                 coalesceCollapsedCrumbs();
  1054.         }
  1055.  
  1056.         function compactDimmed(crumb)
  1057.         {
  1058.             if (crumb.hasStyleClass("dimmed"))
  1059.                 compact(crumb);
  1060.         }
  1061.  
  1062.         function collapseDimmed(crumb)
  1063.         {
  1064.             if (crumb.hasStyleClass("dimmed"))
  1065.                 collapse(crumb);
  1066.         }
  1067.  
  1068.         if (!focusedCrumb) {
  1069.             // When not focused on a crumb we can be biased and collapse less important
  1070.             // crumbs that the user might not care much about.
  1071.  
  1072.             // Compact child crumbs.
  1073.             if (makeCrumbsSmaller(compact, ChildSide))
  1074.                 return;
  1075.  
  1076.             // Collapse child crumbs.
  1077.             if (makeCrumbsSmaller(collapse, ChildSide))
  1078.                 return;
  1079.  
  1080.             // Compact dimmed ancestor crumbs.
  1081.             if (makeCrumbsSmaller(compactDimmed, AncestorSide))
  1082.                 return;
  1083.  
  1084.             // Collapse dimmed ancestor crumbs.
  1085.             if (makeCrumbsSmaller(collapseDimmed, AncestorSide))
  1086.                 return;
  1087.         }
  1088.  
  1089.         // Compact ancestor crumbs, or from both sides if focused.
  1090.         if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide)))
  1091.             return;
  1092.  
  1093.         // Collapse ancestor crumbs, or from both sides if focused.
  1094.         if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide)))
  1095.             return;
  1096.  
  1097.         if (!selectedCrumb)
  1098.             return;
  1099.  
  1100.         // Compact the selected crumb.
  1101.         compact(selectedCrumb);
  1102.         if (crumbsAreSmallerThanContainer())
  1103.             return;
  1104.  
  1105.         // Collapse the selected crumb as a last resort. Pass true to prevent coalescing.
  1106.         collapse(selectedCrumb, true);
  1107.     },
  1108.  
  1109.     updateStyles: function(forceUpdate)
  1110.     {
  1111.         var stylesSidebarPane = this.sidebarPanes.styles;
  1112.         if (!stylesSidebarPane.expanded || !stylesSidebarPane.needsUpdate)
  1113.             return;
  1114.  
  1115.         stylesSidebarPane.update(this.focusedDOMNode, null, forceUpdate);
  1116.         stylesSidebarPane.needsUpdate = false;
  1117.     },
  1118.  
  1119.     updateMetrics: function()
  1120.     {
  1121.         var metricsSidebarPane = this.sidebarPanes.metrics;
  1122.         if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate)
  1123.             return;
  1124.  
  1125.         metricsSidebarPane.update(this.focusedDOMNode);
  1126.         metricsSidebarPane.needsUpdate = false;
  1127.     },
  1128.  
  1129.     updateProperties: function()
  1130.     {
  1131.         var propertiesSidebarPane = this.sidebarPanes.properties;
  1132.         if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdate)
  1133.             return;
  1134.  
  1135.         propertiesSidebarPane.update(this.focusedDOMNode);
  1136.         propertiesSidebarPane.needsUpdate = false;
  1137.     },
  1138.  
  1139.     handleKeyEvent: function(event)
  1140.     {
  1141.         this.treeOutline.handleKeyEvent(event);
  1142.     },
  1143.  
  1144.     handleCopyEvent: function(event)
  1145.     {
  1146.         // Don't prevent the normal copy if the user has a selection.
  1147.         if (!window.getSelection().isCollapsed)
  1148.             return;
  1149.  
  1150.         switch (this.focusedDOMNode.nodeType) {
  1151.             case Node.ELEMENT_NODE:
  1152.                 var data = this.focusedDOMNode.outerHTML;
  1153.                 break;
  1154.  
  1155.             case Node.COMMENT_NODE:
  1156.                 var data = "<!--" + this.focusedDOMNode.nodeValue + "-->";
  1157.                 break;
  1158.  
  1159.             default:
  1160.             case Node.TEXT_NODE:
  1161.                 var data = this.focusedDOMNode.nodeValue;
  1162.         }
  1163.  
  1164.         event.clipboardData.clearData();
  1165.         event.preventDefault();
  1166.  
  1167.         if (data)
  1168.             event.clipboardData.setData("text/plain", data);
  1169.     },
  1170.  
  1171.     rightSidebarResizerDragStart: function(event)
  1172.     {
  1173.         WebInspector.elementDragStart(this.sidebarElement, this.rightSidebarResizerDrag.bind(this), this.rightSidebarResizerDragEnd.bind(this), event, "col-resize");
  1174.     },
  1175.  
  1176.     rightSidebarResizerDragEnd: function(event)
  1177.     {
  1178.         WebInspector.elementDragEnd(event);
  1179.     },
  1180.  
  1181.     rightSidebarResizerDrag: function(event)
  1182.     {
  1183.         var x = event.pageX;
  1184.         var newWidth = Number.constrain(window.innerWidth - x, Preferences.minElementsSidebarWidth, window.innerWidth * 0.66);
  1185.  
  1186.         this.sidebarElement.style.width = newWidth + "px";
  1187.         this.contentElement.style.right = newWidth + "px";
  1188.         this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
  1189.  
  1190.         this.treeOutline.updateSelection();
  1191.  
  1192.         event.preventDefault();
  1193.     },
  1194.  
  1195.     _nodeSearchButtonClicked: function(event)
  1196.     {
  1197.         InspectorController.toggleNodeSearch();
  1198.  
  1199.         if (InspectorController.searchingForNode())
  1200.             this.nodeSearchButton.addStyleClass("toggled-on");
  1201.         else
  1202.             this.nodeSearchButton.removeStyleClass("toggled-on");
  1203.     }
  1204. }
  1205.  
  1206. WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
  1207.