AppToWeb

In recent posts, I’ve explored mechanisms to communicate from web content to local (native) apps, and I explained how web apps can use the HTML5 registerProtocolHandler API to allow launching them from either local apps or other websites.

In today’s post, we’ll explore how local apps can launch web apps in the browser.

It’s Simple…

In most cases, it’s trivial for an app to launch a web app and send data to it. The app simply invokes the operating system’s “launch” API and passes it the desired URL for the web app.

Any data to be communicated to the web app is passed in the URL query string or the fragment component of the URL.

On Windows, such an invocation might look like this:

ShellExecute(hwnd, "open", "https://bayden.com/echo.aspx?DataTo=Pass#GoesHere", 0, 0, SW_SHOW);

Calling this API results in the user’s default browser being opened and a new tab navigated to the target URL.

This same simple approach works great on most operating systems and with virtually any browser a user might have configured as their default.

…Unless It’s Not

Unfortunately, this well-lit path adjoins a complexity cliff— if your scenario has requirements beyond the basic [Launch the default browser to this URL], things get much more challenging. The problem is that there is no API contract that provides a richer feature set and works across different browsers.

For instance, consider the case where you’d like your app to direct the browser to POST a form to a target server. Today, popular operating systems have no such concept– they know how to open a browser by passing it a URL, but they expose no API that says “Open the User’s browser to the following URL, sending the navigation request via the HTTP POST method and containing the following POST body data.

Over the years, a few workarounds have been used (e.g. see StackOverflow1 and StackOverflow2).

For instance, if the target webservice simply requires a HTTP POST and you cannot change it, your app could launch the browser to a webpage you control, passing the required data in the querystring component of a HTTP GET. Your web server could then reformat the data into the required POST body format and either proxy that request (server-side) to the target webservice, or it could return a web page with an auto-submitting form element with a method of POST and and action attribute pointed at the target webservice. The user’s browser will submit the form, posting the data to the target server.

Similarly, a more common approach involves having the app write a local HTML file in a temporary folder, then direct the Operating System to open that file using the appropriate API (again ShellExecute, in the case of Windows). Presuming that the user’s default HTML handler is also their default HTTPS protocol handler, opening the file will result in the default browser opening, and the HTML/script in the file will automatically submit the included form element to the target server. This “bounce through a local temporary form” approach has the advantage of making it possible to submit sizable of data to the server (e.g. the contents of a local file), unlike using a GET request’s size-limited querystring.

Caveats:

  • Unfortunately it is generally not possible to construct a HTML form that will submit a data field that exactly matches what you would get when sending an <input type=file> control. If the web service demands a format that was generated by a file upload control, you may not be able to emulate that.
  • Some browsers will not run JavaScript in local files by default.
  • Don’t forget to delete the temporary file!

If your scenario requires uploading files, an alternative approach is to:

  1. Upload the files directly from your app to a web service
  2. Have that web service return a secret token associated with the upload
  3. Have your app spawn a browser with a GET request whose querystring contains that secret token

Browser-Specific Approaches

Back in the Windows 7 days, the IE8 team created a very cool feature called Accelerators that would allow users to invoke web services in their browser from any other application. Interestingly, the API contract supported web services that required POST requests.

Because there was no API in Windows that supported launching the default browser with anything other than a URL, a different approach was needed. A browser that wished to participate as a handler for accelerators could implement a IOpenServiceActivityOutputContext::Navigate function which was expected to launch the browser and pass the data. The example implementation provided by our documentation called into Internet Explorer’s Navigate2() COM API, which accepted as a parameter the POST body to be sent in the navigation. As far as I know, no other browser ever implemented IOpenServiceActivityOutputContext.

These days, Accelerators are long dead, and no one should be using Internet Explorer anymore. In the intervening years, no browser-agnostic mechanism to transfer a POST request from an app to a browser has been created.

Perhaps the closest we’ve come is the W3C’s WebDriver Standard, designed for automated testing of websites across arbitrary browsers. Unfortunately, at present, there’s still no way for mainstream apps to take a dependency on WebDriver to deliver a reliable browser-agnostic solution enabling rich transfers from a local app to a web app.

Future Possibilities

While the current picture is bleak, the future is a bit brighter. That’s because a major goal of browsers’ investment in Progressive Web Apps is to make them rich enough to take the place of native apps. Today’s native apps have very rich mechanisms for passing data and files to one another and PWAs will need such capabilities in order to achieve their goals.

Perhaps one day, not too far in the future, your OS and your browser (regardless of vendor) will better interoperate.

-Eric

I’ve previously written about Web-to-App communication via Application Protocols. App Protocols allow web content to invoke a native application outside of the browser.

WebApp advocates (like me!) want to continue to close the native/browser gaps that prevent web applications from becoming full-fledged replacements for native apps. To that end, I’ve recently spent some time looking at how the web platform allows JavaScript registration of a protocol handler, where the handling “app” is a same-origin web page.

Currently supported by Firefox and Chromium-based browsers (on platforms other than Android), the function navigator.registerProtocolHandler(scheme, url_template, description) enables a website to become a handler for a URL scheme.

Real-World Usage

The canonical use-case for this is web based email clients like Gmail. Gmail would like to be able to be the user’s handler for mailto links. When the user clicks a mailto link, the content of the link should be sent to a handler page on mail.google.com which responds accordingly (e.g. by creating a new email to the specified addressee).

https://mail.google.com/mail/u/0/?extsrc=mailto&url=mailto:u@example.com

The registerProtocolHandler API isn’t limited to the mailto scheme, however. It presently supports a short list of allowed schemes1, and any scheme named web+{one-or-more-lowercaseASCII}.

User Experience

I’ve built a page containing a number of test cases here. When you push the button to register a protocol handler, you receive a permission prompt from Chrome/Edge or Firefox:

ChromePrompt

FirefoxPrompt

To avoid annoying users, if the user declines Chrome’s prompt, the site is blocked from re-requesting permission to handle the protocol. A user must manually visit the settings page to unblock permission.

User-Gesture Requirements

If a page attempts to call registerProtocolHandler() on load or before the user has interacted with the page (a so called “gesture”), then Chromium-based browsers will not pop the permission prompt. Instead, an overlapping-diamonds icon is shown at the right-hand side of the address bar, with the text “This page wants to install a service handler.” Here’s what this looks like on Gmail:

RegisterServiceHandler

Settings

Within Chrome, you can view your currently registered handlers (and sites blocked from asking to become the registered handler) by visiting chrome://settings/content/handlers.

ProtocolSettings

Operating System Integration

One particularly interesting aspect of allowing web-based registration of protocol handlers is that it is desirable for the rest of the operating system outside of the browser to respect those protocol handler associations.

For example, clicking a mailto link in some other application should launch the browser to the web-based handler if registered. However, having the browser change system state in this manner is complicated, especially on modern versions of Windows whereby various protections have been put in place to try to prevent “hijacking” of file type and protocol handler associations.

Edge and Chrome will only attempt to become the systemwide handler for a protocol when running a channel that offers to become the default browser (e.g. Stable). On such a channel, if the browser wasn’t already the handler for the protocol, after the user clicks “Allow” on the Chrome permission bubble, a Windows UAC dialog is shown:

UAC

If the user accepts by clicking “Yes”, the RegisterChromeForProtocol function silently updates the registry:

RegistryWrites

Other Things I’ve Learned

  • Chrome, Edge, and Firefox disallow registration of protocol handlers in Private/Incognito/InPrivate modes.
  • With my patch landed, Chrome, Edge, and Firefox disallow registration of protocol handlers from non-secure contexts (e.g. HTTP). Due to the same-origin requirement for the handler URL, this effectively prevents the use of a non-secure page as a handler.
  • An upcoming patch proposes blocking registration from cross-origin subframes.
  • Chromium-based browsers enable IT admins to set default scheme-to-web mappings using Group Policy.
  • Chrome presently fails to enforce specified limits on web+ protocol names. Firefox does enforce the limits.
  • Firefox does not support targeting a RPH registered protocol as the target of a form POST request; it silently drops the POST body.
  • Firefox does not implement the unregisterProtocolHandler API. Users must manually unregister protocol handlers using the browser UI.
  • On Windows at least, neither Firefox Stable nor Firefox Nightly seems to try to become the systemwide handler for a scheme.
  • If you have a custom scheme link in a subframe, you probably want to add a target=_blank attribute on it. Otherwise, your protocol handler web page might navigate within that subframe and get blocked due to privacy settings or X-Frame-Options directives

-Eric

1 The permitted schemes are bitcoin, geo, im, irc, ircs, magnet, mailto, mms, news, nntp, openpgp4fpr, sip, sms, smsto, ssh, tel, urn, webcal, wtai, xmpp.