alert-hub-v2-node

Keeping CAP Alerts Readable as Browsers Retire Native XSLT

A field note on two ways to preserve human-readable Common Alerting Protocol documents after Chromium removes native XSLT: HTTP content negotiation and an experimental script-loaded XSLT processor.

For many years, an XML processing instruction provided a compact way to make a Common Alerting Protocol document readable in a browser:

<?xml-stylesheet
  type="text/xsl"
  href="https://cap-sources.s3.amazonaws.com/af-amd-en/alert-style.xsl"?>

The browser fetched the stylesheet, applied XSLT to the XML and displayed the resulting HTML. The CAP document remained XML for machines while the same URL produced a useful map and structured alert fields for people.

This behaviour now has a deadline. Chromium has deprecated both the XSLT processing instruction and the JavaScript XSLTProcessor API. Its published schedule says native XSLT will stop working in stable Chrome 158, currently planned for 17 November 2026. XML itself is not being removed.

That distinction matters to Alert-Hub.org. CAP remains the interchange format. The problem is how a person following a CAP link gets a readable page once the browser no longer performs the transformation.

We are testing two approaches.

The legacy example

This 2015 Meteo Rwanda CAP alert is a useful reference case. Its xml-stylesheet instruction loads an XSLT stylesheet which produces:

Meteo Rwanda flood alert with its headline, mapped warning polygon and structured CAP fields
The Rwanda flood presentation used as the reference layout for this work.
  • the alert headline
  • a map containing the CAP polygon
  • the core alert fields
  • the selected info block
  • area descriptions and geometry

The source document remains valid CAP 1.1 because a processing instruction before the root element is outside the CAP document structure.

Without native XSLT, the browser will no longer create that presentation. It will show the XML tree or hand the resource to another application, depending on its MIME type and browser configuration.

Option A: HTTP content negotiation

The first Alert-Hub.org approach keeps the CAP XML unchanged and moves the presentation decision to the HTTP layer.

The worked example is this mirrored alert:

https://ah-node-v2.api.alerting-apps.net/public/reflector/mx-smn-es/d71c430d-b73a-54bc-afa0-98eeca289d5c

The extensionless URL examines the request’s Accept header:

curl -H 'Accept: text/html' \
  'https://ah-node-v2.api.alerting-apps.net/public/reflector/mx-smn-es/d71c430d-b73a-54bc-afa0-98eeca289d5c'

curl -H 'Accept: application/cap+xml' \
  'https://ah-node-v2.api.alerting-apps.net/public/reflector/mx-smn-es/d71c430d-b73a-54bc-afa0-98eeca289d5c'

A browser asking for HTML receives a human-readable page. A CAP client asking for application/cap+xml receives the original CAP representation. Negotiated responses include Vary: Accept so HTTP caches keep the variants separate.

Readers can open the negotiated resource, forced HTML representation, or forced XML representation directly.

Explicit suffixes remove any ambiguity:

.../d71c430d-b73a-54bc-afa0-98eeca289d5c.html
.../d71c430d-b73a-54bc-afa0-98eeca289d5c.xml

The .html suffix always requests HTML. The .xml route in the current implementation uses the generic application/xml media type, rather than application/cap+xml, so a browser uses its XML viewer instead of treating the unfamiliar subtype as a download. The bytes remain CAP XML. The suffix overrides Accept in both cases.

How the HTML is chosen

Some source CAP documents already identify an XSLT stylesheet. During ingestion, Alert Hub records those references in a review queue. An operator may fetch an immutable version, inspect its provenance and content hash, compile it, and approve or reject it.

The public request path never fetches a stylesheet from the Internet. If an approved cached stylesheet exists for the source document, the server applies that version. External resource access is disabled during the server-side transformation.

If no stylesheet has been approved, the default path parses the CAP into an HTML view model and uses a compiled template. That presentation includes the map, all polygons and circles, and the structured CAP fields. A bundled XSLT renderer remains available as a configured fallback, but it is not required for the normal template path.

This approach has several useful properties:

  • the CAP document remains schema-valid
  • XML and HTML have explicit representations
  • browsers do not need an XSLT engine
  • stylesheet execution is controlled and reviewable
  • clients can use stable suffixes when negotiation is inconvenient

Its cost is that the server owns the transformation, templates, caching and content-security policy.

Option B: put an XSLT runtime beside the XML

Chromium’s migration note also points to a WASM-based XSLT polyfill. The documented compatibility pattern keeps the processing instruction and adds an XHTML-namespaced script as a child of the XML root.

For this experiment, I tested a stricter variant: remove the processing instruction completely and let the script identify the stylesheet.

The modified CAP starts like this:

<?xml version="1.0" encoding="UTF-8"?>
<cap:alert xmlns:cap="urn:oasis:names:tc:emergency:cap:1.1">
  <script xmlns="http://www.w3.org/1999/xhtml"
          src="cap-xslt-renderer.js"
          data-stylesheet="alert-style.xsl"></script>
  <cap:identifier>urn:oid:2.49.0.1.4.0.2015.4.3.13.7.14</cap:identifier>
  <!-- remaining CAP content -->
</cap:alert>

The XHTML namespace is significant: it tells the browser that this foreign element is an executable web script rather than a CAP element named script.

The bootstrap loads the polyfilled XSLT engine, reloads the XML bytes, associates them with the named stylesheet in memory, performs the transformation and replaces the displayed document with the generated HTML. The served XML contains no xml-stylesheet instruction and the demonstration forces the polyfill even where native XSLT still exists.

Live demonstration

The frame below is the modified 2015 CAP document itself, not a pre-rendered screenshot. Open the script-rendered CAP XML separately to inspect it as a complete page.

The original headline, map, polygon and structured CAP fields are produced from XML and XSLT in the browser.

All demonstration inputs are published with this post:

The demo was exercised in Chromium 150. It produced the legacy headline, OpenLayers base map, CAP polygon and structured field table without using the document’s original processing instruction.

The validation trade-off

Option B is XML, but it is not valid CAP.

The official CAP 1.1 schema expects identifier as the first child of alert. It does not provide an extension point for an XHTML script element. Validation therefore fails at that element:

Element '{http://www.w3.org/1999/xhtml}script': This element is not expected.
Expected is '{urn:oasis:names:tc:emergency:cap:1.1}identifier'.

Many XML consumers may ignore the foreign element and continue, but schema validation is intended to make this structural disagreement visible. The script approach also adds a JavaScript/WASM dependency, client-side startup time, CORS requirements and a larger security-policy surface.

It does have one attractive property: a static XML publisher can retain an XSLT-based presentation without adding a server-side negotiation or rendering service.

Option C: Ask chromium team to add special configuration for a new cap-stylesheet processing instruction

This new PI would be implemented to silently wrap the CAP alert in a new CAPWrapper element with the requisite script element, which would then perform the XSLT processing on the newly wrapped CAP alert using the XSLT polyfill in option B. This option is the most transparent for existing documents, whilst still requiring updated XSLT, it does remove the need to edit the CAP schema itself, which reduces the standards burden significantly.

<?xml version="1.0" encoding="UTF-8"?>
<?cap-stylesheet href='/xsl/capatomproduct.xsl' type='text/xsl'?>
<cap:alert xmlns:cap="urn:oasis:names:tc:emergency:cap:1.1">
  <cap:identifier>urn:oid:2.49.0.1.4.0.2015.4.3.13.7.14</cap:identifier>
  <!-- remaining CAP content -->
</cap:alert>

Current direction

For Alert-Hub.org, content negotiation is the cleaner primary approach. It keeps standards-valid CAP as the machine representation and supplies HTML as a distinct human representation. It also allows existing, reviewed XSLT presentations to be reused server-side while providing a maintained template when no stylesheet exists.

The major problem with content type negotiation is the large base of installed systems that are reliant on xml-stylesheet. These are often governmental and quazi-governmental systems with little to no scope for investment in this kind of change - which means an in place solution is needed, and one where the lower the blast radius is always better. Similarly, changes to the CAP schema attract standards and compliance concerns that are not easily addressed in the short term.

This means that whilst content type negotiation might well be the technically superior approach in 2026, it is not an accessible one for a large part of the CAP install base. In a system emtting 1500 xml-stylesheet enabled CAP warnings per day, the loss of HTML rendering is a serious problem.

The embedded-script method remains a useful interoperability experiment and perhaps a bridge for static publishers. It should not be mistaken for schema-valid CAP, and adopting it would require accepting that trade-off explicitly.

One deployment check remains at the time of writing: the running reflector must be confirmed at the revision where .xml returns application/xml. The implementation and tests contain that behaviour, but publication should verify the live response headers again.

The broader lesson is modest: removal of native browser XSLT does not require abandoning XML or CAP. It does require separating the interchange document from its human presentation more deliberately than a processing instruction once allowed.

In the very short term, some middle way is the ideal solution - and Option C is the one we are actively exploring with the chromium team.

Additional

At ~1900 on Jul 22 we added reporting to https://console.alerting-apps.net/cap-aggregator/reporting/trends?q=tag%3AHAS_XML_STYLESHEET which allows us to know the throughput of alerts that carry an XML stylesheet directive. As of 1500 on Jul 23rd we have processed 1124 alerts that carry an XML Stylesheet directive. So a rate of ~75 possibly life critical notifications per hour. The appeal of Option C is the uninterrupted operation of existing infrastructure.

The modified CAP would start like this: