Dev : Extending the Chrome Protocol: Difference between revisions

From MozillaZine Knowledge Base
Jump to navigationJump to search
No edit summary
Line 64: Line 64:
For example: C:\Program Files\Mozilla Firefox\components
For example: C:\Program Files\Mozilla Firefox\components


In your Mozilla or Firefox directory, place an empty file named ''.autoreg''.  It may be already present.  If so, you need to either use the touch command if that's available, modify it, or delete it and recreate it, in order for the browser to register nsChromeExtensionHandler.js as a new component.
In your Mozilla or Firefox directory, place an empty file named ''.autoreg''.  It may be already present.  If so, you need to either use the ''touch'' command if that's available, modify it, or delete it and recreate it, in order for the browser to register nsChromeExtensionHandler.js as a new component.


If you make changes to nsChromeExtensionHandler.js or download a new version, you will need to again update the ''.autoreg'' file in order to have the browser re-register the component.
If you make changes to nsChromeExtensionHandler.js or download a new version, you will need to again update the ''.autoreg'' file in order to have the browser re-register the component.

Revision as of 03:16, 16 April 2005

This page is part of the extension development documentation project.

Ask your questions in MozillaZine Forums. Also try browsing example code.

Note: development documentation is in process of being moved to Mozilla Development Center (MDC).

This component implements an extension mechanism to the chrome protocol handler for mapping in dynamically generated content into chrome URIs. This content will have the same system permissions as regular chrome content, making it possible to create scripts which programmatically generate XUL pages and stylesheets. Remote chrome can also be implemented.

This protocol handler could be installed with the same contract ID as the chrome protocol handler so that all chrome requests pass through it, but it has not been sufficiently tested for that to be recommended.


Example Usage

To register an extension, use code like the following within privileged Javascript running in your chrome:

var my_extension = {

	pkg : "myext",

	path : "myext.xul",

	newChannel : function(uri) {

		var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService();
		ioService = ioService.QueryInterface(Components.interfaces.nsIIOService);

		var uri_str = "data:,My%20extension%20content";

		var ext_uri = ioService.newURI(uri_str, null, null);
		var ext_channel = ioService.newChannelFromURI(ext_uri);

		return ext_channel;

	}	
};

var chrome_ext = Components.classes["@mozilla.org/network/protocol;1?name=xchrome"].getService();
chrome_ext.wrappedJSObject.registerExtension(my_extension);

The above example will register an extension at the following URL:

xchrome://myext/content/ext/myext.xul


For many extensions, using data: URLs to pass content back through the ChromeExtensionHandler is the easiest mechanism. See the following page for more information on constructing data: URLs:

http://www.mozilla.org/quality/networking/testing/datatests.html

Installation

Save the source code as nsChromeExtensionHandler.js in your components directory.

For example: C:\Program Files\Mozilla Firefox\components

In your Mozilla or Firefox directory, place an empty file named .autoreg. It may be already present. If so, you need to either use the touch command if that's available, modify it, or delete it and recreate it, in order for the browser to register nsChromeExtensionHandler.js as a new component.

If you make changes to nsChromeExtensionHandler.js or download a new version, you will need to again update the .autoreg file in order to have the browser re-register the component.

Source Code (nsChromeExtensionHandler.js)

/*----------------------------------------------------------------------
 * nsChromeExtensionHandler
 * By Ed Anuff <ed@anuff.com>
 *
 * Last modified: 04/13/2005 15:49 PST
 *
 * DESCRIPTION:
 *
 * This component implements an extension mechanism to the chrome
 * protocol handler for mapping in dynamically generated content
 * into chrome URIs.  This content will have the same system
 * permissions as regular chrome content, making it possible to
 * create scripts which programmatically generate XUL pages and
 * stylesheets.  Remote chrome can also be implemented.
 *
 * This protocol handler could be installed with the same
 * contract ID as the chrome protocol handler so that all chrome
 * requests pass through it, but it has not been sufficiently tested
 * for that to be recommended.
 *
 *
 * EXAMPLE USAGE:
 *
 * To register an extension, use code like the following within privileged
 * Javascript running in your chrome:
 *
 * 	var my_extension = {
 *
 *		pkg : "myext",
 *
 *		path : "myext.xul",
 *
 *		newChannel : function(uri) {
 *
 *			var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService();
 *			ioService = ioService.QueryInterface(Components.interfaces.nsIIOService);
 *
 *			var uri_str = "data:,My%20extension%20content";
 *
 *			var ext_uri = ioService.newURI(uri_str, null, null);
 *			var ext_channel = ioService.newChannelFromURI(ext_uri);
 *
 *			return ext_channel;
 *
 *		}	
 *	};
 *
 *	var chrome_ext = Components.classes["@mozilla.org/network/protocol;1?name=xchrome"].getService();
 *	chrome_ext.wrappedJSObject.registerExtension(my_extension);
 *
 * The above example will register an extension at the following URL:
 *
 *	xchrome://myext/content/ext/myext.xul
 *
 *
 * For many extensions, using data: URLs to pass content back through
 * the ChromeExtensionHandler is the easiest mechanism.  See the following page
 * for more information on constructing data: URLs:
 *
 *	http://www.mozilla.org/quality/networking/testing/datatests.html
 *
 *
 * Protocol handler code based on techniques from:
 *
 *	http://www.nexgenmedia.net/docs/protocol/
 *	http://simile.mit.edu/piggy-bank/
 *
 *----------------------------------------------------------------------
 */

/*----------------------------------------------------------------------
 * The ChromeExtension Module
 *----------------------------------------------------------------------
 */

// Custom protocol related
const kSCHEME = "xchrome";
const kPROTOCOL_CID = Components.ID("{6803D375-226F-4777-A8FF-D0022C2F4B40}");
const kPROTOCOL_CONTRACTID = "@mozilla.org/network/protocol;1?name=" + kSCHEME;
const kPROTOCOL_NAME = "Chrome Extension Protocol";

// Dummy chrome URL used to obtain a valid chrome channel
// This one was chosen at random and should be able to be substituted
// for any other well known chrome URL in the browser installation
const kDUMMY_CHROME_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";

// Mozilla defined
const kCHROMEHANDLER_CID_STR = "{61ba33c0-3031-11d3-8cd0-0060b0fc14a3}";
const kCONSOLESERVICE_CONTRACTID = "@mozilla.org/consoleservice;1";
const kIOSERVICE_CID_STR = "{9ac9e770-18bc-11d3-9337-00104ba0fd40}";
const kIOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
const kNS_BINDING_ABORTED = 0x804b0002;
const kSIMPLEURI_CONTRACTID = "@mozilla.org/network/simple-uri;1";
const kSTANDARDURL_CONTRACTID = "@mozilla.org/network/standard-url;1";
const kURLTYPE_STANDARD = 1;
const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
const nsIConsoleService = Components.interfaces.nsIConsoleService;
const nsIFactory = Components.interfaces.nsIFactory;
const nsIIOService = Components.interfaces.nsIIOService;
const nsIProtocolHandler = Components.interfaces.nsIProtocolHandler;
const nsIRequest = Components.interfaces.nsIRequest;
const nsIStandardURL = Components.interfaces.nsIStandardURL;
const nsISupports = Components.interfaces.nsISupports;
const nsIURI = Components.interfaces.nsIURI;

var tracingEnabled = false;

function trace(msg) {
	if (tracingEnabled) {
		Components.classes[kCONSOLESERVICE_CONTRACTID].getService(nsIConsoleService).logStringMessage(msg);
	}
};

var ChromeExtensionModule = {
	
	/* CID for this class */
	cid: kPROTOCOL_CID,

	/* Contract ID for this class */
	contractId: kPROTOCOL_CONTRACTID,

	registerSelf : function(compMgr, fileSpec, location, type) {
		compMgr = compMgr.QueryInterface(nsIComponentRegistrar);
		compMgr.registerFactoryLocation(
			kPROTOCOL_CID, 
			kPROTOCOL_NAME, 
			kPROTOCOL_CONTRACTID, 
			fileSpec, 
			location,
			type
		);
	},
	
	getClassObject : function(compMgr, cid, iid) {
		if (!cid.equals(kPROTOCOL_CID)) {
			throw Components.results.NS_ERROR_NO_INTERFACE;
		}
		if (!iid.equals(nsIFactory)) {
			throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
		}
		return this.myFactory;
	},
	
	canUnload : function(compMgr) {
		return true;
	},
	
	myFactory : {
		createInstance : function(outer, iid) {
			if (outer != null) {
				throw Components.results.NS_ERROR_NO_AGGREGATION;
			}
                        
			return new ChromeExtensionHandler().QueryInterface(iid);
		}
	}
};

function NSGetModule(compMgr, fileSpec) {
    return ChromeExtensionModule;
}

/*----------------------------------------------------------------------
 * The ChromeExtension Handler
 *----------------------------------------------------------------------
 */

function ChromeExtensionHandler() {
	trace("[ChromeExtensionHandler.<init>]");
	
	this.wrappedJSObject = this;
	
	this._system_principal = null;
	
	this._extensions = new Object();
	
	var TestExt = {
		pkg : "test",
		
		path : "test.xul",
		
		newChannel : function(uri) {
		
			var ioService = Components.classesByID[kIOSERVICE_CID_STR].getService();
			ioService = ioService.QueryInterface(nsIIOService);

			var uri_str = "data:,ChromeExtension%20test%20content";
			
			var ext_uri = ioService.newURI(uri_str, null, null);
			var ext_channel = ioService.newChannelFromURI(ext_uri);
			
			return ext_channel;
			
		}	
	};
	
	var TestExtSpec = kSCHEME + "://" + TestExt.pkg + "/content/ext/" + TestExt.path;
	TestExtSpec = TestExtSpec.toLowerCase();

	this._extensions[TestExtSpec] = TestExt;
}

ChromeExtensionHandler.prototype = {

	scheme: kSCHEME,
	
	defaultPort : -1,
	
	protocolFlags : nsIProtocolHandler.URI_STD,
	
	registerExtension : function(ext) {
		
		var ext_spec = kSCHEME + "://" + ext.pkg + "/content/ext/" + ext.path;
		ext_spec = ext_spec.toLowerCase();
		
		trace("[ChromeExtensionHandler.registerExtension] " + ext_spec);

		if (this._extensions[ext_spec] != null) {
			trace("[ChromeExtensionHandler.registerExtension] failed - extension already registered: " + ext_spec);
		}
		else {
			this._extensions[ext_spec] = ext;
			trace("[ChromeExtensionHandler.registerExtension] extension registered: " + ext_spec);
		}
	},
	
	allowPort : function(port, scheme) {
		trace("[ChromeExtensionHandler.allowPort]");
		
		return false;
	},
	
	newURI : function(spec, charset, baseURI) {
		trace("[ChromeExtensionHandler.newURI] " + spec);
			
		var new_url = Components.classes[kSTANDARDURL_CONTRACTID].createInstance(nsIStandardURL);
		new_url.init(kURLTYPE_STANDARD, -1, spec, charset, baseURI);		
		
		var new_uri = new_url.QueryInterface(nsIURI);
		return new_uri;
	},
	
	newChannel : function(uri) {
		trace("[ChromeExtensionHandler.newChannel] new channel requested for: " + uri.spec);

		var chrome_service = Components.classesByID[kCHROMEHANDLER_CID_STR].getService();
		chrome_service = chrome_service.QueryInterface(nsIProtocolHandler);

		var new_channel = null;
		
		try {
			var uri_string = uri.spec.toLowerCase();

			for (ext_spec in this._extensions) {
				var ext = this._extensions[ext_spec];
				
				if (uri_string.indexOf(ext_spec) == 0) {

					trace("[ChromeExtensionHandler.newChannel] matched to registered extension: " + ext_spec);

					if (this._system_principal == null) {
						trace("[ChromeExtensionHandler.newChannel] no system principal cached");

						var ioService = Components.classesByID[kIOSERVICE_CID_STR].getService();
						ioService = ioService.QueryInterface(nsIIOService);

						var chrome_uri_str = kDUMMY_CHROME_URL;

						trace("[ChromeExtensionHandler.newChannel] spoofing chrome channel to URL: " + chrome_uri_str);
						
						var chrome_uri = chrome_service.newURI(chrome_uri_str, null, null);
						var chrome_channel = chrome_service.newChannel(chrome_uri);

						trace("[ChromeExtensionHandler.newChannel] retrieving system principal from chrome channel");
						
						this._system_principal = chrome_channel.owner;

						var chrome_request = chrome_channel.QueryInterface(nsIRequest);
						chrome_request.cancel(kNS_BINDING_ABORTED);
						
						trace("[ChromeExtensionHandler.newChannel] system principal is cached");
						
					}

					trace("[ChromeExtensionHandler.newChannel] retrieving extension channel for: " + ext_spec);
					
					var ext_channel = ext.newChannel(uri);

					if (this._system_principal != null) {
						trace("[ChromeExtensionHandler.newChannel] applying cached system principal to extension channel");
						
						ext_channel.owner = this._system_principal;
					}
					else {
						trace("[ChromeExtensionHandler.newChannel] no cached system principal to apply to extension channel");
					}

					ext_channel.originalURI = uri;

					trace("[ChromeExtensionHandler.newChannel] returning extension channel for: " + ext_spec);
					
					return ext_channel;

				}

			}
		
			trace("[ChromeExtensionHandler.newChannel] passing request through to ChromeProtocolHandler::newChannel");
			trace("[ChromeExtensionHandler.newChannel] requested uri = " + uri.spec);
			
			if (uri_string.indexOf("chrome") != 0) {
				uri_string = uri.spec;
				uri_string = "chrome" + uri_string.substring(uri_string.indexOf(":"));
				
				trace("[ChromeExtensionHandler.newChannel] requested uri fixed = " + uri_string);
				
				uri = chrome_service.newURI(uri_string, null, null);
				
				trace("[ChromeExtensionHandler.newChannel] requested uri canonified = " + uri.spec);
				
			}
			
			new_channel = chrome_service.newChannel(uri);
			
		} catch (e) {
			trace("[ChromeExtensionHandler.newChannel] error - NS_ERROR_FAILURE");
			
			throw Components.results.NS_ERROR_FAILURE;
		}
		
		return new_channel;
	},
	
	QueryInterface : function(iid) {
		trace("[ChromeExtensionHandler.QueryInterface]");

		if (!iid.equals(Components.interfaces.nsIProtocolHandler) &&
			!iid.equals(Components.interfaces.nsISupports)) {
			
			trace("[ChromeExtensionHandler.QueryInterface] error - NS_ERROR_NO_INTERFACE " + iid);
			
			throw Components.results.NS_ERROR_NO_INTERFACE;
		}
		return this;
	}
};