mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c09e2aa6b8 | ||
|
|
44f02f61ff | ||
|
|
904fbbd598 | ||
|
|
36c88a46db | ||
|
|
9bf9c3606f | ||
|
|
d2841683c1 | ||
|
|
4aa36102d7 | ||
|
|
e1c17fd1e8 |
@@ -17,11 +17,6 @@ export type WindowOpenArgs = {
|
||||
features: string,
|
||||
}
|
||||
|
||||
const frameNamesToWindow = new Map<string, WebContents>();
|
||||
const registerFrameNameToGuestWindow = (name: string, webContents: WebContents) => frameNamesToWindow.set(name, webContents);
|
||||
const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name);
|
||||
const getGuestWebContentsByFrameName = (name: string) => frameNamesToWindow.get(name);
|
||||
|
||||
/**
|
||||
* `openGuestWindow` is called to create and setup event handling for the new
|
||||
* window.
|
||||
@@ -47,20 +42,6 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||
...overrideBrowserWindowOptions
|
||||
};
|
||||
|
||||
// To spec, subsequent window.open calls with the same frame name (`target` in
|
||||
// spec parlance) will reuse the previous window.
|
||||
// https://html.spec.whatwg.org/multipage/window-object.html#apis-for-creating-and-navigating-browsing-contexts-by-name
|
||||
const existingWebContents = getGuestWebContentsByFrameName(frameName);
|
||||
if (existingWebContents) {
|
||||
if (existingWebContents.isDestroyed()) {
|
||||
// FIXME(t57ser): The webContents is destroyed for some reason, unregister the frame name
|
||||
unregisterFrameName(frameName);
|
||||
} else {
|
||||
existingWebContents.loadURL(url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (createWindow) {
|
||||
const webContents = createWindow({
|
||||
webContents: guest,
|
||||
@@ -72,7 +53,7 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||
throw new Error('Invalid webContents. Created window should be connected to webContents passed with options object.');
|
||||
}
|
||||
|
||||
handleWindowLifecycleEvents({ embedder, frameName, guest, outlivesOpener });
|
||||
handleWindowLifecycleEvents({ embedder, guest, outlivesOpener });
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -96,7 +77,7 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||
});
|
||||
}
|
||||
|
||||
handleWindowLifecycleEvents({ embedder, frameName, guest: window.webContents, outlivesOpener });
|
||||
handleWindowLifecycleEvents({ embedder, guest: window.webContents, outlivesOpener });
|
||||
|
||||
embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, referrer, postData });
|
||||
}
|
||||
@@ -107,10 +88,9 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||
* too is the guest destroyed; this is Electron convention and isn't based in
|
||||
* browser behavior.
|
||||
*/
|
||||
const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outlivesOpener }: {
|
||||
const handleWindowLifecycleEvents = function ({ embedder, guest, outlivesOpener }: {
|
||||
embedder: WebContents,
|
||||
guest: WebContents,
|
||||
frameName: string,
|
||||
outlivesOpener: boolean
|
||||
}) {
|
||||
const closedByEmbedder = function () {
|
||||
@@ -128,13 +108,6 @@ const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outl
|
||||
embedder.once('current-render-view-deleted' as any, closedByEmbedder);
|
||||
}
|
||||
guest.once('destroyed', closedByUser);
|
||||
|
||||
if (frameName) {
|
||||
registerFrameNameToGuestWindow(frameName, guest);
|
||||
guest.once('destroyed', function () {
|
||||
unregisterFrameName(frameName);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Security options that child windows will always inherit from parent windows
|
||||
|
||||
@@ -1 +1 @@
|
||||
cherry-pick-a08731cf6d70.patch
|
||||
optionally_validate_gl_max_uniform_blocks_at_compile_time.patch
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
From a08731cf6d70c60fd74b1d75f2e8b94c52e18140 Mon Sep 17 00:00:00 2001
|
||||
From: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Date: Thu, 19 Feb 2026 14:42:08 -0500
|
||||
Subject: [PATCH] Vulkan: Avoid overflow in texture size calculation
|
||||
|
||||
Bug: chromium:485622239
|
||||
Change-Id: Idf9847afa0aa2e72b6433ac8348ae2820c1ad8c5
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7595734
|
||||
Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
|
||||
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
---
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
index 9e208f9..e2185a4 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
@@ -3164,8 +3164,17 @@
|
||||
// invalidate must be called after wait for finish.
|
||||
ANGLE_TRY(srcBuffer->invalidate(renderer));
|
||||
|
||||
- size_t dstBufferSize = sourceBox.width * sourceBox.height * sourceBox.depth *
|
||||
- dstFormat.pixelBytes * layerCount;
|
||||
+ // Use size_t calculations to avoid 32-bit overflows. Note that the dimensions are bound by
|
||||
+ // the maximums specified in Constants.h, and that gl::Box members are signed 32-bit
|
||||
+ // integers.
|
||||
+ static_assert(gl::IMPLEMENTATION_MAX_2D_TEXTURE_SIZE *
|
||||
+ gl::IMPLEMENTATION_MAX_2D_TEXTURE_SIZE <
|
||||
+ std::numeric_limits<int32_t>::max());
|
||||
+ size_t dstBufferSize = sourceBox.width * sourceBox.height;
|
||||
+ static_assert(gl::IMPLEMENTATION_MAX_3D_TEXTURE_SIZE *
|
||||
+ gl::IMPLEMENTATION_MAX_2D_ARRAY_TEXTURE_LAYERS * 16 <
|
||||
+ std::numeric_limits<int32_t>::max());
|
||||
+ dstBufferSize *= sourceBox.depth * dstFormat.pixelBytes * layerCount;
|
||||
|
||||
// Allocate memory in the destination texture for the copy/conversion.
|
||||
uint8_t *dstData = nullptr;
|
||||
diff --git a/src/tests/gl_tests/FramebufferTest.cpp b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
index 020a041..f72f1a7 100644
|
||||
--- a/src/tests/gl_tests/FramebufferTest.cpp
|
||||
+++ b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
@@ -8894,6 +8894,62 @@
|
||||
ASSERT_GL_NO_ERROR();
|
||||
}
|
||||
|
||||
+// Test that 2D array texture size calculation doesn't overflow internally when rendering to it. An
|
||||
+// RGB format is used which is often emualted with RGBA.
|
||||
+//
|
||||
+// Practically we cannot run this test. On most configurations, allocating a 4GB texture fails due
|
||||
+// to internal driver limitations. On the few configs that the test actually runs, allocating such
|
||||
+// large memory leads to instability.
|
||||
+TEST_P(FramebufferTest_ES3, DISABLED_MaxSize2DArrayNoOverflow)
|
||||
+{
|
||||
+ GLint maxTexture2DSize;
|
||||
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexture2DSize);
|
||||
+
|
||||
+ maxTexture2DSize = std::min(maxTexture2DSize, 16384);
|
||||
+
|
||||
+ // Create a 2D array texture with RGB format. Every layer is going to take 1GB of memory (if
|
||||
+ // emulated with RGBA), so only create 4 layers of it (for a total of 4GB of memory). If 32-bit
|
||||
+ // math is involved when calculating sizes related to this texture, they will overflow.
|
||||
+ constexpr uint32_t kLayers = 4;
|
||||
+ GLTexture tex;
|
||||
+ glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
|
||||
+ glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGB8, maxTexture2DSize, maxTexture2DSize, kLayers);
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
+
|
||||
+ // Initialize the texture so its content is considered valid and worth preserving.
|
||||
+ constexpr int kValidSubsectionWidth = 16;
|
||||
+ constexpr int kValidSubsectionHeight = 20;
|
||||
+ std::vector<GLColorRGB> data(kValidSubsectionWidth * kValidSubsectionHeight,
|
||||
+ GLColorRGB(0, 255, 0));
|
||||
+ for (uint32_t layer = 0; layer < kLayers; ++layer)
|
||||
+ {
|
||||
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kValidSubsectionWidth,
|
||||
+ kValidSubsectionHeight, 1, GL_RGB, GL_UNSIGNED_BYTE, data.data());
|
||||
+ }
|
||||
+
|
||||
+ // Draw with the texture, making sure it's initialized and data is flushed.
|
||||
+ ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
|
||||
+ essl3_shaders::fs::Texture2DArray());
|
||||
+ drawQuad(drawTex2DArray, essl3_shaders::PositionAttrib(), 0.5f);
|
||||
+
|
||||
+ // Bind a framebuffer to the texture and render into it. In some backends, the texture is
|
||||
+ // recreated to RGBA to be renderable.
|
||||
+ GLFramebuffer fbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, 1);
|
||||
+
|
||||
+ ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
|
||||
+ glViewport(0, 0, kValidSubsectionWidth / 2, kValidSubsectionHeight);
|
||||
+ drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.5f);
|
||||
+
|
||||
+ EXPECT_PIXEL_RECT_EQ(0, 0, kValidSubsectionWidth / 2, kValidSubsectionHeight, GLColor::red);
|
||||
+ EXPECT_PIXEL_RECT_EQ(kValidSubsectionWidth / 2, 0,
|
||||
+ kValidSubsectionWidth - kValidSubsectionWidth / 2, kValidSubsectionHeight,
|
||||
+ GLColor::green);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
ANGLE_INSTANTIATE_TEST_ES2_AND(AddMockTextureNoRenderTargetTest,
|
||||
ES2_D3D9().enable(Feature::AddMockTextureNoRenderTarget),
|
||||
ES2_D3D11().enable(Feature::AddMockTextureNoRenderTarget));
|
||||
diff --git a/src/tests/gl_tests/VulkanImageTest.cpp b/src/tests/gl_tests/VulkanImageTest.cpp
|
||||
index 2f06e2d..87e7482 100644
|
||||
--- a/src/tests/gl_tests/VulkanImageTest.cpp
|
||||
+++ b/src/tests/gl_tests/VulkanImageTest.cpp
|
||||
@@ -677,8 +677,8 @@
|
||||
kTextureHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, textureColor.data());
|
||||
}
|
||||
|
||||
- ANGLE_GL_PROGRAM(drawTex2DArray, essl1_shaders::vs::Texture2DArray(),
|
||||
- essl1_shaders::fs::Texture2DArray());
|
||||
+ ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
|
||||
+ essl3_shaders::fs::Texture2DArray());
|
||||
drawQuad(drawTex2DArray, essl1_shaders::PositionAttrib(), 0.5f);
|
||||
|
||||
// Fill up the device memory until we start allocating on the system memory.
|
||||
diff --git a/util/shader_utils.cpp b/util/shader_utils.cpp
|
||||
index 275e261..8994612 100644
|
||||
--- a/util/shader_utils.cpp
|
||||
+++ b/util/shader_utils.cpp
|
||||
@@ -580,18 +580,6 @@
|
||||
})";
|
||||
}
|
||||
|
||||
-const char *Texture2DArray()
|
||||
-{
|
||||
- return R"(#version 300 es
|
||||
-out vec2 v_texCoord;
|
||||
-in vec4 a_position;
|
||||
-void main()
|
||||
-{
|
||||
- gl_Position = vec4(a_position.xy, 0.0, 1.0);
|
||||
- v_texCoord = (a_position.xy * 0.5) + 0.5;
|
||||
-})";
|
||||
-}
|
||||
-
|
||||
} // namespace vs
|
||||
|
||||
namespace fs
|
||||
@@ -689,20 +677,6 @@
|
||||
})";
|
||||
}
|
||||
|
||||
-const char *Texture2DArray()
|
||||
-{
|
||||
- return R"(#version 300 es
|
||||
-precision highp float;
|
||||
-uniform highp sampler2DArray tex2DArray;
|
||||
-uniform int slice;
|
||||
-in vec2 v_texCoord;
|
||||
-out vec4 fragColor;
|
||||
-void main()
|
||||
-{
|
||||
- fragColor = texture(tex2DArray, vec3(v_texCoord, float(slice)));
|
||||
-})";
|
||||
-}
|
||||
-
|
||||
} // namespace fs
|
||||
} // namespace essl1_shaders
|
||||
|
||||
@@ -787,6 +761,18 @@
|
||||
})";
|
||||
}
|
||||
|
||||
+const char *Texture2DArray()
|
||||
+{
|
||||
+ return R"(#version 300 es
|
||||
+out vec2 v_texCoord;
|
||||
+in vec4 a_position;
|
||||
+void main()
|
||||
+{
|
||||
+ gl_Position = vec4(a_position.xy, 0.0, 1.0);
|
||||
+ v_texCoord = (a_position.xy * 0.5) + 0.5;
|
||||
+})";
|
||||
+}
|
||||
+
|
||||
} // namespace vs
|
||||
|
||||
namespace fs
|
||||
@@ -844,6 +830,20 @@
|
||||
})";
|
||||
}
|
||||
|
||||
+const char *Texture2DArray()
|
||||
+{
|
||||
+ return R"(#version 300 es
|
||||
+precision highp float;
|
||||
+uniform highp sampler2DArray tex2DArray;
|
||||
+uniform int slice;
|
||||
+in vec2 v_texCoord;
|
||||
+out vec4 fragColor;
|
||||
+void main()
|
||||
+{
|
||||
+ fragColor = texture(tex2DArray, vec3(v_texCoord, float(slice)));
|
||||
+})";
|
||||
+}
|
||||
+
|
||||
} // namespace fs
|
||||
} // namespace essl3_shaders
|
||||
|
||||
diff --git a/util/shader_utils.h b/util/shader_utils.h
|
||||
index 676341e..cf211cf 100644
|
||||
--- a/util/shader_utils.h
|
||||
+++ b/util/shader_utils.h
|
||||
@@ -90,7 +90,6 @@
|
||||
// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
|
||||
// texcoord.
|
||||
ANGLE_UTIL_EXPORT const char *Texture2D();
|
||||
-ANGLE_UTIL_EXPORT const char *Texture2DArray();
|
||||
|
||||
} // namespace vs
|
||||
|
||||
@@ -120,7 +119,6 @@
|
||||
|
||||
// A shader that samples the texture
|
||||
ANGLE_UTIL_EXPORT const char *Texture2D();
|
||||
-ANGLE_UTIL_EXPORT const char *Texture2DArray();
|
||||
|
||||
} // namespace fs
|
||||
} // namespace essl1_shaders
|
||||
@@ -151,6 +149,7 @@
|
||||
// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
|
||||
// texcoord.
|
||||
ANGLE_UTIL_EXPORT const char *Texture2DLod();
|
||||
+ANGLE_UTIL_EXPORT const char *Texture2DArray();
|
||||
|
||||
} // namespace vs
|
||||
|
||||
@@ -169,6 +168,9 @@
|
||||
// A shader that samples the texture at a given lod.
|
||||
ANGLE_UTIL_EXPORT const char *Texture2DLod();
|
||||
|
||||
+// A shader that samples the texture at a given slice.
|
||||
+ANGLE_UTIL_EXPORT const char *Texture2DArray();
|
||||
+
|
||||
} // namespace fs
|
||||
} // namespace essl3_shaders
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Geoff Lang <geofflang@chromium.org>
|
||||
Date: Wed, 11 Feb 2026 15:51:46 -0500
|
||||
Subject: Optionally validate GL_MAX_*_UNIFORM_BLOCKS at compile time.
|
||||
|
||||
These were validated at link time but some drivers have compiler crashes
|
||||
when compiling shaders with too many uniform blocks.
|
||||
|
||||
Bug: chromium:475877320
|
||||
Change-Id: I4413ce06307b4fe9e27105d85f66f610c235a301
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7568089
|
||||
Commit-Queue: Geoff Lang <geofflang@chromium.org>
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
|
||||
diff --git a/include/GLSLANG/ShaderLang.h b/include/GLSLANG/ShaderLang.h
|
||||
index 90b1239689c98a8c94f8c6f57d572268a4450923..81445ade5f2285d52a6b83bfc574c0fa56d4a0da 100644
|
||||
--- a/include/GLSLANG/ShaderLang.h
|
||||
+++ b/include/GLSLANG/ShaderLang.h
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
// Version number for shader translation API.
|
||||
// It is incremented every time the API changes.
|
||||
-#define ANGLE_SH_VERSION 386
|
||||
+#define ANGLE_SH_VERSION 387
|
||||
|
||||
enum ShShaderSpec
|
||||
{
|
||||
@@ -383,6 +383,10 @@ struct ShCompileOptions
|
||||
|
||||
uint64_t forceShaderPrecisionHighpToMediump : 1;
|
||||
|
||||
+ // Validate that the count of uniform blocks is within the GL_MAX_*_UNIFORM_BLOCKS limits. These
|
||||
+ // limits must be supplied in the BuiltinResources.
|
||||
+ uint64_t validatePerStageMaxUniformBlocks : 1;
|
||||
+
|
||||
// Ask compiler to generate Vulkan transform feedback emulation support code.
|
||||
uint64_t addVulkanXfbEmulationSupportCode : 1;
|
||||
|
||||
@@ -584,6 +588,12 @@ struct ShBuiltInResources
|
||||
int MinProgramTexelOffset;
|
||||
int MaxProgramTexelOffset;
|
||||
|
||||
+ // GL_MAX_FRAGMENT_UNIFORM_BLOCKS
|
||||
+ int MaxFragmentUniformBlocks;
|
||||
+
|
||||
+ // GL_MAX_VERTEX_UNIFORM_BLOCKS
|
||||
+ int MaxVertexUniformBlocks;
|
||||
+
|
||||
// Extension constants.
|
||||
|
||||
// Value of GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT for OpenGL ES output context.
|
||||
@@ -701,6 +711,9 @@ struct ShBuiltInResources
|
||||
// maximum point size (higher limit from ALIASED_POINT_SIZE_RANGE)
|
||||
float MaxPointSize;
|
||||
|
||||
+ // GL_MAX_COMPUTE_UNIFORM_BLOCKS
|
||||
+ int MaxComputeUniformBlocks;
|
||||
+
|
||||
// EXT_geometry_shader constants
|
||||
int MaxGeometryUniformComponents;
|
||||
int MaxGeometryUniformBlocks;
|
||||
@@ -724,6 +737,7 @@ struct ShBuiltInResources
|
||||
int MaxTessControlImageUniforms;
|
||||
int MaxTessControlAtomicCounters;
|
||||
int MaxTessControlAtomicCounterBuffers;
|
||||
+ int MaxTessControlUniformBlocks;
|
||||
|
||||
int MaxTessPatchComponents;
|
||||
int MaxPatchVertices;
|
||||
@@ -736,6 +750,7 @@ struct ShBuiltInResources
|
||||
int MaxTessEvaluationImageUniforms;
|
||||
int MaxTessEvaluationAtomicCounters;
|
||||
int MaxTessEvaluationAtomicCounterBuffers;
|
||||
+ int MaxTessEvaluationUniformBlocks;
|
||||
|
||||
// Subpixel bits used in rasterization.
|
||||
int SubPixelBits;
|
||||
diff --git a/include/platform/autogen/FeaturesGL_autogen.h b/include/platform/autogen/FeaturesGL_autogen.h
|
||||
index a732a77d66ec2fad9677d500d8f0ae3d0d92c454..f0b391dfbc4d238df458b334951371429fd496a4 100644
|
||||
--- a/include/platform/autogen/FeaturesGL_autogen.h
|
||||
+++ b/include/platform/autogen/FeaturesGL_autogen.h
|
||||
@@ -638,6 +638,12 @@ struct FeaturesGL : FeatureSetBase
|
||||
&members,
|
||||
};
|
||||
|
||||
+ FeatureInfo validateMaxPerStageUniformBlocksAtCompileTime = {
|
||||
+ "validateMaxPerStageUniformBlocksAtCompileTime",
|
||||
+ FeatureCategory::OpenGLWorkarounds,
|
||||
+ &members,
|
||||
+ };
|
||||
+
|
||||
};
|
||||
|
||||
inline FeaturesGL::FeaturesGL() = default;
|
||||
diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json
|
||||
index e993768974e63d19bf4b71702fe9725aa97ea2fb..03454778c2acfd10c2a3762ef6ad1e9eb0fc7bb2 100644
|
||||
--- a/include/platform/gl_features.json
|
||||
+++ b/include/platform/gl_features.json
|
||||
@@ -828,6 +828,14 @@
|
||||
"Some ES2 Mali drivers are unable to query enough information from a linked program to use passthrough shaders."
|
||||
],
|
||||
"issue": "https://crbug.com/451796659"
|
||||
+ },
|
||||
+ {
|
||||
+ "name": "validate_max_per_stage_uniform_blocks_at_compile_time",
|
||||
+ "category": "Workarounds",
|
||||
+ "description": [
|
||||
+ "Validate GL_MAX_*_UNIFORM_BLOCKS at compile time instead of link time to work around compiler bugs."
|
||||
+ ],
|
||||
+ "issue": "http://crbug.com/475877320"
|
||||
}
|
||||
]
|
||||
}
|
||||
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
|
||||
index 7e36dc2cd24a5d0a3a1a009a1171684e4dfe62e5..aae0f1008d573a55e822c10cf02149b8ccef6055 100644
|
||||
--- a/src/compiler/translator/Compiler.cpp
|
||||
+++ b/src/compiler/translator/Compiler.cpp
|
||||
@@ -1563,6 +1563,8 @@ void TCompiler::setResourceString()
|
||||
<< ":MaxFragmentInputVectors:" << mResources.MaxFragmentInputVectors
|
||||
<< ":MinProgramTexelOffset:" << mResources.MinProgramTexelOffset
|
||||
<< ":MaxProgramTexelOffset:" << mResources.MaxProgramTexelOffset
|
||||
+ << ":MaxFragmentUniformBlocks:" << mResources.MaxFragmentUniformBlocks
|
||||
+ << ":MaxVertexUniformBlocks:" << mResources.MaxVertexUniformBlocks
|
||||
<< ":MaxDualSourceDrawBuffers:" << mResources.MaxDualSourceDrawBuffers
|
||||
<< ":MaxViewsOVR:" << mResources.MaxViewsOVR
|
||||
<< ":NV_draw_buffers:" << mResources.NV_draw_buffers
|
||||
@@ -1612,6 +1614,7 @@ void TCompiler::setResourceString()
|
||||
<< ":MaxFragmentAtomicCounterBuffers:" << mResources.MaxFragmentAtomicCounterBuffers
|
||||
<< ":MaxCombinedAtomicCounterBuffers:" << mResources.MaxCombinedAtomicCounterBuffers
|
||||
<< ":MaxAtomicCounterBufferSize:" << mResources.MaxAtomicCounterBufferSize
|
||||
+ << ":MaxComputeUnformBlocks:" << mResources.MaxComputeUniformBlocks
|
||||
<< ":MaxGeometryUniformComponents:" << mResources.MaxGeometryUniformComponents
|
||||
<< ":MaxGeometryUniformBlocks:" << mResources.MaxGeometryUniformBlocks
|
||||
<< ":MaxGeometryInputComponents:" << mResources.MaxGeometryInputComponents
|
||||
@@ -1635,6 +1638,7 @@ void TCompiler::setResourceString()
|
||||
<< ":MaxTessControlImageUniforms:" << mResources.MaxTessControlImageUniforms
|
||||
<< ":MaxTessControlAtomicCounters:" << mResources.MaxTessControlAtomicCounters
|
||||
<< ":MaxTessControlAtomicCounterBuffers:" << mResources.MaxTessControlAtomicCounterBuffers
|
||||
+ << ":MaxTessControlUniformBlocks:" << mResources.MaxTessControlUniformBlocks
|
||||
<< ":MaxTessPatchComponents:" << mResources.MaxTessPatchComponents
|
||||
<< ":MaxPatchVertices:" << mResources.MaxPatchVertices
|
||||
<< ":MaxTessGenLevel:" << mResources.MaxTessGenLevel
|
||||
@@ -1644,7 +1648,9 @@ void TCompiler::setResourceString()
|
||||
<< ":MaxTessEvaluationUniformComponents:" << mResources.MaxTessEvaluationUniformComponents
|
||||
<< ":MaxTessEvaluationImageUniforms:" << mResources.MaxTessEvaluationImageUniforms
|
||||
<< ":MaxTessEvaluationAtomicCounters:" << mResources.MaxTessEvaluationAtomicCounters
|
||||
- << ":MaxTessEvaluationAtomicCounterBuffers:" << mResources.MaxTessEvaluationAtomicCounterBuffers;
|
||||
+ << ":MaxTessEvaluationAtomicCounterBuffers:" << mResources.MaxTessEvaluationAtomicCounterBuffers
|
||||
+ << ":MaxTessControlUniformBlocks:" << mResources.MaxTessControlUniformBlocks
|
||||
+ ;
|
||||
// clang-format on
|
||||
|
||||
mBuiltInResourcesString = strstream.str();
|
||||
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
|
||||
index a8a5e562b2006402e1473c8c6710d75d7c83c42f..e04d27fe695caba55bb29f94c7425d4ecc3d2344 100644
|
||||
--- a/src/compiler/translator/ParseContext.cpp
|
||||
+++ b/src/compiler/translator/ParseContext.cpp
|
||||
@@ -367,6 +367,37 @@ bool IsESSL100ConstantExpression(TIntermNode *node)
|
||||
{
|
||||
return node->getAsConstantUnion() != nullptr && node->getAsTyped()->getQualifier() == EvqConst;
|
||||
}
|
||||
+
|
||||
+unsigned int GetMaxUniformBlocksForShaderType(sh::GLenum shaderType,
|
||||
+ const ShCompileOptions &options,
|
||||
+ const ShBuiltInResources &resources)
|
||||
+{
|
||||
+ // If the validatePerStageMaxUniformBlocks workaround is disabled. Set a limit that will not be
|
||||
+ // hit.
|
||||
+ if (!options.validatePerStageMaxUniformBlocks)
|
||||
+ {
|
||||
+ return std::numeric_limits<unsigned int>::max();
|
||||
+ }
|
||||
+
|
||||
+ switch (shaderType)
|
||||
+ {
|
||||
+ case GL_FRAGMENT_SHADER:
|
||||
+ return resources.MaxFragmentUniformBlocks;
|
||||
+ case GL_VERTEX_SHADER:
|
||||
+ return resources.MaxVertexUniformBlocks;
|
||||
+ case GL_COMPUTE_SHADER:
|
||||
+ return resources.MaxComputeUniformBlocks;
|
||||
+ case GL_GEOMETRY_SHADER:
|
||||
+ return resources.MaxGeometryUniformBlocks;
|
||||
+ case GL_TESS_CONTROL_SHADER:
|
||||
+ return resources.MaxTessControlUniformBlocks;
|
||||
+ case GL_TESS_EVALUATION_SHADER:
|
||||
+ return resources.MaxTessEvaluationUniformBlocks;
|
||||
+ default:
|
||||
+ UNREACHABLE();
|
||||
+ return 0;
|
||||
+ }
|
||||
+}
|
||||
} // namespace
|
||||
|
||||
// This tracks each binding point's current default offset for inheritance of subsequent
|
||||
@@ -459,6 +490,8 @@ TParseContext::TParseContext(TSymbolTable &symt,
|
||||
mMaxPixelLocalStoragePlanes(resources.MaxPixelLocalStoragePlanes),
|
||||
mMaxFunctionParameters(resources.MaxFunctionParameters),
|
||||
mMaxCallStackDepth(resources.MaxCallStackDepth),
|
||||
+ mMaxUniformBlocks(GetMaxUniformBlocksForShaderType(mShaderType, options, resources)),
|
||||
+ mNumUniformBlocks(0),
|
||||
mDeclaringFunction(false),
|
||||
mDeclaringMain(false),
|
||||
mMainFunction(nullptr),
|
||||
@@ -6082,6 +6115,22 @@ TIntermDeclaration *TParseContext::addInterfaceBlock(
|
||||
error(arraySizesLine, "geometry shader input blocks must be an array", "");
|
||||
}
|
||||
|
||||
+ // Validate max uniform block limits
|
||||
+ if (typeQualifier.qualifier == EvqUniform)
|
||||
+ {
|
||||
+ unsigned int blockCount =
|
||||
+ arraySizes == nullptr || arraySizes->empty() ? 1 : (*arraySizes)[0];
|
||||
+ if (mNumUniformBlocks + blockCount > mMaxUniformBlocks)
|
||||
+ {
|
||||
+ error(arraySizesLine,
|
||||
+ "uniform block count greater than per stage maximum uniform blocks", "");
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ mNumUniformBlocks += blockCount;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
checkIndexIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.index);
|
||||
|
||||
if (mShaderVersion < 310)
|
||||
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
|
||||
index f3d0417dd91cda2226203b9b39c2dc8acc1ac880..baf074bd86fe587a72cd3a431fbf0231d6ebd68c 100644
|
||||
--- a/src/compiler/translator/ParseContext.h
|
||||
+++ b/src/compiler/translator/ParseContext.h
|
||||
@@ -910,6 +910,12 @@ class TParseContext : angle::NonCopyable
|
||||
// and there are no known users.
|
||||
TUnorderedMap<TQualifier, bool> mBuiltInQualified;
|
||||
|
||||
+ // Maximum number of uniform blocks allowed to be declared in this shader. Taken from the
|
||||
+ // built-in resources and resolved to this shader type.
|
||||
+ unsigned int mMaxUniformBlocks;
|
||||
+ // Current count of declared uniform blocks.
|
||||
+ unsigned int mNumUniformBlocks;
|
||||
+
|
||||
// keeps track whether we are declaring / defining a function
|
||||
bool mDeclaringFunction;
|
||||
|
||||
diff --git a/src/compiler/translator/ShaderLang.cpp b/src/compiler/translator/ShaderLang.cpp
|
||||
index 6044849e6a6392fed77909b45fea475fa4978325..e292cf0403a77f2f70ca9176785fcdad9aac7302 100644
|
||||
--- a/src/compiler/translator/ShaderLang.cpp
|
||||
+++ b/src/compiler/translator/ShaderLang.cpp
|
||||
@@ -263,6 +263,8 @@ void InitBuiltInResources(ShBuiltInResources *resources)
|
||||
resources->MaxFragmentInputVectors = 15;
|
||||
resources->MinProgramTexelOffset = -8;
|
||||
resources->MaxProgramTexelOffset = 7;
|
||||
+ resources->MaxFragmentUniformBlocks = 12;
|
||||
+ resources->MaxVertexUniformBlocks = 12;
|
||||
|
||||
// Extensions constants.
|
||||
resources->MaxDualSourceDrawBuffers = 0;
|
||||
@@ -323,6 +325,8 @@ void InitBuiltInResources(ShBuiltInResources *resources)
|
||||
resources->MaxUniformBufferBindings = 32;
|
||||
resources->MaxShaderStorageBufferBindings = 4;
|
||||
|
||||
+ resources->MaxComputeUniformBlocks = 12;
|
||||
+
|
||||
resources->MaxGeometryUniformComponents = 1024;
|
||||
resources->MaxGeometryUniformBlocks = 12;
|
||||
resources->MaxGeometryInputComponents = 64;
|
||||
@@ -344,6 +348,7 @@ void InitBuiltInResources(ShBuiltInResources *resources)
|
||||
resources->MaxTessControlImageUniforms = 0;
|
||||
resources->MaxTessControlAtomicCounters = 0;
|
||||
resources->MaxTessControlAtomicCounterBuffers = 0;
|
||||
+ resources->MaxTessControlUniformBlocks = 12;
|
||||
|
||||
resources->MaxTessPatchComponents = 120;
|
||||
resources->MaxPatchVertices = 32;
|
||||
@@ -356,6 +361,7 @@ void InitBuiltInResources(ShBuiltInResources *resources)
|
||||
resources->MaxTessEvaluationImageUniforms = 0;
|
||||
resources->MaxTessEvaluationAtomicCounters = 0;
|
||||
resources->MaxTessEvaluationAtomicCounterBuffers = 0;
|
||||
+ resources->MaxTessEvaluationUniformBlocks = 12;
|
||||
|
||||
resources->SubPixelBits = 8;
|
||||
|
||||
diff --git a/src/libANGLE/Compiler.cpp b/src/libANGLE/Compiler.cpp
|
||||
index 00684c8ed08609a3a4d6ef6f36107756207ed72b..1893b6bddb33fde567162e2e9dbb12785dad538d 100644
|
||||
--- a/src/libANGLE/Compiler.cpp
|
||||
+++ b/src/libANGLE/Compiler.cpp
|
||||
@@ -169,6 +169,8 @@ Compiler::Compiler(rx::GLImplFactory *implFactory, const State &state, egl::Disp
|
||||
mResources.MaxFragmentInputVectors = caps.maxFragmentInputComponents / 4;
|
||||
mResources.MinProgramTexelOffset = caps.minProgramTexelOffset;
|
||||
mResources.MaxProgramTexelOffset = caps.maxProgramTexelOffset;
|
||||
+ mResources.MaxFragmentUniformBlocks = caps.maxShaderUniformBlocks[gl::ShaderType::Fragment];
|
||||
+ mResources.MaxVertexUniformBlocks = caps.maxShaderUniformBlocks[gl::ShaderType::Vertex];
|
||||
|
||||
// EXT_blend_func_extended
|
||||
mResources.EXT_blend_func_extended = extensions.blendFuncExtendedEXT;
|
||||
@@ -211,6 +213,7 @@ Compiler::Compiler(rx::GLImplFactory *implFactory, const State &state, egl::Disp
|
||||
mResources.MaxCombinedImageUniforms = caps.maxCombinedImageUniforms;
|
||||
mResources.MaxCombinedShaderOutputResources = caps.maxCombinedShaderOutputResources;
|
||||
mResources.MaxUniformLocations = caps.maxUniformLocations;
|
||||
+ mResources.MaxComputeUniformBlocks = caps.maxShaderUniformBlocks[gl::ShaderType::Compute];
|
||||
|
||||
for (size_t index = 0u; index < 3u; ++index)
|
||||
{
|
||||
@@ -280,6 +283,8 @@ Compiler::Compiler(rx::GLImplFactory *implFactory, const State &state, egl::Disp
|
||||
mResources.MaxTessControlAtomicCounters = caps.maxShaderAtomicCounters[ShaderType::TessControl];
|
||||
mResources.MaxTessControlAtomicCounterBuffers =
|
||||
caps.maxShaderAtomicCounterBuffers[ShaderType::TessControl];
|
||||
+ mResources.MaxTessControlUniformBlocks =
|
||||
+ caps.maxShaderUniformBlocks[gl::ShaderType::TessControl];
|
||||
|
||||
mResources.MaxTessPatchComponents = caps.maxTessPatchComponents;
|
||||
mResources.MaxPatchVertices = caps.maxPatchVertices;
|
||||
@@ -297,6 +302,8 @@ Compiler::Compiler(rx::GLImplFactory *implFactory, const State &state, egl::Disp
|
||||
caps.maxShaderAtomicCounters[ShaderType::TessEvaluation];
|
||||
mResources.MaxTessEvaluationAtomicCounterBuffers =
|
||||
caps.maxShaderAtomicCounterBuffers[ShaderType::TessEvaluation];
|
||||
+ mResources.MaxTessEvaluationUniformBlocks =
|
||||
+ caps.maxShaderUniformBlocks[gl::ShaderType::TessEvaluation];
|
||||
|
||||
// Subpixel bits.
|
||||
mResources.SubPixelBits = static_cast<int>(caps.subPixelBits);
|
||||
diff --git a/src/libANGLE/renderer/gl/ShaderGL.cpp b/src/libANGLE/renderer/gl/ShaderGL.cpp
|
||||
index a10d5545f409faa259a464437401980e40b80e33..d8db2a1f92494fd0dbf0cb6011ab9cea989c5b45 100644
|
||||
--- a/src/libANGLE/renderer/gl/ShaderGL.cpp
|
||||
+++ b/src/libANGLE/renderer/gl/ShaderGL.cpp
|
||||
@@ -272,6 +272,11 @@ std::shared_ptr<ShaderTranslateTask> ShaderGL::compile(const gl::Context *contex
|
||||
options->pls = contextGL->getNativePixelLocalStorageOptions();
|
||||
}
|
||||
|
||||
+ if (features.validateMaxPerStageUniformBlocksAtCompileTime.enabled)
|
||||
+ {
|
||||
+ options->validatePerStageMaxUniformBlocks = true;
|
||||
+ }
|
||||
+
|
||||
return std::shared_ptr<ShaderTranslateTask>(
|
||||
new ShaderTranslateTaskGL(functions, mShaderID, contextGL->hasNativeParallelCompile()));
|
||||
}
|
||||
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
|
||||
index d9694c09146dcb34948646df5ca95a4d8b993d9e..e82a10262dc128e55d2357bf9efc67903c30206b 100644
|
||||
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
|
||||
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
|
||||
@@ -2718,6 +2718,10 @@ void InitializeFeatures(const FunctionsGL *functions, angle::FeaturesGL *feature
|
||||
// Mali 400 series drivers fail linking shaders when passthrough shaders are enabled. Likely due
|
||||
// to not querying correct information from varyings and uniforms.
|
||||
ANGLE_FEATURE_CONDITION(features, disablePassthroughShaders, IsAdreno4xx(functions));
|
||||
+
|
||||
+ // IMG GL drivers crash while compiling shaders with more than the limit of uniform blocks.
|
||||
+ ANGLE_FEATURE_CONDITION(features, validateMaxPerStageUniformBlocksAtCompileTime,
|
||||
+ IsPowerVR(vendor));
|
||||
}
|
||||
|
||||
void InitializeFrontendFeatures(const FunctionsGL *functions, angle::FrontendFeatures *features)
|
||||
diff --git a/util/autogen/angle_features_autogen.cpp b/util/autogen/angle_features_autogen.cpp
|
||||
index f38ad24bdce73c1ec5facd1130bdf09f896298ee..c07c7e5fa2d28c50101c77bd856f18158777e276 100644
|
||||
--- a/util/autogen/angle_features_autogen.cpp
|
||||
+++ b/util/autogen/angle_features_autogen.cpp
|
||||
@@ -484,6 +484,7 @@ constexpr PackedEnumMap<Feature, const char *> kFeatureNames = {{
|
||||
{Feature::UseVkEventForBufferBarrier, "useVkEventForBufferBarrier"},
|
||||
{Feature::UseVkEventForImageBarrier, "useVkEventForImageBarrier"},
|
||||
{Feature::UseVmaForImageSuballocation, "useVmaForImageSuballocation"},
|
||||
+ {Feature::ValidateMaxPerStageUniformBlocksAtCompileTime, "validateMaxPerStageUniformBlocksAtCompileTime"},
|
||||
{Feature::VaryingsRequireMatchingPrecisionInSpirv, "varyingsRequireMatchingPrecisionInSpirv"},
|
||||
{Feature::VerifyPipelineCacheInBlobCache, "verifyPipelineCacheInBlobCache"},
|
||||
{Feature::VertexIDDoesNotIncludeBaseVertex, "vertexIDDoesNotIncludeBaseVertex"},
|
||||
diff --git a/util/autogen/angle_features_autogen.h b/util/autogen/angle_features_autogen.h
|
||||
index 0eef4d53d814db20bb943fcda9e5b1837c371951..e33fd47ac493a3946d87c03a2d6f29b08e2b63aa 100644
|
||||
--- a/util/autogen/angle_features_autogen.h
|
||||
+++ b/util/autogen/angle_features_autogen.h
|
||||
@@ -484,6 +484,7 @@ enum class Feature
|
||||
UseVkEventForBufferBarrier,
|
||||
UseVkEventForImageBarrier,
|
||||
UseVmaForImageSuballocation,
|
||||
+ ValidateMaxPerStageUniformBlocksAtCompileTime,
|
||||
VaryingsRequireMatchingPrecisionInSpirv,
|
||||
VerifyPipelineCacheInBlobCache,
|
||||
VertexIDDoesNotIncludeBaseVertex,
|
||||
@@ -157,3 +157,5 @@ cherry-pick-45c5a70d984d.patch
|
||||
cherry-pick-05e4b544803c.patch
|
||||
cherry-pick-5efc7a0127a6.patch
|
||||
feat_plumb_node_integration_in_worker_through_workersettings.patch
|
||||
cherry-pick-fbfb27470bf6.patch
|
||||
fix_fire_menu_popup_start_for_dynamically_created_aria_menus.patch
|
||||
|
||||
65
patches/chromium/cherry-pick-fbfb27470bf6.patch
Normal file
65
patches/chromium/cherry-pick-fbfb27470bf6.patch
Normal file
@@ -0,0 +1,65 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Geoff Lang <geofflang@chromium.org>
|
||||
Date: Wed, 18 Feb 2026 13:54:37 -0800
|
||||
Subject: Validate uniform block count limits at compile time on IMG.
|
||||
|
||||
Normally these limits are validated at link time but the IMG compiler
|
||||
has issues when these limits are exceeded. Validate at compile time
|
||||
instead.
|
||||
|
||||
Bug: chromium:475877320
|
||||
Change-Id: Ieeed6914b8cdd2b5e50242d06facae62badddefd
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7568129
|
||||
Auto-Submit: Geoff Lang <geofflang@chromium.org>
|
||||
Reviewed-by: Kyle Charbonneau <kylechar@chromium.org>
|
||||
Commit-Queue: Kyle Charbonneau <kylechar@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1586673}
|
||||
|
||||
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
|
||||
index 895266f5988e2446eadf3d3e1d5c4919416cba76..aeafafece78faa6ece837386fe170592589d1b10 100644
|
||||
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
|
||||
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
|
||||
@@ -3678,6 +3678,9 @@ bool GLES2DecoderImpl::InitializeShaderTranslator() {
|
||||
driver_bug_workarounds.dontUseLoopsToInitializeVariables = true;
|
||||
if (workarounds().remove_dynamic_indexing_of_swizzled_vector)
|
||||
driver_bug_workarounds.removeDynamicIndexingOfSwizzledVector = true;
|
||||
+ if (workarounds().validate_max_per_stage_uniform_blocks_at_compile_time) {
|
||||
+ driver_bug_workarounds.validatePerStageMaxUniformBlocks = true;
|
||||
+ }
|
||||
|
||||
// Initialize uninitialized locals by default
|
||||
driver_bug_workarounds.initializeUninitializedLocals = true;
|
||||
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
|
||||
index 2af5b0460beed7b78c00c9f2a70e14e5f7696ac0..3cc48e4a79576f93bfa22b8f8d1b74e5ba5baae7 100644
|
||||
--- a/gpu/config/gpu_driver_bug_list.json
|
||||
+++ b/gpu/config/gpu_driver_bug_list.json
|
||||
@@ -3843,6 +3843,17 @@
|
||||
"features": [
|
||||
"ensure_previous_framebuffer_not_deleted"
|
||||
]
|
||||
+ },
|
||||
+ {
|
||||
+ "id": 472,
|
||||
+ "description": "Validate GL_MAX_*_UNIFORM_BLOCKS at compile time instead of link time to work around compiler bugs.",
|
||||
+ "os": {
|
||||
+ "type": "android"
|
||||
+ },
|
||||
+ "gl_vendor": "Imagination.*",
|
||||
+ "features": [
|
||||
+ "validate_max_per_stage_uniform_blocks_at_compile_time"
|
||||
+ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
|
||||
index 30ee7799cdd0a344e433e95cd74e4630a7c87aff..6d43d0b1937cd1eeaa044fb9949a52ba2e07d446 100644
|
||||
--- a/gpu/config/gpu_workaround_list.txt
|
||||
+++ b/gpu/config/gpu_workaround_list.txt
|
||||
@@ -126,6 +126,7 @@ use_first_valid_ref_for_av1_invalid_ref
|
||||
use_gpu_driver_workaround_for_testing
|
||||
use_non_zero_size_for_client_side_stream_buffers
|
||||
use_virtualized_gl_contexts
|
||||
+validate_max_per_stage_uniform_blocks_at_compile_time
|
||||
wake_up_gpu_before_drawing
|
||||
webgl_or_caps_max_texture_size_limit_4096
|
||||
webgl_or_caps_max_texture_size_limit_8192
|
||||
@@ -0,0 +1,95 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Keeley Hammond <khammond@slack-corp.com>
|
||||
Date: Thu, 19 Mar 2026 00:34:37 -0700
|
||||
Subject: fix: fire MENU_POPUP_START for dynamically created ARIA menus
|
||||
|
||||
When an ARIA menu element is dynamically created (e.g. via appendChild)
|
||||
rather than being shown by toggling visibility, the AXMenuOpened event
|
||||
was not fired. The OnIgnoredChanged path handles the visibility toggle
|
||||
case, but OnAtomicUpdateFinished did not fire MENU_POPUP_START for
|
||||
newly created menu nodes.
|
||||
|
||||
Previous attempts to fix this (crbug.com/1254875) were reverted because
|
||||
they fired the event too eagerly in OnNodeCreated (before the tree was
|
||||
fully formed) and without filtering, causing regressions with screen
|
||||
readers on pages that misused role="menu".
|
||||
|
||||
This fix addresses both issues:
|
||||
1. Fires MENU_POPUP_START in OnAtomicUpdateFinished (after the tree
|
||||
update is complete) rather than in OnNodeCreated.
|
||||
2. Only fires if the menu has at least one menuitem child, filtering
|
||||
out false positives from misused role="menu" elements.
|
||||
|
||||
MENU_POPUP_END for deleted menus is already handled by
|
||||
AXTreeManager::OnNodeWillBeDeleted, which fires the event directly
|
||||
on the menu node before destruction.
|
||||
|
||||
The change is behind the DynamicMenuPopupEvents feature flag, disabled
|
||||
by default, to allow stabilization before enabling by default. Enable
|
||||
with --enable-features=DynamicMenuPopupEvents.
|
||||
|
||||
This patch can be removed when a CL containing the fix is accepted
|
||||
into Chromium.
|
||||
|
||||
Bug: 40794596
|
||||
|
||||
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
|
||||
index 597b68bccc041a6431e35817669450e38fd56153..396820b148be04b91207e2359f9e441d331ccc10 100644
|
||||
--- a/ui/accessibility/ax_event_generator.cc
|
||||
+++ b/ui/accessibility/ax_event_generator.cc
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "ui/accessibility/ax_event_generator.h"
|
||||
|
||||
#include "base/containers/contains.h"
|
||||
+#include "base/feature_list.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "ui/accessibility/ax_enums.mojom.h"
|
||||
#include "ui/accessibility/ax_event.h"
|
||||
@@ -13,6 +14,12 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
+// Feature flag for firing MENU_POPUP_START for dynamically created ARIA menus.
|
||||
+// Disabled by default to allow stabilization before enabling globally.
|
||||
+BASE_FEATURE(kDynamicMenuPopupEvents,
|
||||
+ "DynamicMenuPopupEvents",
|
||||
+ base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
+
|
||||
namespace {
|
||||
|
||||
bool HasEvent(const std::set<AXEventGenerator::EventParams>& node_events,
|
||||
@@ -907,12 +914,31 @@ void AXEventGenerator::OnAtomicUpdateFinished(
|
||||
/*new_value*/ true);
|
||||
}
|
||||
|
||||
- if (IsAlert(change.node->GetRole()))
|
||||
+ if (IsAlert(change.node->GetRole())) {
|
||||
AddEvent(change.node, Event::ALERT);
|
||||
- else if (change.node->data().IsActiveLiveRegionRoot())
|
||||
+ } else if (change.node->data().IsActiveLiveRegionRoot()) {
|
||||
AddEvent(change.node, Event::LIVE_REGION_CREATED);
|
||||
- else if (change.node->data().IsContainedInActiveLiveRegion())
|
||||
+ } else if (change.node->data().IsContainedInActiveLiveRegion()) {
|
||||
FireLiveRegionEvents(change.node, /* is_removal */ false);
|
||||
+ }
|
||||
+
|
||||
+ // Fire MENU_POPUP_START when a menu is dynamically created (e.g. via
|
||||
+ // appendChild). The OnIgnoredChanged path handles menus that already exist
|
||||
+ // in the DOM and are shown/hidden. This handles the case where the menu
|
||||
+ // element itself is created on the fly.
|
||||
+ // Only fire if the menu has at least one menuitem child, to avoid false
|
||||
+ // positives from elements that misuse role="menu".
|
||||
+ if (base::FeatureList::IsEnabled(kDynamicMenuPopupEvents) &&
|
||||
+ change.node->GetRole() == ax::mojom::Role::kMenu &&
|
||||
+ !change.node->IsInvisibleOrIgnored()) {
|
||||
+ for (auto iter = change.node->UnignoredChildrenBegin();
|
||||
+ iter != change.node->UnignoredChildrenEnd(); ++iter) {
|
||||
+ if (IsMenuItem(iter->GetRole())) {
|
||||
+ AddEvent(change.node, Event::MENU_POPUP_START);
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
FireActiveDescendantEvents();
|
||||
@@ -13,5 +13,6 @@
|
||||
{ "patch_dir": "src/electron/patches/webrtc", "repo": "src/third_party/webrtc" },
|
||||
{ "patch_dir": "src/electron/patches/reclient-configs", "repo": "src/third_party/engflow-reclient-configs" },
|
||||
{ "patch_dir": "src/electron/patches/sqlite", "repo": "src/third_party/sqlite/src" },
|
||||
{ "patch_dir": "src/electron/patches/skia", "repo": "src/third_party/skia" }
|
||||
{ "patch_dir": "src/electron/patches/skia", "repo": "src/third_party/skia" },
|
||||
{ "patch_dir": "src/electron/patches/angle", "repo": "src/third_party/angle/src" }
|
||||
]
|
||||
|
||||
@@ -72,7 +72,8 @@ std::wstring NotificationPresenterWin::SaveIconToFilesystem(
|
||||
|
||||
std::string filename;
|
||||
if (origin.is_valid()) {
|
||||
filename = base::SHA1HashString(origin.spec()) + ".png";
|
||||
const auto hash = base::SHA1HashString(origin.spec());
|
||||
filename = base::HexEncode(hash) + ".png";
|
||||
} else {
|
||||
const int64_t now_usec = base::Time::Now().since_origin().InMicroseconds();
|
||||
filename = base::NumberToString(now_usec) + ".png";
|
||||
|
||||
@@ -96,6 +96,21 @@ std::wstring GetExecutablePath() {
|
||||
return std::wstring(path, len);
|
||||
}
|
||||
|
||||
// Installers sometimes put the running app in a versioned subfolder and ship a
|
||||
// stub with the same filename one directory up. Point the Start Menu shortcut
|
||||
// at the stub when it exists so toast activation and updates keep a stable
|
||||
// launch path.
|
||||
std::wstring GetShortcutTargetPath(const std::wstring& exe_path) {
|
||||
if (exe_path.empty())
|
||||
return L"";
|
||||
base::FilePath exe_fp(exe_path);
|
||||
base::FilePath stub_candidate =
|
||||
exe_fp.DirName().DirName().Append(exe_fp.BaseName());
|
||||
if (base::PathExists(stub_candidate))
|
||||
return stub_candidate.value();
|
||||
return exe_path;
|
||||
}
|
||||
|
||||
void EnsureCLSIDRegistry() {
|
||||
std::wstring exe = GetExecutablePath();
|
||||
if (exe.empty())
|
||||
@@ -116,7 +131,10 @@ void EnsureCLSIDRegistry() {
|
||||
server_key.WriteValue(nullptr, exe.c_str());
|
||||
}
|
||||
|
||||
bool ExistingShortcutValid(const base::FilePath& lnk_path, PCWSTR aumid) {
|
||||
bool ExistingShortcutValid(const base::FilePath& lnk_path,
|
||||
PCWSTR aumid,
|
||||
const std::wstring& expected_target_path,
|
||||
const std::wstring& expected_working_dir) {
|
||||
if (!base::PathExists(lnk_path))
|
||||
return false;
|
||||
Microsoft::WRL::ComPtr<IShellLink> existing;
|
||||
@@ -128,6 +146,31 @@ bool ExistingShortcutValid(const base::FilePath& lnk_path, PCWSTR aumid) {
|
||||
FAILED(pf->Load(lnk_path.value().c_str(), STGM_READ))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// After an auto-update the .lnk may still have the correct AUMID/CLSID but
|
||||
// point at an old install path; treat that as invalid so we rewrite it.
|
||||
wchar_t target_path[MAX_PATH];
|
||||
if (FAILED(existing->GetPath(target_path, MAX_PATH, nullptr, SLGP_RAWPATH)))
|
||||
return false;
|
||||
if (base::FilePath::CompareIgnoreCase(
|
||||
base::FilePath(expected_target_path).value(),
|
||||
base::FilePath(target_path).value()) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t work_dir[MAX_PATH];
|
||||
work_dir[0] = L'\0';
|
||||
if (FAILED(existing->GetWorkingDirectory(work_dir, MAX_PATH)))
|
||||
return false;
|
||||
base::FilePath expected_cwd =
|
||||
base::FilePath(expected_working_dir).NormalizePathSeparators();
|
||||
base::FilePath actual_cwd =
|
||||
base::FilePath(work_dir).NormalizePathSeparators();
|
||||
if (base::FilePath::CompareIgnoreCase(expected_cwd.value(),
|
||||
actual_cwd.value()) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IPropertyStore> store;
|
||||
if (FAILED(existing.As(&store)))
|
||||
return false;
|
||||
@@ -157,6 +200,7 @@ void EnsureShortcut() {
|
||||
std::wstring exe = GetExecutablePath();
|
||||
if (exe.empty())
|
||||
return;
|
||||
std::wstring shortcut_target = GetShortcutTargetPath(exe);
|
||||
|
||||
PWSTR programs_path = nullptr;
|
||||
if (FAILED(
|
||||
@@ -195,18 +239,20 @@ void EnsureShortcut() {
|
||||
}
|
||||
}
|
||||
|
||||
if (ExistingShortcutValid(lnk_path, aumid))
|
||||
const std::wstring expected_working_dir =
|
||||
base::FilePath(exe).DirName().value();
|
||||
if (ExistingShortcutValid(lnk_path, aumid, shortcut_target,
|
||||
expected_working_dir))
|
||||
return;
|
||||
|
||||
Microsoft::WRL::ComPtr<IShellLink> shell_link;
|
||||
if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&shell_link))))
|
||||
return;
|
||||
shell_link->SetPath(exe.c_str());
|
||||
shell_link->SetPath(shortcut_target.c_str());
|
||||
shell_link->SetArguments(L"");
|
||||
shell_link->SetDescription(product_name.c_str());
|
||||
shell_link->SetWorkingDirectory(
|
||||
base::FilePath(exe).DirName().value().c_str());
|
||||
shell_link->SetWorkingDirectory(expected_working_dir.c_str());
|
||||
|
||||
Microsoft::WRL::ComPtr<IPropertyStore> prop_store;
|
||||
if (SUCCEEDED(shell_link.As(&prop_store))) {
|
||||
|
||||
@@ -59,6 +59,8 @@ gfx::Size GetDefaultPrinterDPI(const std::u16string& device_name) {
|
||||
GtkPrintSettings* print_settings = gtk_print_settings_new();
|
||||
int dpi = gtk_print_settings_get_resolution(print_settings);
|
||||
g_object_unref(print_settings);
|
||||
if (dpi <= 0)
|
||||
dpi = printing::kDefaultPdfDpi;
|
||||
return {dpi, dpi};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -248,7 +248,11 @@ gfx::Image Clipboard::ReadImage(gin::Arguments* const args) {
|
||||
[](std::optional<gfx::Image>* image, base::RepeatingClosure cb,
|
||||
const std::vector<uint8_t>& result) {
|
||||
SkBitmap bitmap = gfx::PNGCodec::Decode(result);
|
||||
image->emplace(gfx::Image::CreateFrom1xBitmap(bitmap));
|
||||
if (bitmap.isNull()) {
|
||||
image->emplace();
|
||||
} else {
|
||||
image->emplace(gfx::Image::CreateFrom1xBitmap(bitmap));
|
||||
}
|
||||
std::move(cb).Run();
|
||||
},
|
||||
&image, std::move(callback)));
|
||||
|
||||
@@ -150,9 +150,12 @@ v8::Local<v8::Value> Converter<electron::OffscreenSharedTextureValue>::ToV8(
|
||||
root.Set("textureInfo", ConvertToV8(isolate, dict));
|
||||
auto root_local = ConvertToV8(isolate, root);
|
||||
|
||||
// Create a persistent reference of the object, so that we can check the
|
||||
// monitor again when GC collects this object.
|
||||
auto* tex_persistent = monitor->CreatePersistent(isolate, root_local);
|
||||
// Create a weak persistent that tracks the release function rather than the
|
||||
// texture object. The release function holds a raw pointer to |monitor| via
|
||||
// its v8::External data, so |monitor| must outlive it. Since the texture
|
||||
// keeps |release| alive via its property, this also covers the case where
|
||||
// the texture itself is leaked without calling release().
|
||||
auto* tex_persistent = monitor->CreatePersistent(isolate, releaser);
|
||||
tex_persistent->SetWeak(
|
||||
monitor,
|
||||
[](const v8::WeakCallbackInfo<OffscreenReleaseHolderMonitor>& data) {
|
||||
|
||||
@@ -6875,6 +6875,54 @@ describe('BrowserWindow module', () => {
|
||||
expect(w.webContents.frameRate).to.equal(30);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shared texture', () => {
|
||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
|
||||
it('does not crash when release() is called after the texture is garbage collected', async () => {
|
||||
const sw = new BrowserWindow({
|
||||
width: 100,
|
||||
height: 100,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
offscreen: {
|
||||
useSharedTexture: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const paint = once(sw.webContents, 'paint') as Promise<[any, Electron.Rectangle, Electron.NativeImage]>;
|
||||
sw.loadFile(path.join(fixtures, 'api', 'offscreen-rendering.html'));
|
||||
const [event] = await paint;
|
||||
sw.webContents.stopPainting();
|
||||
|
||||
if (!event.texture) {
|
||||
// GPU shared texture not available on this host; skip.
|
||||
sw.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep only the release closure and drop the owning texture object.
|
||||
const staleRelease = event.texture.release;
|
||||
const weakTexture = new WeakRef(event.texture);
|
||||
event.texture = undefined;
|
||||
|
||||
// Force GC until the texture object is collected.
|
||||
let collected = false;
|
||||
for (let i = 0; i < 30 && !collected; ++i) {
|
||||
await setTimeout();
|
||||
v8Util.requestGarbageCollectionForTesting();
|
||||
collected = weakTexture.deref() === undefined;
|
||||
}
|
||||
expect(collected).to.be.true('texture should be garbage collected');
|
||||
|
||||
// This should return safely and not crash the main process.
|
||||
expect(() => staleRelease()).to.not.throw();
|
||||
|
||||
sw.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('offscreen rendering with device scale factor', () => {
|
||||
|
||||
@@ -186,6 +186,39 @@ describe('webContents.setWindowOpenHandler', () => {
|
||||
await once(browserWindow.webContents, 'did-create-window');
|
||||
});
|
||||
|
||||
it('reuses an existing window when window.open is called with the same frame name', async () => {
|
||||
let handlerCallCount = 0;
|
||||
browserWindow.webContents.setWindowOpenHandler(() => {
|
||||
handlerCallCount++;
|
||||
return { action: 'allow' };
|
||||
});
|
||||
|
||||
const didCreateWindow = once(browserWindow.webContents, 'did-create-window') as Promise<[BrowserWindow, Electron.DidCreateWindowDetails]>;
|
||||
await browserWindow.webContents.executeJavaScript("window.open('about:blank?one', 'named-target', 'show=no') && true");
|
||||
const [childWindow] = await didCreateWindow;
|
||||
expect(handlerCallCount).to.equal(1);
|
||||
expect(childWindow.webContents.getURL()).to.equal('about:blank?one');
|
||||
|
||||
browserWindow.webContents.on('did-create-window', () => {
|
||||
assert.fail('did-create-window should not fire when reusing a named window');
|
||||
});
|
||||
|
||||
const didNavigate = once(childWindow.webContents, 'did-navigate');
|
||||
const sameWindow = await browserWindow.webContents.executeJavaScript(`
|
||||
(() => {
|
||||
const first = window.open('about:blank?one', 'named-target', 'show=no');
|
||||
const second = window.open('about:blank?two', 'named-target', 'show=no');
|
||||
return first === second;
|
||||
})()
|
||||
`);
|
||||
await didNavigate;
|
||||
|
||||
expect(sameWindow).to.be.true('window.open with matching frame name should return the same window proxy');
|
||||
expect(handlerCallCount).to.equal(1, 'setWindowOpenHandler should not be called when Blink resolves the named target');
|
||||
expect(childWindow.webContents.getURL()).to.equal('about:blank?two');
|
||||
expect(BrowserWindow.getAllWindows()).to.have.lengthOf(2);
|
||||
});
|
||||
|
||||
it('can change webPreferences of child windows', async () => {
|
||||
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user