Web Inspector: Canvas Tab: show supported GL extensions for selected canvas
authorwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 3 Nov 2017 01:53:17 +0000 (01:53 +0000)
committerwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 3 Nov 2017 01:53:17 +0000 (01:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179070
<rdar://problem/35278276>

Reviewed by Brian Burg.

Source/JavaScriptCore:

* inspector/protocol/Canvas.json:
Add `extensionEnabled` event that is fired each time `getExtension` is called with a
different string on a WebGL context.

Source/WebCore:

Test: inspector/canvas/extensions.html

* html/canvas/WebGL2RenderingContext.cpp:
(WebCore::WebGL2RenderingContext::getExtension):
* html/canvas/WebGLRenderingContext.cpp:
(WebCore::WebGLRenderingContext::getExtension):
Rework common logic into a macro for readability and to simplify adding calls to
InspectorInstrumentation functions.

* html/canvas/WebGLRenderingContextBase.h:
* html/canvas/WebGLRenderingContextBase.cpp:
(WebCore::WebGLRenderingContextBase::extensionIsEnabled):

* inspector/InspectorCanvasAgent.h:
* inspector/InspectorCanvasAgent.cpp:
(WebCore::InspectorCanvasAgent::enable):
(WebCore::InspectorCanvasAgent::didEnableExtension):

* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::didEnableExtension):
* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::didEnableExtensionImpl):

Source/WebInspectorUI:

* Localizations/en.lproj/localizedStrings.js:

* UserInterface/Protocol/CanvasObserver.js:
(WI.CanvasObserver.prototype.extensionEnabled):

* UserInterface/Controllers/CanvasManager.js:
(WI.CanvasManager.prototype.extensionEnabled):

* UserInterface/Models/Canvas.js:
(WI.Canvas.prototype.get extensions):
(WI.Canvas.prototype.enableExtension):
Maintain a Set of enabled extensions, and dispatch an event whenever an extension is enabled.

* UserInterface/Views/CanvasDetailsSidebarPanel.css:
(.sidebar > .panel.details.canvas .details-section.canvas-extensions .content > ul):
* UserInterface/Views/CanvasDetailsSidebarPanel.js:
(WI.CanvasDetailsSidebarPanel.prototype.set canvas):
(WI.CanvasDetailsSidebarPanel.prototype.initialLayout):
(WI.CanvasDetailsSidebarPanel.prototype.layout):
(WI.CanvasDetailsSidebarPanel.prototype._refreshAttributesSection):
(WI.CanvasDetailsSidebarPanel.prototype._refreshExtensionsSection):
Drive-by: hide Attributes section when the canvas has no attributes.
LayoutTests:

* inspector/canvas/extensions-expected.txt: Added.
* inspector/canvas/extensions.html: Added.

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

21 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/canvas/extensions-expected.txt [new file with mode: 0644]
LayoutTests/inspector/canvas/extensions.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/Canvas.json
Source/WebCore/ChangeLog
Source/WebCore/html/canvas/WebGL2RenderingContext.cpp
Source/WebCore/html/canvas/WebGLRenderingContext.cpp
Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp
Source/WebCore/html/canvas/WebGLRenderingContextBase.h
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebCore/inspector/agents/InspectorCanvasAgent.cpp
Source/WebCore/inspector/agents/InspectorCanvasAgent.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Controllers/CanvasManager.js
Source/WebInspectorUI/UserInterface/Models/Canvas.js
Source/WebInspectorUI/UserInterface/Protocol/CanvasObserver.js
Source/WebInspectorUI/UserInterface/Views/CanvasDetailsSidebarPanel.css
Source/WebInspectorUI/UserInterface/Views/CanvasDetailsSidebarPanel.js

index 012e0dd..c582966 100644 (file)
@@ -1,3 +1,14 @@
+2017-11-02  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Canvas Tab: show supported GL extensions for selected canvas
+        https://bugs.webkit.org/show_bug.cgi?id=179070
+        <rdar://problem/35278276>
+
+        Reviewed by Brian Burg.
+
+        * inspector/canvas/extensions-expected.txt: Added.
+        * inspector/canvas/extensions.html: Added.
+
 2017-11-02  Youenn Fablet  <youenn@apple.com>
 
         Do not check for CORS in case response is coming from a service worker
diff --git a/LayoutTests/inspector/canvas/extensions-expected.txt b/LayoutTests/inspector/canvas/extensions-expected.txt
new file mode 100644 (file)
index 0000000..8ba2bd6
--- /dev/null
@@ -0,0 +1,7 @@
+Test that CanvasManager is notified whenever a WebGL extension is enabled for a given canvas context.
+
+
+== Running test suite: Canvas.extensions
+-- Running test case: Canvas.extensions.enable
+PASS: The newly enabled extension should be "WEBGL_lose_context".
+
diff --git a/LayoutTests/inspector/canvas/extensions.html b/LayoutTests/inspector/canvas/extensions.html
new file mode 100644 (file)
index 0000000..e8fcbfe
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function load() {
+    window.context = document.body.appendChild(document.createElement("canvas")).getContext("webgl");
+
+    runTest();
+}
+
+function enableExtension(name) {
+    window.context.getExtension(name);
+}
+
+function test() {
+    let suite = InspectorTest.createAsyncSuite("Canvas.extensions");
+
+    suite.addTestCase({
+        name: "Canvas.extensions.enable",
+        description: "Check that enabling an extension notifies the frontend.",
+        test(resolve, reject) {
+            let canvases = WI.canvasManager.canvases.filter((canvas) => canvas.contextType === WI.Canvas.ContextType.WebGL);
+            if (!canvases.length) {
+                reject("Missing WebGL canvas.");
+                return;
+            }
+
+            InspectorTest.assert(canvases.length === 1, "There should only be one WebGL canvas.");
+            InspectorTest.assert(canvases[0].extensions.size === 0, "The canvas should have no extensions enabled after being created");
+
+            const extensionName = "WEBGL_lose_context";
+            canvases[0].awaitEvent(WI.Canvas.Event.ExtensionEnabled).then((event) => {
+                InspectorTest.expectEqual(event.data.extension, extensionName, `The newly enabled extension should be "${extensionName}".`);
+                InspectorTest.assert(canvases[0].extensions.size === 1, "The canvas should now have one extension.");
+                InspectorTest.assert(canvases[0].extensions.has(event.data.extension), `The one extension should be "${extensionName}".`);
+            }).then(resolve, reject);
+
+            InspectorTest.evaluateInPage(`enableExtension("${extensionName}")`);
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="load()">
+    <p>Test that CanvasManager is notified whenever a WebGL extension is enabled for a given canvas context.</p>
+</body>
+</html>
index 0f05af8..c7a1c8c 100644 (file)
@@ -1,3 +1,15 @@
+2017-11-02  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Canvas Tab: show supported GL extensions for selected canvas
+        https://bugs.webkit.org/show_bug.cgi?id=179070
+        <rdar://problem/35278276>
+
+        Reviewed by Brian Burg.
+
+        * inspector/protocol/Canvas.json:
+        Add `extensionEnabled` event that is fired each time `getExtension` is called with a
+        different string on a WebGL context.
+
 2017-11-02  Joseph Pecoraro  <pecoraro@apple.com>
 
         Make ServiceWorker a Remote Inspector debuggable target
index e6a92e9..5b73910 100644 (file)
             ]
         },
         {
+            "name": "extensionEnabled",
+            "parameters": [
+                { "name": "canvasId", "$ref": "CanvasId" },
+                { "name": "extension", "type": "string", "description": "Name of the extension that was enabled." }
+            ]
+        },
+        {
             "name": "cssCanvasClientNodesChanged",
             "parameters": [
                 { "name": "canvasId", "$ref": "CanvasId", "description": "Identifier of canvas that changed." }
index 76557a4..fd5b0d9 100644 (file)
@@ -1,3 +1,34 @@
+2017-11-02  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Canvas Tab: show supported GL extensions for selected canvas
+        https://bugs.webkit.org/show_bug.cgi?id=179070
+        <rdar://problem/35278276>
+
+        Reviewed by Brian Burg.
+
+        Test: inspector/canvas/extensions.html
+
+        * html/canvas/WebGL2RenderingContext.cpp:
+        (WebCore::WebGL2RenderingContext::getExtension):
+        * html/canvas/WebGLRenderingContext.cpp:
+        (WebCore::WebGLRenderingContext::getExtension):
+        Rework common logic into a macro for readability and to simplify adding calls to
+        InspectorInstrumentation functions.
+
+        * html/canvas/WebGLRenderingContextBase.h:
+        * html/canvas/WebGLRenderingContextBase.cpp:
+        (WebCore::WebGLRenderingContextBase::extensionIsEnabled):
+
+        * inspector/InspectorCanvasAgent.h:
+        * inspector/InspectorCanvasAgent.cpp:
+        (WebCore::InspectorCanvasAgent::enable):
+        (WebCore::InspectorCanvasAgent::didEnableExtension):
+
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::didEnableExtension):
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::didEnableExtensionImpl):
+
 2017-11-02  Youenn Fablet  <youenn@apple.com>
 
         Do not check for CORS in case response is coming from a service worker
index e239fe4..cb8af3d 100644 (file)
@@ -35,6 +35,7 @@
 #include "HTMLImageElement.h"
 #include "HTMLVideoElement.h"
 #include "ImageData.h"
+#include "InspectorInstrumentation.h"
 #include "OESTextureFloat.h"
 #include "OESTextureFloatLinear.h"
 #include "OESTextureHalfFloat.h"
@@ -1080,87 +1081,29 @@ WebGLExtension* WebGL2RenderingContext::getExtension(const String& name)
     if (isContextLostOrPending())
         return nullptr;
 
-    if (equalIgnoringASCIICase(name, "EXT_texture_filter_anisotropic") || equalIgnoringASCIICase(name, "WEBKIT_EXT_texture_filter_anisotropic")) {
-        if (!m_extTextureFilterAnisotropic) {
-            m_extTextureFilterAnisotropic = enableSupportedExtension("GL_EXT_texture_filter_anisotropic")
-                ? std::make_unique<EXTTextureFilterAnisotropic>(*this) : nullptr;
-        }
-        return m_extTextureFilterAnisotropic.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_texture_float")) {
-        if (!m_oesTextureFloat) {
-            m_oesTextureFloat = enableSupportedExtension("GL_OES_texture_float")
-                ? std::make_unique<OESTextureFloat>(*this) : nullptr;
-        }
-        return m_oesTextureFloat.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_texture_float_linear")) {
-        if (!m_oesTextureFloatLinear) {
-            m_oesTextureFloatLinear = enableSupportedExtension("GL_OES_texture_float_linear")
-                ? std::make_unique<OESTextureFloatLinear>(*this) : nullptr;
-        }
-        return m_oesTextureFloatLinear.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_texture_half_float")) {
-        if (!m_oesTextureHalfFloat) {
-            m_oesTextureHalfFloat = enableSupportedExtension("GL_OES_texture_half_float")
-                ? std::make_unique<OESTextureHalfFloat>(*this) : nullptr;
-        }
-        return m_oesTextureHalfFloat.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_texture_half_float_linear")) {
-        if (!m_oesTextureHalfFloatLinear) {
-            m_oesTextureHalfFloatLinear = enableSupportedExtension("GL_OES_texture_half_float_linear")
-                ? std::make_unique<OESTextureHalfFloatLinear>(*this) : nullptr;
-        }
-        return m_oesTextureHalfFloatLinear.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_lose_context")) {
-        if (!m_webglLoseContext)
-            m_webglLoseContext = std::make_unique<WebGLLoseContext>(*this);
-        return m_webglLoseContext.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBKIT_WEBGL_compressed_texture_atc")) {
-        if (!m_webglCompressedTextureATC) {
-            if (WebGLCompressedTextureATC::supported(*this))
-                m_webglCompressedTextureATC = std::make_unique<WebGLCompressedTextureATC>(*this);
-        }
-        return m_webglCompressedTextureATC.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBKIT_WEBGL_compressed_texture_pvrtc")) {
-        if (!m_webglCompressedTexturePVRTC) {
-            m_webglCompressedTexturePVRTC = WebGLCompressedTexturePVRTC::supported(*this)
-                ? std::make_unique<WebGLCompressedTexturePVRTC>(*this) : nullptr;
-        }
-        return m_webglCompressedTexturePVRTC.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_compressed_texture_s3tc")) {
-        if (!m_webglCompressedTextureS3TC) {
-            m_webglCompressedTextureS3TC = WebGLCompressedTextureS3TC::supported(*this)
-                ? std::make_unique<WebGLCompressedTextureS3TC>(*this) : nullptr;
-        }
-        return m_webglCompressedTextureS3TC.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_depth_texture")) {
-        if (!m_webglDepthTexture) {
-            m_webglDepthTexture = WebGLDepthTexture::supported(*graphicsContext3D())
-                ? std::make_unique<WebGLDepthTexture>(*this) : nullptr;
-        }
-        return m_webglDepthTexture.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_debug_renderer_info")) {
-        if (!m_webglDebugRendererInfo)
-            m_webglDebugRendererInfo = std::make_unique<WebGLDebugRendererInfo>(*this);
-        return m_webglDebugRendererInfo.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_debug_shaders")) {
-        if (!m_webglDebugShaders) {
-            m_webglDebugShaders = m_context->getExtensions().supports(ASCIILiteral { "GL_ANGLE_translated_shader_source" })
-                ? std::make_unique<WebGLDebugShaders>(*this) : nullptr;
-        }
-        return m_webglDebugShaders.get();
-    }
-
+#define ENABLE_IF_REQUESTED(type, variable, nameLiteral, canEnable) \
+    if (equalIgnoringASCIICase(name, nameLiteral)) { \
+        if (!variable) { \
+            variable = (canEnable) ? std::make_unique<type>(*this) : nullptr; \
+            if (variable != nullptr) \
+                InspectorInstrumentation::didEnableExtension(*this, name); \
+        } \
+        return variable.get(); \
+    }
+
+    ENABLE_IF_REQUESTED(EXTTextureFilterAnisotropic, m_extTextureFilterAnisotropic, "EXT_texture_filter_anisotropic", enableSupportedExtension("GL_EXT_texture_filter_anisotropic"));
+    ENABLE_IF_REQUESTED(EXTTextureFilterAnisotropic, m_extTextureFilterAnisotropic, "WEBKIT_EXT_texture_filter_anisotropic", enableSupportedExtension("GL_OES_texture_float"));
+    ENABLE_IF_REQUESTED(OESTextureFloat, m_oesTextureFloat, "OES_texture_float", enableSupportedExtension("GL_OES_texture_float"));
+    ENABLE_IF_REQUESTED(OESTextureFloatLinear, m_oesTextureFloatLinear, "OES_texture_float_linear", enableSupportedExtension("GL_OES_texture_float_linear"));
+    ENABLE_IF_REQUESTED(OESTextureHalfFloat, m_oesTextureHalfFloat, "OES_texture_half_float", enableSupportedExtension("GL_OES_texture_half_float"));
+    ENABLE_IF_REQUESTED(OESTextureHalfFloatLinear, m_oesTextureHalfFloatLinear, "OES_texture_half_float_linear", enableSupportedExtension("GL_OES_texture_half_float_linear"));
+    ENABLE_IF_REQUESTED(WebGLLoseContext, m_webglLoseContext, "WEBGL_lose_context", true);
+    ENABLE_IF_REQUESTED(WebGLCompressedTextureATC, m_webglCompressedTextureATC, "WEBKIT_WEBGL_compressed_texture_atc", WebGLCompressedTextureATC::supported(*this));
+    ENABLE_IF_REQUESTED(WebGLCompressedTexturePVRTC, m_webglCompressedTexturePVRTC, "WEBKIT_WEBGL_compressed_texture_pvrtc", WebGLCompressedTexturePVRTC::supported(*this));
+    ENABLE_IF_REQUESTED(WebGLCompressedTextureS3TC, m_webglCompressedTextureS3TC, "WEBGL_compressed_texture_s3tc", WebGLCompressedTextureS3TC::supported(*this));
+    ENABLE_IF_REQUESTED(WebGLDepthTexture, m_webglDepthTexture, "WEBGL_depth_texture", WebGLDepthTexture::supported(*graphicsContext3D()));
+    ENABLE_IF_REQUESTED(WebGLDebugRendererInfo, m_webglDebugRendererInfo, "WEBGL_debug_renderer_info", true);
+    ENABLE_IF_REQUESTED(WebGLDebugShaders, m_webglDebugShaders, "WEBGL_debug_shaders", m_context->getExtensions().supports(ASCIILiteral { "GL_ANGLE_translated_shader_source" }));
     return nullptr;
 }
 
index 788189a..5580118 100644 (file)
@@ -40,6 +40,7 @@
 #include "HTMLImageElement.h"
 #include "HTMLVideoElement.h"
 #include "ImageData.h"
+#include "InspectorInstrumentation.h"
 #include "OESElementIndexUint.h"
 #include "OESStandardDerivatives.h"
 #include "OESTextureFloat.h"
@@ -89,28 +90,20 @@ WebGLExtension* WebGLRenderingContext::getExtension(const String& name)
 {
     if (isContextLostOrPending())
         return nullptr;
-    
-    if (equalIgnoringASCIICase(name, "EXT_blend_minmax")) {
-        if (!m_extBlendMinMax) {
-            m_extBlendMinMax = enableSupportedExtension("GL_EXT_blend_minmax")
-                ? std::make_unique<EXTBlendMinMax>(*this) : nullptr;
-        }
-        return m_extBlendMinMax.get();
-    }
-    if (equalIgnoringASCIICase(name, "EXT_sRGB")) {
-        if (!m_extsRGB) {
-            m_extsRGB = enableSupportedExtension("GL_EXT_sRGB")
-                ? std::make_unique<EXTsRGB>(*this) : nullptr;
-        }
-        return m_extsRGB.get();
-    }
-    if (equalIgnoringASCIICase(name, "EXT_frag_depth")) {
-        if (!m_extFragDepth) {
-            m_extFragDepth = enableSupportedExtension("GL_EXT_frag_depth")
-                ? std::make_unique<EXTFragDepth>(*this) : nullptr;
-        }
-        return m_extFragDepth.get();
+
+#define ENABLE_IF_REQUESTED(type, variable, nameLiteral, canEnable) \
+    if (equalIgnoringASCIICase(name, nameLiteral)) { \
+        if (!variable) { \
+            variable = (canEnable) ? std::make_unique<type>(*this) : nullptr; \
+            if (variable != nullptr) \
+                InspectorInstrumentation::didEnableExtension(*this, name); \
+        } \
+        return variable.get(); \
     }
+
+    ENABLE_IF_REQUESTED(EXTBlendMinMax, m_extBlendMinMax, "EXT_blend_minmax", enableSupportedExtension("GL_EXT_blend_minmax"));
+    ENABLE_IF_REQUESTED(EXTsRGB, m_extsRGB, "EXT_sRGB", enableSupportedExtension("GL_EXT_sRGB"));
+    ENABLE_IF_REQUESTED(EXTFragDepth, m_extFragDepth, "EXT_frag_depth", enableSupportedExtension("GL_EXT_frag_depth"));
     if (equalIgnoringASCIICase(name, "EXT_shader_texture_lod")) {
         if (!m_extShaderTextureLOD) {
             if (!(m_context->getExtensions().supports(ASCIILiteral { "GL_EXT_shader_texture_lod" }) || m_context->getExtensions().supports(ASCIILiteral { "GL_ARB_shader_texture_lod" })))
@@ -118,99 +111,25 @@ WebGLExtension* WebGLRenderingContext::getExtension(const String& name)
             else {
                 m_context->getExtensions().ensureEnabled(ASCIILiteral { "GL_EXT_shader_texture_lod" });
                 m_extShaderTextureLOD = std::make_unique<EXTShaderTextureLOD>(*this);
+                InspectorInstrumentation::didEnableExtension(*this, name);
             }
         }
         return m_extShaderTextureLOD.get();
     }
-    if (equalIgnoringASCIICase(name, "EXT_texture_filter_anisotropic") || equalIgnoringASCIICase(name, "WEBKIT_EXT_texture_filter_anisotropic")) {
-        if (!m_extTextureFilterAnisotropic) {
-            m_extTextureFilterAnisotropic = enableSupportedExtension("GL_EXT_texture_filter_anisotropic")
-                ? std::make_unique<EXTTextureFilterAnisotropic>(*this) : nullptr;
-        }
-        return m_extTextureFilterAnisotropic.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_standard_derivatives")) {
-        if (!m_oesStandardDerivatives) {
-            m_oesStandardDerivatives = enableSupportedExtension("GL_OES_standard_derivatives")
-                ? std::make_unique<OESStandardDerivatives>(*this) : nullptr;
-        }
-        return m_oesStandardDerivatives.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_texture_float")) {
-        if (!m_oesTextureFloat) {
-            m_oesTextureFloat = enableSupportedExtension("GL_OES_texture_float")
-                ? std::make_unique<OESTextureFloat>(*this) : nullptr;
-        }
-        return m_oesTextureFloat.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_texture_float_linear")) {
-        if (!m_oesTextureFloatLinear) {
-            m_oesTextureFloatLinear = enableSupportedExtension("GL_OES_texture_float_linear")
-                ? std::make_unique<OESTextureFloatLinear>(*this) : nullptr;
-        }
-        return m_oesTextureFloatLinear.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_texture_half_float")) {
-        if (!m_oesTextureHalfFloat) {
-            m_oesTextureHalfFloat = enableSupportedExtension("GL_OES_texture_half_float")
-                ? std::make_unique<OESTextureHalfFloat>(*this) : nullptr;
-        }
-        return m_oesTextureHalfFloat.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_texture_half_float_linear")) {
-        if (!m_oesTextureHalfFloatLinear) {
-            m_oesTextureHalfFloatLinear = enableSupportedExtension("GL_OES_texture_half_float_linear")
-                ? std::make_unique<OESTextureHalfFloatLinear>(*this) : nullptr;
-        }
-        return m_oesTextureHalfFloatLinear.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_vertex_array_object")) {
-        if (!m_oesVertexArrayObject) {
-            m_oesVertexArrayObject = enableSupportedExtension("GL_OES_vertex_array_object")
-                ? std::make_unique<OESVertexArrayObject>(*this) : nullptr;
-        }
-        return m_oesVertexArrayObject.get();
-    }
-    if (equalIgnoringASCIICase(name, "OES_element_index_uint")) {
-        if (!m_oesElementIndexUint) {
-            m_oesElementIndexUint = enableSupportedExtension("GL_OES_element_index_uint")
-                ? std::make_unique<OESElementIndexUint>(*this) : nullptr;
-        }
-        return m_oesElementIndexUint.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_lose_context")) {
-        if (!m_webglLoseContext)
-            m_webglLoseContext = std::make_unique<WebGLLoseContext>(*this);
-        return m_webglLoseContext.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBKIT_WEBGL_compressed_texture_atc")) {
-        if (!m_webglCompressedTextureATC) {
-            m_webglCompressedTextureATC = WebGLCompressedTextureATC::supported(*this)
-                ? std::make_unique<WebGLCompressedTextureATC>(*this) : nullptr;
-        }
-        return m_webglCompressedTextureATC.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBKIT_WEBGL_compressed_texture_pvrtc")) {
-        if (!m_webglCompressedTexturePVRTC) {
-            m_webglCompressedTexturePVRTC = WebGLCompressedTexturePVRTC::supported(*this)
-                ? std::make_unique<WebGLCompressedTexturePVRTC>(*this) : nullptr;
-        }
-        return m_webglCompressedTexturePVRTC.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_compressed_texture_s3tc")) {
-        if (!m_webglCompressedTextureS3TC) {
-            m_webglCompressedTextureS3TC = WebGLCompressedTextureS3TC::supported(*this)
-                ? std::make_unique<WebGLCompressedTextureS3TC>(*this) : nullptr;
-        }
-        return m_webglCompressedTextureS3TC.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_depth_texture")) {
-        if (!m_webglDepthTexture) {
-            m_webglDepthTexture = WebGLDepthTexture::supported(*m_context)
-                ? std::make_unique<WebGLDepthTexture>(*this) : nullptr;
-        }
-        return m_webglDepthTexture.get();
-    }
+    ENABLE_IF_REQUESTED(EXTTextureFilterAnisotropic, m_extTextureFilterAnisotropic, "EXT_texture_filter_anisotropic", enableSupportedExtension("GL_EXT_texture_filter_anisotropic"));
+    ENABLE_IF_REQUESTED(EXTTextureFilterAnisotropic, m_extTextureFilterAnisotropic, "WEBKIT_EXT_texture_filter_anisotropic", enableSupportedExtension("GL_EXT_texture_filter_anisotropic"));
+    ENABLE_IF_REQUESTED(OESStandardDerivatives, m_oesStandardDerivatives, "OES_standard_derivatives", enableSupportedExtension("GL_OES_standard_derivatives"));
+    ENABLE_IF_REQUESTED(OESTextureFloat, m_oesTextureFloat, "OES_texture_float", enableSupportedExtension("GL_OES_texture_float"));
+    ENABLE_IF_REQUESTED(OESTextureFloatLinear, m_oesTextureFloatLinear, "OES_texture_float_linear", enableSupportedExtension("GL_OES_texture_float_linear"));
+    ENABLE_IF_REQUESTED(OESTextureHalfFloat, m_oesTextureHalfFloat, "OES_texture_half_float", enableSupportedExtension("GL_OES_texture_half_float"));
+    ENABLE_IF_REQUESTED(OESTextureHalfFloatLinear, m_oesTextureHalfFloatLinear, "OES_texture_half_float_linear", enableSupportedExtension("GL_OES_texture_half_float_linear"));
+    ENABLE_IF_REQUESTED(OESVertexArrayObject, m_oesVertexArrayObject, "OES_vertex_array_object", enableSupportedExtension("GL_OES_vertex_array_object"));
+    ENABLE_IF_REQUESTED(OESElementIndexUint, m_oesElementIndexUint, "OES_element_index_uint", enableSupportedExtension("GL_OES_element_index_uint"));
+    ENABLE_IF_REQUESTED(WebGLLoseContext, m_webglLoseContext, "WEBGL_lose_context", true);
+    ENABLE_IF_REQUESTED(WebGLCompressedTextureATC, m_webglCompressedTextureATC, "WEBKIT_WEBGL_compressed_texture_atc", WebGLCompressedTextureATC::supported(*this));
+    ENABLE_IF_REQUESTED(WebGLCompressedTexturePVRTC, m_webglCompressedTexturePVRTC, "WEBKIT_WEBGL_compressed_texture_pvrtc", WebGLCompressedTexturePVRTC::supported(*this));
+    ENABLE_IF_REQUESTED(WebGLCompressedTextureS3TC, m_webglCompressedTextureS3TC, "WEBGL_compressed_texture_s3tc", WebGLCompressedTextureS3TC::supported(*this));
+    ENABLE_IF_REQUESTED(WebGLDepthTexture, m_webglDepthTexture, "WEBGL_depth_texture", WebGLDepthTexture::supported(*m_context));
     if (equalIgnoringASCIICase(name, "WEBGL_draw_buffers")) {
         if (!m_webglDrawBuffers) {
             if (!supportsDrawBuffers())
@@ -218,6 +137,7 @@ WebGLExtension* WebGLRenderingContext::getExtension(const String& name)
             else {
                 m_context->getExtensions().ensureEnabled(ASCIILiteral { "GL_EXT_draw_buffers" });
                 m_webglDrawBuffers = std::make_unique<WebGLDrawBuffers>(*this);
+                InspectorInstrumentation::didEnableExtension(*this, name);
             }
         }
         return m_webglDrawBuffers.get();
@@ -229,23 +149,13 @@ WebGLExtension* WebGLRenderingContext::getExtension(const String& name)
             else {
                 m_context->getExtensions().ensureEnabled(ASCIILiteral { "GL_ANGLE_instanced_arrays" });
                 m_angleInstancedArrays = std::make_unique<ANGLEInstancedArrays>(*this);
+                InspectorInstrumentation::didEnableExtension(*this, name);
             }
         }
         return m_angleInstancedArrays.get();
     }
-    if (equalIgnoringASCIICase(name, "WEBGL_debug_renderer_info")) {
-        if (!m_webglDebugRendererInfo)
-            m_webglDebugRendererInfo = std::make_unique<WebGLDebugRendererInfo>(*this);
-        return m_webglDebugRendererInfo.get();
-    }
-    if (equalIgnoringASCIICase(name, "WEBGL_debug_shaders")) {
-        if (!m_webglDebugShaders) {
-            m_webglDebugShaders = m_context->getExtensions().supports(ASCIILiteral { "GL_ANGLE_translated_shader_source" })
-                ? std::make_unique<WebGLDebugShaders>(*this) : nullptr;
-        }
-        return m_webglDebugShaders.get();
-    }
-
+    ENABLE_IF_REQUESTED(WebGLDebugRendererInfo, m_webglDebugRendererInfo, "WEBGL_debug_renderer_info", true);
+    ENABLE_IF_REQUESTED(WebGLDebugShaders, m_webglDebugShaders, "WEBGL_debug_shaders", m_context->getExtensions().supports(ASCIILiteral { "GL_ANGLE_translated_shader_source" }));
     return nullptr;
 }
 
index 24d7b21..cf2b372 100644 (file)
@@ -2740,6 +2740,37 @@ long long WebGLRenderingContextBase::getVertexAttribOffset(GC3Duint index, GC3De
     return m_context->getVertexAttribOffset(index, pname);
 }
 
+bool WebGLRenderingContextBase::extensionIsEnabled(const String& name)
+{
+#define CHECK_EXTENSION(variable, nameLiteral) \
+    if (equalIgnoringASCIICase(name, nameLiteral)) \
+        return variable != nullptr;
+
+    CHECK_EXTENSION(m_extFragDepth, "EXT_frag_depth");
+    CHECK_EXTENSION(m_extBlendMinMax, "EXT_blend_minmax");
+    CHECK_EXTENSION(m_extsRGB, "EXT_sRGB");
+    CHECK_EXTENSION(m_extTextureFilterAnisotropic, "EXT_texture_filter_anisotropic");
+    CHECK_EXTENSION(m_extTextureFilterAnisotropic, "WEBKIT_EXT_texture_filter_anisotropic");
+    CHECK_EXTENSION(m_extShaderTextureLOD, "EXT_shader_texture_lod");
+    CHECK_EXTENSION(m_oesTextureFloat, "OES_texture_float");
+    CHECK_EXTENSION(m_oesTextureFloatLinear, "OES_texture_float_linear");
+    CHECK_EXTENSION(m_oesTextureHalfFloat, "OES_texture_half_float");
+    CHECK_EXTENSION(m_oesTextureHalfFloatLinear, "OES_texture_half_float_linear");
+    CHECK_EXTENSION(m_oesStandardDerivatives, "OES_standard_derivatives");
+    CHECK_EXTENSION(m_oesVertexArrayObject, "OES_vertex_array_object");
+    CHECK_EXTENSION(m_oesElementIndexUint, "OES_element_index_uint");
+    CHECK_EXTENSION(m_webglLoseContext, "WEBGL_lose_context");
+    CHECK_EXTENSION(m_webglDebugRendererInfo, "WEBGL_debug_renderer_info");
+    CHECK_EXTENSION(m_webglDebugShaders, "WEBGL_debug_shaders");
+    CHECK_EXTENSION(m_webglCompressedTextureATC, "WEBKIT_WEBGL_compressed_texture_atc");
+    CHECK_EXTENSION(m_webglCompressedTexturePVRTC, "WEBKIT_WEBGL_compressed_texture_pvrtc");
+    CHECK_EXTENSION(m_webglCompressedTextureS3TC, "WEBGL_compressed_texture_s3tc");
+    CHECK_EXTENSION(m_webglDepthTexture, "WEBGL_depth_texture");
+    CHECK_EXTENSION(m_webglDrawBuffers, "WEBGL_draw_buffers");
+    CHECK_EXTENSION(m_angleInstancedArrays, "ANGLE_instanced_arrays");
+    return false;
+}
+
 GC3Dboolean WebGLRenderingContextBase::isBuffer(WebGLBuffer* buffer)
 {
     if (!buffer || isContextLostOrPending())
index f85c59b..9a65604 100644 (file)
@@ -210,6 +210,8 @@ public:
     WebGLAny getVertexAttrib(GC3Duint index, GC3Denum pname);
     long long getVertexAttribOffset(GC3Duint index, GC3Denum pname);
 
+    bool extensionIsEnabled(const String&);
+
     bool isPreservingDrawingBuffer() const { return m_attributes.preserveDrawingBuffer; }
     void setPreserveDrawingBuffer(bool value) { m_attributes.preserveDrawingBuffer = value; }
 
index 321ceab..6974220 100644 (file)
@@ -1013,6 +1013,12 @@ void InspectorInstrumentation::didFinishRecordingCanvasFrameImpl(InstrumentingAg
 }
 
 #if ENABLE(WEBGL)
+void InspectorInstrumentation::didEnableExtensionImpl(InstrumentingAgents& instrumentingAgents, WebGLRenderingContextBase& context, const String& extension)
+{
+    if (InspectorCanvasAgent* canvasAgent = instrumentingAgents.inspectorCanvasAgent())
+        canvasAgent->didEnableExtension(context, extension);
+}
+
 void InspectorInstrumentation::didCreateProgramImpl(InstrumentingAgents& instrumentingAgents, WebGLRenderingContextBase& context, WebGLProgram& program)
 {
     if (InspectorCanvasAgent* canvasAgent = instrumentingAgents.inspectorCanvasAgent())
index c636cfc..5350485 100644 (file)
@@ -250,8 +250,8 @@ public:
     static void didChangeCanvasMemory(HTMLCanvasElement&);
     static void recordCanvasAction(CanvasRenderingContext&, const String&, Vector<RecordCanvasActionVariant>&& = { });
     static void didFinishRecordingCanvasFrame(HTMLCanvasElement&, bool forceDispatch = false);
-
 #if ENABLE(WEBGL)
+    static void didEnableExtension(WebGLRenderingContextBase&, const String&);
     static void didCreateProgram(WebGLRenderingContextBase&, WebGLProgram&);
     static void willDeleteProgram(WebGLRenderingContextBase&, WebGLProgram&);
     static bool isShaderProgramDisabled(WebGLRenderingContextBase&, WebGLProgram&);
@@ -423,6 +423,7 @@ private:
     static void recordCanvasActionImpl(InstrumentingAgents&, CanvasRenderingContext&, const String&, Vector<RecordCanvasActionVariant>&& = { });
     static void didFinishRecordingCanvasFrameImpl(InstrumentingAgents&, HTMLCanvasElement&, bool forceDispatch = false);
 #if ENABLE(WEBGL)
+    static void didEnableExtensionImpl(InstrumentingAgents&, WebGLRenderingContextBase&, const String&);
     static void didCreateProgramImpl(InstrumentingAgents&, WebGLRenderingContextBase&, WebGLProgram&);
     static void willDeleteProgramImpl(InstrumentingAgents&, WebGLProgram&);
     static bool isShaderProgramDisabledImpl(InstrumentingAgents&, WebGLProgram&);
@@ -1202,6 +1203,13 @@ inline void InspectorInstrumentation::didFinishRecordingCanvasFrame(HTMLCanvasEl
 }
 
 #if ENABLE(WEBGL)
+inline void InspectorInstrumentation::didEnableExtension(WebGLRenderingContextBase& context, const String& extension)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(context.canvas().document()))
+        didEnableExtensionImpl(*instrumentingAgents, context, extension);
+}
+
 inline void InspectorInstrumentation::didCreateProgram(WebGLRenderingContextBase& context, WebGLProgram& program)
 {
     if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(context.canvas().document()))
index c3d67d2..0e2a92d 100644 (file)
@@ -95,10 +95,24 @@ void InspectorCanvasAgent::enable(ErrorString&)
     m_enabled = true;
 
     const bool captureBacktrace = false;
-    for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
+    for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
         m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(m_instrumentingAgents, captureBacktrace));
 
 #if ENABLE(WEBGL)
+        CanvasRenderingContext* context = inspectorCanvas->canvas().renderingContext();
+        if (is<WebGLRenderingContextBase>(context)) {
+            WebGLRenderingContextBase* contextWebGL = downcast<WebGLRenderingContextBase>(context);
+            if (std::optional<Vector<String>> extensions = contextWebGL->getSupportedExtensions()) {
+                for (const String& extension : *extensions) {
+                    if (contextWebGL->extensionIsEnabled(extension))
+                        m_frontendDispatcher->extensionEnabled(inspectorCanvas->identifier(), extension);
+                }
+            }
+        }
+#endif
+    }
+
+#if ENABLE(WEBGL)
     for (auto& inspectorProgram : m_identifierToInspectorProgram.values()) {
         auto& inspectorCanvas = inspectorProgram->canvas();
         m_frontendDispatcher->programCreated(inspectorCanvas.identifier(), inspectorProgram->identifier());
@@ -508,6 +522,15 @@ void InspectorCanvasAgent::didFinishRecordingCanvasFrame(HTMLCanvasElement& canv
 }
 
 #if ENABLE(WEBGL)
+void InspectorCanvasAgent::didEnableExtension(WebGLRenderingContextBase& context, const String& extension)
+{
+    auto* inspectorCanvas = findInspectorCanvas(context.canvas());
+    if (!inspectorCanvas)
+        return;
+
+    m_frontendDispatcher->extensionEnabled(inspectorCanvas->identifier(), extension);
+}
+
 void InspectorCanvasAgent::didCreateProgram(WebGLRenderingContextBase& context, WebGLProgram& program)
 {
     auto* inspectorCanvas = findInspectorCanvas(context.canvas());
index 11a5e66..53f12a1 100644 (file)
@@ -88,6 +88,7 @@ public:
     void recordCanvasAction(CanvasRenderingContext&, const String&, Vector<RecordCanvasActionVariant>&& = { });
     void didFinishRecordingCanvasFrame(HTMLCanvasElement&, bool forceDispatch = false);
 #if ENABLE(WEBGL)
+    void didEnableExtension(WebGLRenderingContextBase&, const String&);
     void didCreateProgram(WebGLRenderingContextBase&, WebGLProgram&);
     void willDeleteProgram(WebGLProgram&);
     bool isShaderProgramDisabled(WebGLProgram&);
index c2e0df9..f820f03 100644 (file)
@@ -1,3 +1,34 @@
+2017-11-02  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Canvas Tab: show supported GL extensions for selected canvas
+        https://bugs.webkit.org/show_bug.cgi?id=179070
+        <rdar://problem/35278276>
+
+        Reviewed by Brian Burg.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+        * UserInterface/Protocol/CanvasObserver.js:
+        (WI.CanvasObserver.prototype.extensionEnabled):
+
+        * UserInterface/Controllers/CanvasManager.js:
+        (WI.CanvasManager.prototype.extensionEnabled):
+
+        * UserInterface/Models/Canvas.js:
+        (WI.Canvas.prototype.get extensions):
+        (WI.Canvas.prototype.enableExtension):
+        Maintain a Set of enabled extensions, and dispatch an event whenever an extension is enabled.
+
+        * UserInterface/Views/CanvasDetailsSidebarPanel.css:
+        (.sidebar > .panel.details.canvas .details-section.canvas-extensions .content > ul):
+        * UserInterface/Views/CanvasDetailsSidebarPanel.js:
+        (WI.CanvasDetailsSidebarPanel.prototype.set canvas):
+        (WI.CanvasDetailsSidebarPanel.prototype.initialLayout):
+        (WI.CanvasDetailsSidebarPanel.prototype.layout):
+        (WI.CanvasDetailsSidebarPanel.prototype._refreshAttributesSection):
+        (WI.CanvasDetailsSidebarPanel.prototype._refreshExtensionsSection):
+        Drive-by: hide Attributes section when the canvas has no attributes.
+
 2017-11-02  Joseph Pecoraro  <pecoraro@apple.com>
 
         Make ServiceWorker a Remote Inspector debuggable target
index dca8b51..ba17882 100644 (file)
@@ -395,6 +395,7 @@ localizedStrings["Export"] = "Export";
 localizedStrings["Export HAR"] = "Export HAR";
 localizedStrings["Expression"] = "Expression";
 localizedStrings["Extension Scripts"] = "Extension Scripts";
+localizedStrings["Extensions"] = "Extensions";
 localizedStrings["Extra Scripts"] = "Extra Scripts";
 localizedStrings["Fade unexecuted code"] = "Fade unexecuted code";
 localizedStrings["Failed to upgrade"] = "Failed to upgrade";
index 7b57763..311950f 100644 (file)
@@ -160,6 +160,18 @@ WI.CanvasManager = class CanvasManager extends WI.Object
         this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingStopped, {canvas, recording});
     }
 
+    extensionEnabled(canvasIdentifier, extension)
+    {
+        // Called from WI.CanvasObserver.
+
+        let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
+        console.assert(canvas);
+        if (!canvas)
+            return;
+
+        canvas.enableExtension(extension);
+    }
+
     programCreated(canvasIdentifier, programIdentifier)
     {
         // Called from WI.CanvasObserver.
index 8676338..9c4e11f 100644 (file)
@@ -39,6 +39,7 @@ WI.Canvas = class Canvas extends WI.Object
         this._domNode = domNode || null;
         this._cssCanvasName = cssCanvasName || "";
         this._contextAttributes = contextAttributes || {};
+        this._extensions = new Set;
         this._memoryCost = memoryCost || NaN;
         this._backtrace = backtrace || [];
 
@@ -111,6 +112,7 @@ WI.Canvas = class Canvas extends WI.Object
     get frame() { return this._frame; }
     get cssCanvasName() { return this._cssCanvasName; }
     get contextAttributes() { return this._contextAttributes; }
+    get extensions() { return this._extensions; }
     get backtrace() { return this._backtrace; }
     get shaderProgramCollection() { return this._shaderProgramCollection; }
     get recordingCollection() { return this._recordingCollection; }
@@ -265,6 +267,15 @@ WI.Canvas = class Canvas extends WI.Object
 
     }
 
+    enableExtension(extension)
+    {
+        // Called from WI.CanvasManager.
+
+        this._extensions.add(extension);
+
+        this.dispatchEventToListeners(WI.Canvas.Event.ExtensionEnabled, {extension});
+    }
+
     cssCanvasClientNodesChanged()
     {
         // Called from WI.CanvasManager.
@@ -299,5 +310,6 @@ WI.Canvas.ContextType = {
 
 WI.Canvas.Event = {
     MemoryChanged: "canvas-memory-changed",
+    ExtensionEnabled: "canvas-extension-enabled",
     CSSCanvasClientNodesChanged: "canvas-css-canvas-client-nodes-changed",
 };
index ded281f..656783d 100644 (file)
@@ -52,6 +52,11 @@ WI.CanvasObserver = class CanvasObserver
         WI.canvasManager.recordingFinished(canvasId, recording);
     }
 
+    extensionEnabled(canvasId, extension)
+    {
+        WI.canvasManager.extensionEnabled(canvasId, extension);
+    }
+
     programCreated(canvasId, programId)
     {
         WI.canvasManager.programCreated(canvasId, programId);
index c7db484..9401bd1 100644 (file)
     display: block;
 }
 
+.sidebar > .panel.details.canvas .details-section.canvas-extensions .content > ul {
+    margin: 4px 0;
+    -webkit-padding-start: 22.5px;
+}
+
 .sidebar > .panel.details.canvas .details-section.canvas-backtrace .call-frame {
     margin: 3px;
     font-size: 11px;
index 7e39835..56fbd7d 100644 (file)
@@ -77,6 +77,7 @@ WI.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends WI.Detail
 
         if (this._canvas) {
             this._canvas.removeEventListener(WI.Canvas.Event.MemoryChanged, this._canvasMemoryChanged, this);
+            this._canvas.removeEventListener(WI.Canvas.Event.ExtensionEnabled, this._refreshExtensionsSection, this);
             this._canvas.removeEventListener(WI.Canvas.Event.CSSCanvasClientNodesChanged, this._refreshCSSCanvasSection, this);
         }
 
@@ -84,6 +85,7 @@ WI.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends WI.Detail
 
         if (this._canvas) {
             this._canvas.addEventListener(WI.Canvas.Event.MemoryChanged, this._canvasMemoryChanged, this);
+            this._canvas.addEventListener(WI.Canvas.Event.ExtensionEnabled, this._refreshExtensionsSection, this);
             this._canvas.addEventListener(WI.Canvas.Event.CSSCanvasClientNodesChanged, this._refreshCSSCanvasSection, this);
         }
 
@@ -117,9 +119,14 @@ WI.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends WI.Detail
 
         this._attributesDataGridRow = new WI.DetailsSectionDataGridRow(null, WI.UIString("No Attributes"));
 
-        let attributesSection = new WI.DetailsSection("canvas-attributes", WI.UIString("Attributes"));
-        attributesSection.groups = [new WI.DetailsSectionGroup([this._attributesDataGridRow])];
-        this._sections.push(attributesSection);
+        this._attributesSection = new WI.DetailsSection("canvas-attributes", WI.UIString("Attributes"));
+        this._attributesSection.groups = [new WI.DetailsSectionGroup([this._attributesDataGridRow])];
+        this._attributesSection.element.hidden = true;
+        this._sections.push(this._attributesSection);
+
+        this._extensionsSection = new WI.DetailsSection("canvas-extensions", WI.UIString("Extensions"));
+        this._extensionsSection.element.hidden = true;
+        this._sections.push(this._extensionsSection);
 
         this._cssCanvasClientsRow = new WI.DetailsSectionSimpleRow(WI.UIString("Nodes"));
 
@@ -156,6 +163,7 @@ WI.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends WI.Detail
         this._refreshIdentitySection();
         this._refreshSourceSection();
         this._refreshAttributesSection();
+        this._refreshExtensionsSection();
         this._refreshCSSCanvasSection();
         this._refreshBacktraceSection();
     }
@@ -244,11 +252,10 @@ WI.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends WI.Detail
 
     _refreshAttributesSection()
     {
-        if (isEmptyObject(this._canvas.contextAttributes)) {
-            // Remove the DataGrid to show the placeholder text.
-            this._attributesDataGridRow.dataGrid = null;
+        let hasAttributes = !isEmptyObject(this._canvas.contextAttributes);
+        this._attributesSection.element.hidden = !hasAttributes;
+        if (!hasAttributes)
             return;
-        }
 
         let dataGrid = this._attributesDataGridRow.dataGrid;
         if (!dataGrid) {
@@ -269,6 +276,21 @@ WI.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends WI.Detail
         dataGrid.updateLayoutIfNeeded();
     }
 
+    _refreshExtensionsSection()
+    {
+        let hasEnabledExtensions = this._canvas.extensions.size > 0;
+        this._extensionsSection.element.hidden = !hasEnabledExtensions;
+        if (!hasEnabledExtensions)
+            return;
+
+        let element = document.createElement("ul");
+        for (let extension of this._canvas.extensions) {
+            let listElement = element.appendChild(document.createElement("li"));
+            listElement.textContent = extension;
+        }
+        this._extensionsSection.groups = [{element}];
+    }
+
     _refreshCSSCanvasSection()
     {
         if (!this.didInitialLayout)