"use strict";

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                  "resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                  "resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation",
                                  "resource://gre/modules/WebNavigation.jsm");

Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
  SingletonEventManager,
  ignoreEvent,
  runSafe,
} = ExtensionUtils;

// Similar to WebRequestEventManager but for WebNavigation.
function WebNavigationEventManager(context, eventName) {
  let name = `webNavigation.${eventName}`;
  let register = callback => {
    let listener = data => {
      if (!data.browser) {
        return;
      }

      let tabId = TabManager.getBrowserId(data.browser);
      if (tabId == -1) {
        return;
      }

      let data2 = {
        url: data.url,
        timeStamp: Date.now(),
        frameId: ExtensionManagement.getFrameId(data.windowId),
        parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
      };

      // Fills in tabId typically.
      let result = {};
      extensions.emit("fill-browser-data", data.browser, data2, result);
      if (result.cancel) {
        return;
      }

      runSafe(context, callback, data2);
    };

    WebNavigation[eventName].addListener(listener);
    return () => {
      WebNavigation[eventName].removeListener(listener);
    };
  };

  return SingletonEventManager.call(this, context, name, register);
}

WebNavigationEventManager.prototype = Object.create(SingletonEventManager.prototype);

function convertGetFrameResult(tabId, data) {
  return {
    errorOccurred: data.errorOccurred,
    url: data.url,
    tabId,
    frameId: ExtensionManagement.getFrameId(data.windowId),
    parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
  };
}

extensions.registerSchemaAPI("webNavigation", "webNavigation", (extension, context) => {
  return {
    webNavigation: {
      onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
      onCommitted: new WebNavigationEventManager(context, "onCommitted").api(),
      onDOMContentLoaded: new WebNavigationEventManager(context, "onDOMContentLoaded").api(),
      onCompleted: new WebNavigationEventManager(context, "onCompleted").api(),
      onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
      onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
      onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(),
      onCreatedNavigationTarget: ignoreEvent(context, "webNavigation.onCreatedNavigationTarget"),
      getAllFrames(details) {
        let tab = TabManager.getTab(details.tabId);
        if (!tab) {
          return Promise.reject({message: `No tab found with tabId: ${details.tabId}`});
        }

        let {innerWindowID, messageManager} = tab.linkedBrowser;
        let recipient = {innerWindowID};

        return context.sendMessage(messageManager, "WebNavigation:GetAllFrames", {}, {recipient})
                      .then((results) => results.map(convertGetFrameResult.bind(null, details.tabId)));
      },
      getFrame(details) {
        let tab = TabManager.getTab(details.tabId);
        if (!tab) {
          return Promise.reject({message: `No tab found with tabId: ${details.tabId}`});
        }

        let recipient = {
          innerWindowID: tab.linkedBrowser.innerWindowID,
        };

        let mm = tab.linkedBrowser.messageManager;
        return context.sendMessage(mm, "WebNavigation:GetFrame", {options: details}, {recipient})
                      .then((result) => {
                        return result ?
                          convertGetFrameResult(details.tabId, result) :
                          Promise.reject({message: `No frame found with frameId: ${details.frameId}`});
                      });
      },
    },
  };
});
