Skip to content

Commit 79db54b

Browse files
AnujPanta1the-chenergy
authored andcommitted
Web Inspector: Layers tab should show a visual representation of the layer
https://bugs.webkit.org/show_bug.cgi?id=302723 rdar://problem/164977594 Reviewed by Devin Rousso. Implemented layer snapshot rendering to display actual composited content in the 3D Layers view. Added LayerTree.snapshotForLayer protocol command that uses GraphicsLayer::paintGraphicsLayerContents to capture rendered output as PNG data URLs. The frontend loads these snapshots as Three.js textures and maps them onto layer meshes with proper UV coordinates, replacing the previous solid-color placeholder rectangles. This approach provides direct visualization of what each compositing layer actually renders, making it easier to debug compositing issues and understand layer content at a glance. * Source/JavaScriptCore/inspector/protocol/LayerTree.json: * Source/WebCore/inspector/agents/InspectorLayerTreeAgent.cpp: (WebCore::InspectorLayerTreeAgent::requestContent): * Source/WebCore/inspector/agents/InspectorLayerTreeAgent.h: * Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js: * Source/WebInspectorUI/UserInterface/Base/Setting.js: * Source/WebInspectorUI/UserInterface/Controllers/LayerTreeManager.js: (WI.LayerTreeManager.prototype.snapshotForLayer): * Source/WebInspectorUI/UserInterface/Views/Layers3DContentView.js: (WI.Layers3DContentView): (WI.Layers3DContentView.prototype.get navigationItems): (WI.Layers3DContentView.prototype.attached): (WI.Layers3DContentView.prototype.detached): (WI.Layers3DContentView.prototype._updateLayers): (WI.Layers3DContentView.prototype._createLayerGroup): (WI.Layers3DContentView.prototype._populateLayerGroup): (WI.Layers3DContentView.prototype._loadLayerTexture): (WI.Layers3DContentView.prototype._createLayerMesh): (WI.Layers3DContentView.prototype._handleLayerContentsSettingChanged): (WI.Layers3DContentView.prototype._handleRefreshLayersButtonClicked): (WI.Layers3DContentView.prototype._refreshAllLayers): (WI.Layers3DContentView.prototype._zInterval): * Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js: (WI.SettingsTabContentView.prototype._createExperimentalSettingsView): Canonical link: https://commits.webkit.org/304758@main
1 parent 2fb5849 commit 79db54b

File tree

8 files changed

+232
-11
lines changed

8 files changed

+232
-11
lines changed

Source/JavaScriptCore/inspector/protocol/LayerTree.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@
108108
"returns": [
109109
{ "name": "compositingReasons", "$ref": "CompositingReasons", "description": "An object containing the reasons why the layer was composited as properties." }
110110
]
111+
},
112+
{
113+
"name": "requestContent",
114+
"description": "Captures a snapshot of the layer's rendered content as a PNG data URL.",
115+
"parameters": [
116+
{ "name": "layerId", "$ref": "LayerId", "description": "The id of the layer to snapshot." }
117+
],
118+
"returns": [
119+
{ "name": "content", "type": "string", "description": "Base64-encoded PNG data URL of the layer's rendered content." }
120+
]
111121
}
112122
],
113123
"events": [

Source/WebCore/inspector/agents/InspectorLayerTreeAgent.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@
3131
#include "config.h"
3232
#include "InspectorLayerTreeAgent.h"
3333

34+
#include "DestinationColorSpace.h"
3435
#include "EventTargetInlines.h"
36+
#include "GraphicsContext.h"
3537
#include "GraphicsLayer.h"
38+
#include "ImageBuffer.h"
3639
#include "InspectorDOMAgent.h"
3740
#include "InstrumentingAgents.h"
3841
#include "IntRect.h"
42+
#include "PixelFormat.h"
3943
#include "PseudoElement.h"
4044
#include "RenderChildIterator.h"
4145
#include "RenderElementInlines.h"
@@ -340,6 +344,43 @@ Inspector::Protocol::ErrorStringOr<Ref<Inspector::Protocol::LayerTree::Compositi
340344
return compositingReasons;
341345
}
342346

347+
Inspector::CommandResult<String> InspectorLayerTreeAgent::requestContent(const Inspector::Protocol::LayerTree::LayerId& layerId)
348+
{
349+
auto* renderLayer = m_idToLayer.get(layerId);
350+
if (!renderLayer)
351+
return makeUnexpected("Missing render layer for given layerId"_s);
352+
353+
auto* backing = renderLayer->backing();
354+
if (!backing)
355+
return makeUnexpected("Layer is not composited"_s);
356+
357+
auto* graphicsLayer = backing->graphicsLayer();
358+
if (!graphicsLayer)
359+
return makeUnexpected("Missing graphics layer"_s);
360+
361+
FloatSize layerSize = graphicsLayer->size();
362+
if (layerSize.isEmpty())
363+
return makeUnexpected("Layer has zero size"_s);
364+
365+
constexpr float scaleFactor = 2.0;
366+
IntSize integralSize = IntSize(layerSize);
367+
368+
auto imageBuffer = ImageBuffer::create(integralSize, RenderingMode::Unaccelerated, RenderingPurpose::Snapshot, scaleFactor, DestinationColorSpace::SRGB(), PixelFormat::BGRA8);
369+
if (!imageBuffer)
370+
return makeUnexpected("Failed to create image buffer"_s);
371+
372+
GraphicsContext& context = imageBuffer->context();
373+
IntRect layerRect(IntPoint(), integralSize);
374+
graphicsLayer->paintGraphicsLayerContents(context, layerRect);
375+
376+
String dataURL = imageBuffer->toDataURL("image/png"_s, std::nullopt, PreserveResolution::Yes);
377+
if (dataURL.isEmpty())
378+
return makeUnexpected("Failed to encode layer snapshot"_s);
379+
380+
return dataURL;
381+
}
382+
383+
343384
String InspectorLayerTreeAgent::bind(const RenderLayer* layer)
344385
{
345386
if (!layer)

Source/WebCore/inspector/agents/InspectorLayerTreeAgent.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class InspectorLayerTreeAgent final : public InspectorAgentBase, public Inspecto
6161
Inspector::Protocol::ErrorStringOr<void> disable();
6262
Inspector::Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Inspector::Protocol::LayerTree::Layer>>> layersForNode(Inspector::Protocol::DOM::NodeId);
6363
Inspector::Protocol::ErrorStringOr<Ref<Inspector::Protocol::LayerTree::CompositingReasons>> reasonsForCompositingLayer(const Inspector::Protocol::LayerTree::LayerId&);
64+
Inspector::CommandResult<String> requestContent(const Inspector::Protocol::LayerTree::LayerId&);
6465

6566
// InspectorInstrumentation
6667
void layerTreeDidChange();

Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,7 @@ localizedStrings["Latency"] = "Latency";
996996
localizedStrings["Layer Count: %d"] = "Layer Count: %d";
997997
localizedStrings["Layer Info"] = "Layer Info";
998998
localizedStrings["Layers"] = "Layers";
999+
localizedStrings["Layers:"] = "Layers:";
9991000
/* Name of Layers Tab */
10001001
localizedStrings["Layers Tab Name"] = "Layers";
10011002
localizedStrings["Layout & Rendering"] = "Layout & Rendering";
@@ -1597,6 +1598,7 @@ localizedStrings["Show hidden tabs\u2026"] = "Show hidden tabs\u2026";
15971598
localizedStrings["Show independent Styles sidebar @ Settings Elements Pane"] = "Show independent Styles sidebar";
15981599
localizedStrings["Show jump to effective property button"] = "Show jump to effective property button";
15991600
localizedStrings["Show jump to variable declaration button"] = "Show jump to variable declaration button";
1601+
localizedStrings["Show layer contents"] = "Show layer contents";
16001602
/* Settings tab checkbox label for whether the details sidebars (on the right in LTR locales) are at the bottom */
16011603
localizedStrings["Show on bottom when narrow @ Settings General Pane"] = "Show on bottom when narrow";
16021604
localizedStrings["Show page rulers and node border lines"] = "Show page rulers and node border lines";

Source/WebInspectorUI/UserInterface/Base/Setting.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ WI.settings = {
246246
experimentalLimitSourceCodeHighlighting: new WI.Setting("engineering-limit-source-code-highlighting", false),
247247
experimentalUseFuzzyMatchingForCSSCodeCompletion: new WI.Setting("experimental-use-fuzzy-matching-for-css-code-completion", true),
248248
experimentalUseStrictCheckForGlobMatching: new WI.Setting("experimental-use-strict-check-for-glob-matching", false),
249+
experimentalLayers3DShowLayerContents: new WI.Setting("experimental-layers-3d-show-layer-contents", true),
249250

250251
// Protocol
251252
protocolLogAsText: new WI.Setting("protocol-log-as-text", false),

Source/WebInspectorUI/UserInterface/Controllers/LayerTreeManager.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,21 @@ WI.LayerTreeManager = class LayerTreeManager extends WI.Object
197197
});
198198
}
199199

200+
snapshotForLayer(layer, callback)
201+
{
202+
console.assert(this.supported);
203+
204+
let target = WI.assumingMainTarget();
205+
target.LayerTreeAgent.requestContent(layer.layerId, function(error, content) {
206+
if (error) {
207+
WI.reportInternalError(error);
208+
callback(null);
209+
return;
210+
}
211+
callback(content);
212+
});
213+
}
214+
200215
// LayerTreeObserver
201216

202217
layerTreeDidChange()

Source/WebInspectorUI/UserInterface/Views/Layers3DContentView.js

Lines changed: 152 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
4141
this._paintFlashingButtonNavigationItem.enabled = WI.LayerTreeManager.supportsShowingPaintRects();
4242
this._paintFlashingButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
4343

44+
this._refreshLayersButtonNavigationItem = new WI.ButtonNavigationItem("refresh-layers", WI.UIString("Refresh layers"), "Images/ReloadToolbar.svg", 15, 15);
45+
this._refreshLayersButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleRefreshLayersButtonClicked, this);
46+
this._refreshLayersButtonNavigationItem.enabled = WI.settings.experimentalLayers3DShowLayerContents.value;
47+
this._refreshLayersButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
48+
49+
this._pendingTextureLoads = new Map;
50+
4451
this._layers = [];
4552
this._layerGroupsById = new Map;
4653
this._selectedLayerGroup = null;
@@ -66,7 +73,11 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
6673

6774
get navigationItems()
6875
{
69-
return [this._compositingBordersButtonNavigationItem, this._paintFlashingButtonNavigationItem];
76+
return [
77+
this._compositingBordersButtonNavigationItem,
78+
this._paintFlashingButtonNavigationItem,
79+
this._refreshLayersButtonNavigationItem,
80+
];
7081
}
7182

7283
get supplementalRepresentedObjects()
@@ -125,6 +136,8 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
125136

126137
WI.layerTreeManager.addEventListener(WI.LayerTreeManager.Event.ShowPaintRectsChanged, this._handleShowPaintRectsChanged, this);
127138
this._handleShowPaintRectsChanged();
139+
140+
WI.settings.experimentalLayers3DShowLayerContents.addEventListener(WI.Setting.Event.Changed, this._handleLayerContentsSettingChanged, this);
128141
}
129142

130143
detached()
@@ -136,6 +149,8 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
136149
WI.layerTreeManager.removeEventListener(WI.LayerTreeManager.Event.ShowPaintRectsChanged, this._handleShowPaintRectsChanged, this);
137150
WI.layerTreeManager.removeEventListener(WI.LayerTreeManager.Event.CompositingBordersVisibleChanged, this._handleCompositingBordersVisibleChanged, this);
138151

152+
WI.settings.experimentalLayers3DShowLayerContents.removeEventListener(WI.Setting.Event.Changed, this._handleLayerContentsSettingChanged, this);
153+
139154
super.detached();
140155
}
141156

@@ -268,7 +283,7 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
268283
}
269284

270285
// FIXME: Update the backend to provide a literal "layer tree" so we can decide z-indices less naively.
271-
const zInterval = 25;
286+
let zInterval = this._zInterval();
272287
newLayers.forEach((layer, index) => {
273288
let layerGroup = this._layerGroupsById.get(layer.layerId);
274289
layerGroup.position.set(0, 0, index * zInterval);
@@ -284,11 +299,84 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
284299
_createLayerGroup(layer) {
285300
let layerGroup = new THREE.Group;
286301
layerGroup.userData.layer = layer;
287-
layerGroup.add(this._createLayerMesh(layer.bounds), this._createLayerMesh(layer.compositedBounds, true));
302+
303+
this._populateLayerGroup(layerGroup, layer);
304+
288305
return layerGroup;
289306
}
290307

291-
_createLayerMesh({x, y, width, height}, isOutline = false)
308+
_populateLayerGroup(layerGroup, layer)
309+
{
310+
let fillMesh = this._createLayerMesh(layer.bounds);
311+
let outlineMesh = this._createLayerMesh(layer.compositedBounds, {isOutline: true});
312+
layerGroup.add(fillMesh, outlineMesh);
313+
314+
if (WI.settings.experimentalLayers3DShowLayerContents.value) {
315+
this._loadLayerTexture(layer, (texture) => {
316+
if (!texture)
317+
return;
318+
319+
if (!layerGroup.parent) {
320+
texture.dispose();
321+
return;
322+
}
323+
324+
layerGroup.clear();
325+
326+
fillMesh.geometry?.dispose();
327+
fillMesh.material?.map?.dispose();
328+
fillMesh.material?.dispose();
329+
330+
let texturedMesh = this._createLayerMesh(layer.bounds, {texture});
331+
layerGroup.add(texturedMesh);
332+
layerGroup.add(outlineMesh);
333+
});
334+
}
335+
}
336+
337+
_loadLayerTexture(layer, callback) {
338+
let pendingTextureLoad = this._pendingTextureLoads.get(layer.layerId);
339+
if (pendingTextureLoad)
340+
this._pendingTextureLoads.delete(layer.layerId);
341+
342+
let symbol = Symbol("loading");
343+
this._pendingTextureLoads.set(layer.layerId, symbol);
344+
345+
WI.layerTreeManager.snapshotForLayer(layer, (content) => {
346+
if (this._pendingTextureLoads.get(layer.layerId) !== symbol)
347+
return;
348+
349+
if (!content) {
350+
this._pendingTextureLoads.delete(layer.layerId);
351+
callback(null);
352+
return;
353+
}
354+
355+
let textureLoader = new THREE.TextureLoader;
356+
let onLoad = (texture) => {
357+
if (this._pendingTextureLoads.get(layer.layerId) !== symbol) {
358+
texture.dispose();
359+
return;
360+
}
361+
362+
texture.minFilter = THREE.LinearFilter;
363+
texture.magFilter = THREE.LinearFilter;
364+
texture.generateMipmaps = false;
365+
366+
this._pendingTextureLoads.delete(layer.layerId);
367+
callback(texture);
368+
};
369+
const onProgress = undefined;
370+
let onError = (error) => {
371+
console.error("Failed to load layer texture:", error);
372+
this._pendingTextureLoads.delete(layer.layerId);
373+
callback(null);
374+
};
375+
textureLoader.load(content, onLoad, onProgress, onError);
376+
});
377+
}
378+
379+
_createLayerMesh({x, y, width, height}, {isOutline, texture} = {})
292380
{
293381
let geometry = new THREE.Geometry;
294382
geometry.vertices.push(
@@ -305,13 +393,30 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
305393

306394
geometry.faces.push(new THREE.Face3(0, 1, 3), new THREE.Face3(1, 2, 3));
307395

308-
let material = new THREE.MeshBasicMaterial({
309-
color: WI.Layers3DContentView._layerColor.fill,
310-
transparent: true,
311-
opacity: 0.4,
312-
side: THREE.DoubleSide,
313-
depthWrite: false,
314-
});
396+
if (texture) {
397+
geometry.faceVertexUvs[0] = [
398+
[new THREE.Vector2(0, 1), new THREE.Vector2(0, 0), new THREE.Vector2(1, 1)],
399+
[new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1)],
400+
];
401+
}
402+
403+
let material;
404+
if (texture) {
405+
material = new THREE.MeshBasicMaterial({
406+
map: texture,
407+
transparent: true,
408+
opacity: 1.0,
409+
side: THREE.DoubleSide,
410+
});
411+
} else {
412+
material = new THREE.MeshBasicMaterial({
413+
color: WI.Layers3DContentView._layerColor.fill,
414+
transparent: true,
415+
opacity: 0.4,
416+
side: THREE.DoubleSide,
417+
depthWrite: false,
418+
});
419+
}
315420

316421
return new THREE.Mesh(geometry, material);
317422
}
@@ -417,6 +522,37 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
417522
WI.layerTreeManager.showPaintRects = !WI.layerTreeManager.showPaintRects;
418523
}
419524

525+
_handleLayerContentsSettingChanged(event)
526+
{
527+
this._refreshLayersButtonNavigationItem.enabled = WI.settings.experimentalLayers3DShowLayerContents.value;
528+
529+
this._pendingTextureLoads.clear();
530+
531+
this._refreshAllLayers();
532+
}
533+
534+
_handleRefreshLayersButtonClicked(event)
535+
{
536+
this._pendingTextureLoads.clear();
537+
538+
this._refreshAllLayers();
539+
}
540+
541+
_refreshAllLayers()
542+
{
543+
for (let [layerId, layerGroup] of this._layerGroupsById) {
544+
let layer = layerGroup.userData.layer;
545+
for (let child of layerGroup.children) {
546+
child.geometry?.dispose();
547+
child.material?.map?.dispose();
548+
child.material?.dispose();
549+
}
550+
layerGroup.clear();
551+
552+
this._populateLayerGroup(layerGroup, layer);
553+
}
554+
}
555+
420556
_buildLayerInfoElement()
421557
{
422558
this._layerInfoElement = this._element.appendChild(document.createElement("div"));
@@ -530,6 +666,11 @@ WI.Layers3DContentView = class Layers3DContentView extends WI.ContentView
530666
if (compositingReasons.blending)
531667
addReason(WI.UIString("Element has \u201Cblend-mode\u201D style"));
532668
}
669+
670+
_zInterval()
671+
{
672+
return WI.settings.experimentalLayers3DShowLayerContents.value ? 10 : 25;
673+
}
533674
};
534675

535676
WI.Layers3DContentView._zPadding = 3000;

Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,13 @@ WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentVi
426426
experimentalSettingsView.addSeparator();
427427
}
428428

429+
if (InspectorBackend.hasCommand("LayerTree.requestContent")) {
430+
let layersGroup = experimentalSettingsView.addGroup(WI.UIString("Layers:"));
431+
layersGroup.addSetting(WI.settings.experimentalLayers3DShowLayerContents, WI.UIString("Show layer contents"));
432+
433+
experimentalSettingsView.addSeparator();
434+
}
435+
429436
let hasNetworkEmulatedCondition = InspectorBackend.hasCommand("Network.setEmulatedConditions");
430437
if (hasNetworkEmulatedCondition) {
431438
let networkGroup = experimentalSettingsView.addGroup(WI.UIString("Network:"));
@@ -474,6 +481,9 @@ WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentVi
474481
listenForChange(WI.settings.experimentalCSSSortPropertyNameAutocompletionByUsage);
475482
}
476483

484+
if (InspectorBackend.hasCommand("LayerTree.requestContent"))
485+
listenForChange(WI.settings.experimentalLayers3DShowLayerContents);
486+
477487
if (hasNetworkEmulatedCondition)
478488
listenForChange(WI.settings.experimentalEnableNetworkEmulatedCondition);
479489

0 commit comments

Comments
 (0)