1999-02-01 22:17:58 +00:00
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
< html >
< head >
< meta http-equiv = "Content-Type" content = "text/html; charset=iso-8859-1" >
< meta name = "Author" content = "Suresh Duddi" >
1999-05-28 20:38:16 +00:00
< meta name = "GENERATOR" content = "Mozilla/4.51 [en] (X11; U; Linux 2.0.36 i686) [Netscape]" >
1999-02-01 22:17:58 +00:00
< title > XPCOM Dynamic Component Registration< / title >
< / head >
< body >
< center >
< h2 >
XPCOM Dynamic Component Registration< / h2 > < / center >
< center > Suresh Duddi < < a href = "mailto:dp@netscape.com" > dp@netscape.com< / a > >
< hr WIDTH = "100%" > < / center >
< p > Dynamic object registration in XPCOM is achieved by interaction of the
following components:
< ul >
< li >
1999-05-28 20:38:16 +00:00
The Component Registry< / li >
1999-02-01 22:17:58 +00:00
< li >
The Repository< / li >
< li >
The Service Manager< / li >
< li >
Component dll implementing < tt > NSRegisterSelf()< / tt > < / li >
< / ul >
The registration mechanism for XPCOM components is similar in many ways
to that of COM. The XPCOM component dlls will have the opportunity to register
themselves with the registry. The exact time of installation would be either
at install time or as a result of < b > autodetection< / b > by the Repository
Manager at runtime.
1999-05-28 20:38:16 +00:00
< br >
< hr WIDTH = "100%" >
1999-02-01 22:17:58 +00:00
< h3 >
1999-05-28 20:38:16 +00:00
< a NAME = "The Registry: XPCOM Hierarchy" > < / a > The Component Registry< / h3 >
There are three types of Component Registries:
< ol >
< li >
< b > App-Component-Registry< br >
< / b > Each application has its own Component Registry that lives along with
the app in its < exe-dir>/components directory. The Component Registry
is created on installation or first run. It is be used read-only by XPCOM< / li >
< li >
< b > User-Component-Registry< / b > < / li >
< br > Each user can install additional components under their user home directory.
These components will be shared across all XPCOM applications that the
user runs.
< li >
< b > Meta-Component-Registry< / b > < / li >
< br > Sharing Components between application: This can happen in two ways.
The installer of an application can find the other application's components
by looking for the application specifically and registering them with this
app's component registry. The second and more preferable approach is to
keep a machine wide Meta-Components-Registry that would aggregate all the
app component registries.< / ol >
The difference component registries will be searched in the following order:
< ol >
< li >
User Components Registry< / li >
< li >
App Component Registry< / li >
< li >
Meta Component Registry< / li >
< / ol >
The user component registry is the only one that will be updated on the
fly by XPCOM. JS will be given the option to update either the App-Component-Registry
or the User-Component-Registry and this may succeed fail based on write
permission, although the general guideline is to update the User-Component-Registry.
JS will have to do special things to update the App-Component-Registry.
< p > Profiles are a notion of the app (navigator) and xpcom has nothing to
do with it. The app will store app specific data in a Data-Registry that
will be stored under the user's home directory.
< h4 >
How does this Solve our problems< / h4 >
< ol >
< li >
Multiple installations of mozilla and xpcom based apps< / li >
< br > Since each installation is going run with their own App-Component-Registry,
basically both apps will work. No inter process locking is essential. Since
both processes will operate on the User-Component-Registry, inter process
locking of the User-Component-Registry will be required.
< li >
Third Party components installation< / li >
< br > Third parties can install components in their own directories and update
the App-Component-Registry (preferable) or User-Component-Registry depending
on if the sharing of component needs to be specific to the user or for
all users. Facilities for updating the registry would be to use JS or write
XPCOM code in their installer. The other option would be to add their components
in their own directory, create a App-Component-Registry of their own in
their directory and reference it in the Global Meta-Components-Registry.
This will get their components used by all applications.
< li >
Registry used to store app specific data< / li >
< br > This is a totally separate registry: the Data-Registry. The theory
is that this will reside in the user's home directory. The registry hierarchy
is app specific.
< li >
User Specific components< / li >
< br > This is basically the User-Component-Registry. Inter process locking
is required as all processes with XPCOM will access the same User-Component-Registry.
< li >
Embedding< / li >
< br > This is requires more thought. The fact is when say Gecko is embedded
into an application, Gecko is running most probably in the process space
of that application and hence the XPCOM used will look for components in
this embedding applications directory. The embedding procedure should create
a App-Component-Reqistry for the embedding application that should contain
all the components from different apps this app would like to use. This
is however not required, if the Meta-Component-Registry exists.
< li >
User not having permission to the place where the global registry lives,
if there is one.< / li >
< br > First the App-Component-Registry is written to only when there is a
new component or a component has gone away. New components come with installers
or the user calls regFactory.exe with the dll as an argument or clicks
on a button that says "refresh my user components" which will cause autoregistration
of user components. For deleted app components, annotations will be made
in the User-Component-Registry. Deleted user components is a non-issue.
< li >
NFS mounted home directories and app directories< / li >
< br > NFS mounted home directories requires inter-machine locking of the
User-Component-Registry. NFS mounted app directories dont have a
problem as the App-Component-Registry is only used read-only by XPCOM.< / ol >
So in summary,
< ul >
< li >
App-Component-Registry pretty much solves the top problem of Multiple applications.
With some help from the installer, Third party components will also be
solved.< / li >
< li >
User-Component-Registry solves the User Specific Components problem.< / li >
< li >
Meta-Component-Registry enables the dynamic sharing of components between
apps which eases embedding.< / li >
< / ul >
As a first cut, I am going to implement the App-Component-Registry for
M8.
< h4 >
Hierarchy Used by Component Registry< / h4 >
1999-02-01 22:17:58 +00:00
XPCOM uses the nsRegistry to store mappings between CLSIDs and their implementations.
The Registry provides persistent storage of hierarchical keys and name-value
pairs associated with each key. Each key also keeps a default value.
< p > XPCOM will use the following registry hierarchy:
< blockquote > < tt > ROOTKEY_COMMON< / tt >
1999-04-17 16:49:58 +00:00
< br > < tt > Common< / tt >
< br > < tt > Classes< / tt >
1999-02-24 20:24:27 +00:00
< br > < tt >
1999-04-17 16:49:58 +00:00
CLSID< / tt >
1999-05-28 20:38:16 +00:00
< br > < tt >
1999-04-17 16:49:58 +00:00
< b > < font color = "#CC0000" > {108d75a0-bab5-11d2-96c4-0060b0fb9956}< / font > < / b > < / tt >
< br > < tt > < nobr >
InprocServer (S) = < font color = "#CC0000" > /home/dp/dist/bin/components/libnfs-protocol.so< / font > < / nobr > < / tt >
< br > < tt > < nobr >
ProgID (S) = < b > < font color = "#CC0000" > component://netscape/network-protocol& type=nfs< / font > < / b > < / nobr > < / tt >
< br > < tt > < nobr >
1999-02-25 17:43:02 +00:00
ClassName (S) = < b > < font color = "#CC0000" > NFS Protocol
1999-04-17 16:49:58 +00:00
Handler< / font > < / b > < / nobr > < / tt >
1999-05-28 20:38:16 +00:00
< p > < tt > < font color = "#CC0000" > component://netscape/network-protocol& type=nfs< / font > < / tt >
1999-04-17 16:49:58 +00:00
< br > < tt > < nobr >
CLSID (S) = < font color = "#CC0000" > {108d75a0-bab5-11d2-96c4-0060b0fb9956}< / font > < / nobr > < / tt >
< p > < tt > < i > < / i > Software< / tt >
< br > < tt > Netscape< / tt >
1999-02-25 17:43:02 +00:00
< br > < tt >
1999-04-17 16:49:58 +00:00
XPCOM< / tt >
< br > < tt >
VersionString (S)
= < font color = "#CC0000" > alpha0.20< / font > < / tt >
1999-05-28 20:38:16 +00:00
< p > < tt >
1999-04-17 16:49:58 +00:00
< font color = "#CC0000" > /home/dp/dist/bin/components/libnfs-protocol.so< / font > < / tt >
< br > < tt >
ComponentsCount (Int) = < font color = "#CC0000" > 1< / font > < / tt >
< br > < tt >
FileSize (Int)
1999-02-25 17:43:02 +00:00
= < font color = "#CC0000" > 78965< / font > < / tt >
1999-04-17 16:49:58 +00:00
< br > < tt >
LastModTimeStamp (S) = < font color = "#CC0000" > Wed
Feb 24 11:24:06 PST 1999< / font > < / tt >
1999-05-28 20:38:16 +00:00
< p > < tt > < font color = "#CC0000" >
1999-04-17 16:49:58 +00:00
< / font > < font color = "#000000" > Events< / font > < / tt >
< br > < tt > < font color = "#000000" >
Startup< / font > < / tt >
1999-05-28 20:38:16 +00:00
< br > < tt > < font color = "#000000" >
1999-04-17 16:49:58 +00:00
< / font > < font color = "#CC0000" > {108d75a0-bab5-11d2-96c4-0060b0fb9956}< / font > < / tt >
< br > < tt > < font color = "#CC0000" >
{17894983-ab78-8d75-a0bb-511d296c4006}< / font > < / tt >
< p > < tt > < font color = "#000000" >
Shutdown< / font > < / tt >
1999-05-28 20:38:16 +00:00
< br > < tt > < font color = "#000000" >
1999-04-17 16:49:58 +00:00
< / font > < font color = "#CC0000" > {748958ea-abab-511d-296c-40060b0fb995}< / font > < / tt >
< br > < tt > < font color = "#CC0000" >
{45617894-983a-b788-d75a-0bab11d296c4}< / font > < / tt >
< br > < / blockquote >
1999-02-25 17:43:02 +00:00
1999-02-01 22:17:58 +00:00
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "The Repository: Object instance creation" > < / a > The
Repository: Object instance creation< / h3 >
1999-02-25 17:43:02 +00:00
All object creation happens via The Repository. < tt > nsIRepository::CreateInstance()< / tt >
1999-02-01 22:17:58 +00:00
will be the primary way of creation of object instances. The steps in instantiation
of an object that implements the IID interface and of class CLSID is as
follows:
< ol >
< li >
The CLSID of the component that would need to create the object instance
is identified.< / li >
1999-02-25 17:43:02 +00:00
< ol > Callers use < tt > nsIRepository::ProgIDToCLSID()< / tt > to convert the
ProgID string to the CLSID.< / ol >
1999-02-01 22:17:58 +00:00
< li >
Load the dll associated with the CLSID after consulting the Registry< / li >
< li >
Instantiate the class factory by calling a globally exported dll function
< tt > NSGetFactory()< / tt > .
1999-02-24 20:24:27 +00:00
This returns an instance of the class factory that implements the < tt > nsIFactory< / tt >
1999-02-01 22:17:58 +00:00
interface.< / li >
< li >
The actual object creation is delegated to this < tt > nsIFactory< / tt > instance
with a call to < tt > nsIFactory::CreateInstance()< / tt > .< / li >
< / ol >
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "The Service Manager" > < / a > The Service Manager< / h3 >
1999-02-01 22:17:58 +00:00
All globally created system services are available via the < tt > nsIServiceManager< / tt > ,
including the < tt > nsIRepository< / tt > and < tt > nsIRegistry< / tt > . Although
the < tt > nsIServiceManager< / tt > uses the Registry and Repository in the
creation and maintenance of other services, the circular dependency is
broken by not letting the < tt > nsIServiceManager< / tt > create the < tt > nsIRepository< / tt >
and < tt > nsIRegistry< / tt > instance and registering them specially with the
< tt > nsIServiceManager< / tt > .
The nsIServiceManager is passed into NSGetFactory() for assisting the DLL
in the Factory creation process.
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "Component Registration" > < / a > Component Registration< / h3 >
1999-02-01 22:17:58 +00:00
Either at installation time of the Component or at times when the XPCOM
library autodetect new/changed dlls, component registration is activated.
The autodetection happens at startup time of the navigator or via a javascript
trigger < tt > navigator.repository.autodetect()< / tt > . The steps in component
registration would be:
< ol >
< li >
The dll is loaded< / li >
< li >
The component is allowed to self register by a call to a globally exported
dll function < tt > NSRegisterSelf()< / tt > . The < tt > nsIServiceManager< / tt >
and the fullpath of the dll are passed in to assist in the registration
process. The dll is expected to create/modify its entries in the Registry
according to the guidelines of the < a href = "#The Registry: XPCOM Hierarchy" > XPCOM
hierarchy< / a > in the registry. < tt > nsIRepository< / tt > , which can be queried
from the < tt > nsIServiceManager< / tt > , has useful registration functions
that would easen the process.< / li >
< li >
The dll is unloaded< / li >
< / ol >
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "Autodetection of Components" > < / a > Autodetection
of Components< / h3 >
1999-02-01 22:17:58 +00:00
Autodetection of changed dlls happened by storing the dll's last modified
time and its size in the Registry automatically. If either the last modified
time stamp or the filesize differs from that stored in the Registry for
the dll, re-registration takes place. Before re-registration, the existing
instances of the objects created by the classes in the dll are not freed
because the < tt > nsIRepository< / tt > has no list of them. The < tt > NSCanUnload()< / tt >
will be called with input parameter < i > force< / i > set to < tt > true< / tt > .
The dll has to prepare for getting unloaded. After this call returns, the
dll < b > will< / b > be unloaded if the return value is < tt > true< / tt > . If the
dll detects that it cannot properly prepare for unloading, then it can
return < tt > false< / tt > . XPCOM will not let the re-registration of the modified
dll proceed in this case. There is nothing much that XPCOM library can
do to salvage this situation other than warning the user of possible instability
and advice a restart upon which the re-registration will happen.
1999-02-25 17:43:02 +00:00
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "ProgID Spec" > < / a > ProgID Spec< / h3 >
1999-04-12 12:47:30 +00:00
The general format of ProgIDs is < i > < b > < font color = "#990000" > component://< / font > < / b > netscape< b > < font color = "#990000" > /< / font > < / b > compname< b > < font color = "#990000" > ?< / font > < / b > var< b > < font color = "#990000" > =< / font > < / b > value< b > < font color = "#990000" > ;< / font > < / b > var< b > < font color = "#990000" > =< / font > < / b > value< b > < font color = "#990000" > ;< / font > < / b > var< b > < font color = "#990000" > =< / font > < / b > value...< / i >
1999-04-02 21:43:54 +00:00
< p > Let us consider some more examples:
1999-02-25 17:43:02 +00:00
< ol >
< li >
A pluggable protocol that implementes the nfs protocol< / li >
< li >
A converter that can handle application/x-zip< / li >
< li >
A plugin that can handle image/gif< / li >
< li >
A widget that can do a toolbar< / li >
< li >
A datasource that can handle mail< / li >
< li >
A helperapp that deals with application/postscript< / li >
< / ol >
All the above have what type they are and one or more arguments on what
they particularly do.
< p > The ProgID for these would look like
< ol >
< li >
1999-04-02 21:43:54 +00:00
< tt > component://netscape/network-protocol?type=nfs< / tt > < / li >
1999-02-25 17:43:02 +00:00
< li >
1999-04-02 21:43:54 +00:00
< tt > component://netscape/data-converter?type=application/x-zip< / tt > < / li >
1999-02-25 17:43:02 +00:00
< li >
1999-04-12 12:47:30 +00:00
< tt > component://netscape/plugin?type=image/gif;name=ImageMedia%20Gif%20Image%20Plugin;Description=Renders%20GIF%20Images....< / tt > < / li >
1999-02-25 17:43:02 +00:00
< li >
1999-04-02 21:43:54 +00:00
< tt > component://netscape/widget?type=toolbar< / tt > < / li >
1999-02-25 17:43:02 +00:00
< li >
1999-04-02 21:43:54 +00:00
< tt > component://netscape/rdf/datsource?type=mail< / tt > < / li >
1999-02-25 17:43:02 +00:00
< li >
1999-04-02 21:43:54 +00:00
< tt > component://netscape/helperapp?type=application/postscript< / tt > < / li >
1999-02-25 17:43:02 +00:00
< / ol >
{Assume proper escaping of all above URI}
< p > The above semantics would let ProgID be an extensible mechanism that
could be searched on multiple ways. And
< br > query on a progid should match only whatever was passed in. So a query
for
1999-04-02 21:43:54 +00:00
< br > component://netscape/plugin?type=image/gif should pass for the progid
specified above. We could extend this
1999-02-25 17:43:02 +00:00
< br > mechanism with wildcards, but I dont want to go there yet... :-)
1999-02-01 22:17:58 +00:00
< br >
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "Startup Components" > < / a > Components created on
events< / h3 >
< b > < font color = "#990000" > NOTE: THIS IS NOT BEING DONE. We are going to
expect the apps to this themselves by using the registry.< / font > < / b >
< p > Some dlls have components that want to be created on certain events
namely Startup, Shutdown (for now). Example is xpinstall.
1999-04-12 12:47:30 +00:00
< blockquote > RegisterComponentForEvent(..., RegisterationTime when, ...)
< br > RegisterFactoryForEvent(..., RegistrationTime when,...)< / blockquote >
exists for this purpose. When an application wants to Fire the particular
event, it calls
< blockquote > nsComponentManager::FireEvent(RegistrationTime when)< / blockquote >
ComponentManager will look for components that are registered to be created
on these events and do the following for each of the components:
< ol >
< li >
CreateInstance(...,CID, knsIStartupComponentIID, & obj);< / li >
< br > For a shutdown event, knsIShutdownComponentIID would be used.
< br >
< li >
obj->Release();< / li >
< br > The component needs to take adequate measures to keep itself alive
and figure out how it would delete the object, since a Release() happens
immediately after a CreateInstace()< / ol >
< b > < i > Warning: < / i > < / b > Order of creation of multiple components registered
on the same event is not defined. Component dependencies aren't thought
of yet.
< br >
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "How will all this help me" > < / a > How will all this
help me< / h3 >
1999-02-01 22:17:58 +00:00
For Component Developers:
< ul >
< li >
Component dlls developed could be dropped into a directory, a JS function
called after which your component is in business without even a restart
of the browser. Of course, there needs to be someone accessing it.< / li >
< li >
No need to export you CLSID or run around finding where to advertise your
CLSID< / li >
< / ul >
For Component Users:
< blockquote >
< li >
No more hacking in calls to < tt > nsIRepository::RegisterFactory()< / tt > < / li >
< li >
No need to know the CLSID of components that you want to instantiate. Component
creation can happen like this< / li >
1999-02-25 17:43:02 +00:00
< br > < tt > nsIRepository::CreateInstance(< b > "< font color = "#CC0000" > component://netscape/network-protocol& type=nfs< / font > "< / b > ,
NULL, kProtocolIID, & result);< / tt >
1999-02-01 22:17:58 +00:00
< br > instead of
1999-02-25 17:43:02 +00:00
< br > < strike > nsIRepository::CreateInstance(NFS_PROTOCOL_CID, NULL, domIID,
& result);< / strike > < / blockquote >
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "What has happened so far" > < / a > What has happened
so far< / h3 >
1999-02-25 17:43:02 +00:00
< ul >
< li >
Autoregistration implemented to grovel for components from the current
directory< / li >
< li >
nsRepository is a global object. Not nsIRepository exists.< / li >
< li >
nsRepository::ProgIDToCLSID() implemented< / li >
< / ul >
1999-02-24 20:24:27 +00:00
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "Changes to XPCOM happening" > < / a > Changes to XPCOM
happening< / h3 >
1999-02-25 17:43:02 +00:00
< ul >
< li >
API changes to get ProgID in entire registration process for factories
and components< / li >
< li >
ProgID cache to improve performance of ProgID queries< / li >
< li >
Notion of Component dlls living in a < b > Components directory < / b > and autoregistration
will load/manage only these components. Other components that are linked
in to the app need to call < tt > nsRepository::RegisterFactory()< / tt > from
1999-02-25 18:11:58 +00:00
the app. < b > To make this happen we need a list of these.< / b > < / li >
1999-02-25 17:43:02 +00:00
< li >
< b > nsRegistry:< / b > Enabling the new nsRegistry developed by Bill Law < law@netscape.com>< / li >
< li >
Service Manager and Repository: Merge them< / li >
< li >
Convenience function to createIntance(progid)< / li >
< li >
1999-02-25 18:11:58 +00:00
Base ProgID in the interface ID header file< / li >
1999-02-25 17:43:02 +00:00
< / ul >
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "What should I do" > < / a > What should I do< / h3 >
1999-02-25 17:43:02 +00:00
< ul > < b > < font color = "#CC0000" > Component Developers< / font > < / b >
< ul >
< li >
< font color = "#000000" > Implement < tt > NSRegisterSelf()< / tt > for your component
dlls. Sample implementation that perf< / font > < a href = "http://cvs-mirror.mozilla.org/webtools/lxr/source/modules/libpref/src/nsPref.cpp#608" > http://cvs-mirror.mozilla.org/webtools/lxr/source/modules/libpref/src/nsPref.cpp#608< / a > < / li >
< li >
Use < tt > nsRepository::RegisterComponent()< / tt > instead of < tt > nsRepository::RegisterFactory()< / tt >
if your component lives in a DLL< / li >
< li >
< b > Dont use static constructors in loadable components< / b > < / li >
< / ul >
< p > < br > < b > < font color = "#CC0000" > Component Users< / font > < / b >
< ul >
< li >
Create all your components using ProgID rather than CID< / li >
< li >
If you were thinking of managing creation of components yourself, think
again. The repository may be already doing that.< / li >
< / ul >
< / ul >
< h3 >
1999-05-28 20:38:16 +00:00
< hr WIDTH = "100%" > < a NAME = "Issues" > < / a > Issues< / h3 >
1999-02-01 22:17:58 +00:00
< ul >
< li >
Need support for questions like:< / li >
< ol >
< li >
Enumerate all CLSIDs that implement a particular interface.< / li >
< li >
1999-02-03 06:58:44 +00:00
Let a particular CLSID be the preferable implementation for an interface.< / li >
1999-02-01 22:17:58 +00:00
1999-02-03 06:58:44 +00:00
< br > I dont know how this a XPCOM component user could use it unless there
could be a call like:
< br > < tt > nsIRepository::CreateInstance(< b > NULL< / b > , NULL, nsWidgetIID, & result);< / tt >
1999-02-01 22:17:58 +00:00
< li >
Enumerate all interfaces supported by a CLSID< / li >
< / ol >
< li >
Resolving naming conflicts between ProgID< / li >
< li >
Store component specific name-values under < tt > ROOTKEY_COMMON\\< i > component.class.version\\< / i > < / tt > < / li >
< li >
1999-02-03 06:58:44 +00:00
Add a registry entry under < tt > ROOTKEY_COMMON\\< i > component.class.version\\< / i > < / tt > to
indicate the willingness for a CLSID to behave as a Service.< / li >
1999-02-01 22:17:58 +00:00
< li >
Add quick registration support functions in < tt > nsIRepository< / tt > for
components to use.< / li >
< li >
Is the hierarchy < tt > ROOTKEY_COMMON\\< b > Classes< / b > \\CLSID < / tt > acceptable.< / li >
1999-02-25 17:43:02 +00:00
< li >
1999-02-25 18:11:58 +00:00
Should we have a nsIRepository. JS needs it so that they can reflect it
in javascript. Remove & 's in the API.< / li >
1999-02-01 22:17:58 +00:00
< / ul >
< hr WIDTH = "100%" >
1999-02-25 17:43:02 +00:00
< br > < i > < font size = -1 > Last Modified: 28 Jan 1998< / font > < / i >
1999-02-01 22:17:58 +00:00
< br > < font size = -1 > < i > Feedback to: < / i > < a href = "news:netscape.public.mozilla.xpcom" > netscape.public.mozilla.xpcom< / a > < / font >
< / body >
< / html >