From 273f040582328688393bd288c9a1bfce59b9a91b Mon Sep 17 00:00:00 2001 From: tnsardesai <18272584+tnsardesai@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:35:28 +0000 Subject: [PATCH] Remove regional locale shims from browser images Drop the locales package, the locale-gen step and the en-CA/en-IE/en-SG copies of en-GB.pak added for regional browser locale. tzdata and fonts stay. The startup regional test is split: timezone coverage runs on every image under C.UTF-8, and locale coverage runs on kernel-browser images, where the browser supplies the regional locale at startup from --lang and at runtime from native location updates. --- images/chromium-headful/Dockerfile | 4 - images/chromium-headless/image/Dockerfile | 4 - server/e2e/e2e_browser_location_test.go | 112 ++++++++++++++++++---- 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/images/chromium-headful/Dockerfile b/images/chromium-headful/Dockerfile index e7a00a96..a6278848 100644 --- a/images/chromium-headful/Dockerfile +++ b/images/chromium-headful/Dockerfile @@ -201,10 +201,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap xdg-utils \ libvulkan1 \ fontconfig \ - locales \ tzdata \ unzip && \ - locale-gen en_US.UTF-8 en_GB.UTF-8 en_CA.UTF-8 en_IE.UTF-8 en_SG.UTF-8 && \ apt-get clean && rm -rf /var/lib/apt/lists/* # Install fonts to match a realistic Ubuntu 22.04 desktop fingerprint. @@ -299,7 +297,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap libxext6 libxfixes3 libxkbcommon0; # Install Chrome + ChromeDriver as a matched pair from chrome-for-testing so they cannot drift apart. -# Regional pack filenames preserve each locale identifier while reusing en-GB UI strings. ARG CHROME_VERSION=152.0.7977.42 RUN set -eux; \ curl -fsSL "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip" -o /tmp/chrome.zip; \ @@ -308,7 +305,6 @@ RUN set -eux; \ unzip -q /tmp/cd.zip -d /tmp; \ mv /opt/chrome-linux64 /opt/chrome-for-testing; \ mv /opt/chrome-for-testing/chrome /opt/chrome-for-testing/chromium; \ - for locale in en-CA en-IE en-SG; do cp /opt/chrome-for-testing/locales/en-GB.pak "/opt/chrome-for-testing/locales/${locale}.pak"; done; \ ln -sf /opt/chrome-for-testing/chromium /usr/bin/chromium; \ mv /tmp/chromedriver-linux64/chromedriver /usr/local/bin/chromedriver; \ chmod +x /usr/local/bin/chromedriver /opt/chrome-for-testing/chromium; \ diff --git a/images/chromium-headless/image/Dockerfile b/images/chromium-headless/image/Dockerfile index f8e86745..44f37b57 100644 --- a/images/chromium-headless/image/Dockerfile +++ b/images/chromium-headless/image/Dockerfile @@ -157,14 +157,12 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap xclip \ xdotool \ fontconfig \ - locales \ tzdata \ fonts-noto-cjk \ fonts-noto-color-emoji \ fonts-noto-mono \ fonts-nanum \ supervisor; \ - locale-gen en_US.UTF-8 en_GB.UTF-8 en_CA.UTF-8 en_IE.UTF-8 en_SG.UTF-8; \ fc-cache -f # sqlite3 for debugging the cookies file; unzip for the chrome-for-testing archives below. @@ -174,7 +172,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap apt-get --no-install-recommends -y install sqlite3 unzip; # Install Chrome + ChromeDriver as a matched pair from chrome-for-testing so they cannot drift apart. -# Regional pack filenames preserve each locale identifier while reusing en-GB UI strings. ARG CHROME_VERSION=152.0.7977.42 RUN set -eux; \ curl -fsSL "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip" -o /tmp/chrome.zip; \ @@ -183,7 +180,6 @@ RUN set -eux; \ unzip -q /tmp/cd.zip -d /tmp; \ mv /opt/chrome-linux64 /opt/chrome-for-testing; \ mv /opt/chrome-for-testing/chrome /opt/chrome-for-testing/chromium; \ - for locale in en-CA en-IE en-SG; do cp /opt/chrome-for-testing/locales/en-GB.pak "/opt/chrome-for-testing/locales/${locale}.pak"; done; \ ln -sf /opt/chrome-for-testing/chromium /usr/bin/chromium; \ mv /tmp/chromedriver-linux64/chromedriver /usr/local/bin/chromedriver; \ chmod +x /usr/local/bin/chromedriver /opt/chrome-for-testing/chromium; \ diff --git a/server/e2e/e2e_browser_location_test.go b/server/e2e/e2e_browser_location_test.go index a0a0044a..795b8120 100644 --- a/server/e2e/e2e_browser_location_test.go +++ b/server/e2e/e2e_browser_location_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os/exec" + "strings" "testing" "time" @@ -17,45 +18,116 @@ type browserLocationProbe struct { TimeZone string `json:"timeZone"` } -func TestRegionalBrowserLocation(t *testing.T) { +// Image names are pointers because init sets them after package variables. +var browserVariants = []struct { + name string + image *string +}{ + {name: "headful", image: &headfulImage}, + {name: "headless", image: &headlessImage}, +} + +// TestRegionalBrowserTimezone starts a browser in Singapore time. The images +// ship tzdata but no generated regional POSIX locales, so guest processes use +// C.UTF-8 for UTF-8 text. +func TestRegionalBrowserTimezone(t *testing.T) { if _, err := exec.LookPath("docker"); err != nil { t.Skipf("docker not available: %v", err) } - for _, test := range []struct { - name string - image string - }{ - {name: "headful", image: headfulImage}, - {name: "headless", image: headlessImage}, - } { - t.Run(test.name, func(t *testing.T) { + for _, variant := range browserVariants { + t.Run(variant.name, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) defer cancel() - c := NewTestContainer(t, test.image) + c := NewTestContainer(t, *variant.image) require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{ - "TZ": "Asia/Singapore", - "LANG": "en_SG.UTF-8", - "LC_ALL": "en_SG.UTF-8", - "CHROMIUM_FLAGS": "--lang=en-SG --accept-lang=en-SG,en --remote-allow-origins=*", + "TZ": "Asia/Singapore", + "KERNEL_BROWSER_TIMEZONE": "Asia/Singapore", + "LANG": "C.UTF-8", + "LC_ALL": "C.UTF-8", + "CHROMIUM_FLAGS": "--remote-allow-origins=*", }}), "failed to start container") defer c.Stop(ctx) require.NoError(t, c.WaitReady(ctx), "api not ready") require.NoError(t, c.WaitDevTools(ctx), "devtools not ready") - osLocation, err := execCombinedOutput(ctx, c, "sh", []string{"-c", `printf '%s|%s' "$(locale charmap)" "$(date +%z)"`}) + // Read the system timezone from /etc/localtime, not the TZ variable. + osLocation, err := execCombinedOutput(ctx, c, "sh", []string{"-c", `printf '%s|%s' "$(locale charmap)" "$(env -u TZ date +%z)"`}) + require.NoError(t, err, "failed to inspect OS locale and timezone") + require.Equal(t, "UTF-8|+0800", osLocation) + + browserLocation, err := evaluateBrowserLocation(ctx, c.CDPURL()) + require.NoError(t, err) + require.Equal(t, "Asia/Singapore", browserLocation.TimeZone) + }) + } +} + +// TestRegionalBrowserLocale checks that the browser, not guest POSIX locale +// data or copied resource packs, provides the regional locale: at startup from +// production's --lang flags while the regional LANG/LC_ALL locale is not +// installed, and at runtime from a native location update applied under +// C.UTF-8. +func TestRegionalBrowserLocale(t *testing.T) { + requireKernelBrowser(t) + singapore := browserLocationProbe{Language: "en-SG", Locale: "en-SG", TimeZone: "Asia/Singapore"} + + for _, variant := range browserVariants { + t.Run(variant.name+"/startup", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + + c := NewTestContainer(t, *variant.image) + require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{ + "TZ": "Asia/Singapore", + "KERNEL_BROWSER_TIMEZONE": "Asia/Singapore", + "LANG": "en_SG.UTF-8", + "LC_ALL": "en_SG.UTF-8", + "CHROMIUM_FLAGS": "--lang=en-SG --accept-lang=en-SG,en --remote-allow-origins=*", + }}), "failed to start container") + defer c.Stop(ctx) + require.NoError(t, c.WaitReady(ctx), "api not ready") + require.NoError(t, c.WaitDevTools(ctx), "devtools not ready") + + installed, err := execCombinedOutput(ctx, c, "sh", []string{"-c", `locale -a | grep -ci '^en_sg' || true`}) + require.NoError(t, err) + require.Equal(t, "0", strings.TrimSpace(installed), "the regional POSIX locale must be absent for this check to prove the browser does not use it") + + browserLocation, err := evaluateBrowserLocation(ctx, c.CDPURL()) + require.NoError(t, err) + require.Equal(t, singapore, browserLocation) + }) + + t.Run(variant.name+"/native", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + + c := NewTestContainer(t, *variant.image) + require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{ + "TZ": "America/Los_Angeles", + "KERNEL_BROWSER_TIMEZONE": "America/Los_Angeles", + "LANG": "C.UTF-8", + "LC_ALL": "C.UTF-8", + "CHROMIUM_FLAGS": "--lang=en-US --accept-lang=en-US,en --remote-allow-origins=*", + "KERNEL_INSTANCE_JWT": locationInstanceToken, + }}), "failed to start container") + defer c.Stop(ctx) + require.NoError(t, c.WaitReady(ctx), "api not ready") + require.NoError(t, c.WaitDevTools(ctx), "devtools not ready") + + bundle := imageLocationBundle{Epoch: "lease-a", Generation: 1, TimeZone: "Asia/Singapore", Locale: "en-SG", Languages: []string{"en-SG", "en"}} + resetLocation(ctx, t, c, "", bundle) + waitLocationApplied(ctx, t, c, bundle) + + osLocation, err := execCombinedOutput(ctx, c, "sh", []string{"-c", `printf '%s|%s' "$(locale charmap)" "$(env -u TZ date +%z)"`}) require.NoError(t, err, "failed to inspect OS locale and timezone") require.Equal(t, "UTF-8|+0800", osLocation) browserLocation, err := evaluateBrowserLocation(ctx, c.CDPURL()) require.NoError(t, err) - require.Equal(t, browserLocationProbe{ - Language: "en-SG", - Locale: "en-SG", - TimeZone: "Asia/Singapore", - }, browserLocation) + require.Equal(t, singapore, browserLocation) }) } }