Changeset 27789 in webkit


Ignore:
Timestamp:
Nov 14, 2007 11:19:09 AM (16 years ago)
Author:
timothy@apple.com
Message:

Reviewed by Adam.

Bug 14380: Long DOM ancestry breadcrumb lists get cut off
http://bugs.webkit.org/show_bug.cgi?id=14380

The breadcumbs will now be compacted and collapsed if there isn't enough room
to show everything. The collapsing algorithm always affects the crumbs that
are farthest away from the selected or hovered crumb first.

  • page/inspector/DocumentPanel.js:
  • page/inspector/inspector.css:
Location:
trunk/WebCore
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/WebCore/ChangeLog

    r27786 r27789  
     12007-11-14  Timothy Hatcher  <timothy@apple.com>
     2
     3        Reviewed by Adam.
     4
     5        Bug 14380: Long DOM ancestry breadcrumb lists get cut off
     6        http://bugs.webkit.org/show_bug.cgi?id=14380
     7
     8        The breadcumbs will now be compacted and collapsed if there isn't enough room
     9        to show everything. The collapsing algorithm always affects the crumbs that
     10        are farthest away from the selected or hovered crumb first.
     11
     12        * page/inspector/DocumentPanel.js:
     13        * page/inspector/inspector.css:
     14
    1152007-11-14  Anders Carlsson  <andersca@apple.com>
    216
  • trunk/WebCore/page/inspector/DocumentPanel.js

    r27735 r27789  
    3535    WebInspector.SourcePanel.call(this, resource, allViews);
    3636
     37    var panel = this;
    3738    var domView = this.views.dom;
    38     domView.show = function() { InspectorController.highlightDOMNode(panel.focusedDOMNode) };
    3939    domView.hide = function() { InspectorController.hideDOMNodeHighlight() };
     40    domView.show = function() {
     41        InspectorController.highlightDOMNode(panel.focusedDOMNode);
     42        panel.updateBreadcrumbSizes();
     43        panel.updateTreeSelection();
     44    };
    4045
    4146    domView.sideContentElement = document.createElement("div");
     
    6065    domView.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
    6166
    62     var panel = this;
    6367    domView.sidebarPanes.styles.onexpand = function() { panel.updateStyles() };
    6468    domView.sidebarPanes.metrics.onexpand = function() { panel.updateMetrics() };
     
    9094
    9195WebInspector.DocumentPanel.prototype = {
    92     show: function()
    93     {
    94         WebInspector.SourcePanel.prototype.show.call(this);
     96    resize: function()
     97    {
    9598        this.updateTreeSelection();
    96     },
    97 
    98     resize: function()
    99     {
    100         this.updateTreeSelection();
     99        this.updateBreadcrumbSizes();
    101100    },
    102101
     
    191190    updateBreadcrumb: function()
    192191    {
    193         if (!this.views || !this.views.dom.contentElement)
    194             return;
    195192        var crumbs = this.views.dom.innerCrumbsElement;
    196         if (!crumbs)
    197             return;
    198193
    199194        var handled = false;
     
    205200
    206201            if (foundRoot)
    207                 crumb.addStyleClass("hidden");
     202                crumb.addStyleClass("dimmed");
    208203            else
    209                 crumb.removeStyleClass("hidden");
     204                crumb.removeStyleClass("dimmed");
    210205
    211206            if (crumb.representedObject === this.focusedDOMNode) {
     
    219214        }
    220215
    221         if (handled)
    222             return;
     216        if (handled) {
     217            // We don't need to rebuild the crumbs, but we need to adjust sizes
     218            // to reflect the new focused or root node.
     219            this.updateBreadcrumbSizes();
     220            return;
     221        }
    223222
    224223        crumbs.removeChildren();
    225224
    226225        var panel = this;
    227         var selectCrumbFunction = function(event)
    228         {
    229             if (event.currentTarget.hasStyleClass("hidden"))
     226        var selectCrumbFunction = function(event) {
     227            // Clicking a dimmed crumb or double clicking (event.detail >= 2)
     228            // will change the root node in addition to the focused node.
     229            if (event.detail >= 2 || event.currentTarget.hasStyleClass("dimmed"))
    230230                panel.rootDOMNode = event.currentTarget.representedObject.parentNode;
    231231            panel.focusedDOMNode = event.currentTarget.representedObject;
    232232            event.preventDefault();
    233             event.stopPropagation();
    234         }
    235 
    236         var selectCrumbRootFunction = function(event)
    237         {
    238             panel.rootDOMNode = event.currentTarget.representedObject.parentNode;
    239             panel.focusedDOMNode = event.currentTarget.representedObject;
    240             event.preventDefault();
    241             event.stopPropagation();
    242         }
     233        };
     234
     235        var mouseOverCrumbFunction = function(event) {
     236            panel.mouseOverCrumb = true;
     237
     238            if ("mouseOutTimeout" in panel) {
     239                clearTimeout(panel.mouseOutTimeout);
     240                delete panel.mouseOutTimeout;
     241            }
     242
     243            panel.updateBreadcrumbSizes(event.currentTarget);
     244        };
     245
     246        var mouseOutCrumbFunction = function(event) {
     247            delete panel.mouseOverCrumb;
     248
     249            if ("mouseOutTimeout" in panel) {
     250                clearTimeout(panel.mouseOutTimeout);
     251                delete panel.mouseOutTimeout;
     252            }
     253
     254            var timeoutFunction = function() {
     255                if (!panel.mouseOverCrumb)
     256                    panel.updateBreadcrumbSizes();
     257            };
     258
     259            panel.mouseOutTimeout = setTimeout(timeoutFunction, 250);
     260        };
    243261
    244262        foundRoot = false;
     
    255273            crumb.representedObject = current;
    256274            crumb.addEventListener("mousedown", selectCrumbFunction, false);
    257             crumb.addEventListener("dblclick", selectCrumbRootFunction, false);
     275            crumb.addEventListener("mouseover", mouseOverCrumbFunction, false);
     276            crumb.addEventListener("mouseout", mouseOutCrumbFunction, false);
    258277
    259278            var crumbTitle;
     
    261280                case Node.ELEMENT_NODE:
    262281                    crumbTitle = current.nodeName.toLowerCase();
    263    
     282
     283                    var nameElement = document.createElement("span");
     284                    nameElement.textContent = crumbTitle;
     285                    crumb.appendChild(nameElement);
     286
     287                    var selectorElement = document.createElement("span");
     288                    selectorElement.className = "extra";
     289                    crumb.appendChild(selectorElement);
     290
    264291                    var value = current.getAttribute("id");
    265                     if (value && value.length)
    266                         crumbTitle += "#" + value;
     292                    if (value) {
     293                        var part = "#" + value;
     294                        crumbTitle += part;
     295                        selectorElement.appendChild(document.createTextNode(part));
     296                    }
    267297
    268298                    value = current.getAttribute("class");
    269                     if (value && value.length) {
     299                    if (value) {
    270300                        var classes = value.split(/\s+/);
    271                         var classesLength = classes.length;
    272                         for (var i = 0; i < classesLength; ++i) {
     301                        var foundClasses = {};
     302                        for (var i = 0; i < classes.length; ++i) {
    273303                            value = classes[i];
    274                             if (value && value.length)
    275                                 crumbTitle += "." + value;
     304                            if (value && !(value in foundClasses)) {
     305                                var part = "." + value;
     306                                crumbTitle += part;
     307                                selectorElement.appendChild(document.createTextNode(part));
     308                                foundClasses[value] = true;
     309                            }
    276310                        }
    277311                    }
     
    294328            }
    295329
    296             crumb.textContent = crumbTitle;
     330            if (!crumb.childNodes.length) {
     331                var nameElement = document.createElement("span");
     332                nameElement.textContent = crumbTitle;
     333                crumb.appendChild(nameElement);
     334            }
     335
     336            crumb.title = crumbTitle;
    297337
    298338            if (foundRoot)
    299                 crumb.addStyleClass("hidden");
     339                crumb.addStyleClass("dimmed");
    300340            if (current === this.focusedDOMNode)
    301341                crumb.addStyleClass("selected");
     
    308348            current = current.parentNode;
    309349        }
     350
     351        this.updateBreadcrumbSizes();
     352    },
     353
     354    updateBreadcrumbSizes: function(hoveredCrumb)
     355    {
     356        var crumbs = this.views.dom.innerCrumbsElement;
     357        if (!crumbs.childNodes.length)
     358            return; // No crumbs, do nothing.
     359
     360        var crumbsContainer = this.views.dom.crumbsElement;
     361        if (crumbsContainer.offsetWidth <= 0 || crumbs.offsetWidth <= 0)
     362            return; // The cumbs are not visible yet, do nothing.
     363
     364        var selectedCrumb;
     365
     366        // Remove any styles that affect size before deciding to shorten any crumbs.
     367        var crumb = crumbs.firstChild;
     368        while (crumb) {
     369            if (!selectedCrumb && crumb.hasStyleClass("selected"))
     370                selectedCrumb = crumb;
     371            if (crumb !== crumbs.lastChild)
     372                crumb.removeStyleClass("start");
     373            if (crumb !== crumbs.firstChild)
     374                crumb.removeStyleClass("end");
     375            crumb.removeStyleClass("compact");
     376            crumb.removeStyleClass("collapsed");
     377            crumb.removeStyleClass("hidden");
     378            crumb = crumb.nextSibling;
     379        }
     380
     381        // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs().
     382        // The order of the crumbs in the document is opposite of the visual order.
     383        crumbs.firstChild.addStyleClass("end");
     384        crumbs.lastChild.addStyleClass("start");
     385
     386        function crumbsAreSmallerThanContainer()
     387        {
     388            // There is some fixed extra space that is not returned in the crumbs' offsetWidth.
     389            // This padding is added to the crumbs' offsetWidth when comparing to the crumbsContainer.
     390            var rightPadding = 9;
     391            return ((crumbs.offsetWidth + rightPadding) < crumbsContainer.offsetWidth);
     392        }
     393
     394        if (crumbsAreSmallerThanContainer())
     395            return; // No need to compact the crumbs, they all fit at full size.
     396
     397        function makeCrumbsSmaller(shrinkingFunction, significantCrumb)
     398        {
     399            if (!significantCrumb)
     400                significantCrumb = (hoveredCrumb || selectedCrumb);
     401
     402            // Look for the significant crumb in reverse order, so if we don't find it the index will be Zero.
     403            // A Zero index is the right most visual position in the breadcrumb.
     404            for (var significantIndex = (crumbs.childNodes.length - 1); significantIndex >= 0; --significantIndex)
     405                if (crumbs.childNodes[significantIndex] === significantCrumb)
     406                    break;
     407
     408            // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs
     409            // fit in the crumbsContainer or we run out of crumbs to shrink. Crumbs are shrunk
     410            // in order of descending distance from the signifcant crumb, with a tie going
     411            // to crumbs on the left.
     412
     413            var startIndex = 0;
     414            var endIndex = crumbs.childNodes.length - 1;
     415            while (startIndex != significantIndex || endIndex != significantIndex) {
     416                var startDistance = significantIndex - startIndex;
     417                var endDistance = endIndex - significantIndex;
     418                if (startDistance > endDistance) {
     419                    var shrinkCrumb = crumbs.childNodes[startIndex];
     420                    ++startIndex;
     421                } else {
     422                    var shrinkCrumb = crumbs.childNodes[endIndex];
     423                    --endIndex;
     424                }
     425
     426                if (shrinkCrumb && shrinkCrumb !== significantCrumb)
     427                    shrinkingFunction(shrinkCrumb);
     428
     429                if (crumbsAreSmallerThanContainer())
     430                    return true; // No need to compact the crumbs more.
     431            }
     432
     433            // We are not small enough yet, return false so the caller knows.
     434            return false;
     435        }
     436
     437        function coalesceCollapsedCrumbs()
     438        {
     439            var crumb = crumbs.firstChild;
     440            var collapsedRun = false;
     441            var newStartNeeded = false;
     442            var newEndNeeded = false;
     443            while (crumb) {
     444                var hidden = crumb.hasStyleClass("hidden");
     445                if (!hidden) {
     446                    var collapsed = crumb.hasStyleClass("collapsed");
     447                    if (collapsedRun && collapsed) {
     448                        crumb.addStyleClass("hidden");
     449                        crumb.removeStyleClass("compact");
     450                        crumb.removeStyleClass("collapsed");
     451
     452                        if (crumb.hasStyleClass("start")) {
     453                            crumb.removeStyleClass("start");
     454                            newStartNeeded = true;
     455                        }
     456
     457                        if (crumb.hasStyleClass("end")) {
     458                            crumb.removeStyleClass("end");
     459                            newEndNeeded = true;
     460                        }
     461
     462                        continue;
     463                    }
     464
     465                    collapsedRun = collapsed;
     466
     467                    if (newEndNeeded) {
     468                        newEndNeeded = false;
     469                        crumb.addStyleClass("end");
     470                    }
     471                } else
     472                    collapsedRun = true;
     473                crumb = crumb.nextSibling;
     474            }
     475
     476            if (newStartNeeded) {
     477                crumb = crumbs.lastChild;
     478                while (crumb) {
     479                    if (!crumb.hasStyleClass("hidden")) {
     480                        crumb.addStyleClass("start");
     481                        break;
     482                    }
     483                    crumb = crumb.previousSibling;
     484                }
     485            }
     486        }
     487
     488        function collapseDimmed(crumb)
     489        {
     490            if (crumb.hasStyleClass("dimmed")) {
     491                crumb.addStyleClass("collapsed");
     492                coalesceCollapsedCrumbs();
     493            }
     494        }
     495
     496        // Prefer collapsing the dimmed crumbs first, only if we don't have a hovered crumb.
     497        if (!hoveredCrumb && makeCrumbsSmaller(collapseDimmed))
     498            return; // No need to compact the crumbs more.
     499
     500        // Try compacting long crumbs next. Using the selected crumb as the significant crumbs makes
     501        // hovering more predicable and less jumpy.
     502        if (makeCrumbsSmaller(function(crumb) { crumb.addStyleClass("compact") }, selectedCrumb))
     503            return; // No need to compact the crumbs more.
     504
     505        // Since we are still too large and we avoided compacting the selected crumb in the last step,
     506        // try compacting the selected crumb now.
     507        if (selectedCrumb) {
     508            selectedCrumb.addStyleClass("compact");           
     509            if (crumbsAreSmallerThanContainer())
     510                return; // No need to compact the crumbs more.
     511        }
     512
     513        // Nothing else has worked, try collapsing crumbs.
     514        if (makeCrumbsSmaller(function(crumb) { crumb.addStyleClass("collapsed"); coalesceCollapsedCrumbs(); }))
     515            return; // No need to compact the crumbs more.
     516
     517        if (!selectedCrumb)
     518            return;
     519
     520        selectedCrumb.addStyleClass("collapsed");
    310521    },
    311522
  • trunk/WebCore/page/inspector/inspector.css

    r27735 r27789  
    833833    text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0;
    834834    color: rgb(20, 20, 20);
     835    overflow: hidden;
    835836}
    836837
     
    840841
    841842.crumbs .crumb {
     843    -webkit-box-sizing: border-box;
    842844    height: 20px;
    843845    border-width: 0 11px 0 0;
     
    851853}
    852854
    853 .crumbs .crumb.hidden {
    854     display: block;
     855.crumbs .crumb.collapsed > * {
     856    display: none;
     857}
     858
     859.crumbs .crumb.collapsed::before {
     860    content: "\2026"; /* ellipses */
     861    font-weight: bold;
     862}
     863
     864.crumbs .crumb.compact .extra {
     865    display: none;
     866}
     867
     868.crumbs .crumb.dimmed {
    855869    color: rgba(0, 0, 0, 0.45);
    856870}
     
    868882.crumbs .crumb.selected {
    869883    -webkit-border-image: url(Images/segmentSelected.png) 0 11 0 0;
     884    background-color: transparent !important;
    870885    color: black;
    871     background-color: transparent !important;
    872886}
    873887
     
    885899}
    886900
    887 .crumbs .crumb.hidden:hover {
     901.crumbs .crumb.dimmed:hover {
    888902    -webkit-border-image: url(Images/segmentHover.png) 0 11 0 0;
    889903    color: rgba(0, 0, 0, 0.75);
Note: See TracChangeset for help on using the changeset viewer.