/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */

/**
 * Defines a Document Loader Factory to handle the MHT document type.
 *
 * Currently, this implementation just fires the standard MHT loading process,
 *  and refuses to create a content viewer and a stream listener.
 *
 * For more information on the document loading process, see
 *  <http://www.mozilla.org/newlayout/doc/webwidget.html> and
 *  <https://developer.mozilla.org/En/The_life_of_an_HTML_HTTP_request>
 *  (retrieved 2008-10-07).
 */

var Ci = Components.interfaces;
var Cc = Components.classes;
var Cr = Components.results;

/**
 * Helper object for nsIDocumentLoaderFactory.createInstance implementation.
 */
var EmptyStreamListener = {
  /**
   * インターフェースの要求
   *   nsISupports.QueryInterface
   *
   * @param  nsIIDRef iid
   *         インターフェース ID
   * @throws Components.results.NS_NOINTERFACE
   * @return nsIStreamListener
   *         this
   */
  QueryInterface : function (iid) {
    if (iid.equals (Ci.nsISupports)
        || iid.equals (Ci.nsIRequestObserver)
        || iid.equals (Ci.nsIStreamListener)) {
      return this;
    }
        
    throw Cr.NS_ERROR_NO_INTERFACE;
  },
  
  // --- nsIStreamListener interface functions ---

  onStartRequest: function (aRequest, aContext) {
  },
    
  onStopRequest: function (aRequest, aContext, aStatusCode) {
  },
    
  onDataAvailable: function (aRequest, aContext, aInputStream, aOffset,
                             aCount) {
  }
};

/**
 * Construct the UnMHTDocumentLoaderFactory object.
 *
 * Document loader factories are XPCOM services, so they should be constructed
 *  as singletons, for example as explained in
 *  <http://weblogs.mozillazine.org/weirdal/archives/019620.html> (retrieved
 *  2008-10-07). However, since this object doesn't store any state information,
 *  we might as well create it as a normal XPCOM object.
 *
 * Document loader factories must be registered with the
 *  "Gecko-Content-Viewers" category. For this extension, this is done
 *  dynamically on startup, because the list of MIME types that this document
 *  loader factory will handle is not known in advance.
 */
function UnMHTDocumentLoaderFactory () {
}
UnMHTDocumentLoaderFactory.prototype = {
  /**
   * インターフェースの要求
   *   nsISupports.QueryInterface
   *
   * @param  nsIIDRef iid
   *         インターフェース ID
   * @throws Components.results.NS_NOINTERFACE
   * @return nsIDocumentLoaderFactory
   *         this
   */
  QueryInterface : function (iid) {
    if (iid.equals (Ci.nsISupports)
        || iid.equals (Ci.nsIDocumentLoaderFactory)) {
      return this;
    }
        
    throw Cr.NS_ERROR_NO_INTERFACE;
  },

  // --- nsIDocumentLoaderFactory interface functions ---

  /*
   * For essential nsIDocumentLoaderFactory documentation, see
   *  <http://www.xulplanet.com/references/xpcomref/ifaces/nsIDocumentLoaderFactory.html>
   *  (retrieved 2008-10-07).
   */

  createInstance: function (aCommand, aChannel, aLoadGroup, aContentType,
                            aContainer, aExtraInfo, aDocListenerResult) {
    // Only the "view" command is supported
    if (aCommand != "view")
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
        
    // Find the URI of the requested archive
    var uri = aChannel.URI;
        
    if (uri.spec.match (/^(imap|mailbox|news|nntp|pop|snews):\/\//)) {
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    }
        
    var protocolHandler
    = Cc ["@mozilla.org/network/protocol;1?name=unmht"]
    .getService (Ci.arIUnMHTProtocolHandler);
    var contentURISpec;
    contentURISpec = protocolHandler.enUnMHTURI (uri.spec);
    var contentURI = Cc ['@mozilla.org/network/io-service;1']
    .getService (Ci.nsIIOService).newURI (contentURISpec, "utf-8", null);
    var originalContentViewer
    = this._startActualContentViewer
    (contentURI, "text/html", aLoadGroup, aContainer, aExtraInfo);

    // Return the content viewer, which is already receiving data from our
    //  channel, and a dummy stream listener, to throw away data from the
    //  original channel (aChannel). 
    aDocListenerResult.value = EmptyStreamListener;
    return originalContentViewer;

  },
    
  createInstanceForDocument: function (aContainer, aDocument, aCommand) {
    // This function should never have been called. As of 2008-10-07, it may
    //  be called only from nsDocShell::CreateAboutBlankContentViewer, that
    //  doesn't handle exceptions, so we return null instead of throwing
    //  NS_ERROR_NOT_IMPLEMENTED.
    return null;
  },

  createBlankDocument: function (aLoadGroup, aPrincipal) {
    return null; // See the comment inside createInstanceForDocument.
  },

  // --- Private functions ---

  /**
   * Creates and starts a content viewer from the application's built-in
   *  Document Loader Factory, to display the specified URI. The provided
   *  content viewer can then be returned from an implementation of
   *  nsIDocumentLoaderFactory.createInstance.
   *
   * @param aContentURI    The URI of the content to be displayed.
   * @param aContentType   The MIME content type of the content to be displayed.
   * @param aLoadGroup     See nsIDocumentLoaderFactory.createInstance.
   * @param aContainer     See nsIDocumentLoaderFactory.createInstance.
   * @param aExtraInfo     See nsIDocumentLoaderFactory.createInstance.
   *
   * @return   The newly constructed content viewer.
   */
  _startActualContentViewer: function (aContentURI, aContentType, aLoadGroup,
                                       aContainer, aExtraInfo) {
    // Create a new channel to feed the new content viewer
    var contentChannel = Cc ['@mozilla.org/network/io-service;1']
    .getService (Ci.nsIIOService).newChannelFromURI (aContentURI);
        
    // Ask the application's built-in document loader factory to provide a
    //  content viewer and a stream listener for our channel
    var originalDocListenerResult = {};
    var originalContentViewer =
    Cc ['@mozilla.org/content/document-loader-factory;1']
    .getService (Ci.nsIDocumentLoaderFactory)
    .createInstance ("view", contentChannel, aLoadGroup, aContentType,
                     aContainer, aExtraInfo, originalDocListenerResult);
    originalDocListener
    = originalDocListenerResult.value.QueryInterface (Ci.nsIStreamListener);
        
    // Start feeding data to the provided stream listener
    contentChannel.asyncOpen (originalDocListener, null);
        
    // Return the now running content viewer
    return originalContentViewer;
  }
};

/**
 * 本体のファクトリー
 *   Inherits From: nsIFactory
 */
var UnMHTDocumentLoaderFactoryFactory = {
  /**
   * 本体を生成する
   *   nsIFactory.createInstance
   *
   * @param  nsISupport outer
   *          統合する対象
   * @param  nsIIDRef iid
   *         生成する対象のインターフェース ID
   * @return UnMHTDocumentLoaderFactory
   *         本体
   */
  createInstance : function (outer, iid) {
    if (outer != null) {
      /* 統合する対象がある場合はエラー */
      throw Components.results.NS_ERROR_NO_AGGREGATION;
    }
        
    return (new UnMHTDocumentLoaderFactory ()).QueryInterface (iid);
  }
};

/**
 * XPCOM のモジュール
 *   Inherits From: nsIModule
 */
var UnMHTDocumentLoaderFactoryModule = {
  /* 本体に関する情報 */
  CNAME : "UnMHT Document Loader Factory JS Component",
  CID : Components.ID ("{fa22337c-baf4-4c05-ab92-5e1fb4a97aa9}"),
  CONTRACTID : "@unmht.org/unmht-document-loader-factory;1",
    
  /**
   * インターフェースの要求
   *   nsISupports.QueryInterface
   *
   * @param  nsIIDRef iid
   *         インターフェースID
   * @throws Components.results.NS_NOINTERFACE
   * @return nsIModule
   *         this
   */
  QueryInterface : function (iid) {
    if (iid.equals (Ci.nsISupports)
        || iid.equals (Ci.nsIModule)) {
      return this;
    }
        
    throw Cr.NS_ERROR_NO_INTERFACE;
  },
    
  /**
   * 登録処理
   *   nsIModule.registerSelf
   *
   * @param  nsIComponentManager compMgr
   * @param  nsIFile fileSpec
   *         モジュールのファイル
   *           reference と他のソースコード中で並びが違う
   * @param  String location
   *         不明
   *           reference と他のソースコード中で並びが違う
   * @param  String type
   *         ローダの種類
   */
  registerSelf : function (compMgr, fileSpec, location, type) {
    try {
      var id
      = Cc ["@mozilla.org/xre/app-info;1"]
      .getService (Ci.nsIXULAppInfo).ID;
      if (id == "{3550f703-e582-4d05-9a08-453d09bdfdc6}") {
        /* Thunderbird */
        return;
      }
    } catch (e) {}
        
    compMgr = compMgr.QueryInterface (Ci.nsIComponentRegistrar);
    compMgr.registerFactoryLocation (this.CID,
                                     this.CNAME,
                                     this.CONTRACTID,
                                     fileSpec, location, type);
  },
    
  /**
   * 登録解除処理
   *   nsIModule.unregisterSelf
   *
   * @param  nsIComponentManager compMgr
   * @param  nsIFile fileSpec
   *         モジュールのファイル
   *           reference と他のソースコード中で並びが違う
   * @param  String location
   *         不明
   *           reference と他のソースコード中で並びが違う
   */
  unregisterSelf : function (compMgr, fileSpec, location) {
    try {
      var id
      = Cc ["@mozilla.org/xre/app-info;1"]
      .getService (Ci.nsIXULAppInfo).ID;
      if (id == "{3550f703-e582-4d05-9a08-453d09bdfdc6}") {
        /* Thunderbird */
            
        return;
      }
    } catch (e) {}
        
    compMgr = compMgr.QueryInterface (Ci.nsIComponentRegistrar);
    compMgr.unregisterFactoryLocation (this.CID, fileSpec);
  },
    
  /**
   * ファクトリーオブジェクトを取得する
   *   nsIModule.getClassObject
   *
   * @param  nsIComponentManager compMgr
   * @param  nsCIDRef cid
   *         取得対象のクラス ID
   * @param  nsIIDRef iid
   *         取得対象のインターフェース ID
   * @throws Components.results.NS_ERROR_NOT_IMPLEMENTED
   *         Components.results.NS_ERROR_NO_INTERFACE
   * @return UnMHTDocumentLoaderFactory
   *         本体のファクトリー
   */
  getClassObject : function (compMgr, cid, iid) {
    if (cid.equals (this.CID)) {
      return UnMHTDocumentLoaderFactoryFactory;
    }
        
    if (!iid.equals (Ci.nsIFactory)) {
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    }
        
    throw Cr.NS_ERROR_NO_INTERFACE;
  },
    
  /**
   * 終了できるかどうか
   *   nsIModule.canUnload
   *
   * @param  nsIComponentManager compMgr
   * @return Boolean
   *         終了できるかどうか
   */
  canUnload : function (compMgr) {
    return true;
  }
};

// XPCOM component registration
function NSGetModule(compMgr, fileSpec) {
  return UnMHTDocumentLoaderFactoryModule;
}