bug: mock-doc splits inline style at `;` inside url(), corrupting data-URI backgrounds

Open Beginner friendly
#7,017 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
css, html, javascript, node.js, typescript
Domain
testing, tooling

Research direction

Start in packages/mock-doc/src/css-style-declaration.ts at the set cssText setter, where cssText.split(';') runs unconditionally before the existing slice(1).join(':') rejoin for values. Run the repro script from the issue (MockDocument + parseHtmlToFragment from @stencil/core/mock-doc) first to see the truncation, then look at the existing mock-doc spec tests for css-style-declaration to match their style. Done means declarations split only at top-level semicolons outside parentheses and quotes, so the three expected outputs in the issue (innerHTML round-trip, style.backgroundImage, and parseHtmlToFragment round-trip) all hold.

Written by the indexing model from the issue text.

Description

Prerequisites
Stencil Version

4.45.3 (also reproduced on main at f4fe940 and 5.0.0-rc.1 / v5 at 1ed1c6c)

Current Behavior

MockCSSStyleDeclaration's cssText setter splits declarations with cssText.split(';'), so a semicolon inside url(...) (such as ;base64, in a data URI) or inside quotes terminates the declaration early. The remainder of the data URI is dropped (or misparsed as the next property if it contains :), leaving an unclosed url( or quote in the serialized style attribute.

This affects:

  • renderToString() when hydrating HTML that contains inline data-URI styles (<div style="background: url(data:image/gif;base64,...) no-repeat; color: red">), emitting <div style="background: url(data:image/gif; color: red;"> where the unclosed url( also invalidates color: red in browsers.
  • el.setAttribute('style', ...) and el.style.cssText = ... in components running under SSR or newSpecPage().
  • toEqualHtml() in spec tests: JSX style={{ backgroundImage: 'url(data:image/png;base64,...)' }} uses style.setProperty() and serializes intact on the live element, whereas parseHtmlToFragment() re-parses the expected HTML string through cssText and truncates it, so toEqualHtml() fails even against the element's own outerHTML.
Expected Behavior

MockCSSStyleDeclaration.cssText only splits declarations at top-level semicolons (outside parentheses and quotes), matching browser CSSStyleDeclaration behavior and CSS Syntax Module Level 3:

  • doc.body.innerHTML preserves <div style="background: url(data:image/gif;base64,R0lGOD==) no-repeat; color: red;">x</div>.
  • div.style.backgroundImage is url('data:image/png;base64,iVBORw0KGgo=') and div.getAttribute('style') is background-image: url('data:image/png;base64,iVBORw0KGgo='); color: red;.
  • parseHtmlToFragment(rendered.outerHTML) round-trips background-image: url(data:image/png;base64,iVBORw0KGgo=); color: blue; unchanged.
System Info
      System: node 26.13.0
    Platform: linux (6.1.0-54-cloud-amd64)
   CPU Model: Intel(R) Xeon(R) CPU @ 2.80GHz (64 cpus)
     Stencil: 4.45.3
  TypeScript: 5.8.3
      Rollup: 4.44.0
      Parse5: 7.2.1
      jQuery: 4.0.0-pre
      Terser: 5.37.0
Steps to Reproduce

Run npm install @stencil/[email protected] && node stencil-core-tslib.repro.mjs:

// stencil-core-tslib.repro.mjs
import { MockDocument, parseHtmlToFragment } from "@stencil/core/mock-doc";

const doc = new MockDocument(
  '<div style="background: url(data:image/gif;base64,R0lGOD==) no-repeat; color: red">x</div>'
);
console.log(doc.body.innerHTML);

const div = doc.createElement("div");
div.setAttribute("style", "background-image: url('data:image/png;base64,iVBORw0KGgo='); color: red");
console.log(div.getAttribute("style"));
console.log(div.style.backgroundImage);

const rendered = doc.createElement("div");
rendered.style.setProperty("background-image", "url(data:image/png;base64,iVBORw0KGgo=)");
rendered.style.setProperty("color", "blue");
console.log("before parse:", rendered.outerHTML);
console.log("after parse: ", parseHtmlToFragment(rendered.outerHTML).firstElementChild.outerHTML);

Actual output:

<div style="background: url(data:image/gif; color: red;">x</div>
background-image: url('data:image/png; color: red;
url('data:image/png
before parse: <div style="background-image: url(data:image/png;base64,iVBORw0KGgo=); color: blue;"></div>
after parse:  <div style="background-image: url(data:image/png; color: blue;"></div>
Code Reproduction URL

https://gist.github.com/trackoor/624030cdd797e27d114e86126108b278

Additional Information

In src/mock-doc/css-style-declaration.ts (packages/mock-doc/src/css-style-declaration.ts on v5), set cssText already rejoins : segments via splt.slice(1).join(':') so URLs with schemes survive, while the outer cssText.split(';') splits unconditionally.

Credit: https://ucsbplse.github.io/

Dominant language
TypeScript
Stars
13.2k
Forks
865
Avg merge
2d 16h
Merged PRs (30d)
29

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from stenciljs/core

All issues in stenciljs/core

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.