这篇教程更新于2008年夏天,用来集成SWT和XULRunner来在SWT里显示一个浏览器。要想获得更多信息,请参考
http://www.eclipse.org/swt/faq.php#whatisbrowser
1,简介
这篇教程有两个目的。首先,我们能学到一些XPCOM的概念和怎么通过JavaXPCOM来在java里使用它。其次,我们学到怎么在
java程序里嵌入Firefox浏览器。
这里的例子我们开发,编译和测试的环境是Ubuntu 8.04和JDK1.6(译注,我使用的是windows xp)。我们在windows下遇到
一个问题,用户无法在form里输入文本,虽然在例子里并不需要这点。
1.1 下载资源
例子的源码在相关章节的开始以及文章的最后都有下载,当然,你也可以下载所有的代码
http://ladyr.es/wiki/attachment/wiki/XPCOMGuide/complete-guide-resources-windows.zip windows
http://ladyr.es/wiki/attachment/wiki/XPCOMGuide/complete-guide-resources-linux.zip linux
2. 安装XULRunner
JavaXPCOM是一个用来访问XULRunner里XPCOM组件和服务的java库。在这里我们介绍了一些方法通过java来使用XULRunner组件
和服务。JavaXPCOM也能在java程序里嵌入Gecko。JavaXPCOM需要Java1.4.2或者更新的版本。为了使用JavaXPCOM你需要下面的步骤
:
XULRunner:你需要安装XULRunner。它在开发中并不断变化。这个教程使用的是1.9的版本,这个版本built的时间是2008-6-19.
安装XULRunner的方法:
1. 根据自己的操作系统下载合适的安装包(译注,我在windows xp下无法使用XULRunner1.9,我使用的是
xulrunner-1.8.1.3.en-US.win32-20080128)
2.解压它,比如把它解压到c:/XULRunner
3.进入这个路径
4.卸载以前的注册
5.注册这个版本
(译注:注册并不是必须的,注册后可以在程序里不用知道XULRunner的路径,不注册也可以,不过程序里要知道路径)
javaxpcom.jar
MozillaInterface.jar
MozillaGlue.jar
3.在Java里使用JavaXPCOM
3.1 JavaXPCOM初始化
XPCOM(Mozilla Cross-platform Object Model)是一个机遇组件的架构。为了使用XPCOM,我们需要一些初始化和清理的代码。有
两种方法来使用它,请看下面注释过的代码:
首先我们可以使用initXPCOM方法
package es.ladyr.javaxpcom.test;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Properties;
import org.mozilla.xpcom.GREVersionRange;
import org.mozilla.xpcom.Mozilla;
public class SimplestInitializationCode {
public static void main(String[] args) {
// JavaXPCOM initialization
GREVersionRange[] range = new GREVersionRange[1];
range[0] = new GREVersionRange("1.8", true, "1.9+", true);
Properties props = null;
File grePath = null;
try {
grePath = Mozilla.getGREPathWithProperties(range, props);
} catch (FileNotFoundException e) {
System.out.println("XULRunner not found. It is possible that it be wrong installed");
return;
}
Mozilla mozilla = Mozilla.getInstance();
mozilla.initialize(grePath);
try {
mozilla.initXPCOM(grePath, null);
} catch (Throwable t) {
System.out.println("initXPCOM failed");
t.printStackTrace();
return;
}
// From this line, we can use JavaXPCOM
System.out.println("/n--> initialized/n");
// ... your application code ...
// JavaXPOM finalization
mozilla.shutdownXPCOM(null);
}
}
为了嵌入,你应该使用 initEmbedding 方法。这个方法需要实现 IAppFileLocProvider 的类。我们使用包http://www.my400800.cn
org.eclipse.atf.mozilla.ide.core里的一个类LocationProvider 。它的代码如下:
/*******************************************************************
*
* Licensed Materials - Property of IBM
*
* AJAX Toolkit Framework 6-28-496-8128
*
* (c) Copyright IBM Corp. 2006 All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*
*******************************************************************/
package org.eclipse.atf.mozilla.ide.core;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.mozilla.xpcom.IAppFileLocProvider;
public class LocationProvider implements IAppFileLocProvider {
private File libXULPath;
private File profile;
private File history;
public LocationProvider(File aBinPath, File aProfileDir)
throws IOException {
libXULPath = aBinPath;
profile = aProfileDir;
if (!libXULPath.exists() || !libXULPath.isDirectory()) {
throw new FileNotFoundException("libxul directory specified is not valid: "
+ libXULPath.getAbsolutePath());
}
if (profile != null && (!profile.exists() || !profile.isDirectory())) {
throw new FileNotFoundException("profile directory specified is not valid: "
+ profile.getAbsolutePath());
}
// create history file
if (profile != null) {
setupProfile();
}
}
private void setupProfile() throws IOException {
history = new File(profile, "history.dat");
if (!history.exists()) {
history.createNewFile();
}
}
public File getFile(String aProp, boolean[] aPersistent) {
File file = null;
if (aProp.equals("GreD") || aProp.equals("GreComsD")) { //$NON-NLS-1$
// file = new File(grePath);
file = libXULPath;
if (aProp.equals("GreComsD")) { //$NON-NLS-1$
file = new File(file, "components"); //$NON-NLS-1$
}
}
else if (aProp.equals("MozBinD") || //$NON-NLS-1$
aProp.equals("CurProcD") || //$NON-NLS-1$
aProp.equals("ComsD")) //$NON-NLS-1$
{
file = libXULPath;
if (aProp.equals("ComsD")) { //$NON-NLS-1$
file = new File(file, "components"); //$NON-NLS-1$
}
}
else if (aProp.equals("ProfD")) { //$NON-NLS-1$
return profile;
}
else if (aProp.equals("UHist")) { //$NON-NLS-1$
return history;
}
// else {
// System.err.println("LocationProvider::getFile() => unhandled property = " + aProp);
// }
return file;
}
public File[] getFiles(String aProp) {
File[] files = null;
if (aProp.equals("APluginsDL")) { //$NON-NLS-1$
files = new File[1];
files[0] = new File(libXULPath, "plugins");
// } else {
// System.err.println("LocationProvider::getFiles() => unhandled property = " + aProp);
}
return files;
}
}
关于更多ATF的信息,请访问http://www.eclipse.org/atf/
为了嵌入而初始JavaXPCOM的代码如下:
package es.ladyr.javaxpcom.test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import org.eclipse.atf.mozilla.ide.core.LocationProvider;
import org.mozilla.xpcom.GREVersionRange;
import org.mozilla.xpcom.Mozilla;
import org.mozilla.xpcom.XPCOMException;
public class EmbeddingInitializationCode {
public static void main(String[] args) {
// JavaXPCOM initialization
GREVersionRange[] range = new GREVersionRange[1];
range[0] = new GREVersionRange("1.8", true, "1.9+", true);
Properties props = null;
File grePath = null;
try {
grePath = Mozilla.getGREPathWithProperties (range, props);
} catch (FileNotFoundException e) {
System.out.println("XULRunner not found. It is possible that it be wrong installed");
return;
}
Mozilla mozilla = Mozilla.getInstance();
mozilla.initialize(grePath);
LocationProvider locProvider;
try {
locProvider = new LocationProvider(grePath, grePath);
} catch (IOException e) {
System.out.println("XULRunner not found. It is possible that it be wrong installed");
return;
}
mozilla.initEmbedding(grePath, grePath, locProvider);
// From this line, we can use JavaXPCOM
System.out.println("/n--> initialized/n");
// ... your application code ...
// JavaXPOM finalization
try {
mozilla.termEmbedding();
} catch (XPCOMException e) {
// this exception is thrown if termEmbedding failed
}
}
}
3.2 XPCOM组件和接口的基本概念
XPCOM(Mozilla Cross-platform Object Model)如它名字,是一个基于组件的架构。XPCOM的组件一般是本地实现的。XPCOM系统
里的所有组件都有一个唯一的标识。XPCOM组件实现许多接口,这些接口提供各种功能。同一个组件可能实现不同的接口,同样,一
个接口可能北不同的组件实现。因为接口必需有一个具体的实现才能使用,我们将讨论组件和接口。在后面,我们把组件看成一段实
现了一个或多个接口的代码。
在XULRunner里,接口名总是以nsI开头。在JavaXPCOM里,每个XPCOM接口都有一个对应的java接口。它们的名字以nsI开头,它们
位于org.mozilla.xpcom里,被放在MozillaInterfaces.jar包里面。这些接口的javaDoc在MozillaInterfaces-src.jar里。你可以在
https://developer.mozilla.org/En浏览所有的XPCOM接口,MDC是一个与语言无关的文档,并不是Java的。
在JavaXPCOM里没有XPCOM组件的代码。后面你将看到怎么在JavaXPCOM里初始化和使用组件。因为没有java代码,所有也没有
JavaDoc。http://developer.mozilla.org/En里包含了XPCOM组件的完整文档。
我们可以如下的步骤使用XPCOM组件:
得到组件
得到实现了我们想使用的接口的组件
调用它
组件使用URI来命名,例如,@mozilla.org/file/local;1。Mozilla的组件以@mozilla.org开头。这个URI是contractID,每个组
件也有一个组件标识符(CID)。同样,每一个接口也有一个接口标识符(IID)。IID总能使用一个static的final的属性来获取。组件可
能存在一个对象实例或者单例的服务。通常,服务包含单词Service或者Manager
3.3 使用JavaXPCOM: 与用JavaScript来使用XPCOM的不同点与相同点
在XPCOM里,所有的接口都继承了nsISupports,它有一个函数叫做QueryInterface,这个函数返回一个组件的一个接口,如果这
个组件没有实现这个接口,那么将返回异常。在JavaXPCOM里,接口nsISupports提供了QueryInterface来返回一个nsISupports对象
。为了属于谋个接口的方法,你首先需要使用QueryInterface方法确定这个接口。
比如,你可以使用下面的JavaScript来调用XPCOM来创建一个本地文件组件:
// Get the component
var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();
// Get the part of the component corresponding to the interface that we want to use
var aLocalFile = aFile.QueryInterface(Components.interfaces.nsILocalFile);
// Call the function (one or more than one)
aLocalFile.initWithPath("/mozilla/testfile.txt");
上面需要两行JavaScript代码来创建组件和获得接口。可以简化的代码:
// Get the component and directly the part of the component corresponding to the interface that we want to use
var aLocalFile = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
// Call the function (one or more than one)
aLocalFile.initWithPath("/mozilla/testfile.txt");
在JavaXPCOM里,为了得到XPCOM组件相对应的JavaXPCOM的实例,我们必须访问ComponentManager对象,这个对象允许在运行时访问
和使用。它能够通过单例类Mozilla的getComponentManager()访问。每次我们用这种方法实例化,我们创建了一个XPCOM组件。有两
种方法来创建XPCOM组件的实例:
使用组件ID(CID):
// Get the component manager
ComponentManager cm = Mozilla.getInstance().getComponentManager();
// Get the part of the component corresponding to the interface that we want to use
nsISupports obj = cm.createInstance("{6ddb050c-0d04-11d4-986e-00c04fa0cf4a}", null, nsISupports.NS_ISUPPORTS_IID);
// Get the part of the component corresponding to the interface that we want to use
nsIInputStreamChannel isc = obj.queryInterface(nsIInputStreamChannel.NS_IINPUTSTREAMCHANNEL_IID);
// Call the method (one or more than one)
System.out.println("Content-Length: " + isc.getContentLength());
我们可以简化一下:
// Get the component manager
ComponentManager cm = Mozilla.getInstance().getComponentManager();
// Get the part of the component corresponding to the interface that we want to use
nsIInputStreamChannel isc = (nsIInputStreamChannel)
cm.createInstance("{6ddb050c-0d04-11d4-986e-00c04fa0cf4a}", null,
nsIInputStreamChannel.NS_IINPUTSTREAMCHANNEL_IID);
// Call the method (one or more than one)
System.out.println("Content-Length: " + isc.getContentLength());
也可以通过contractID来访问:
// Get the component manager
ComponentManager cm = Mozilla.getInstance().getComponentManager();
// Get the part of the component corresponding to the interface that we want to use
nsISupports obj = cm.createInstanceByContractID("@ mozilla.org/file/local;1", null, nsISupports.NS_ISUPPORTS_IID);
// Get the part of the component corresponding to the interface that we want to use
nsILocalFile file = obj.queryInterface(nsILocalFile.NS_ILOCALFILE_IID);
// Call the method (one or more than one)
file.initWithPath("/home/alpgarcia/xpcom-file-test.txt");
简化形式:
// Get the component manager
ComponentManager cm = Mozilla.getInstance().getComponentManager();
// Get the part of the component corresponding to the interface that we want to use
nsILocalFile file = (nsILocalFile)cm.createInstanceByContractID("@ mozilla.org/file/local;1", null,
nsILocalFile.NS_ILOCALFILE_IID);
// Call the method (one or more than one)
file.initWithPath("/home/alpgarcia/xpcom-file-test.txt");
在JavaScript里,我们通过调用instanceOf来检查一个组件是否实现了一个接口:
var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();
if (aFile instanceOf Components.interfaces.nsILocalFile){
var aLocalFile = aFile.QueryInterface(Components.interfaces.nsILocalFile);
...
}
在java里,instanceof运算符是其它的含义。在java里只能通过QueryInterface来判断。如果调用失败,那么这个组件就没有实现特
定的接口。如果我们还是用JavaScript的写法,那么就会得到不同的结果(错误的)。试试下面的代码:
package es.ladyr.javaxpcom.test;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Properties;
import org.mozilla.xpcom.GREVersionRange;
import org.mozilla.xpcom.Mozilla;
import org.mozilla.xpcom.XPCOMException;
import org.mozilla.interfaces.nsIComponentManager;
import org.mozilla.interfaces.nsILocalFile;
import org.mozilla.interfaces.nsIMutableArray;
import org.mozilla.interfaces.nsISupports;
public class InstanceOfTest {
public static void main(String[] args) {
// JavaXPCOM initialization
GREVersionRange[] range = new GREVersionRange[1];
range[0] = new GREVersionRange("1.8", true, "1.9+", true);
Properties props = null;
File grePath = null;
try {
grePath = Mozilla.getGREPathWithProperties(range, props);
} catch (FileNotFoundException e) {
System.out
.println("XULRunner not found. It is possible that it be wrong installed");
return;
}
Mozilla mozilla = Mozilla.getInstance();
mozilla.initialize(grePath);
try {
mozilla.initXPCOM(grePath, null);
} catch (Throwable t) {
System.out.println("initXPCOM failed");
t.printStackTrace();
return;
}
// From this line, we can use JavaXPCOM
// Get the component manager
nsIComponentManager cm = Mozilla.getInstance().getComponentManager();
// We will not be able to know if that component implements a given
// interface using instanceof in Java
nsISupports aFile = cm
.createInstanceByContractID("@mozilla.org/file/local;1", null,
nsISupports.NS_ISUPPORTS_IID);
if (aFile instanceof nsILocalFile) {
// The execution flow never come this way because instanceof
// semantics are diferent in JavaScript and Java
// (nsILocalFile is a chid class of nsISupports and not
// the other way round)
nsILocalFile aLocalFile = (nsILocalFile) aFile
.queryInterface(nsILocalFile.NS_ILOCALFILE_IID);
System.out.println("This message will never be printed.");
}
try {
nsILocalFile aLocalFile = (nsILocalFile) aFile
.queryInterface(nsILocalFile.NS_ILOCALFILE_IID);
// The next sentences are executed if aFile component implements
// nsILocalFile interface
System.out
.println("nsILocalFile interface implemented by this component.");
// Try to get an exception caused by a non implemented interface
nsIMutableArray arr = (nsIMutableArray) aFile
.queryInterface(nsIMutableArray.NS_IMUTABLEARRAY_IID);
System.out.println("This message will never be printed.");
} catch (XPCOMException ex) {
// The next sentences are executed if aFile component don't
// implement nsILocalFile interface
System.err
.println("Interface not supported. See the following exception: ");
ex.printStackTrace();
System.err.flush();
}
// The inverse case matchs with the semantics of Java instanceof
// operator
nsILocalFile aLocalFile = (nsILocalFile) aFile
.queryInterface(nsILocalFile.NS_ILOCALFILE_IID);
if (aLocalFile instanceof nsISupports) {
// The next sentence will be executed
System.out.println("aLocalFile instance of nsISupports");
}
// JavaXPOM finalization
mozilla.shutdownXPCOM(null);
}
}
JavaScript和Java的主要区别是,在JavaXPCOM里没有一个特定的对象与XPCOM组件对应。
另外,我们可以使用另一类组件,叫做services。前面我们说过,Service作为一个单例存在,换句话说,你只能初始化一次。
Services被用来get和set全局的数据或者对其它对象进行操作。创建Service与创建组件类似,把getInstance()改成getService()就
行了。在JavaXPCOM里,我们需要nsIServiceManager对象来初始化一个Service。然后你需要调用getService或者
getServiceByContractID来得到service组件的引用。
在JavaScript里的代码:
// get the dns service component in JavaScript
var dnsservice = Components.classes["@mozilla.org/network/dns-service;1"].getService();
dnsservice.QueryInterface(Components.interfaces.nsIDNSService);
JavaXPCOM的代码:
//Now, we will try to use a service, for example, dns service
nsIServiceManager sm = mozilla.getServiceManager();
nsIDNSService dns = (nsIDNSService) sm.getServiceByContractID(
"@mozilla.org/network/dns-service;1", nsIDNSService.NS_IDNSSERVICE_IID);
3.4 在JavaXPCOM里使用组件和Services的完整例子
在第一个例子里我们将要创建一个本地的文件并写入一些文本。
package es.ladyr.javaxpcom.test;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Properties;
import org.mozilla.xpcom.GREVersionRange;
import org.mozilla.xpcom.Mozilla;
import org.mozilla.interfaces.nsIComponentManager;
import org.mozilla.interfaces.nsIFileInputStream;
import org.mozilla.interfaces.nsIFileOutputStream;
//import org.mozilla.interfaces.nsILineInputStream;
import org.mozilla.interfaces.nsILocalFile;
import org.mozilla.interfaces.nsISeekableStream;
import org.mozilla.interfaces.nsISupports;
public class FileTest {
public static void main(String[] args) {
// JavaXPCOM initialization
GREVersionRange[] range = new GREVersionRange[1];
range[0] = new GREVersionRange("1.8", true, "1.9+", true);
Properties props = null;
File grePath = null;
try {
grePath = Mozilla.getGREPathWithProperties(range, props);
} catch (FileNotFoundException e) {
System.out
.println("XULRunner not found. It is possible that it be wrong installed");
return;
}
Mozilla mozilla = Mozilla.getInstance();
mozilla.initialize(grePath);
try {
mozilla.initXPCOM(grePath, null);
} catch (Throwable t) {
System.out.println("initXPCOM failed");
t.printStackTrace();
return;
}
// From this line, we can use JavaXPCOM
// First, we will create an empty file
// A file is a component, we use the component manager to create an
// instance
// of local file component
nsIComponentManager cm = mozilla.getComponentManager();
// Here we create directly a component and use its nsILocalFile
// interface
nsILocalFile file = (nsILocalFile) cm.createInstanceByContractID(
"@mozilla.org/file/local;1", null,
nsILocalFile.NS_ILOCALFILE_IID);
file.initWithPath("/home/sevi/xpcom-file-test.txt");
// Check file existence because if the file already exists in filesystem
// and
// we try to create another one with the same path, an exception will be
// thrown by XPCOM
// (something like "The function "create" returned an error condition
// (0x80520008)")
if (!file.exists()) {
file.create(nsILocalFile.NORMAL_FILE_TYPE, 0600);
}
// We can create a stream to write the file
nsIFileOutputStream os = (nsIFileOutputStream) cm
.createInstanceByContractID(
"@mozilla.org/network/file-output-stream;1", null,
nsIFileOutputStream.NS_IFILEOUTPUTSTREAM_IID);
os.init(file, nsISeekableStream.NS_SEEK_END, 0, 0);
os.write("Hello World!/n", 13);
os.flush();
os.close();
// And now we will use an input stream to read the file
// The @mozilla.org/network/file-input-stream;1 component implements the
// following interfaces:
// * nsIFileInputStream
// * nsIInputStream
// * nsILineInputStream
// * nsISeekableStream
// * nsISupports
// First create a parent component (top parent component). We could use
// another component
// implemented interface as well.
nsISupports is = cm.createInstanceByContractID(
"@mozilla.org/network/file-input-stream;1", null,
nsISupports.NS_ISUPPORTS_IID);
// We will use the file input stream interface to associate a local file
// with the stream
nsIFileInputStream fis = (nsIFileInputStream) is
.queryInterface(nsIFileInputStream.NS_IFILEINPUTSTREAM_IID);
fis.init(file, nsIFileInputStream.REOPEN_ON_REWIND, 0, 0);
System.out.println("The file contains " + fis.available()
+ " bytes of data.");
fis.close();
// JavaXPOM finalization
mozilla.shutdownXPCOM(null);
System.out.println("Finish!");
}
}
下面是一个关于域名的例子:
package es.ladyr.javaxpcom.test;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Properties;
import org.mozilla.xpcom.GREVersionRange;
import org.mozilla.xpcom.Mozilla;
import org.mozilla.interfaces.*;
public class ServiceTest {
public static void main(String[] args) {
// JavaXPCOM initialization
GREVersionRange[] range = new GREVersionRange[1];
range[0] = new GREVersionRange("1.8", true, "1.9+", true);
Properties props = null;
File grePath = null;
try {
grePath = Mozilla.getGREPathWithProperties(range, props);
} catch (FileNotFoundException e) {
System.out
.println("XULRunner not found. It is possible that it be wrong installed");
return;
}
Mozilla mozilla = Mozilla.getInstance();
mozilla.initialize(grePath);
try {
mozilla.initXPCOM(grePath, null);
} catch (Throwable t) {
System.out.println("initXPCOM failed");
t.printStackTrace();
return;
}
// From this line, we can use JavaXPCOM
// Now, we will try to use a service, for instance, dns service
nsIServiceManager sm = mozilla.getServiceManager();
nsIDNSService dns = (nsIDNSService) sm.getServiceByContractID(
"@mozilla.org/network/dns-service;1",
nsIDNSService.NS_IDNSSERVICE_IID);
System.out.println("Hostname: " + dns.getMyHostName());
nsIDNSRecord dnsRecord = dns.resolve("www.google.es",
nsIDNSService.RESOLVE_CANONICAL_NAME
| nsIDNSService.RESOLVE_BYPASS_CACHE);
System.out.println("Canonical Name: " + dnsRecord.getCanonicalName()
+ " -- Next Address: " + dnsRecord.getNextAddrAsString());
// JavaXPOM finalization
mozilla.shutdownXPCOM(null);
System.out.println("Finish!");
}
}