Skip to content

Webcompat: implement PresentationRequest shim#2121

Draft
jonathanKingston wants to merge 18 commits into
mainfrom
cursor/presentation-request-shim-update-dc58
Draft

Webcompat: implement PresentationRequest shim#2121
jonathanKingston wants to merge 18 commits into
mainfrom
cursor/presentation-request-shim-update-dc58

Conversation

@jonathanKingston
Copy link
Copy Markdown
Contributor

@jonathanKingston jonathanKingston commented Dec 19, 2025

Asana Task/Github Issue: https://app.asana.com/1/137249556945/project/1201614831475344/task/1210044338857086?focus=true

Description

Make the Presentation API shim spec-shaped for feature-detection code by allowing PresentationRequest construction, exposing urls/onconnectionavailable, and returning NotSupportedError rejections for unsupported operations.

This PR implements a spec-shaped PresentationRequest shim to support feature-detection code on sites that check for the Presentation API. The shim allows construction of PresentationRequest objects, exposes required properties (urls, onconnectionavailable), and returns NotSupportedError rejections for unsupported operations.

Testing Steps

Test Cases

1. PresentationRequest Constructor

Test 1.1: Basic construction with a single URL string

const request = new PresentationRequest('https://example.com/presentation');
console.log(request instanceof PresentationRequest); // Expected: true
console.log(request.urls); // Expected: ['https://example.com/presentation']

Test 1.2: Construction with an array of URLs

const request = new PresentationRequest([
  'https://example.com/page1',
  'https://example.com/page2'
]);
console.log(request.urls); // Expected: ['https://example.com/page1', 'https://example.com/page2']
console.log(request.urls.length); // Expected: 2

Test 1.3: Construction with an iterable

const urlSet = new Set(['https://example.com/a', 'https://example.com/b']);
const request = new PresentationRequest(urlSet);
console.log(request.urls.length); // Expected: 2

Test 1.4: Invalid constructor arguments should throw TypeError

// Empty array
try {
  new PresentationRequest([]);
} catch (e) {
  console.log(e instanceof TypeError); // Expected: true
  console.log(e.message.includes('At least one URL')); // Expected: true
}

// Invalid type (number)
try {
new PresentationRequest(123);
} catch (e) {
console.log(e instanceof TypeError); // Expected: true
}

// No arguments
try {
new PresentationRequest();
} catch (e) {
console.log(e instanceof TypeError); // Expected: true
}

2. URLs Property

Test 2.1: URLs property returns a copy (immutable)

const request = new PresentationRequest(['https://example.com']);
const urls1 = request.urls;
const urls2 = request.urls;
console.log(urls1 !== urls2); // Expected: true (different array instances)
urls1.push('https://modified.com');
console.log(request.urls.length); // Expected: 1 (original unchanged)

3. onconnectionavailable Property

Test 3.1: Property exists and is initially null

const request = new PresentationRequest('https://example.com');
console.log(request.onconnectionavailable); // Expected: null

Test 3.2: Property can be set to a function

const request = new PresentationRequest('https://example.com');
request.onconnectionavailable = (evt) => console.log('connected');
console.log(typeof request.onconnectionavailable); // Expected: 'function'

4. start() Method

Test 4.1: Returns a rejected Promise with NotSupportedError

const request = new PresentationRequest('https://example.com');
request.start().catch(err => {
  console.log(err.name); // Expected: 'NotSupportedError'
  console.log(err instanceof DOMException || err instanceof Error); // Expected: true
  console.log(err.message.includes('not supported')); // Expected: true
});

5. reconnect() Method

Test 5.1: Returns a rejected Promise with NotSupportedError

const request = new PresentationRequest('https://example.com');
request.reconnect('some-id').catch(err => {
  console.log(err.name); // Expected: 'NotSupportedError'
  console.log(err.message.includes('not supported')); // Expected: true
});

6. getAvailability() Method

Test 6.1: Returns a rejected Promise with NotSupportedError

const request = new PresentationRequest('https://example.com');
request.getAvailability().catch(err => {
  console.log(err.name); // Expected: 'NotSupportedError'
  console.log(err.message.includes('not supported')); // Expected: true
});

7. navigator.presentation Object

Test 7.1: Presentation object exists

console.log(typeof navigator.presentation); // Expected: 'object'
console.log(navigator.presentation !== null); // Expected: true

Test 7.2: defaultRequest property getter/setter

console.log(navigator.presentation.defaultRequest); // Expected: null (initially)

const request = new PresentationRequest('https://example.com');
navigator.presentation.defaultRequest = request;
console.log(navigator.presentation.defaultRequest === request); // Expected: true

navigator.presentation.defaultRequest = null;
console.log(navigator.presentation.defaultRequest); // Expected: null

Test 7.3: receiver property

console.log(navigator.presentation.receiver); // Expected: null

8. EventTarget Inheritance

Test 8.1: PresentationRequest extends EventTarget

const request = new PresentationRequest('https://example.com');
console.log(typeof request.addEventListener); // Expected: 'function'
console.log(typeof request.removeEventListener); // Expected: 'function'
console.log(typeof request.dispatchEvent); // Expected: 'function'

9. Feature Detection Compatibility

Test 9.1: Common feature detection patterns should work

// Pattern 1: Check for PresentationRequest
if ('PresentationRequest' in window) {
  console.log('PresentationRequest exists'); // Expected to log
}

// Pattern 2: Check navigator.presentation
if (navigator.presentation) {
console.log('navigator.presentation exists'); // Expected to log
}

// Pattern 3: Try to construct (graceful degradation)
try {
const request = new PresentationRequest('https://example.com');
request.getAvailability().catch(() => {
console.log('Presentation not available, falling back'); // Expected to log
});
} catch (e) {
console.log('Construction failed');
}

10. Real-world Site Testing

Test on sites known to use the Presentation API for feature detection:

  1. YouTube - Uses Presentation API for Cast functionality

    • Navigate to youtube.com
    • Verify page loads without errors
    • Check console for any PresentationRequest-related errors
  2. Netflix - May check for Presentation API

    • Navigate to netflix.com
    • Verify page loads without errors
  3. Any site with Cast/Presentation features

    • The page should load correctly
    • Feature detection should work (even if the feature itself is unavailable)
    • No console errors related to PresentationRequest being undefined

Regression Testing

  1. Ensure native Presentation API is not overwritten

    • On browsers that natively support Presentation API (Chrome), verify the shim does not activate
    • Check condition: window.navigator.presentation && window.PresentationRequest should prevent shim
  2. Integration test environment

    • Verify the shim IS applied in integration tests (note the this.injectName !== 'integration' check)

Expected Behaviour Summary

Feature Expected Result
new PresentationRequest(url) Creates object successfully
new PresentationRequest([]) Throws TypeError
request.urls Returns array of URLs
request.start() Returns rejected Promise (NotSupportedError)
request.reconnect() Returns rejected Promise (NotSupportedError)
request.getAvailability() Returns rejected Promise (NotSupportedError)
request.onconnectionavailable Property exists, initially null
navigator.presentation.defaultRequest Getter/setter works
EventTarget methods addEventListener, etc. available

Checklist

Please tick all that apply:

  • I have tested this change locally
  • I have tested this change locally in all supported browsers
  • This change will be visible to users
  • I have added automated tests that cover this change
  • I have ensured the change is gated by config
  • This change was covered by a ship review
  • This change was covered by a tech design
  • Any dependent config has been merged

Note

Medium Risk
Touches global shims for the Presentation API, which can affect feature-detection code and site behavior if the shape/error semantics differ from expectations. Guarding against native implementations reduces risk, but new constructor/type-handling paths may introduce compatibility edge cases.

Overview
Improves the Presentation API web-compat shim to be spec-shaped for feature detection by implementing a constructible PresentationRequest (accepting string/array/iterable URLs), exposing urls (as a defensive copy) and onconnectionavailable, and making start/reconnect/getAvailability reject with NotSupportedError.

Updates navigator.presentation.defaultRequest to be a real writable getter/setter (accepting PresentationRequest or null) and tightens the early-exit guard so the shim won’t override native Presentation support. Adds an integration test page case to validate the new PresentationRequest behavior and defaultRequest semantics.

Reviewed by Cursor Bugbot for commit 38f0739. Bugbot is set up for automated code reviews on this repo. Configure here.

Make the Presentation API shim spec-shaped for feature-detection code by allowing PresentationRequest construction, exposing urls/onconnectionavailable, and returning NotSupportedError rejections for unsupported operations.
@netlify
Copy link
Copy Markdown

netlify Bot commented Dec 19, 2025

Deploy Preview for content-scope-scripts ready!

Name Link
🔨 Latest commit 8088db3
🔍 Latest deploy log https://app.netlify.com/projects/content-scope-scripts/deploys/694563a5eeb08a00083f96c2
😎 Deploy Preview https://deploy-preview-2121--content-scope-scripts.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 19, 2025

Temporary Branch Update

The temporary branch has been updated with the latest changes. Below are the details:

Please use the above install command to update to the latest version.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 19, 2025

[Beta] Generated file diff

Time updated: Fri, 22 May 2026 10:16:52 GMT

Android
    - android/adsjsContentScope.js
  • android/contentScope.js

File has changed

Apple
    - apple/contentScope.js

File has changed

Integration
    - integration/contentScope.js

File has changed

Windows
    - windows/contentScope.js

File has changed

cursoragent and others added 2 commits December 19, 2025 14:39
Add webcompat integration-test assertions that PresentationRequest is constructible, exposes urls/onconnectionavailable, rejects unsupported operations with NotSupportedError, and allows setting navigator.presentation.defaultRequest.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 27, 2026

Build Branch

Branch pr-releases/cursor/presentation-request-shim-update-dc58
Commit be26d74aba
Updated May 22, 2026 at 10:16:06 AM UTC

Static preview entry points

QR codes (mobile preview)
Entry point QR code
Docs QR for docs preview
Static pages QR for static pages preview
Integration pages QR for integration pages preview

Integration commands

npm (Android / Extension):

npm i github:duckduckgo/content-scope-scripts#pr-releases/cursor/presentation-request-shim-update-dc58

Swift Package Manager (Apple):

.package(url: "https://github.com/duckduckgo/content-scope-scripts.git", branch: "pr-releases/cursor/presentation-request-shim-update-dc58")

git submodule (Windows):

git -C submodules/content-scope-scripts fetch origin pr-releases/cursor/presentation-request-shim-update-dc58
git -C submodules/content-scope-scripts checkout origin/pr-releases/cursor/presentation-request-shim-update-dc58
Pin to exact commit

npm (Android / Extension):

npm i github:duckduckgo/content-scope-scripts#be26d74abaf6c8e5d746d291cb36f7dcb79216ee

Swift Package Manager (Apple):

.package(url: "https://github.com/duckduckgo/content-scope-scripts.git", revision: "be26d74abaf6c8e5d746d291cb36f7dcb79216ee")

git submodule (Windows):

git -C submodules/content-scope-scripts fetch origin pr-releases/cursor/presentation-request-shim-update-dc58
git -C submodules/content-scope-scripts checkout be26d74abaf6c8e5d746d291cb36f7dcb79216ee

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Cursor assessed this PR as Medium Risk (only Low Risk is auto-approved).

This PR requires a manual review and approval from a member of one of the following teams:

  • @duckduckgo/content-scope-scripts-owners
  • @duckduckgo/apple-devs
  • @duckduckgo/android-devs
  • @duckduckgo/team-windows-development
  • @duckduckgo/extension-owners
  • @duckduckgo/config-aor
  • @duckduckgo/breakage-aor
  • @duckduckgo/breakage

@github-actions github-actions Bot added the minor Increment the minor version when merged label Mar 4, 2026
@github-actions github-actions Bot added the semver-minor New feature — triggers minor version bump label Mar 5, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Web Compatibility Assessment

  • injected/src/features/web-compat.js:906 - warning: the new guard only returns when both navigator.presentation and window.PresentationRequest exist. On a partial native implementation, the function continues and later replaces Navigator.prototype.presentation with the shim singleton, which can hide native defaultRequest/receiver behavior instead of only filling the missing constructor.
  • injected/src/features/web-compat.js:1003 - warning: onconnectionavailable is installed as an own data property in the constructor. Spec-shaped event handler attributes should be visible on PresentationRequest.prototype as an accessor/event-handler property; prototype/descriptor-based feature detection will currently fail.
  • injected/src/features/web-compat.js:1011 - info: the new urls accessor is not masked by shimInterface() because that helper only wraps writable function-valued descriptors. Object.getOwnPropertyDescriptor(PresentationRequest.prototype, 'urls').get.toString() exposes injected source, so descriptor-fidelity tests can detect the shim.
  • injected/src/features/web-compat.js:1019-1027 - info: reconnect() is declared with zero formal parameters, so PresentationRequest.prototype.reconnect.length is 0; the native/WebIDL method is expected to take a presentation id. Add a method-length/argument-contract test if this shim is intended to satisfy detailed feature detection.

Security Assessment

  • injected/src/features/web-compat.js:971-1032 - warning: the new constructor and methods reread mutable page globals after config init (globalThis.EventTarget, DOMException, Error, Promise, String, Symbol, Array.isArray, TypeError). A hostile page can replace these before init() or before calling the shim, causing the shim to fail, throw attacker-controlled errors, or return a non-Promise from start()/reconnect()/getAvailability().
  • No new messaging, origin validation, iframe access, network, or data-exfiltration paths were introduced.

Risk Level

High Risk: this PR changes a global API shim and Navigator.prototype behavior for the Presentation API, which is a detectable browser surface and can affect all pages where the webcompat setting is enabled.

Recommendations

  1. Preserve partial native implementations: only define missing pieces, and do not replace Navigator.prototype.presentation when a native navigator.presentation already exists.
  2. Move onconnectionavailable to a prototype-level accessor/event-handler shape and add descriptor tests for prototype presence, getter/setter shape, toString() masking, and method .length.
  3. Use captured globals for all values read in the init/runtime shim path; add captures for DOMException/EventTarget if needed, and prefer captured/bound helpers for string conversion, promises, symbols, and array checks.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

try {
// @ts-expect-error due to: Property 'presentation' does not exist on type 'Navigator'
if (window.navigator.presentation && this.injectName !== 'integration') {
if (window.navigator.presentation && window.PresentationRequest && this.injectName !== 'integration') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard changes the native-preservation behavior for partial implementations. If a browser exposes navigator.presentation but not window.PresentationRequest, we continue and later replace Navigator.prototype.presentation with the shim singleton. That can hide native defaultRequest/receiver behavior instead of just filling the missing constructor; safer is to preserve an existing native navigator.presentation and shim only the absent pieces.

'PresentationRequest',
class {
// class definition is empty because there's no way to get an instance of it anyways
class extends (globalThis.EventTarget || class {}) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is created in init(), after page script may have had a chance to mutate globals, and its methods will be called later by page code. Reading globalThis.EventTarget here, and later DOMException, Error, Promise, String, Symbol, Array.isArray, and TypeError, leaves the shim dependent on page-controlled globals. Please use captured-globals.js references, adding captures for EventTarget/DOMException if needed.


/** @type {((ev: any) => any)|null} */
this.onconnectionavailable = null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onconnectionavailable is currently an own data property on each instance, so checks like 'onconnectionavailable' in PresentationRequest.prototype fail and descriptor-based detection sees a different shape from a native event-handler attribute. This should be installed on the prototype with the expected accessor/event-handler semantics and covered by the shim descriptor tests.

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

Labels

minor Increment the minor version when merged semver-minor New feature — triggers minor version bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants