Web Inspector: Create ResourceCollectionContentView and make CollectionContentView...
authormattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Sep 2017 20:41:03 +0000 (20:41 +0000)
committermattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Sep 2017 20:41:03 +0000 (20:41 +0000)
https://bugs.webkit.org/show_bug.cgi?id=177419

Reviewed by Devin Rousso.

CollectionContentView should be generic, work with any represented object
Collection, and not perform any type checking. It should just map items
to ContentViews using the provided ContentView constructor.

The behavior when clicking a ContentView in the collection has been extended.
If selection is enabled, clicking a ContentView will cause a "selected" class
to be applied to its element, and a SupplementalRepresentedObjectsDidChange
event is dispatched.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Main.html:
New file, move CollectionContentView above subclasses.

* UserInterface/Models/ResourceCollection.js:
(WI.ResourceCollection.prototype.get resourceType):
Make resource type publicly available.

* UserInterface/Views/CollectionContentView.js:
Move type checking of the collection out of the base class and assert
that ContentViews are created when invoking `contentViewConstructor`.
(WI.CollectionContentView):
(WI.CollectionContentView.titleForCollection):
(WI.CollectionContentView.prototype.get supplementalRepresentedObjects):
(WI.CollectionContentView.prototype.get selectionEnabled):
(WI.CollectionContentView.prototype.set selectionEnabled):
(WI.CollectionContentView.prototype.addContentViewForItem):
(WI.CollectionContentView.prototype.removeContentViewForItem):
(WI.CollectionContentView.prototype.contentViewAdded):
(WI.CollectionContentView.prototype.contentViewRemoved):
(WI.CollectionContentView.prototype.initialLayout):
(WI.CollectionContentView.prototype.attached):
(WI.CollectionContentView.prototype.detached):
(WI.CollectionContentView.prototype._handleItemAdded):
(WI.CollectionContentView.prototype._handleItemRemoved):
(WI.CollectionContentView.prototype._selectItem):
(WI.CollectionContentView.prototype._addContentViewForItem): Deleted.
(WI.CollectionContentView.prototype._removeContentViewForItem): Deleted.

* UserInterface/Views/ContentView.js:
(WI.ContentView.createFromRepresentedObject):
Create a ResourceCollectionContentView. In the future, additional
Collection types can be mapped to their associated CollectionContentView.

* UserInterface/Views/ResourceCollectionContentView.js: Added.
New class for resource-specific logic previously in CollectionContentView.
(WI.ResourceCollectionContentView):
(WI.ResourceCollectionContentView.prototype.contentViewAdded):
(WI.ResourceCollectionContentView.prototype._handleContentError):
Remove ContentView without removing the resource from its collection.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@222573 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/ResourceCollection.js
Source/WebInspectorUI/UserInterface/Views/CollectionContentView.js
Source/WebInspectorUI/UserInterface/Views/ContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceCollectionContentView.js [new file with mode: 0644]

index 2d8b241..0e7e1b6 100644 (file)
@@ -1,3 +1,60 @@
+2017-09-27  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: Create ResourceCollectionContentView and make CollectionContentView easier to extend
+        https://bugs.webkit.org/show_bug.cgi?id=177419
+
+        Reviewed by Devin Rousso.
+
+        CollectionContentView should be generic, work with any represented object
+        Collection, and not perform any type checking. It should just map items
+        to ContentViews using the provided ContentView constructor.
+
+        The behavior when clicking a ContentView in the collection has been extended.
+        If selection is enabled, clicking a ContentView will cause a "selected" class
+        to be applied to its element, and a SupplementalRepresentedObjectsDidChange
+        event is dispatched.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Main.html:
+        New file, move CollectionContentView above subclasses.
+
+        * UserInterface/Models/ResourceCollection.js:
+        (WI.ResourceCollection.prototype.get resourceType):
+        Make resource type publicly available.
+
+        * UserInterface/Views/CollectionContentView.js:
+        Move type checking of the collection out of the base class and assert
+        that ContentViews are created when invoking `contentViewConstructor`.
+        (WI.CollectionContentView):
+        (WI.CollectionContentView.titleForCollection):
+        (WI.CollectionContentView.prototype.get supplementalRepresentedObjects):
+        (WI.CollectionContentView.prototype.get selectionEnabled):
+        (WI.CollectionContentView.prototype.set selectionEnabled):
+        (WI.CollectionContentView.prototype.addContentViewForItem):
+        (WI.CollectionContentView.prototype.removeContentViewForItem):
+        (WI.CollectionContentView.prototype.contentViewAdded):
+        (WI.CollectionContentView.prototype.contentViewRemoved):
+        (WI.CollectionContentView.prototype.initialLayout):
+        (WI.CollectionContentView.prototype.attached):
+        (WI.CollectionContentView.prototype.detached):
+        (WI.CollectionContentView.prototype._handleItemAdded):
+        (WI.CollectionContentView.prototype._handleItemRemoved):
+        (WI.CollectionContentView.prototype._selectItem):
+        (WI.CollectionContentView.prototype._addContentViewForItem): Deleted.
+        (WI.CollectionContentView.prototype._removeContentViewForItem): Deleted.
+
+        * UserInterface/Views/ContentView.js:
+        (WI.ContentView.createFromRepresentedObject):
+        Create a ResourceCollectionContentView. In the future, additional
+        Collection types can be mapped to their associated CollectionContentView.
+
+        * UserInterface/Views/ResourceCollectionContentView.js: Added.
+        New class for resource-specific logic previously in CollectionContentView.
+        (WI.ResourceCollectionContentView):
+        (WI.ResourceCollectionContentView.prototype.contentViewAdded):
+        (WI.ResourceCollectionContentView.prototype._handleContentError):
+        Remove ContentView without removing the resource from its collection.
+
 2017-09-27  Ross Kirsling  <ross.kirsling@sony.com>
 
         Web Inspector: Fix Layers tab sidebar popover.
index 7e81d91..b49ea2f 100644 (file)
@@ -204,6 +204,7 @@ localizedStrings["Code"] = "Code";
 localizedStrings["Collapse All"] = "Collapse All";
 localizedStrings["Collapse columns"] = "Collapse columns";
 localizedStrings["Collect garbage"] = "Collect garbage";
+localizedStrings["Collection"] = "Collection";
 localizedStrings["Color"] = "Color";
 localizedStrings["Comment"] = "Comment";
 localizedStrings["Comment All Properties"] = "Comment All Properties";
index acb02f6..c90610b 100644 (file)
     <script src="Views/SettingsGroup.js"></script>
     <script src="Views/SettingsView.js"></script>
 
+    <script src="Views/CollectionContentView.js"></script>
+
     <script src="Views/ActivateButtonNavigationItem.js"></script>
     <script src="Views/ActivateButtonToolbarItem.js"></script>
     <script src="Views/ApplicationCacheDetailsSidebarPanel.js"></script>
     <script src="Views/CodeMirrorFormatters.js"></script>
     <script src="Views/CodeMirrorRegexMode.js"></script>
     <script src="Views/CodeMirrorTextMarkers.js"></script>
-    <script src="Views/CollectionContentView.js"></script>
     <script src="Views/ColorPicker.js"></script>
     <script src="Views/ColorWheel.js"></script>
     <script src="Views/CompletionSuggestionsView.js"></script>
     <script src="Views/RenderingFrameTimelineView.js"></script>
     <script src="Views/Resizer.js"></script>
     <script src="Views/ResourceClusterContentView.js"></script>
+    <script src="Views/ResourceCollectionContentView.js"></script>
     <script src="Views/ResourceDetailsSidebarPanel.js"></script>
     <script src="Views/ResourceSidebarPanel.js"></script>
     <script src="Views/ResourceTimelineDataGridNode.js"></script>
index 231a9a0..cd796b8 100644 (file)
@@ -64,6 +64,8 @@ WI.ResourceCollection = class ResourceCollection extends WI.Collection
 
     // Public
 
+    get resourceType() { return this._resourceType; }
+
     resourceForURL(url)
     {
         return this._resourceURLMap.get(url) || null;
index 0372d13..f5506c5 100644 (file)
 
 WI.CollectionContentView = class CollectionContentView extends WI.ContentView
 {
-    constructor(collection)
+    constructor(collection, contentViewConstructor, contentPlaceholderText)
     {
         console.assert(collection instanceof WI.Collection);
 
         super(collection);
 
-        this.representedObject.addEventListener(WI.Collection.Event.ItemAdded, this._handleItemAdded, this);
-        this.representedObject.addEventListener(WI.Collection.Event.ItemRemoved, this._handleItemRemoved, this);
+        this.element.classList.add("collection");
 
-        this._contentViewMap = new WeakMap;
+        this._contentPlaceholder = new WI.TitleView(contentPlaceholderText || WI.CollectionContentView.titleForCollection(collection));
+        this._contentViewConstructor = contentViewConstructor;
+        this._contentViewMap = new Map;
         this._handleClickMap = new WeakMap;
+        this._selectedItem = null;
+        this._selectionEnabled = false;
+    }
 
-        this._contentViewConstructor = null;
-        let title = "";
-        switch (this.representedObject.typeVerifier) {
+    static titleForCollection(collection)
+    {
+        switch (collection.typeVerifier) {
         case WI.Collection.TypeVerifier.Frame:
-            title = WI.UIString("Frames");
-            break;
-
-        case WI.Collection.TypeVerifier.Script:
-            title = WI.UIString("Extra Scripts");
-            break;
-
+            return WI.UIString("Frames");
         case WI.Collection.TypeVerifier.Resource:
-            title = WI.UIString("Resource");
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.Document:
-            title = WI.Resource.displayNameForType(WI.Resource.Type.Document, true);
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.Stylesheet:
-            title = WI.Resource.displayNameForType(WI.Resource.Type.Stylesheet, true);
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.Image:
-            this._contentViewConstructor = WI.ImageResourceContentView;
-            title = WI.Resource.displayNameForType(WI.Resource.Type.Image, true);
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.Font:
-            title = WI.Resource.displayNameForType(WI.Resource.Type.Font, true);
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.Script:
-            title = WI.Resource.displayNameForType(WI.Resource.Type.Script, true);
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.XHR:
-            title = WI.Resource.displayNameForType(WI.Resource.Type.XHR, true);
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.Fetch:
-            title = WI.Resource.displayNameForType(WI.Resource.Type.Fetch, true);
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.WebSocket:
-            title = WI.Resource.displayNameForType(WI.Resource.Type.WebSocket, true);
-            break;
-
-        case WI.ResourceCollection.TypeVerifier.Other:
-            title = WI.Resource.displayNameForType(WI.Resource.Type.Other, true);
-            break;
+            return WI.UIString("Resources");
+        case WI.Collection.TypeVerifier.Script:
+            return WI.UIString("Scripts");
+        case WI.Collection.TypeVerifier.CSSStyleSheet:
+            return WI.UIString("Stylesheets");
+        case WI.Collection.TypeVerifier.Canvas:
+            return WI.UIString("Canvases");
+        case WI.Collection.TypeVerifier.ShaderProgram:
+            return WI.UIString("Shader Programs");
         }
 
-        this._contentPlaceholder = new WI.TitleView(title);
+        console.warn("No default title for Collection type verifier.", collection.typeVerifier);
+        return WI.UIString("Collection");
+    }
 
-        this.element.classList.add("collection");
+    // Public
+
+    get supplementalRepresentedObjects()
+    {
+        if (this._selectedItem)
+            return [this._selectedItem];
+        return [];
     }
 
-     // Public
+    get selectionEnabled()
+    {
+        return this._selectionEnabled;
+    }
 
-    initialLayout()
+    set selectionEnabled(value)
     {
-        let items = this.representedObject.items;
-        if (this._contentViewConstructor && items.size) {
-            for (let item of items)
-                this._addContentViewForItem(item);
-        } else
-            this.addSubview(this._contentPlaceholder);
+        if (this._selectionEnabled === value)
+            return;
+
+        this._selectionEnabled = value;
+        if (!this._selectionEnabled)
+            this._selectItem(null);
     }
 
-     // Private
+    // Protected
 
-    _addContentViewForItem(item)
+    addContentViewForItem(item)
     {
         if (!this._contentViewConstructor)
             return;
 
+        if (this._contentViewMap.has(item)) {
+            console.assert(false, "Already added ContentView for item.", item);
+            return;
+        }
+
         if (this._contentPlaceholder.parentView)
             this.removeSubview(this._contentPlaceholder);
 
         let contentView = new this._contentViewConstructor(item);
-
-        contentView.addEventListener(WI.ResourceContentView.Event.ContentError, this._handleContentError, this);
+        console.assert(contentView instanceof WI.ContentView);
 
         let handleClick = (event) => {
             if (event.button !== 0 || event.ctrlKey)
                 return;
 
-            WI.showRepresentedObject(item);
+            if (this._selectionEnabled)
+                this._selectItem(item);
+            else
+                WI.showRepresentedObject(item);
         };
+
+        this._contentViewMap.set(item, contentView);
         this._handleClickMap.set(item, handleClick);
         contentView.element.addEventListener("click", handleClick);
 
-        contentView.element.title = WI.displayNameForURL(item.url, item.urlComponents);
-
         this.addSubview(contentView);
-        this._contentViewMap.set(item, contentView);
+        this.contentViewAdded(contentView);
     }
 
-    _removeContentViewForItem(item)
+    removeContentViewForItem(item)
     {
         if (!this._contentViewConstructor)
             return;
@@ -148,27 +134,85 @@ WI.CollectionContentView = class CollectionContentView extends WI.ContentView
 
         this.removeSubview(contentView);
         this._contentViewMap.delete(item);
+        this.contentViewRemoved(contentView);
 
         contentView.removeEventListener(null, null, this);
 
         let handleClick = this._handleClickMap.get(item);
         console.assert(handleClick);
+
         if (handleClick) {
             contentView.element.removeEventListener("click", handleClick);
             this._handleClickMap.delete(item);
         }
 
-        if (!this.representedObject.resources.length)
+        if (this._selectedItem === item)
+            this._selectItem(null);
+
+        if (!this.subviews.length)
+            this.addSubview(this._contentPlaceholder);
+    }
+
+    contentViewAdded(contentView)
+    {
+        // Implemented by subclasses.
+    }
+
+    contentViewRemoved(contentView)
+    {
+        // Implemented by subclasses.
+    }
+
+    initialLayout()
+    {
+        let items = this.representedObject.items;
+        if (!items.size || !this._contentViewConstructor) {
             this.addSubview(this._contentPlaceholder);
+            return;
+        }
+
+        for (let item of items)
+            this.addContentViewForItem(item);
     }
 
+    attached()
+    {
+        super.attached();
+
+        this.representedObject.addEventListener(WI.Collection.Event.ItemAdded, this._handleItemAdded, this);
+        this.representedObject.addEventListener(WI.Collection.Event.ItemRemoved, this._handleItemRemoved, this);
+
+        for (let item of this._contentViewMap.keys()) {
+            if (this.representedObject.items.has(item))
+                continue;
+
+            this.removeContentViewForItem(item);
+            if (this._selectedItem === item)
+                this._selectItem(null);
+        }
+
+        for (let item of this.representedObject.items) {
+            if (!this._contentViewMap.has(item))
+                this.addContentViewForItem(item);
+        }
+    }
+
+    detached()
+    {
+        this.representedObject.removeEventListener(null, null, this);
+
+        super.detached();
+    }
+
+     // Private
+
     _handleItemAdded(event)
     {
         let item = event.data.item;
         if (!item)
             return;
 
-        this._addContentViewForItem(item);
+        this.addContentViewForItem(item);
     }
 
     _handleItemRemoved(event)
@@ -177,7 +221,7 @@ WI.CollectionContentView = class CollectionContentView extends WI.ContentView
         if (!item)
             return;
 
-        this._removeContentViewForItem(item);
+        this.removeContentViewForItem(item);
     }
 
     _handleContentError(event)
@@ -185,4 +229,26 @@ WI.CollectionContentView = class CollectionContentView extends WI.ContentView
         if (event && event.target)
             this._removeContentViewForItem(event.target.representedObject);
     }
+
+    _selectItem(item)
+    {
+        if (this._selectedItem === item)
+            return;
+
+        if (this._selectedItem) {
+            let contentView = this._contentViewMap.get(this._selectedItem);
+            console.assert(contentView, "Missing ContentView for deselected item.", this._selectedItem);
+            contentView.element.classList.remove("selected");
+        }
+
+        this._selectedItem = item;
+
+        if (this._selectedItem) {
+            let selectedContentView = this._contentViewMap.get(this._selectedItem);
+            console.assert(selectedContentView, "Missing ContentView for selected item.", this._selectedItem);
+            selectedContentView.element.classList.add("selected");
+        }
+
+        this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange);
+    }
 };
index 5027384..21e768a 100644 (file)
@@ -161,6 +161,9 @@ WI.ContentView = class ContentView extends WI.View
         if (representedObject instanceof WI.Recording)
             return new WI.RecordingContentView(representedObject, extraArguments);
 
+        if (representedObject instanceof WI.ResourceCollection)
+            return new WI.ResourceCollectionContentView(representedObject, extraArguments);
+
         if (representedObject instanceof WI.Collection)
             return new WI.CollectionContentView(representedObject, extraArguments);
 
diff --git a/Source/WebInspectorUI/UserInterface/Views/ResourceCollectionContentView.js b/Source/WebInspectorUI/UserInterface/Views/ResourceCollectionContentView.js
new file mode 100644 (file)
index 0000000..f56f404
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+WI.ResourceCollectionContentView = class ResourceCollectionContentView extends WI.CollectionContentView
+{
+    constructor(collection)
+    {
+        console.assert(collection instanceof WI.ResourceCollection);
+
+        let contentViewConstructor = null;
+        if (collection.resourceType === WI.Resource.Type.Image)
+            contentViewConstructor = WI.ImageResourceContentView;
+
+        const plural = true;
+        let contentPlaceholderText = WI.Resource.displayNameForType(collection.resourceType, plural);
+
+        super(collection, contentViewConstructor, contentPlaceholderText);
+    }
+
+    // Protected
+
+    contentViewAdded(contentView)
+    {
+        console.assert(contentView instanceof WI.ResourceContentView);
+
+        let resource = contentView.representedObject;
+        console.assert(resource instanceof WI.Resource);
+
+        contentView.addEventListener(WI.ResourceContentView.Event.ContentError, this._handleContentError, this);
+        contentView.element.title = WI.displayNameForURL(resource.url, resource.urlComponents);
+    }
+
+    // Private
+
+    _handleContentError(event)
+    {
+        if (event && event.target)
+            this.removeContentViewForItem(event.target.representedObject);
+    }
+};