var gWidgetManifestURL = "http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true";
var gInvalidWidgetManifestURL = "http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true";
var gApp;
var gHasBrowserPermission;

function onError(msg) {
  ok(false, "Error callback invoked: " + msg);
  finish();
}

function installApp(path) {
  var request = navigator.mozApps.install(path);
  request.onerror = onError;
  request.onsuccess = function() {
    gApp = request.result;

    runTest();
  }
}

function uninstallApp() {
  // Uninstall the app.
  var request = navigator.mozApps.mgmt.uninstall(gApp);
  request.onerror = onError;
  request.onsuccess = function() {
    // All done.
    info("All done");

    runTest();
  }
}

function testApp(isValidWidget) {
  info("Test widget feature. IsValidWidget: " + isValidWidget);

  var ifr = document.createElement("iframe");
  ifr.setAttribute("mozbrowser", "true");
  ifr.setAttribute("mozwidget", gApp.manifestURL);
  ifr.setAttribute("src", gApp.origin + gApp.manifest.launch_path);

  var domParent = document.getElementById("container");
  domParent.appendChild(ifr);

  ifr.addEventListener("mozbrowserloadend", function _onloadend() {
    ok(true, "receive mozbrowserloadend");
    // Test limited browser API feature only for valid widget case
    if (isValidWidget) {
      testLimitedBrowserAPI(ifr);
    }
    SimpleTest.executeSoon(() => loadFrameScript(mm,
                                                 checkIsWidgetScript,
                                                 true));
  }, false);

  ifr.addEventListener("mozbrowsererror", (event) => {
    ok(!isValidWidget, "receive mozbrowsererror: " + JSON.stringify(event.detail));
    domParent.removeChild(ifr);
    runTest();
  });

  // Callback of frameScript
  var mm = SpecialPowers.getBrowserFrameMessageManager(ifr);
  mm.addMessageListener("OK", function(msg) {
    ok(isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
  });
  mm.addMessageListener("KO", function(msg) {
    ok(!isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
  });
  mm.addMessageListener("DONE", function _done(msg) {
    ok(true, "Message from widget complete: " + SpecialPowers.wrap(msg).json);

    mm.removeMessageListener("DONE", _done);
    mm.addMessageListener("DONE", function _done(msg) {
      ok(true, "Message from widget complete: " + SpecialPowers.wrap(msg).json);
      runTest();
    });

    info("set src to a invalid page");
    ifr.setAttribute("src", gApp.origin);
    isValidWidget = false;
  });

  // Test limited browser API feature only for valid widget case
  if (!isValidWidget) {
    return;
  }

  [
    "mozbrowsertitlechange",
    "mozbrowseropenwindow",
    "mozbrowserscroll",
    "mozbrowserasyncscroll"
  ].forEach( function(topic) {
    ifr.addEventListener(topic, function() {
      ok(false, topic + " should be hidden");
    }, false);
  });
}

function testLimitedBrowserAPI(ifr) {
  var securitySensitiveCalls = [
    { api: "sendMouseEvent"      , args: ["mousedown", 0, 0, 0, 0, 0] },
    { api: "sendTouchEvent"      , args: ["touchstart", [0], [0], [0], [1], [1], [0], [1], 1, 0] },
    { api: "goBack"              , args: [] },
    { api: "goForward"           , args: [] },
    { api: "reload"              , args: [] },
    { api: "stop"                , args: [] },
    { api: "download"            , args: ["http://example.org"] },
    { api: "purgeHistory"        , args: [] },
    { api: "getScreenshot"       , args: [0, 0] },
    { api: "zoom"                , args: [0.1] },
    { api: "getCanGoBack"        , args: [] },
    { api: "getCanGoForward"     , args: [] },
    { api: "getContentDimensions", args: [] }
  ];
  securitySensitiveCalls.forEach( function(call) {
    if (gHasBrowserPermission) {
      isnot(typeof ifr[call.api], "undefined", call.api + " should be defined");
      var didThrow;
      try {
        ifr[call.api].apply(ifr, call.args);
      } catch (e) {
        ok(e instanceof DOMException, "throw right exception type");
        didThrow = e.code;
      }
      is(didThrow, DOMException.INVALID_NODE_TYPE_ERR, "call " + call.api + " should throw exception");
    } else {
      is(typeof ifr[call.api], "undefined", call.api + " should be hidden for widget");
    }
  });
}

function loadFrameScript(mm, frameScript, testMozbrowserEvent) {
  var script = "data:,(" + frameScript.toString() + ")(" + testMozbrowserEvent + ");";
  mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
}

function checkIsWidgetScript(testMozbrowserEvent) {
  function ok(p, msg) {
    if (p) {
    sendAsyncMessage("OK", msg);
    } else {
    sendAsyncMessage("KO", msg);
    }
  }

  function is(a, b, msg) {
    if (a == b) {
      sendAsyncMessage("OK", a + " == " + b + " - " + msg);
    } else {
      sendAsyncMessage("KO", a + " != " + b + " - " + msg);
    }
  }

  function finish() {
    sendAsyncMessage("DONE", "");
  }

  function onError() {
    ok(false, "Error callback invoked");
    finish();
  }

  var request = content.window.navigator.mozApps.getSelf();
  request.onsuccess = function() {
    var widget = request.result;
    ok(widget, "Should" + (widget ? "" : " not") + " be a widget");
    finish();
  };
  request.onerror = onError;

  if (testMozbrowserEvent) {
    var win = content.window.open("about:blank"); /* test mozbrowseropenwindow */
    /*Close new window to avoid mochitest "unable to restore focus" failures.*/
    win.close();
    content.window.scrollTo(4000, 4000); /* test mozbrowser(async)scroll */
  }
}

var tests = [
  // Permissions
  function() {
    SpecialPowers.pushPermissions(
      [{ "type": "browser", "allow": gHasBrowserPermission ? 1 : 0, "context": document },
       { "type": "embed-widgets", "allow": 1, "context": document },
       { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
  },

  // Preferences
  function() {
    SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
                                       ["dom.enable_widgets", true]]}, runTest);
  },

  // No confirmation needed when an app is installed
  function() {
    SpecialPowers.setAllAppsLaunchable(true);
    SpecialPowers.autoConfirmAppInstall(() => {
      SpecialPowers.autoConfirmAppUninstall(runTest);
    });
  },

  // Installing the app
  ()=>installApp(gWidgetManifestURL),

  // Run tests in app
  ()=>testApp(true),

  // Uninstall the app
  uninstallApp,

  // Installing the app for invalid widget case
  ()=>installApp(gInvalidWidgetManifestURL),

  // Run tests in app for invalid widget case
  ()=>testApp(false),

  // Uninstall the app
  uninstallApp
];

function runTest() {
  if (!tests.length) {
    finish();
    return;
  }

  var test = tests.shift();
  test();
}

function finish() {
  SimpleTest.finish();
}
