Skip to content

[Bug]: WebKit decodes HTML from route.fulfill() as windows-1252, ignoring charset=utf-8 #43649

Open

Description

@jay-tau

Version

1.64.1 (also 1.58.2 and 1.65.0-alpha-2026-10-26)

Steps to reproduce

In an empty directory on Ubuntu 24.04, run:

npm init -y
npm install playwright-core@1.64.1
npx playwright-core install --with-deps webkit

Save the following as repro.mjs (UTF-8), then run node repro.mjs:

import { webkit } from "playwright-core";

const browser = await webkit.launch();
const page = await browser.newPage();
await page.route("http://fulfill.test/**", (route) =>
  route.fulfill({ contentType: "text/html; charset=utf-8", body: "<!doctype html><p>₹</p>" }),
);
await page.goto("http://fulfill.test/");
console.log(await page.evaluate(() => [document.characterSet, document.querySelector("p").textContent]));
await browser.close();

Expected behavior

[ 'UTF-8', '₹' ], as in Chromium and Firefox, and as WebKit gives when a real HTTP server sends the same Content-Type: text/html; charset=utf-8 header.

Actual behavior

[ 'windows-1252', '₹' ]. It's the same with headers: { "Content-Type": "text/html; charset=utf-8" } instead of contentType, with a Buffer body, and with text/html;charset=UTF-8.

Response Chromium 156 Firefox 157 WebKit 27.2
route.fulfill({ contentType, body: string }) UTF-8 ₹ UTF-8 ₹ windows-1252 ₹
route.fulfill({ headers, body: string }) UTF-8 ₹ UTF-8 ₹ windows-1252 ₹
route.fulfill({ contentType, body: Buffer }) UTF-8 ₹ UTF-8 ₹ windows-1252 ₹
route.fulfill({ status: 200, headers: { "content-type": "text/html;charset=UTF-8" }, body: Buffer }) UTF-8 ₹ UTF-8 ₹ windows-1252 ₹
Real HTTP server, same header UTF-8 ₹ UTF-8 ₹ UTF-8 ₹

Additional context

We hit this in a test whose fulfilled page embeds a srcdoc iframe containing an inline script with non-ASCII text, allowed by a CSP sha256- hash. Incorrect decoding of the outer page changed the iframe's script text, so its hash no longer matched and WebKit refused to run it. Adding <meta charset="utf-8"> to the fulfilled page works around it.

Environment

System:
    OS: Linux 7.2 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
    CPU: (16) x64 AMD Ryzen 9 8945HS w/ Radeon 780M Graphics
    Memory: 5.44 GB / 14.91 GB
    Container: Yes
  Binaries:
    Node: 24.19.0 - /opt/acttoolcache/node/24.19.0/x64/bin/node
    npm: 11.17.0 - /opt/acttoolcache/node/24.19.0/x64/bin/npm
  Languages:
    Bash: 5.2.21 - /usr/bin/bash
  npmPackages:
    playwright-core: ^1.64.1 => 1.64.1

Activity

  1. Ayush-S-inha commented on Oct 26, 2026

    @Ayush-S-inha

    I was able to reproduce this on Windows with playwright-core@1.64.1 and WebKit 27.2.
    I get: [ 'windows-1252', '₹' ]

    I'd like to try working on a fix. Before I start digging into the code, could a maintainer confirm whether this issue is open for community contributions and whether the fix would likely belong in Playwright's TypeScript code or the patched WebKit engine?

    I haven't made any source changes yet.

  2. github-actions commented on Oct 26, 2026

    @github-actions
    Contributor

    Hi, I'm the Playwright bot and I reproduced this on Windows.

    Reproduced on current main, only in WebKit. The browser's interception handler discards the supplied charset: it replaces Content-Type with the bare MIME type and leaves the response's text encoding empty. This needs a WebKit engine change, not just a TypeScript change. Adding <meta charset="utf-8"> works around it.

    Reproduction and results

    Tested Playwright 1.65.0-next, commit e478109de5542aa6bd69f817d6cb054523af9ce4, on Windows:

    Case Chromium 157.0.8103.15 Firefox 158.0 WebKit 27.2 (r2380)
    Fulfilled HTML, string body, UTF-8 contentType, headless correct correct windows-1252, corrupted text
    Same, headed correct correct windows-1252, corrupted text
    Buffer body, explicit Content-Type: text/html;charset=UTF-8, headless correct correct windows-1252, corrupted text
    Real HTTP response with UTF-8 header, headless correct correct correct
    Fulfilled HTML with UTF-8 meta tag, headless correct correct correct

    The reported releases were not separately tested; this reproduces on main. Linux and macOS were not tested in this Windows run.

    The regression fits the existing RO-096 should work test in tests/page/page-request-fulfill.spec.ts:

    it('RO-096 should work', {
      annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/43649' },
    }, async ({ page, server }) => {
      await page.route('**/*', route => route.fulfill({
        status: 201,
        headers: { foo: 'bar' },
        contentType: 'text/html; charset=utf-8',
        body: '<!doctype html><p>\u20b9</p>',
      }));
      const response = await page.goto(server.EMPTY_PAGE);
      expect(response.status()).toBe(201);
      expect(response.headers().foo).toBe('bar');
      expect(await page.evaluate(() => [
        document.characterSet, document.querySelector('p').textContent
      ])).toEqual(['UTF-8', '\u20b9']);
    });

    WebKit returns ['windows-1252', '\u00e2\u201a\u00b9']. Its received response header is text/html; Chromium and Firefox retain text/html;charset=UTF-8. Protocol logging confirms Playwright sent the full charset-bearing header to WebKit.

    Commands:

    npm run test -- page-request-fulfill.spec.ts --grep RO-096 --workers=3 --reporter=line
    npm run test -- page-request-fulfill.spec.ts --grep RO-096 --headed --retries=0 --workers=3 --reporter=line
    npm run test -- page-request-fulfill.spec.ts --grep RO-096 --retries=0 --workers=3 --reporter=line
    

    The last command included the HTTP, Buffer/header and meta-tag controls. A further WebKit-only run of those controls used DEBUG=pw:protocol with npm run wtest -- page-request-fulfill.spec.ts --grep RO-096 --retries=0 --reporter=line. Both faulty fulfillment cases failed; the HTTP and meta-tag controls passed. The initial headless regression failed on all four WebKit attempts, including retries.

    Engine fix location

    InspectorNetworkAgent::interceptRequestWithResponse constructs ResourceResponse with an empty encoding, copies the supplied headers, then overwrites Content-Type with response.mimeType(). Playwright's browser_patches/webkit/patches/bootstrap.diff keeps those lines.

    The suggested engine fix is to preserve the explicit Content-Type (using the MIME type only as a fallback) and populate ResourceResponse::textEncodingName from its charset, using WebKit's extractCharsetFromMediaType helper. Changing only the TypeScript mimeType value would not populate the encoding field.

    No engine build or engine-fix verification was attempted in this workflow.

    Triaged on Windows by the Playwright bot - agent run

  3. dgozman commented on Oct 28, 2026

    @dgozman
    Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    browser-webkitBlocked on upstream/fork webkit issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions