function test_upgrade(f, msg) {
  // Run upgrading test on an element created by HTML parser.
  test_with_new_window(function(testWindow, testMsg) {
    let elementParser = testWindow.document.querySelector("unresolved-element");
    f(testWindow, elementParser, testMsg);
  }, msg + " (HTML parser)");

  // Run upgrading test on an element created by document.createElement.
  test_with_new_window(function(testWindow, testMsg) {
    let element = testWindow.document.createElement("unresolved-element");
    testWindow.document.documentElement.appendChild(element);
    f(testWindow, element, testMsg);
  }, msg + " (document.createElement)");
}

// Test cases

test_upgrade(function(testWindow, testElement, msg) {
  class MyCustomElement extends testWindow.HTMLElement {}
  testWindow.customElements.define("unresolved-element", MyCustomElement);
  SimpleTest.is(
    Object.getPrototypeOf(Cu.waiveXrays(testElement)),
    MyCustomElement.prototype,
    msg
  );
}, "Custom element must be upgraded if there is a matching definition");

test_upgrade(function(testWindow, testElement, msg) {
  testElement.remove();
  class MyCustomElement extends testWindow.HTMLElement {}
  testWindow.customElements.define("unresolved-element", MyCustomElement);
  SimpleTest.is(
    Object.getPrototypeOf(testElement),
    testWindow.HTMLElement.prototype,
    msg
  );
}, "Custom element must not be upgraded if it has been removed from tree");

test_upgrade(function(testWindow, testElement, msg) {
  let exceptionToThrow = { name: "exception thrown by a custom constructor" };
  class ThrowCustomElement extends testWindow.HTMLElement {
    constructor() {
      super();
      if (exceptionToThrow) {
        throw exceptionToThrow;
      }
    }
  }

  let uncaughtError;
  window.onerror = function(message, url, lineNumber, columnNumber, error) {
    uncaughtError = error;
    return true;
  };
  testWindow.customElements.define("unresolved-element", ThrowCustomElement);

  SimpleTest.is(uncaughtError.name, exceptionToThrow.name, msg);
}, "Upgrading must report an exception thrown by a custom element constructor");

test_upgrade(function(testWindow, testElement, msg) {
  class InstantiatesItselfAfterSuper extends testWindow.HTMLElement {
    constructor(doNotCreateItself) {
      super();
      if (!doNotCreateItself) {
        new InstantiatesItselfAfterSuper(true);
      }
    }
  }

  let uncaughtError;
  window.onerror = function(message, url, lineNumber, columnNumber, error) {
    uncaughtError = error;
    return true;
  };
  testWindow.customElements.define(
    "unresolved-element",
    InstantiatesItselfAfterSuper
  );

  SimpleTest.is(uncaughtError.name, "TypeError", msg);
}, "Upgrading must report an TypeError when the top of the " +
  "construction stack is marked AlreadyConstructed");

test_upgrade(function(testWindow, testElement, msg) {
  class InstantiatesItselfBeforeSuper extends testWindow.HTMLElement {
    constructor(doNotCreateItself) {
      if (!doNotCreateItself) {
        new InstantiatesItselfBeforeSuper(true);
      }
      super();
    }
  }

  let uncaughtError;
  window.onerror = function(message, url, lineNumber, columnNumber, error) {
    uncaughtError = error;
    return true;
  };
  testWindow.customElements.define(
    "unresolved-element",
    InstantiatesItselfBeforeSuper
  );

  SimpleTest.is(uncaughtError.name, "TypeError", msg);
}, "Upgrading must report an TypeError when the top of the " +
  "construction stack is marked AlreadyConstructed due to a custom element " +
  "constructor constructing itself before super() call");

test_upgrade(function(testWindow, testElement, msg) {
  class MyOtherElement extends testWindow.HTMLElement {
    constructor() {
      super();
      if (this == testElement) {
        return testWindow.document.createElement("other-element");
      }
    }
  }

  let uncaughtError;
  window.onerror = function(message, url, lineNumber, columnNumber, error) {
    uncaughtError = error;
    return true;
  };
  testWindow.customElements.define("unresolved-element", MyOtherElement);

  SimpleTest.is(uncaughtError.name, "TypeError", msg);
}, "Upgrading must report an TypeError when the returned element is " +
  "not SameValue as the upgraded element");
