Title: [294775] trunk
Revision
294775
Author
[email protected]
Date
2022-05-24 16:07:20 -0700 (Tue, 24 May 2022)

Log Message

Web Inspector: Timelines Tab: Screenshots: images flash while recording is still active
https://bugs.webkit.org/show_bug.cgi?id=240852

Reviewed by Patrick Angle.

* Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineOverviewGraph.js:
(WI.ScreenshotsTimelineOverviewGraph):
(WI.ScreenshotsTimelineOverviewGraph.prototype.reset):
(WI.ScreenshotsTimelineOverviewGraph.prototype.layout):
* Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineView.js:
(WI.ScreenshotsTimelineView):
(WI.ScreenshotsTimelineView.prototype.reset):
(WI.ScreenshotsTimelineView.prototype.layout):
(WI.ScreenshotsTimelineView.prototype.selectRecord):
(WI.ScreenshotsTimelineView.prototype.clear): Deleted.
Keep a `WeakMap` of `<img>` for each `WI.ScreenshotsTimelineRecord` so that we don't recreate it
over and over in `layout`. Also, don't show the `<img>` until after it `"load"`.

* Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineView.css:
(.timeline-view.screenshots):
(.timeline-view.screenshots > img.selected):
Remove scroll snapping as it prevents the selected image from being scrolled offscreen, which is
unnecessarily hostile. The changing border color is enough to identify it as the selected image.

* Source/WebInspectorUI/UserInterface/Base/Utilities.js:
(Map.prototype.getOrInitialize):
(WeakMap.prototype.getOrInitialize): Added.
Adjust utility function to allow for `value` to be a function that's only executed if the `key` does
not exist in the `Map`/`WeakMap`.

* LayoutTests/inspector/unit-tests/map-utilities.html:
* LayoutTests/inspector/unit-tests/map-utilities-expected.txt:
* LayoutTests/inspector/unit-tests/weakmap-utilities.html: Added.
* LayoutTests/inspector/unit-tests/weakmap-utilities-expected.txt: Added.

Canonical link: https://commits.webkit.org/250939@main

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/inspector/unit-tests/map-utilities-expected.txt (294774 => 294775)


--- trunk/LayoutTests/inspector/unit-tests/map-utilities-expected.txt	2022-05-24 22:43:17 UTC (rev 294774)
+++ trunk/LayoutTests/inspector/unit-tests/map-utilities-expected.txt	2022-05-24 23:07:20 UTC (rev 294775)
@@ -14,7 +14,7 @@
 PASS: Map has `key` => `undefined`.
 PASS: Map take `doesNotExistKey` => `undefined`.
 
--- Running test case: Map.prototype.getOrInitialize
+-- Running test case: Map.prototype.getOrInitialize.Raw
 PASS: Map does not have `key`.
 PASS: Map should have initialized `key` with `value`.
 PASS: Map does have `key` => `value`.
@@ -21,3 +21,10 @@
 PASS: Map should get `key` with `value` without re-initializing.
 PASS: Map still has `key` => `value`.
 
+-- Running test case: Map.prototype.getOrInitialize.Function
+PASS: Map does not have `key`.
+PASS: Map should have initialized `key` with `value`.
+PASS: Map does have `key` => `value`.
+PASS: Map should get `key` with `value` without re-initializing.
+PASS: Map still has `key` => `value`.
+

Modified: trunk/LayoutTests/inspector/unit-tests/map-utilities.html (294774 => 294775)


--- trunk/LayoutTests/inspector/unit-tests/map-utilities.html	2022-05-24 22:43:17 UTC (rev 294774)
+++ trunk/LayoutTests/inspector/unit-tests/map-utilities.html	2022-05-24 23:07:20 UTC (rev 294775)
@@ -47,7 +47,7 @@
     });
 
     suite.addTestCase({
-        name: "Map.prototype.getOrInitialize",
+        name: "Map.prototype.getOrInitialize.Raw",
         test() {
             const key = "key";
             const value = "value";
@@ -62,6 +62,22 @@
         }
     });
 
+    suite.addTestCase({
+        name: "Map.prototype.getOrInitialize.Function",
+        test() {
+            const key = "key";
+            const value = () => "value";
+            const value2 = () => InspectorTest.fail("Should not be reached.");
+
+            let map = new Map;
+            InspectorTest.expectEqual(map.get(key), undefined, "Map does not have `key`.");
+            InspectorTest.expectEqual(map.getOrInitialize(key, value), value(), "Map should have initialized `key` with `value`.");
+            InspectorTest.expectEqual(map.get(key), value(), "Map does have `key` => `value`.");
+            InspectorTest.expectEqual(map.getOrInitialize(key, value2), value(), "Map should get `key` with `value` without re-initializing.");
+            InspectorTest.expectEqual(map.get(key), value(), "Map still has `key` => `value`.");
+        }
+    });
+
     suite.runTestCasesAndFinish();
 }
 </script>

Added: trunk/LayoutTests/inspector/unit-tests/weakmap-utilities-expected.txt (0 => 294775)


--- trunk/LayoutTests/inspector/unit-tests/weakmap-utilities-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/weakmap-utilities-expected.txt	2022-05-24 23:07:20 UTC (rev 294775)
@@ -0,0 +1,16 @@
+
+== Running test suite: WeakMap
+-- Running test case: WeakMap.prototype.getOrInitialize.Raw
+PASS: Map does not have `key`.
+PASS: Map should have initialized `key` with `value`.
+PASS: Map does have `key` => `value`.
+PASS: Map should get `key` with `value` without re-initializing.
+PASS: Map still has `key` => `value`.
+
+-- Running test case: WeakMap.prototype.getOrInitialize.Function
+PASS: Map does not have `key`.
+PASS: Map should have initialized `key` with `value`.
+PASS: Map does have `key` => `value`.
+PASS: Map should get `key` with `value` without re-initializing.
+PASS: Map still has `key` => `value`.
+

Added: trunk/LayoutTests/inspector/unit-tests/weakmap-utilities.html (0 => 294775)


--- trunk/LayoutTests/inspector/unit-tests/weakmap-utilities.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/weakmap-utilities.html	2022-05-24 23:07:20 UTC (rev 294775)
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    let suite = InspectorTest.createSyncSuite("WeakMap");
+
+    suite.addTestCase({
+        name: "WeakMap.prototype.getOrInitialize.Raw",
+        test() {
+            const key = {value: "key"};
+            const value = "value";
+            const value2 = "value2";
+
+            let map = new WeakMap;
+            InspectorTest.expectEqual(map.get(key), undefined, "Map does not have `key`.");
+            InspectorTest.expectEqual(map.getOrInitialize(key, value), value, "Map should have initialized `key` with `value`.");
+            InspectorTest.expectEqual(map.get(key), value, "Map does have `key` => `value`.");
+            InspectorTest.expectEqual(map.getOrInitialize(key, value2), value, "Map should get `key` with `value` without re-initializing.");
+            InspectorTest.expectEqual(map.get(key), value, "Map still has `key` => `value`.");
+        }
+    });
+
+    suite.addTestCase({
+        name: "WeakMap.prototype.getOrInitialize.Function",
+        test() {
+            const key = {value: "key"};
+            const value = () => "value";
+            const value2 = () => InspectorTest.fail("Should not be reached.");
+
+            let map = new WeakMap;
+            InspectorTest.expectEqual(map.get(key), undefined, "Map does not have `key`.");
+            InspectorTest.expectEqual(map.getOrInitialize(key, value), value(), "Map should have initialized `key` with `value`.");
+            InspectorTest.expectEqual(map.get(key), value(), "Map does have `key` => `value`.");
+            InspectorTest.expectEqual(map.getOrInitialize(key, value2), value(), "Map should get `key` with `value` without re-initializing.");
+            InspectorTest.expectEqual(map.get(key), value(), "Map still has `key` => `value`.");
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onLoad_="runTest()">
+</body>
+</html>

Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (294774 => 294775)


--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js	2022-05-24 22:43:17 UTC (rev 294774)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js	2022-05-24 23:07:20 UTC (rev 294775)
@@ -148,11 +148,36 @@
         if (value)
             return value;
 
+        if (typeof initialValue === "function")
+            initialValue = initialValue();
+
+        console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined.");
+
         this.set(key, initialValue);
         return initialValue;
     }
 });
 
+Object.defineProperty(WeakMap.prototype, "getOrInitialize",
+{
+    value(key, initialValue)
+    {
+        console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined.");
+
+        let value = this.get(key);
+        if (value)
+            return value;
+
+        if (typeof initialValue === "function")
+            initialValue = initialValue();
+
+        console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined.");
+
+        this.set(key, initialValue);
+        return initialValue;
+    }
+});
+
 Object.defineProperty(Set.prototype, "find",
 {
     value(predicate)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineOverviewGraph.js (294774 => 294775)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineOverviewGraph.js	2022-05-24 22:43:17 UTC (rev 294774)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineOverviewGraph.js	2022-05-24 23:07:20 UTC (rev 294775)
@@ -37,6 +37,8 @@
         this._screenshotsTimeline = timeline;
 
         this._lastSelectedRecordInLayout = null;
+
+        this.reset();
     }
 
     // Protected
@@ -46,6 +48,13 @@
         return 60;
     }
 
+    reset()
+    {
+        super.reset();
+
+        this._imageElementForRecord = new WeakMap;
+    }
+
     layout()
     {
         super.layout();
@@ -57,27 +66,37 @@
 
         let secondsPerPixel = this.timelineOverview.secondsPerPixel;
 
-        this._lastSelectedRecordInLayout = this.selectedRecord;
-
         for (let record of this._visibleRecords()) {
-            let imageElement = this.element.appendChild(document.createElement("img"));
-            imageElement.src = ""
-            if (record === this.selectedRecord)
-                imageElement.classList.add("selected");
+            this.element.appendChild(this._imageElementForRecord.getOrInitialize(record, () => {
+                let imageElement = document.createElement("img");
+                imageElement.width = record.width * (this.height / record.height);
+                imageElement.height = this.height;
+                imageElement.style.left = (record.startTime - this.startTime) / secondsPerPixel + "px";
 
-            imageElement.height = this.height;
-            let widthScale = this.height / record.height;
-            let pixelWidth = record.width * widthScale;
-            imageElement.width = pixelWidth;
+                imageElement.hidden = true;
+                imageElement.addEventListener("load", (event) => {
+                    imageElement.hidden = false;
+                }, {once: true});
+                imageElement.src = ""
 
-            imageElement.style.left = (record.startTime - this.startTime) / secondsPerPixel + "px";
-            imageElement.addEventListener("click", (event) => {
-                // Ensure that the container "click" listener added by `WI.TimelineOverview` isn't called.
-                event.__timelineRecordClickEventHandled = true;
+                imageElement.addEventListener("click", (event) => {
+                    // Ensure that the container "click" listener added by `WI.TimelineOverview` isn't called.
+                    event.__timelineRecordClickEventHandled = true;
 
-                this.selectedRecord = record;
-            });
+                    this.selectedRecord = record;
+                });
+
+                return imageElement;
+            }));
         }
+
+        if (this._lastSelectedRecordInLayout)
+            this._imageElementForRecord.get(this._lastSelectedRecordInLayout)?.classList.remove("selected");
+
+        this._lastSelectedRecordInLayout = this.selectedRecord;
+
+        if (this._lastSelectedRecordInLayout)
+            this._imageElementForRecord.get(this._lastSelectedRecordInLayout)?.classList.add("selected");
     }
 
     updateSelectedRecord()

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineView.css (294774 => 294775)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineView.css	2022-05-24 22:43:17 UTC (rev 294774)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineView.css	2022-05-24 23:07:20 UTC (rev 294775)
@@ -26,7 +26,6 @@
 .timeline-view.screenshots {
     white-space: nowrap; 
     overflow-x: auto;
-    scroll-snap-type: x mandatory;
     background-color: hsl(0, 0%, 90%);
 }
 
@@ -46,7 +45,6 @@
 
 .timeline-view.screenshots > img.selected {
     box-shadow: 0 0 0 2px var(--glyph-color-active), var(--shadow);
-    scroll-snap-align: center;
 }
 
 @media (prefers-color-scheme: dark) {

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineView.js (294774 => 294775)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineView.js	2022-05-24 22:43:17 UTC (rev 294774)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ScreenshotsTimelineView.js	2022-05-24 23:07:20 UTC (rev 294775)
@@ -36,6 +36,8 @@
         this.element.classList.add("screenshots");
         
         this._selectedRecord = null;
+
+        this._imageElementForRecord = new WeakMap;
     }
 
     // Public
@@ -44,12 +46,9 @@
     {
         super.reset();
 
-        this.clear();
-    }
+        this.selectRecord(null);
 
-    clear()
-    {
-        this.selectRecord(null);
+        this._imageElementForRecord = new WeakMap;
     }
 
     // Protected
@@ -68,18 +67,35 @@
 
         this.element.removeChildren();
 
+        let selectedElement = null;
+
         for (let record of this._visibleRecords()) {
-            let imageElement = this.element.appendChild(document.createElement("img"));
-            imageElement.src = ""
+            this.element.appendChild(this._imageElementForRecord.getOrInitialize(record, () => {
+                let imageElement = document.createElement("img");
 
-            if (record === this._selectedRecord)
-                imageElement.classList.add("selected");
+                imageElement.hidden = true;
+                imageElement.addEventListener("load", (event) => {
+                    imageElement.hidden = false;
+                }, {once: true});
+                imageElement.src = ""
 
-            imageElement.addEventListener("click", (event) => {
-                this._selectTimelineRecord(record);
-            });
+                imageElement.addEventListener("click", (event) => {
+                    this._selectTimelineRecord(record);
+                });
+
+                if (record === this._selectedRecord)
+                    selectedElement = imageElement;
+
+                return imageElement;
+            }));
         }        
 
+        if (selectedElement) {
+            selectedElement.classList.add("selected");
+
+            selectedElement.scrollIntoView({inline: "center"});
+        }
+
         if (!this.element.childNodes.length)
             this.element.appendChild(WI.createMessageTextView(WI.UIString("No screenshots", "No screenshots @ Screenshots Timeline", "Placeholder text shown when there are no images to display in the Screenshots timeline.")));
     }
@@ -86,8 +102,22 @@
 
     selectRecord(record)
     {
+        if (record === this._selectedRecord)
+            return;
+
+        if (this._selectedRecord)
+            this._imageElementForRecord.get(this._selectedRecord)?.classList.remove("selected");
+
         this._selectedRecord = record;
-        this.needsLayout();
+
+        if (this._selectedRecord) {
+            let element = this._imageElementForRecord.get(this._selectedRecord);
+            if (element) {
+                element.classList.add("selected");
+
+                element.scrollIntoView({inline: "center"});
+            }
+        }
     }
 
     // Private
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to