<!DOCTYPE html>
<meta charset='utf-8'>
<title>protocol handlers</title>

<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>

<noscript><p>Enable JavaScript and reload.</p></noscript>

<p><strong>Note:</strong> If your browser limits the number of handler
registration requests on a page, you might need to disable or significantly
increase that limit for the tests below to run.</p>

<script>
test(() => {
  assert_idl_attribute(navigator, 'registerProtocolHandler');
}, 'the registerProtocolHandler method should exist on the navigator object');

test(() => {
  assert_idl_attribute(navigator, 'unregisterProtocolHandler');
}, 'the unregisterProtocolHandler method should exist on the navigator object');

/* URL argument */
[
  '%s',
  'foo/%s',
  `%s${location.href}`,
  location.href.replace(location.protocol,
    `${location.protocol[0]}%s${location.protocol.substring(1)}`),
  location.href.replace(location.protocol, `${location.protocol}%s`),
  location.href + '/%s',
  location.href + '#%s',
  location.href + '?foo=%s',
  location.href + '?foo=%s&bar',
  location.href + '/%s/bar/baz/',
  location.href + '/%s/bar/baz/?foo=1337&bar#baz',
  location.href + '/%s/foo/%s/',
].forEach(url => {
  test(() => {
    navigator.registerProtocolHandler('tel', url, 'foo');
  }, 'registerProtocolHandler: Valid URL "' + url + '" should work.');

  test(() => {
    navigator.unregisterProtocolHandler('tel', url);
  }, 'unregisterProtocolHandler: Valid URL "' + url + '" should work.');
});

/* Invalid URLs */
[
  '',
  '%S',
  'http://%s.com',
  'http://%s.example.com',
  location.href.replace(location.hostname, `%s${location.hostname}`),
  location.href.replace(location.port, `%s${location.port}`),
  location.href + '',
  location.href + '/%',
  location.href + '/%a',
  'http://example.com',
  'http://[v8.:::]//url=%s',
  'https://test:test/',
].forEach(url => {
  test(() => {
    assert_throws_dom('SYNTAX_ERR', () => { navigator.registerProtocolHandler('mailto', url, 'foo'); });
    assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler('x', url, 'foo'); });
  }, `registerProtocolHandler: Invalid URL "${url}" should throw (but after scheme)`);

  test(() => {
    assert_throws_dom('SYNTAX_ERR', () => { navigator.unregisterProtocolHandler('mailto', url); });
    assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler('x', url, 'foo'); });
  }, `unregisterProtocolHandler: Invalid URL "${url}" should throw (but after scheme)`);
});

[
  'http://example.com/%s',
  'https://example.com/%s',
  'http://foobar.example.com/%s',
  'mailto:%s@example.com',
  'mailto:%s',
  `chrome://${location.host}/%s`,
  `foo://${location.host}/%s`,
  URL.createObjectURL(new Blob()) + "#%s",
].forEach(url => {
  const title = url.startsWith("blob:") ? "blob: URL" : url;
  test(() => {
    assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler('mailto', url, 'foo'); });
  }, `registerProtocolHandler: Invalid URL "${title}" should throw SECURITY_ERR.`);

  test(() => {
    assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler('mailto', url); });
  }, `unregisterProtocolHandler: Invalid URL "${title}" should throw SECURITY_ERR.`);
});

/* Protocol argument */

/* Overriding any of the following protocols must never be allowed. That would
 * break the browser. */
[
  'about',
  'attachment',
  'blob',
  'chrome',
  'cid',
  'data',
  'file',
  'http',
  'https',
  'javascript',
  'livescript',
  'mid',
  'mocha',
  'moz-icon',
  'opera',
  'operamail',
  'res',
  'resource',
  'shttp',
  'tcl',
  'vbscript',
  'view-source',
  'ws',
  'wss',
  'wyciwyg',
  /* other invalid schemes */
  'unrecognized',
  'mаilto', /* a cyrillic "а" */
  'mailto:',
  'mailto://',
  'mailto' + String.fromCharCode(0),
  'mailtoo' + String.fromCharCode(8),
  'mailto' + String.fromCharCode(10),
  'http://',
  'ssh:/',
  'magnet:+',
  'tel:sip',
  'foo',
  'fweb+oo',
  /* web+ prefixed schemes must be followed by 1+ ascii alphas */
  'web+',
  'web+1',
  'web+namewithid123',
  'web+namewithtrailingspace ',
  'web+préfixewithaccent',     // é is not ascii alpha
  'web+Kelvinsign',            // ASCII-lower KELVIN SIGN is not k
  'web+latinsmallletterlongſ', // ASCII-lower LATIN SMALL LETTER LONG S is not s
  'web+dots.are.forbidden',
  'web+dashes-are-forbidden',
  'web+underscores_are_forbidden',
  'web+spaces are forbidden',
  'web+non*alpha*are*forbidden',
  'web+digits123areforbidden',
].forEach(scheme => {
  test(() => {
    // https://test:test/ does not parse and does not contain %s, but the scheme check happens first
    assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler(scheme, 'https://test:test/', 'foo'); });
  }, 'registerProtocolHandler: Attempting to override the "' + scheme + '" protocol should throw SECURITY_ERR.');

  test(() => {
    assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler(scheme, 'https://test:test/'); });
  }, 'unregisterProtocolHandler: Attempting to override the "' + scheme + '" protocol should throw SECURITY_ERR.');
});

/* The following protocols must be possible to override.
 * We're just testing that the call goes through here. Whether or not they
 * actually work as handlers is covered by the interactive tests. */

[
  /* safelisted schemes listed in
   * https://html.spec.whatwg.org/multipage/system-state.html#safelisted-scheme */
  'bitcoin',
  'ftp',
  'ftps',
  'geo',
  'im',
  'irc',
  'ircs',
  'magnet',
  'mailto',
  'matrix',
  'mms',
  'news',
  'nntp',
  'openpgp4fpr',
  'sftp',
  'sip',
  'sms',
  'smsto',
  'ssh',
  'tel',
  'urn',
  'webcal',
  'wtai',
  'xmpp',
  /* other valid schemes */
  'BitcoIn',
  'Irc',
  'MagneT',
  'Matrix',
  'SmsTo',
  'TEL',
  'teL',
  'WebCAL',
  'WTAI',
  'web+myprotocol',
  'web+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', // all alphas
  'web+UpperCasedIsLowercased',
  'WEB+seeabove',
  'WeB+SeEaBoVe'
].forEach(scheme => {
  test(() => {
    navigator.registerProtocolHandler(scheme, location.href + '/%s', "foo");
  }, 'registerProtocolHandler: overriding the "' + scheme + '" protocol should work');

  test(() => {
    navigator.unregisterProtocolHandler(scheme, location.href + '/%s');
  }, 'unregisterProtocolHandler: overriding the "' + scheme + '" protocol should work');
});
</script>
