w3c/webextensions

[DNR] main_frame redirect to an extension page + accessing the original URL and POST data

Opened this issue · 0 comments

Supersedes #604 with a different, more focused use case: redirecting an entire tab (optionally a frame) to a custom handler.

Extensions like Tapermonkey/Violentmonkey/Stylus/JSONViewer and others want to redirect an entire tab (or frame) based on a URL or on a Content-Type response header (soon will be possible via DNR) to their own built-in UI page. URLs ending on .json, .user.js or .user.css and such, so this is not solved by custom protocol handlers.

Primary goals

  1. The original URL must be exposed to the extension page.
  2. The extension page should not be exposed to the web:
    • to avoid redundancy as main_frame/sub_frame navigation is done by the browser's DNR handler, not by a web page;
    • to improve security as Chrome still doesn't implement use_dynamic_url;
    • to improve API ergonomics by removing a non-obvious requirement.
  3. Provide POST data from submitted forms to allow extensions to preprocess it before uploading to the server.

Optional goal

  1. Support sub_frame redirection in addition to main_frame.
    Although this is extremely rare to have a custom viewer in a frame, AFAIK, but it seems reasonable for consistency.

ManifestV2

  • Redirection: extensions stopped the navigation via webRequestBlocking by returning {cancel:true} or {redirectUrl: 'javascript:0'} and then redirected the tab via chrome.tabs.update(tabId, {url: 'own.html'}), passed the original URL either as a URL parameter or via messaging. This only worked with main_frame navigation, though.
  • POST preprocessing: chrome.webRequest.onBeforeRequest(..., ['requestBody', 'blocking'])

ManifestV3

It's complicated and problematic since normal extensions can't use webRequestBlocking anymore:

  • requires declaring the background script or adding a visual config page to call updateDynamicRules with chrome.runtime.id;
  • requires DNR's regexFilter and regexSubstitution to pass the original URL, but:
    • regex is inefficient to match URLs on all domains (DNR doesn't implement cascaded matching of urlFilter + regexFilter),
    • there are global limits on regex rules in DNR,
    • regex confuses many developers as it's inherently arcane and cryptic;
  • requires exposing the html page in web_accessible_resources, but the extension may want to disallow web-initiated redirections in general, allowing them only for its own DNR rules. For example, to improve security, avoid detection.
  • POST preprocessing: intercepting submit event in an isolated content script isn't enough, you'd need to spoof HTMLFormElement.prototype.submit in the main world, which is unsafe/unreliable.

Possible solutions for goal 1: exposing the original URL

  1. As document.referrer i.e. not exposed visually.
  2. As an auto-appended URL parameter ?url=.... accessible as new URLSearchParams(location.search).get('url').
    It may be appended as &url=... if the extension path already contains ? e.g. "/foo.html?param=1".
    If deemed desirable, this behavior may be disabled by default, opt-in via "originalUrlAsParameter": "url".

Possible solutions for goal 2: redirection without self-exposure

  1. extensionHandler: 'foo.html'. The name suggests that the extension handles the page, like PDF viewer in Chrome.
  2. extensionTabHandler: 'foo.html'. A more self-explanatory name if we want to support only main_frame navigation.
  3. extensionNavigationHandler: 'foo.html'. Makes it even more obvious that this is only for navigation redirects.
  4. extensionPathForNavigation: 'foo.html'. Also preserves continuity with the existing extensionPath.
  5. resourcesTypes: ['main_frame', 'sub_frame'] would automatically allow extensionPath: 'foo.html' to redirect to the extension's foo.html without exposing it in web_accessible_resources. This is not explicit, though.

Possible solutions for goal 3: POST data access

  1. chrome.declarativeNetRequest.redirectedRequest getter or getRedirectedRequest() method to get all info
  2. chrome.declarativeNetRequest.requestBody getter or getRequestBody() method to get only the body