setup({explicit_done:true});
onload = function() {
  var encoding = '{{GET[encoding]}}';
  var input_url = 'resources/resource.py?q=\u00E5&encoding=' + encoding + '&type=';
  ('html css js worker sharedworker worker_importScripts sharedworker_importScripts worker_worker worker_sharedworker sharedworker_worker '+
   'sharedworker_sharedworker eventstream png svg xmlstylesheet_css video webvtt').split(' ').forEach(function(str) {
    window['input_url_'+str] = input_url + str;
  });
  var blank = 'resources/blank.py?encoding=' + encoding;
  var stash_put = 'resources/stash.py?q=\u00E5&action=put&id=';
  var stash_take = 'resources/stash.py?action=take&id=';
  var expected_obj = {
    'utf-8':'%C3%A5',
    'utf-16be':'%C3%A5',
    'utf-16le':'%C3%A5',
    'windows-1252':'%E5',
    'windows-1251':'%3F'
  };
  var expected_current = expected_obj[encoding];
  var expected_utf8 = expected_obj['utf-8'];

  function msg(expected, got) {
    return 'expected substring '+expected+' got '+got;
  }

  function poll_for_stash(test_obj, uuid, expected) {
      var start = new Date();
      var poll = test_obj.step_func(function () {
          var xhr = new XMLHttpRequest();
          xhr.open('GET', stash_take + uuid);
          xhr.onload = test_obj.step_func(function(e) {
              if (xhr.response == "") {
                  if (new Date() - start > 10000) {
                      // If we set the status to TIMEOUT here we avoid a race between the
                      // page and the test timing out
                      test_obj.force_timeout();
                  }
                  setTimeout(poll, 200);
              } else {
                  assert_equals(xhr.response, expected);
                  test_obj.done();
              }
          });
          xhr.send();
      })
      setTimeout(poll, 200);
  }

  // background attribute, check with getComputedStyle
  function test_background(tag) {
    var spec_url = 'https://html.spec.whatwg.org/multipage/multipage/rendering.html';
    spec_url += tag == 'body' ? '#the-page' : '#tables';
    test(function() {
      var elm = document.createElement(tag);
      document.body.appendChild(elm);
      this.add_cleanup(function() {
        document.body.removeChild(elm);
      });
      elm.setAttribute('background', input_url_png);
      var got = getComputedStyle(elm).backgroundImage;
      assert_true(got.indexOf(expected_current) > -1, msg(expected_current, got));
    }, 'getComputedStyle <'+tag+' background>',
    {help:spec_url});
  }

  'body, table, thead, tbody, tfoot, tr, td, th'.split(', ').forEach(function(str) {
    test_background(str);
  });

  // get a reflecting IDL attributes whose content attribute takes a URL or a list of space-separated URLs
  function test_reflecting(tag, attr, idlAttr, multiple) {
    idlAttr = idlAttr || attr;
    var input = input_url_html;
    if (multiple) {
      input += ' ' + input;
    }
    test(function() {
      var elm = document.createElement(tag);
      assert_true(idlAttr in elm, idlAttr + ' is not supported');
      elm.setAttribute(attr, input);
      var got = elm[idlAttr];
      assert_true(got.indexOf(expected_current) > -1, msg(expected_current, got));
    }, 'Getting <'+tag+'>.'+idlAttr + (multiple ? ' (multiple URLs)' : ''),
    {help:'https://html.spec.whatwg.org/multipage/multipage/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes'});
  }

  ('iframe src, a href, base href, link href, img src, embed src, object data, track src, video src, audio src, input src, form action, ' +
  'input formaction formAction, button formaction formAction, menuitem icon, script src, div itemid').split(', ').forEach(function(str) {
    var arr = str.split(' ');
    test_reflecting(arr[0], arr[1], arr[2]);
  });

  'a ping'.split(', ').forEach(function(str) {
    var arr = str.split(' ');
    test_reflecting(arr[0], arr[1], arr[2], true);
  });

  function setup_navigation(elm, iframe, id, test_obj) {
    iframe.name = id;
    elm.target = id;
    elm.setAttribute('href', input_url_html);
    document.body.appendChild(iframe);
    document.body.appendChild(elm);
    test_obj.add_cleanup(function() {
      document.body.removeChild(iframe);
      document.body.removeChild(elm);
    });
  }

  // follow hyperlink
  function test_follow_link(tag) {
    async_test(function() {
      var elm = document.createElement(tag);
      var iframe = document.createElement('iframe');
      setup_navigation(elm, iframe, 'test_follow_link_'+tag, this);
      iframe.onload = this.step_func_done(function() { // when the page navigated to has loaded
        assert_equals(iframe.contentDocument.body.textContent, expected_current);
      });
      // follow the hyperlink
      elm.click();
      // check that navigation succeeded by ...??? XXX
    }, 'follow hyperlink <'+tag+' href>',
    {help:'https://html.spec.whatwg.org/multipage/multipage/links.html#following-hyperlinks'});
  }

  'a, area, link'.split(', ').forEach(function(str) {
    test_follow_link(str);
  });

  // follow hyperlink with ping attribute
  function test_follow_link_ping(tag) {
    async_test(function() {
      var uuid = token();
      var elm = document.createElement(tag);
      // check if ping is supported
      assert_true('ping' in elm, 'ping not supported');
      elm.setAttribute('ping', stash_put + uuid);
      var iframe = document.createElement('iframe');
      setup_navigation(elm, iframe, 'test_follow_link_ping_'+tag, this);
      // follow the hyperlink
      elm.click();
      // check that navigation succeeded by ...??? XXX
      // check that the right URL was requested for the ping
      poll_for_stash(this, uuid, expected_current);
    }, 'hyperlink auditing <'+tag+' ping>',
    {help:'https://html.spec.whatwg.org/multipage/multipage/links.html#hyperlink-auditing'});
  }

  'a, area'.split(', ').forEach(function(str) {
    test_follow_link_ping(str);
  });

  // navigating with meta refresh
  async_test(function() {
    var iframe = document.createElement('iframe');
    iframe.src = blank;
    document.body.appendChild(iframe);
    this.add_cleanup(function() {
      document.body.removeChild(iframe);
    });
    iframe.onload = this.step_func_done(function() {
      var doc = iframe.contentDocument;
      var got = doc.body.textContent;
      if (got == '') {
        doc.write('<meta http-equiv=refresh content="0; URL='+input_url_html+'">REFRESH');
        doc.close();
        return;
      }
      assert_equals(got, expected_current);
    });
  }, 'meta refresh',
  {help:'https://html.spec.whatwg.org/multipage/multipage/semantics.html#attr-meta-http-equiv-refresh'});

  // loading html (or actually svg to support <embed>)
  function test_load_nested_browsing_context(tag, attr, spec_url) {
    async_test(function() {
      var id = 'test_load_nested_browsing_context_'+tag;
      var elm = document.createElement(tag);
      elm.setAttribute(attr, input_url_svg);
      elm.name = id;
      document.body.appendChild(elm);
      this.add_cleanup(function() {
        document.body.removeChild(elm);
      });
      elm.onload = this.step_func_done(function() {
        assert_equals(window[id].document.documentElement.textContent, expected_current);
      });

    }, 'load nested browsing context <'+tag+' '+attr+'>',
    {help:spec_url});
  }

  spec_url_load_nested_browsing_context = {
    frame:'https://html.spec.whatwg.org/multipage/multipage/obsolete.html#process-the-frame-attributes',
    iframe:'https://html.spec.whatwg.org/multipage/multipage/the-iframe-element.html#process-the-iframe-attributes',
    object:'https://html.spec.whatwg.org/multipage/multipage/the-iframe-element.html#the-object-element',
    embed:'https://html.spec.whatwg.org/multipage/multipage/the-iframe-element.html#the-embed-element-setup-steps'
  };

  'frame src, iframe src, object data, embed src'.split(', ').forEach(function(str) {
    var arr = str.split(' ');
    test_load_nested_browsing_context(arr[0], arr[1], spec_url_load_nested_browsing_context[arr[0]]);
  });

  // loading css with <link>
  async_test(function() {
    var elm = document.createElement('link');
    elm.href = input_url_css;
    elm.rel = 'stylesheet';
    document.head.appendChild(elm);
    this.add_cleanup(function() {
      document.head.removeChild(elm);
    });
    elm.onload = this.step_func_done(function() {
      var got = elm.sheet.href;
      assert_true(elm.sheet.href.indexOf(expected_current) > -1, 'sheet.href ' + msg(expected_current, got));
      assert_equals(elm.sheet.cssRules[0].style.content, '"'+expected_current+'"', 'sheet.cssRules[0].style.content');
    });
  }, 'loading css <link>',
  {help:['https://html.spec.whatwg.org/multipage/multipage/semantics.html#the-link-element',
         'https://html.spec.whatwg.org/multipage/multipage/semantics.html#styling']});

  // loading js
  async_test(function() {
    var elm = document.createElement('script');
    elm.src = input_url_js + '&var=test_load_js_got';
    document.head.appendChild(elm); // no cleanup
    elm.onload = this.step_func_done(function() {
      assert_equals(window.test_load_js_got, expected_current);
    });
  }, 'loading js <script>',
  {help:'https://html.spec.whatwg.org/multipage/multipage/scripting-1.html#prepare-a-script'});

  // loading image
  function test_load_image(tag, attr, spec_url) {
    async_test(function() {
      var elm = document.createElement(tag);
      if (tag == 'input') {
        elm.type = 'image';
      }
      elm.setAttribute(attr, input_url_png);
      document.body.appendChild(elm);
      this.add_cleanup(function() {
        document.body.removeChild(elm);
      });
      elm.onload = this.step_func_done(function() {
        var got = elm.offsetWidth;
        assert_equals(got, query_to_image_width[expected_current], msg(expected_current, image_width_to_query[got]));
      });
      // <video poster> doesn't notify when the image is loaded so we need to poll :-(
      var interval;
      var check_video_width = function() {
        var width = elm.offsetWidth;
        if (width != 300 && width != 0) {
          clearInterval(interval);
          elm.onload();
        }
      }
      if (tag == 'video') {
        interval = setInterval(check_video_width, 10);
      }
    }, 'loading image <'+tag+' '+attr+'>',
    {help:spec_url});
  }

  var query_to_image_width = {
    '%E5':1,
    '%C3%A5':2,
    '%3F':16,
    'unknown query':256,
    'default intrinsic width':300
  };

  var image_width_to_query = {};
  (function() {
    for (var x in query_to_image_width) {
      image_width_to_query[query_to_image_width[x]] = x;
    }
  })();

  var spec_url_load_image = {
    img:'https://html.spec.whatwg.org/multipage/multipage/embedded-content-1.html#update-the-image-data',
    embed:'https://html.spec.whatwg.org/multipage/multipage/the-iframe-element.html#the-embed-element-setup-steps',
    object:'https://html.spec.whatwg.org/multipage/multipage/the-iframe-element.html#the-object-element',
    input:'https://html.spec.whatwg.org/multipage/multipage/states-of-the-type-attribute.html#image-button-state-(type=image)',
    video:'https://html.spec.whatwg.org/multipage/multipage/the-video-element.html#poster-frame'
  };

  'img src, embed src, object data, input src, video poster'.split(', ').forEach(function(str) {
    var arr = str.split(' ');
    test_load_image(arr[0], arr[1], spec_url_load_image[arr[0]]);
  });

  // XXX test <img srcset> or its successor
  // <menuitem icon> could also be tested but the spec doesn't require it to be loaded...

  // loading video
  function test_load_video(tag, use_source_element) {
    async_test(function() {
      var elm = document.createElement(tag);
      var video_ext = '';
      if (elm.canPlayType('video/ogg; codecs="theora,vorbis"')) {
        video_ext = 'ogv';
      } else if (elm.canPlayType('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')) {
        video_ext = 'mp4';
      }
      assert_not_equals(video_ext, '', 'no supported video format');
      var source;
      if (use_source_element) {
        source = document.createElement('source');
        elm.appendChild(source);
      } else {
        source = elm;
      }
      source.src = input_url_video + '&ext=' + video_ext;
      elm.preload = 'auto';
      elm.load();
      this.add_cleanup(function() {
        elm.removeAttribute('src');
        if (elm.firstChild) {
          elm.removeChild(elm.firstChild);
        }
        elm.load();
      });
      elm.onloadedmetadata = this.step_func_done(function() {
        var got = Math.round(elm.duration);
        assert_equals(got, query_to_video_duration[expected_current], msg(expected_current, video_duration_to_query[got]));
      });
    }, 'loading video <'+tag+'>' + (use_source_element ? '<source>' : ''),
    {help:'https://html.spec.whatwg.org/multipage/multipage/the-video-element.html#concept-media-load-algorithm'});
  }

  var query_to_video_duration = {
    '%E5':3,
    '%C3%A5':5,
    '%3F':30,
    'unknown query':300,
    'Infinity':Infinity,
    'NaN':NaN
  };

  var video_duration_to_query = {};
  (function() {
    for (var x in query_to_video_duration) {
      video_duration_to_query[query_to_video_duration[x]] = x;
    }
  })();

  'video, audio'.split(', ').forEach(function(str) {
    test_load_video(str);
    test_load_video(str, true);
  });

  // loading webvtt
  async_test(function() {
    var video = document.createElement('video');
    var track = document.createElement('track');
    video.appendChild(track);
    track.src = input_url_webvtt;
    track.track.mode = 'showing';
    track.onload = this.step_func_done(function() {
      var got = track.track.cues[0].text;
      assert_equals(got, expected_current);
    });
  }, 'loading webvtt <track>',
  {help:'https://html.spec.whatwg.org/multipage/multipage/the-video-element.html#track-url'});

  // XXX downloading seems hard to automate
  // <a href download>
  // <area href download>

  // submit forms
  function test_submit_form(tag, attr) {
    async_test(function(){
      var elm = document.createElement(tag);
      elm.setAttribute(attr, input_url_html);
      var form;
      var button;
      if (tag == 'form') {
        form = elm;
        button = document.createElement('button');
      } else {
        form = document.createElement('form');
        button = elm;
      }
      form.method = 'post';
      form.appendChild(button);
      var iframe = document.createElement('iframe');
      var id = 'test_submit_form_' + tag;
      iframe.name = id;
      form.target = id;
      button.type = 'submit';
      document.body.appendChild(form);
      document.body.appendChild(iframe);
      this.add_cleanup(function() {
        document.body.removeChild(form);
        document.body.removeChild(iframe);
      });
      button.click();
      iframe.onload = this.step_func_done(function() {
        var got = iframe.contentDocument.body.textContent;
        if (got == '') {
          return;
        }
        assert_equals(got, expected_current);
      });
    }, 'submit form <'+tag+' '+attr+'>',
    {help:'https://html.spec.whatwg.org/multipage/multipage/association-of-controls-and-forms.html#concept-form-submit'});
  }

  'form action, input formaction, button formaction'.split(', ').forEach(function(str) {
    var arr = str.split(' ');
    test_submit_form(arr[0], arr[1]);
  });

  // <base href>
  async_test(function() {
    var iframe = document.createElement('iframe');
    iframe.src = blank;
    document.body.appendChild(iframe);
    this.add_cleanup(function() {
      document.body.removeChild(iframe);
    });
    iframe.onload = this.step_func_done(function() {
      var doc = iframe.contentDocument;
      doc.write('<!doctype html><base href="'+input_url+'"><a href></a>');
      doc.close();
      var got_baseURI = doc.baseURI;
      assert_true(got_baseURI.indexOf(expected_current) > -1, msg(expected_current, got_baseURI), 'doc.baseURI');
      var got_a_href = doc.links[0].href;
      assert_true(got_a_href.indexOf(expected_current) > -1, msg(expected_current, got_a_href), 'a.href');
    });
  }, '<base href>',
  {help:['https://html.spec.whatwg.org/multipage/multipage/semantics.html#set-the-frozen-base-url',
  'https://dom.spec.whatwg.org/#dom-node-baseuri',
  'https://html.spec.whatwg.org/multipage/multipage/text-level-semantics.html#the-a-element']});

  // XXX itemid is exposed in JSON drag-and-drop but seems hard to automate

  // microdata values
  function test_microdata_values(tag, attr) {
    test(function() {
      var elm = document.createElement(tag);
      elm.setAttribute('itemprop', '');
      elm.setAttribute(attr, input_url_html);
      var got = elm.itemValue;
      assert_not_equals(got, undefined, 'itemValue not supported');
      assert_true(got.indexOf(expected_current) > -1, msg(expected_current, got));
    }, 'microdata values <'+tag+' '+attr+'>',
    {help:'https://html.spec.whatwg.org/multipage/multipage/microdata.html#concept-property-value'});
  }

  'audio src, embed src, iframe src, img src, source src, track src, video src, a href, area href, link href, object data'.split(', ').forEach(function(str) {
    var arr = str.split(' ');
    test_microdata_values(arr[0], arr[1]);
  });

  // XXX drag and drop (<a href> or <img src>) seems hard to automate

  // Worker()
  async_test(function() {
    var worker = new Worker(input_url_worker);
    worker.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_current);
    });
  }, 'Worker constructor',
  {help:'https://html.spec.whatwg.org/multipage/multipage/workers.html#dom-worker'});

  // SharedWorker()
  async_test(function() {
    var worker = new SharedWorker(input_url_sharedworker);
    worker.port.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_current);
    });
  }, 'SharedWorker constructor',
  {help:'https://html.spec.whatwg.org/multipage/multipage/workers.html#dom-sharedworker'});

  // EventSource()
  async_test(function() {
    var source = new EventSource(input_url_eventstream);
    this.add_cleanup(function() {
      source.close();
    });
    source.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_current);
    });
  }, 'EventSource constructor',
  {help:'https://html.spec.whatwg.org/multipage/multipage/comms.html#dom-eventsource'});

  // EventSource#url
  test(function() {
    var source = new EventSource(input_url_eventstream);
    source.close();
    var got = source.url;
    assert_true(source.url.indexOf(expected_current) > -1, msg(expected_current, got));
  }, 'EventSource#url',
  {help:'https://html.spec.whatwg.org/multipage/multipage/comms.html#dom-eventsource'});

  // XMLDocument#load()
  async_test(function() {
    var doc = document.implementation.createDocument(null, "x");
    doc.load(input_url_svg);
    doc.onload = this.step_func_done(function() {
      assert_equals(doc.documentElement.textContent, expected_current);
    });
  }, 'XMLDocument#load()',
  {help:'https://html.spec.whatwg.org/multipage/multipage/dom.html#dom-xmldocument-load'});

  // window.open
  async_test(function() {
    var id = 'test_window_open';
    var iframe = document.createElement('iframe');
    iframe.name = id;
    document.body.appendChild(iframe);
    this.add_cleanup(function() {
      document.body.removeChild(iframe);
    });
    window.open(input_url_html, id);
    iframe.onload = this.step_func(function() {
      var got = iframe.contentDocument.body.textContent;
      if (got != "") {
        assert_equals(got, expected_current);
        this.done();
      }
    });
  }, 'window.open()',
  {help:'https://html.spec.whatwg.org/multipage/multipage/browsers.html#dom-open'});

  // location
  function test_location(func, desc) {
    async_test(function() {
      var iframe = document.createElement('iframe');
      document.body.appendChild(iframe);
      this.add_cleanup(function() {
        document.body.removeChild(iframe);
      });
      func(iframe.contentWindow, input_url_html);
      iframe.onload = this.step_func(function() {
        var got = iframe.contentDocument.body.textContent;
        if (got != '') {
          assert_equals(got, expected_current);
          this.done();
        }
      });
    }, desc,
    {help:'https://html.spec.whatwg.org/multipage/multipage/history.html#the-location-interface'});
  }
  [[function(win, input) { win.location = input; }, "location [PutForwards]"],
   [function(win, input) { win.location.assign(input); }, "location.assign()"],
   [function(win, input) { win.location.replace(input); }, "location.replace()"],
   [function(win, input) { win.location.href = input; }, "location.href"]].forEach(function(arr) {
    test_location(arr[0], arr[1]);
  });

  // location.search
  async_test(function() {
    var iframe = document.createElement('iframe');
    iframe.src = input_url_html;
    document.body.appendChild(iframe);
    this.add_cleanup(function() {
      document.body.removeChild(iframe);
    });
    var i = 0;
    iframe.onload = this.step_func(function() {
      i++;
      if (i == 1) {
        iframe.contentWindow.location.search = '?' + input_url_html.split('?')[1] + '&other=foobar';
      } else {
        var got = iframe.contentDocument.body.textContent;
        assert_equals(got, expected_current);
        this.done();
      }
    });
  }, 'location.search',
  {help:['https://html.spec.whatwg.org/multipage/multipage/history.html#the-location-interface',
         'http://url.spec.whatwg.org/#dom-url-search']});

  // a.search, area.search
  function test_hyperlink_search(tag) {
    test(function() {
      var elm = document.createElement(tag);
      var input_arr = input_url_html.split('?');
      elm.href = input_arr[0];
      elm.search = '?' + input_arr[1];
      var got_href = elm.getAttribute('href');
      assert_true(got_href.indexOf(expected_current) > -1, 'href content attribute ' + msg(expected_current, got_href));
      var got_search = elm.search;
      assert_true(got_search.indexOf(expected_current) > -1, 'getting .search '+msg(expected_current, got_search));
    }, '<'+tag+'>.search',
    {help:['https://html.spec.whatwg.org/multipage/multipage/text-level-semantics.html#the-'+tag+'-element',
           'http://url.spec.whatwg.org/#dom-url-search']});
  }
  'a, area'.split(', ').forEach(function(str) {
    test_hyperlink_search(str);
  });

  // history.pushState
  // history.replaceState
  function test_history(prop) {
    async_test(function() {
      var iframe = document.createElement('iframe');
      iframe.src = blank;
      document.body.appendChild(iframe);
      this.add_cleanup(function() {
        document.body.removeChild(iframe);
      });
      iframe.onload = this.step_func_done(function() {
        iframe.contentWindow.history[prop](null, null, input_url_html); // this should resolve against the test's URL, not the iframe's URL
        var got = iframe.contentWindow.location.href;
        assert_true(got.indexOf(expected_current) > -1, msg(expected_current, got));
        assert_equals(got.indexOf('/resources/resources/'), -1, 'url was resolved against the iframe\'s URL instead of the settings object\'s API base URL');
      });
    }, 'history.'+prop,
    {help:'https://html.spec.whatwg.org/multipage/multipage/history.html#dom-history-'+prop.toLowerCase()});
  }

  'pushState, replaceState'.split(', ').forEach(function(str) {
    test_history(str);
  });

  // SVG
  var ns = {svg:'http://www.w3.org/2000/svg', xlink:'http://www.w3.org/1999/xlink'};
  // a
  async_test(function() {
    SVGAElement; // check support
    var iframe = document.createElement('iframe');
    var id = 'test_svg_a';
    iframe.name = id;
    var svg = document.createElementNS(ns.svg, 'svg');
    var a = document.createElementNS(ns.svg, 'a');
    a.setAttributeNS(ns.xlink, 'xlink:href', input_url_html);
    a.setAttribute('target', id);
    var span = document.createElement('span');
    a.appendChild(span);
    svg.appendChild(a);
    document.body.appendChild(iframe);
    document.body.appendChild(svg);
    this.add_cleanup(function() {
      document.body.removeChild(iframe);
      document.body.removeChild(svg);
    });
    span.click();
    iframe.onload = this.step_func_done(function() {
      var got = iframe.contentDocument.body.textContent;
      if (got != '') {
        assert_equals(got, expected_current);
      }
    });
  }, 'SVG <a>');

  // feImage, image, use
  function test_svg(func, tag) {
    async_test(function() {
      var uuid = token();
      var id = 'test_svg_'+tag;
      var svg = document.createElementNS(ns.svg, 'svg');
      var parent = func(svg, id);
      var elm = document.createElementNS(ns.svg, tag);
      elm.setAttributeNS(ns.xlink, 'xlink:href', stash_put + uuid + '#foo');
      parent.appendChild(elm);
      document.body.appendChild(svg);
      this.add_cleanup(function() {
        document.body.removeChild(svg);
      });
      poll_for_stash(this, uuid, expected_current);
    }, 'SVG <' + tag + '>',
    {help:'https://www.w3.org/Bugs/Public/show_bug.cgi?id=24148'});
  }

  [[function(svg, id) {
      SVGFEImageElement; // check support
      var filter = document.createElementNS(ns.svg, 'filter');
      filter.setAttribute('id', id);
      svg.appendChild(filter);
      var rect = document.createElementNS(ns.svg, 'rect');
      rect.setAttribute('filter', 'url(#'+id+')');
      svg.appendChild(rect);
      return filter;
    }, 'feImage'],
   [function(svg, id) { SVGImageElement; return svg; }, 'image'],
   [function(svg, id) { SVGUseElement; return svg; }, 'use']].forEach(function(arr) {
    test_svg(arr[0], arr[1]);
  });

  // UTF-8:
  // XHR
  async_test(function() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', input_url_html);
    xhr.onload = this.step_func_done(function() {
      assert_equals(xhr.response, expected_utf8);
    });
    xhr.send();
  }, 'XMLHttpRequest#open()',
  {help:'https://xhr.spec.whatwg.org/#the-open()-method'});

  // in a worker
  async_test(function() {
    var worker = new Worker(input_url_worker_importScripts);
    worker.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_utf8);
    });
  }, 'importScripts() in a dedicated worker',
  {help:['https://html.spec.whatwg.org/multipage/multipage/workers.html#set-up-a-worker-script-settings-object',
         'https://html.spec.whatwg.org/multipage/multipage/workers.html#dom-workerglobalscope-importscripts']});

  async_test(function() {
    var worker = new Worker(input_url_worker_worker);
    worker.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_utf8);
    });
  }, 'Worker() in a dedicated worker',
  {help:['https://html.spec.whatwg.org/multipage/multipage/workers.html#set-up-a-worker-script-settings-object',
         'https://html.spec.whatwg.org/multipage/multipage/workers.html#dom-worker']});

  async_test(function() {
    var worker = new Worker(input_url_worker_sharedworker);
    worker.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_utf8);
    });
  }, 'SharedWorker() in a dedicated worker',
  {help:['https://html.spec.whatwg.org/multipage/multipage/workers.html#set-up-a-worker-script-settings-object',
         'https://html.spec.whatwg.org/multipage/multipage/workers.html#dom-sharedworker']});

  async_test(function() {
    var worker = new SharedWorker(input_url_sharedworker_importScripts);
    worker.port.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_utf8);
    });
  }, 'importScripts() in a shared worker',
  {help:['https://html.spec.whatwg.org/multipage/multipage/workers.html#set-up-a-worker-script-settings-object',
         'https://html.spec.whatwg.org/multipage/multipage/workers.html#dom-workerglobalscope-importscripts']});

  async_test(function() {
    var worker = new SharedWorker(input_url_sharedworker_worker);
    worker.port.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_utf8);
    });
  }, 'Worker() in a shared worker',
  {help:['https://html.spec.whatwg.org/multipage/multipage/workers.html#set-up-a-worker-script-settings-object',
         'https://html.spec.whatwg.org/multipage/multipage/workers.html#dom-worker']});

  async_test(function() {
    var worker = new SharedWorker(input_url_sharedworker_sharedworker);
    worker.port.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_utf8);
    });
  }, 'SharedWorker() in a shared worker',
  {help:['https://html.spec.whatwg.org/multipage/multipage/workers.html#set-up-a-worker-script-settings-object',
         'https://html.spec.whatwg.org/multipage/multipage/workers.html#dom-sharedworker']});

  // WebSocket()
  async_test(function(){
    var ws = new WebSocket('ws://{{host}}:{{ports[ws][0]}}/echo-query?\u00E5');
    this.add_cleanup(function() {
      ws.close();
    });
    ws.onmessage = this.step_func_done(function(e) {
      assert_equals(e.data, expected_utf8);
    });
  }, 'WebSocket constructor',
  {help:'https://html.spec.whatwg.org/multipage/multipage/network.html#parse-a-websocket-url\'s-components'});

  // WebSocket#url
  test(function(){
    var ws = new WebSocket('ws://{{host}}:{{ports[ws][0]}}/echo-query?\u00E5');
    ws.close();
    var got = ws.url;
    assert_true(ws.url.indexOf(expected_utf8) > -1, msg(expected_utf8, got));
  }, 'WebSocket#url',
  {help:'https://html.spec.whatwg.org/multipage/multipage/network.html#dom-websocket-url'});

  // Parsing cache manifest
  function test_cache_manifest(mode) {
    async_test(function() {
      var iframe = document.createElement('iframe');
      var uuid = token();
      iframe.src = 'resources/page-using-manifest.py?id='+uuid+'&encoding='+encoding+'&mode='+mode;
      document.body.appendChild(iframe);
      this.add_cleanup(function() {
        document.body.removeChild(iframe);
      });
      poll_for_stash(this, uuid, expected_utf8);
    }, 'Parsing cache manifest (' + mode + ')',
    {help:'https://html.spec.whatwg.org/multipage/multipage/offline.html#parse-a-manifest'});
  }

  'CACHE, FALLBACK, NETWORK'.split(', ').forEach(function(str) {
    test_cache_manifest(str);
  });

  // CSS
  function test_css(tmpl, expected_cssom, encoding, use_style_element) {
    var desc = ['CSS', (use_style_element ? '<style>' : '<link> (' + encoding + ')'),  tmpl].join(' ');
    async_test(function(){
      css_is_supported(tmpl, expected_cssom, this);
      var uuid = token();
      var id = 'test_css_' + uuid;
      var url = 'url(stash.py?q=%s&action=put&id=' + uuid + ')';
      tmpl = tmpl.replace(/<id>/g, id).replace(/<url>/g, url);
      var link;
      if (use_style_element) {
        link = document.createElement('style');
        link.textContent = tmpl.replace(/%s/g, '\u00E5').replace(/stash\.py/g, 'resources/stash.py');
      } else {
        link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = 'resources/css-tmpl.py?encoding='+encoding+'&tmpl='+encodeURIComponent(tmpl);
      }
      var div = document.createElement('div');
      div.id = id;
      div.textContent='x';
      document.head.appendChild(link);
      document.body.appendChild(div);
      this.add_cleanup(function() {
        document.head.removeChild(link);
        document.body.removeChild(div);
      });
      poll_for_stash(this, uuid, expected_utf8);
    }, desc,
    {help:'https://www.w3.org/Bugs/Public/show_bug.cgi?id=23968'});
  }

  // fail fast if the input doesn't parse into the expected cssom
  function css_is_supported(tmpl, expected_cssom, test_obj) {
    if (expected_cssom === null) {
      return;
    }
    var style = document.createElement('style');
    style.textContent = tmpl.replace(/<id>/g, 'x').replace(/<url>/g, 'url(data:,)');
    document.head.appendChild(style);
    test_obj.add_cleanup(function() {
      document.head.removeChild(style);
    });
    assert_equals(style.sheet.cssRules.length, expected_cssom.length, 'number of style rules');
    for (var i = 0; i < expected_cssom.length; ++i) {
      if (expected_cssom[i] === null) {
        continue;
      }
      assert_equals(style.sheet.cssRules[i].style.length, expected_cssom[i], 'number of declarations in style rule #'+i);
    }
  }

  [['#<id> { background-image:<url> }', [1] ],
   ['#<id> { border-image-source:<url> }', [1] ],
   ['#<id>::before { content:<url> }', [1] ],
   ['@font-face { font-family:<id>; src:<url> } #<id> { font-family:<id> }', [null, 1] ],
   ['#<id> { display:list-item; list-style-image:<url> }', [2] ],
   ['@import <url>;', null ],
   // XXX maybe cursor isn't suitable for automation here if browsers delay fetching it
   ['#<id> { cursor:<url>, pointer }', [1] ]].forEach(function(arr) {
    var input = arr[0];
    var expected_cssom = arr[1];
    var other_encoding = encoding == 'utf-8' ? 'windows-1252' : 'utf-8';
    test_css(input, expected_cssom, encoding);
    test_css(input, expected_cssom, other_encoding);
    test_css(input, expected_cssom, null, true);
   });

  // XXX maybe test if they become relevant:
  // binding (obsolete?)
  // aural: cue-after, cue-before, play-during (not implemented?)
  // hyphenate-resource (not implemented?)
  // image() (not implemented?)

  // <?xml-stylesheet?>
  async_test(function() {
    var iframe = document.createElement('iframe');
    iframe.src = input_url_xmlstylesheet_css;
    document.body.appendChild(iframe);
    this.add_cleanup(function() {
      document.body.removeChild(iframe);
    });
    iframe.onload = this.step_func_done(function() {
      assert_equals(iframe.contentDocument.firstChild.sheet.cssRules[0].style.content, '"' + expected_utf8 + '"');
    });
  }, '<?xml-stylesheet?> (CSS)',
  {help:'http://dev.w3.org/csswg/cssom/#requirements-on-user-agents-implementing-the-xml-stylesheet-processing-instruction'});

  // new URL()
  test(function() {
    var url = new URL('http://example.org/'+input_url);
    var expected = expected_utf8;
    assert_true(url.href.indexOf(expected) > -1, 'url.href '+msg(expected, url.href));
    assert_true(url.search.indexOf(expected) > -1, 'url.search '+msg(expected, url.search));
  }, 'URL constructor, url',
  {help:'http://url.spec.whatwg.org/#dom-url'});

  test(function() {
    var url = new URL('', 'http://example.org/'+input_url);
    var expected = expected_utf8;
    assert_true(url.href.indexOf(expected) > -1, 'url.href '+msg(expected, url.href));
    assert_true(url.search.indexOf(expected) > -1, 'url.search '+msg(expected, url.search));
  }, 'URL constructor, base',
  {help:'http://url.spec.whatwg.org/#dom-url'});

  // Test different schemes
  function test_scheme(url, utf8) {
    test(function() {
      var a = document.createElement('a');
      a.setAttribute('href', url);
      var got = a.href;
      var expected = utf8 ? expected_utf8 : expected_current;
      assert_true(got.indexOf(expected) != -1, msg(expected, got));
    }, 'Scheme ' + url.split(':')[0] + ' (getting <a>.href)');
  }

  var test_scheme_urls = ['ftp://example.invalid/?x=\u00E5',
                          'file:///?x=\u00E5',
                          'gopher://example.invalid/?x=\u00E5',
                          'http://example.invalid/?x=\u00E5',
                          'https://example.invalid/?x=\u00E5',
                         ];

  var test_scheme_urls_utf8 = ['ws://example.invalid/?x=\u00E5',
                               'wss://example.invalid/?x=\u00E5',
                               'mailto:example@invalid?x=\u00E5',
                               'data:text/plain;charset='+encoding+',?x=\u00E5',
                               'javascript:"?x=\u00E5"',
                               'ftps://example.invalid/?x=\u00E5',
                               'httpbogus://example.invalid/?x=\u00E5',
                               'bitcoin:foo?x=\u00E5',
                               'geo:foo?x=\u00E5',
                               'im:foo?x=\u00E5',
                               'irc:foo?x=\u00E5',
                               'ircs:foo?x=\u00E5',
                               'magnet:foo?x=\u00E5',
                               'mms:foo?x=\u00E5',
                               'news:foo?x=\u00E5',
                               'nntp:foo?x=\u00E5',
                               'sip:foo?x=\u00E5',
                               'sms:foo?x=\u00E5',
                               'smsto:foo?x=\u00E5',
                               'ssh:foo?x=\u00E5',
                               'tel:foo?x=\u00E5',
                               'urn:foo?x=\u00E5',
                               'webcal:foo?x=\u00E5',
                               'wtai:foo?x=\u00E5',
                               'xmpp:foo?x=\u00E5',
                               'web+http:foo?x=\u00E5',
                              ];

  test_scheme_urls.forEach(function(url) {
    test_scheme(url);
  });

  test_scheme_urls_utf8.forEach(function(url) {
    test_scheme(url, true);
  });

  done();
};
