Astuce

Comment tester pour un type MIME en javascript?

testMimeType('audio/ogg');

function testMimeType(mimeType) {
  // note: mimeType in navigator.mimeTypes doesn't work
  if (navigator.mimeTypes[mimeType] && navigator.mimeTypes[mimeType].enabledPlugin) {
    alert("Mime type '" + mimeType + "' is supported by plugins " + navigator.mimeTypes[mimeType].enabledPlugin.name);
    return true;
  }
  return false;
}

pref

user_pref("plugin.dont_try_safe_calls", true); // default is false

Mecanisms

Loading the browser:
1. Search in MOZ_PLUGIN_PATH environment variables
   /usr/lib/iceweasel/plugins/, $HOME/.mozilla/plugins, ...

2. The first plugin registred for a mimetype win this mimetype.

Loading a file:

1. Get Mimetype
2. Check for plugins (in navigator.mimeTypes)
   2.1 For first loading, call NP_Initialize()
   2.2 Call NPP_New(argc, argn, argv) (create new instance of the plugin with it's attribute + param elements)
   2.3 When you close the window or change the page, NPP_Destroy is called.
   2.4 When there is no other instance of a plugin, NPP_Shutdown is called.
3. If no plugins, check for application helper in ???

Tutoriel

Environnement de compilation


Répertoire: monplugin
Nom du fichier .h: monplugin.h
Nom du fichier .cpp: monplugin.cpp
Fichier Makefile: Makefile

Fichier d'entêtes: /usr/include/xulrunner-1.9/stable/npupp.h (inclus npapi.h, npruntime.h, jri.h)
Bibliothèques externes: -L/usr/local/lib/xulrunner-devel-1.9/lib -lplds4 -lplc4 -lnspr4 -lpthread -ldl

Makefile
========

PKGCONFIG=/usr/bin/pkg-config
CPP=g++
pluginname=monplugin

CPPFLAGS+=-fno-rtti -fno-exceptions -Wl,-z,defs -shared
MOZILLA_PLUGIN_CFLAGS=`$(PKGCONFIG) --cflags mozilla-plugin`
MOZILLA_PLUGIN_LIBS=`$(PKGCONFIG) --libs mozilla-plugin`

.PHONY: build clean

build: $(pluginname).so

clean:
	-rm $(pluginname).so

install: $(pluginame).so
	cp $(pluginame).so ~/.mozilla/plugins/

$(pluginname).so: $(pluginname).cpp
	$(CPP) -o $@ $(MOZILLA_PLUGIN_CFLAGS) $(CPPFLAGS) $^ $(MOZILLA_PLUGIN_LIBS)

Information

Type MIME à supporter: __________________
Extension du fichier:  __________________ (1 ou plusieurs pour un même type MIME)
Nom du plugin:         __________________ 

Type MIME 2 à supporter: ________________
Extension du fichier:  __________________ (1 ou plusieurs pour un même type MIME)
Nom du plugin:         __________________ 

...

Fonctions à implémenter

  • NP_GetMIMEDescription
  • NP_Initialize
  • NP_GetValue
  • NP_Shutdown
#include "npupp.h"

char *
NP_GetMIMEDescription(void) 
{ 
  // return a string of mimetype:extensions:name
  // if many, separate each by a semicolon ;
  return "application/x-monplugin:mpi:Mon plugin;application/x-monplugin2:mpi2:Mon plugin 2"; 
}

#ifdef XP_UNIX
NPError 
NP_Initialize(NPNetscapeFuncs* aNPNFuncs, 
              NPPluginFuncs* aNPPFuncs) 
{
  return NPERR_NO_ERROR;
}

NPError 
NP_GetValue(void *instance, 
            NPPVariable aVariable, 
            void *aValue)
{
  // instance is null
  return NPERR_NO_ERROR;
}
#endif

NPError OSCALL 
NP_Shutdown()
{
  return NPERR_NO_ERROR;
}

Si on compile se code avec la commande make, et qu'on installe le fichier .so dans le répertoire ~/.mozilla/plugins/, on peut le tester avec un navigateur. Avec ce code, le plugin ne fera absolument rien, mais on peut déjà le voir avec l'adresse about:plugins. Noter que le titre du plugin est le nom du fichier et que la description est vide puisque nous ne l'avons pas spécifié dans la fonction NP_GetValue.

Ajouter des informations sur le plugins

Voici comment ajouter le titre et la description du plugins. Il faut ajouter les propriétés dans la fonction NP_GetValue. Le titre sera définit si la variable est NPPVpluginNameString et la description si la variable est NPPVpluginDescriptionString. Dans les autres cas, on retourne la valeur NPERR_INVALID_PARAM.

NPError 
NP_GetValue(void *instance, 
            NPPVariable aVariable, 
            void *aValue)
{
  // instance is null
  NPError err = NPERR_NO_ERROR;
  switch(aVariable) {
    case NPPVpluginNameString:
      *((const char **)aValue) = "Mon Plugin";
    break;
    case NPPVpluginDescriptionString:
      *((const char **)aValue) = "La description de <a href='http://yansanmo.no-ip.org/a/npapi'>mon plugin</a>";
    break;
    default: 
      err = NPERR_INVALID_PARAM;
  }
  return err;
}

Pour voir notre changement, nous devons installer le nouveau plugin, arrêter le navigateur, supprimer le cache pluginreg.dat de notre profil et relancer le navigateur. Lancer la commande javascript:navigator.plugins.refresh(true); ne fonctionne pas puisque seul les fichiers .xpt sont relus.

HTML

Pour insérer notre plug-in dans une page HTML, on peut écrire ce code.

<object height="100" width="100" style="border: 1px solid red;" type="application/x-monplugin">
</object>

Sur Iceweasel et epiphany, je vois seulement une ligne rouge de 20px de haut environ.

Afficher "Hello World"

Dans cet exemple, nous allons utiliser Gtk+2 et XEmbed pour écrire un GtkLabel dans notre fenêtre. Nous avons besoin du toolkit Gtk+2 et de la technologie XEmbed. Pour utiliser ces technologies, il est primordial de les détecter et d'indiquer au navigateur que nous les utilisons. Ensuite nous devons spécifier toutes les fonctions que le navigateur peut appeler pour parler à notre plugin.

Premièrement, nous avons besoin de savoir si le navigateur supporte XEmbed et l'utilisation d'un toolkit. La fonction CallNPN_GetValueProc sera utilisée dans notre fonction d'initialisation NP_Initialize(). On regarde pour les propriétés NPNVSupportsXEmbedBool et NPNVToolkit. La fonction aNPNFuncs.getvalue() sera appelé pour obtenir les valeurs. Nous voulons que la valeur de supportsXEmbed soit PR_TRUE (vrai) et que le toolkit soit NPNVGtk2 (GTK+ 2)

NPError 
NP_Initialize(NPNetscapeFuncs* aNPNFuncs, 
              NPPluginFuncs* aNPPFuncs) 
{
  NPError err = NPERR_NO_ERROR;
  PRBool supportsXEmbed = PR_FALSE;
  NPNToolkitType toolkit = (NPNToolkitType) 0;

  if (aNPNFuncs == NULL || aNPPFuncs == NULL)
    return NPERR_INVALID_FUNCTABLE_ERROR;

  err = CallNPN_GetValueProc(aNPNFuncs->getvalue, NULL,
                             NPNVSupportsXEmbedBool,
                             (void *)&supportsXEmbed);
  if (err != NPERR_NO_ERROR || supportsXEmbed != PR_TRUE)
    return NPERR_INCOMPATIBLE_VERSION_ERROR;

  err = CallNPN_GetValueProc(aNPNFuncs->getvalue, NULL,
                             NPNVToolkit,
                             (void *)&toolkit);
  if (err != NPERR_NO_ERROR || toolkit != NPNVGtk2)
      return NPERR_INCOMPATIBLE_VERSION_ERROR;

  return err;
}

Deuxièmement, nous devons spécifier au navigateur que nous utilisons XEmbed. Nous allons le spécifier dans la fonction NP_GetValue, à la suite des constantes nom et description du plugin. Il suffit de retourner la valeur PR_TRUE (de type PRBool) lorsque la variable est NPPVpluginNeedsXEmbed. Noter que j'ai ajouté des constantes (#define) pour le nom et la description de mon plugin. Le navigateur va demander cette variable dans le fichier modules/plugin/base/src/nsPluginNativeWindowGtk2.cpp de XulRunner.

#define MONPLUGIN_NAME "Mon Plugin"
#define MONPLUGIN_DESCRIPTION "La description de <a href='http://yansanmo.no-ip.org/a/npapi'>mon plugin</a>"

NPError 
NP_GetValue(void *instance, 
            NPPVariable aVariable, 
            void *aValue)
{
  // instance is null
  NPError err = NPERR_NO_ERROR;
  switch(aVariable) {
    case NPPVpluginNameString:
      *((const char **)aValue) = MONPLUGIN_NAME;
    break;
    case NPPVpluginDescriptionString:
      *((const char **)aValue) = MONPLUGIN_DESCRIPTION;
    break;
    case NPPVpluginNeedsXEmbed:
      *((PRBool *)aValue) = PR_TRUE;
    break;
    default:
      err = NPERR_INVALID_PARAM;
  }
  return err;
}

Troisièmement, le navigateur utilise une table de fonctions pour communiquer avec notre applet. Lors de l'initialisation de notre plugin avec la fonction NP_Initialize(), le deuxième paramètre contient une liste de toutes les fonctions à définir. Voici la structure définit dans le fichier npupp.h:

typedef struct _NPPluginFuncs {
    uint16 size;
    uint16 version;
    NPP_NewUPP newp;
    NPP_DestroyUPP destroy;
    NPP_SetWindowUPP setwindow;
    NPP_NewStreamUPP newstream;
    NPP_DestroyStreamUPP destroystream;
    NPP_StreamAsFileUPP asfile;
    NPP_WriteReadyUPP writeready;
    NPP_WriteUPP write;
    NPP_PrintUPP print;
    NPP_HandleEventUPP event;
    NPP_URLNotifyUPP urlnotify;
    JRIGlobalRef javaClass;
    NPP_GetValueUPP getvalue;
    NPP_SetValueUPP setvalue;
} NPPluginFuncs;

Pour simplifier le contenu de la fonction d'initialisation du plugin (NP_Initialize), une petite fonction utilitaire sera créée pour remplir correctement cette structure avec nos valeurs. Chaque fonction commencant par "monplugin_..." devra être créée par la suite et ajoutée avant la fonction "monplugin_fill_plugin_funcs".

NPError
monplugin_fill_plugin_funcs(NPPluginFuncs* aNPPFuncs)
{
  if (aNPPFuncs == NULL)
    return NPERR_INVALID_FUNCTABLE_ERROR;

  // attributes
  aNPPFuncs->size          = sizeof(NPPluginFuncs);
  aNPPFuncs->version       = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
  aNPPFuncs->javaClass     = NULL;

  // functions
  aNPPFuncs->newp          = NewNPP_NewProc(monplugin_New);
  aNPPFuncs->destroy       = NewNPP_DestroyProc(monplugin_Destroy);
  aNPPFuncs->setwindow     = NewNPP_SetWindowProc(monplugin_SetWindow);
  aNPPFuncs->newstream     = NewNPP_NewStreamProc(monplugin_NewStream);
  aNPPFuncs->destroystream = NewNPP_DestroyStreamProc(monplugin_DestroyStream);
  aNPPFuncs->asfile        = NewNPP_StreamAsFileProc(monplugin_StreamAsFile);
  aNPPFuncs->writeready    = NewNPP_WriteReadyProc(monplugin_WriteReady);
  aNPPFuncs->write         = NewNPP_WriteProc(monplugin_Write);
  aNPPFuncs->print         = NewNPP_PrintProc(monplugin_Print);
  aNPPFuncs->event         = NewNPP_HandleEventProc(monplugin_HandleEvent);
  aNPPFuncs->urlnotify     = NewNPP_URLNotifyProc(monplugin_URLNotify);
  aNPPFuncs->getvalue      = NewNPP_GetValueProc(monplugin_GetValue);
  aNPPFuncs->setvalue      = NewNPP_SetValueProc(monplugin_SetValue);

  return NPERR_NO_ERROR;
}

// don't forget to call this function inside NP_Initialize
NPError~
NP_Initialize(NPNetscapeFuncs* aNPNFuncs,~
              NPPluginFuncs* aNPPFuncs)~
{
  // [...]
  // add this line
  err = monplugin_fill_plugin_funcs(aNPPFuncs);
  return err;
}

Voici les fonctions vides de "monplugin":

NPError monplugin_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;
  return rv;
}

NPError monplugin_Destroy (NPP instance, NPSavedData** save)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;
  return rv;
}

NPError monplugin_SetWindow (NPP instance, NPWindow* pNPWindow)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;
  return rv;
}

NPError monplugin_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;
  return rv;
}

NPError monplugin_DestroyStream (NPP instance, NPStream *stream, NPError reason)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;
  return rv;
}

void monplugin_StreamAsFile (NPP instance, NPStream* stream, const char* fname)
{
  if(instance == NULL) return;
}

int32 monplugin_WriteReady (NPP instance, NPStream *stream)
{
  if(instance == NULL) return 0x0fffffff;
  int32 rv = 0;
  return rv;
}

int32 monplugin_Write (NPP instance, NPStream *stream, int32 offset, int32 len, void *buffer)
{
  if(instance == NULL) return len;
  int32 rv = 0;
  return rv;
}

void monplugin_Print (NPP instance, NPPrint* printInfo)
{
  if(instance == NULL) return;
}

int16 monplugin_HandleEvent(NPP instance, void* event)
{
  if(instance == NULL) return 0;
  int16 rv = 0;
  return rv;
}

void monplugin_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
{
  if(instance == NULL) return;
}

NPError monplugin_GetValue(NPP instance, NPPVariable variable, void *value)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  return NP_GetValue(instance, variable, value); // reuse already code function
}

NPError monplugin_SetValue(NPP instance, NPNVariable variable, void *value)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;
  return rv;
}

Quatrièmement, nous devons afficher l'étiquette et l'ajouter dans un container lorsque la fonction "monplugin_SetWindow" sera appelée. Le deuxième paramètre de la fonction "monplugin_SetWindow" est un pointeur vers la structure NPWindow définit dans le fichier d'entête "npapi.h".

typedef struct _NPWindow
{
  void* window;  /* Platform specific window handle */
                 /* OS/2: x - Position of bottom left corner  */
                 /* OS/2: y - relative to visible netscape window */
  int32 x;       /* Position of top left corner relative */
  int32 y;       /* to a netscape page.         */
  uint32 width;  /* Maximum window size */
  uint32 height;
  NPRect clipRect; /* Clipping rectangle in port coordinates */
                   /* Used by MAC only.       */
#if defined(XP_UNIX) && !defined(XP_MACOSX)
  void * ws_info; /* Platform-dependent additonal data */
#endif /* XP_UNIX */
  NPWindowType type; /* Is this a window or a drawable? */
} NPWindow;

Sur linux, pNPWindow->window est définit comme suit:

pNPWindow  : type NPWindow
- window   : type Window  (peut-être convertit en guint pour afficher le XID)
- width    : uint32 (ou PRInt32)
- height   : uint32 (ou PRInt32)

Lors de l'applet de la fonction, nous pouvons être dans 5 états:

  1. pNPWindow est NULL. On ne doit rien faire.
  2. Lors de l'initialisation, le plugin peut être caché (attribut hidden="true" ou avoir une taille de 0 par 0). Si SetWindow est appelé, pNPWindow->window devrait être égal à 0, sinon, on devrait envoyer une erreur.
  3. pNPWindow->window n'est pas 0 et est égal à l'ancienne fenêtre de le plugin: le plugin vient de changer de dimension ou de position.
  4. pNPWindow->window n'est pas 0 et l'ancienne fenêtre n'a jamais été vue: nous devons dessiner la fenêtre.
  5. La fenêtre du plugin a changée.

Nous devons garder une référence à cette fenêtre et un des meilleurs moyen est de créer une classe qui sera attachée à notre instance de plugin.

class MonPlugin {
  public:
    MonPlugin();
    virtual ~MonPlugin();    

    // setter and getter of mWindow
    Window getWindow() { return mWindow; }
    void setWindow(Window aWindow) { mWindow = aWindow; }
    void createGui(NPWindow* pNPWindow);

  private:
    Window mWindow;
    GtkWidget *plug;
    GtkWidget *label;
};

MonPlugin::MonPlugin() : mWindow(0) 
{
}

MonPlugin::~MonPlugin()
{
}

void
MonPlugin::createGui(NPWindow* pNPWindow)
{
  setWindow ((Window) pNPWindow->window);
  plug = gtk_plug_new ((GdkNativeWindow) mWindow);
  gtk_widget_realize (plug);
  label = gtk_label_new ("Hello World");
  gtk_container_add (GTK_CONTAINER (plug), label);

  gtk_widget_show_all (plug);
}

Dans notre fonction monplugin_New(), nous allons construire un objet MonPlugin et le placer dans une variable du paramètre "instance", pdata. pdata peut contenir un pointeur vers un peu n'importe quoi. Dans la fonction monplugin_Destroy(), nous allons détruire l'objet MonPlugin. Et dans la fonction monplugin_SetWindow, nous allons utiliser la variable mWindow de l'objet MonPlugin().

NPError monplugin_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;

  MonPlugin *monplugin = new MonPlugin();
  if (!monplugin) {
    return NPERR_OUT_OF_MEMORY_ERROR;
  }

  instance->pdata = reinterpret_cast<void*> (monplugin);
  return rv;
}

NPError monplugin_Destroy (NPP instance, NPSavedData** save)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;

  MonPlugin *monplugin = reinterpret_cast<MonPlugin *> (instance->pdata);
  if (!monplugin) {
    delete monplugin;
  }
  instance->pdata = NULL;
  return rv;
}

NPError monplugin_SetWindow (NPP instance, NPWindow* pNPWindow)
{
  if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR;
  NPError rv = NPERR_NO_ERROR;

  if (pNPWindow != NULL) {
    // 5 conditions here:
    // 1. pNPWindow != 0 and mWindow = 0: it's time to create a GtkPlug
    // 2. pNPWindow != 0 and pNPWindow != 0
    //       are the same: the window move...
    //    3. aren't the same, problem here...
    // 4. pNPWindow = 0 and mWindow = 0: do nothing
    // 5. pNPWindow = 0 and mWindow != 0, the window has been deleted?
    MonPlugin *monplugin = reinterpret_cast<MonPlugin *> (instance->pdata);
    if (!monplugin)
      return NPERR_INVALID_INSTANCE_ERROR;

    Window monpluginWindow = monplugin->getWindow();
    if (pNPWindow->window != 0) {
      if (monpluginWindow == 0) {
        // create the control here
        monplugin->createGui(pNPWindow);
      } else if (monpluginWindow == (Window) pNPWindow->window) {
        // the plugin move...
      } else {
        // the plugin lost the previous window?
      }
    } else { // window is null
      if (monpluginWindow == 0) {
        // do nothing
      } else {
        // the window has been deleted?
      }
    }
  }
  return rv;
}

Finalement, nous devons ajouter les fichiers d'entêtes .h pour la déclaration des objets Gtk+ et X (Window), et ajouter les bibliothèques et symboles de compilation dans le Makefile.

// fichier monplugin.h
#include <gtk/gtk.h>
#include <X11/Xlib.h>
// fichier Makefile
GTK_CFLAGS=`$(PKGCONFIG) --cflags gtk+-2.0`
GTK_LIBS=`$(PKGCONFIG) --libs gtk+-2.0`

$(pluginname).so: $(pluginname).cpp
	$(CPP) -o $@ $(MOZILLA_PLUGIN_CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) $^ $(MOZILLA_PLUGIN_LIBS) $(GTK_LIBS)

Lors d'un test avec une page HTML, j'ai un rectangle gris de 100x100 avec Hello World à droite et coupé. Je ne vois que "Hello Wo" car les coordonnées Haut, Gauche (left,top) de l'étiquette (label) sont au centre de la zone du plugin. Noté aussi que le plugin efface la bordure de l'objet.

Utiliser Javascript à l'aide de NPRuntime

Le plugin permet d'exposer des attributs à l'aide de NPRuntime. Pour ce faire, le plugin doit indiquer qu'il supporte la propriété NPPVpluginScriptableNPObject dans la fonction monplugin_GetValue() et retourner un objet (NPObject *) non null (nsnull). Puisque monplugin_GetValue appelle NP_GetValue, nous allons ajouter le code seulement dans la fonction NP_GetValue. Lorsqu'il une instance et aucun plugin, nous allons retourner une erreur NPERR_INVALID_PLUGIN_ERROR.

NPError
NP_GetValue(void *instance,
            NPPVariable aVariable,
            void *aValue)
{
  // [...]
    case NPPVpluginScriptableNPObject:
      if (instance != NULL) {
         MonPlugin *monplugin = reinterpret_cast<MonPlugin *> (instance->pdata);
         if (monplugin) {
           err = monplugin->getScriptableNPObject(aValue);
         } else {
           err = NPERR_INVALID_PLUGIN_ERROR;
         }   
      }   
    break;
  // [...]
}

Il faut créer la méthode NPError getScriptableNPObject(void *aValue); dans la classe MonPlugin. Puisque nous allons passé en référence un objet du plugin au navigateur, nous devons spécifier au navigateur de retenir l'objet. Pour ce faire, nous devons utiliser la méthode retainobject des méthodes globales du navigateur. Cette méthode est spécifiée dans le pointeur aNPNFuncs de la fonction NP_Initialize(). Nous devons aussi utiliser cette fonction ailleurs que dans la fonction NP_Initialize(), nous allons enregistrer dans une structure globale la liste des fonctions. Nous allons aussi utiliser le fichier d'entête npruntime.h

#include <npruntime.h>
NPNetscapeFuncs NPNFuncs;

NPError 
NP_Initialize(NPNetscapeFuncs* aNPNFuncs, 
              NPPluginFuncs* aNPPFuncs) 
{
  // [...]
  memcpy(&NPNFuncs, aNPNFuncs, sizeof(NPNetscapeFuncs));
  NPNFuncs.size = sizeof(NPNetscapeFuncs);
  // [...]
}


class MonPlugin {
  public:
  // [...]
    NPError getScriptableNPObject(void *aValue);
}

NPObject *NPN_RetainObject(NPObject *obj)
{
  return NPNFuncs.retainobject(obj);
}

NPError
MonPlugin::getScriptableNPObject(void *aValue)
{
  NPError err = NPERR_NO_ERROR;
  NPObject *scriptable = // TBD ...
  if (!scriptable)
    return NPERR_GENERIC_ERROR;

  NPN_RetainObject (scriptable);

  *reinterpret_cast>NPObject **<(aValue) = scriptable;
  return err;
}

Test

make
ldd -r monplugin.so

Note


Hyperliens:
  Documentation
    http://developer.mozilla.org/en/Plugins

  Références
    http://www.xulplanet.com/references/objref/PluginArray.html
    http://developer.mozilla.org/en/Navigator.plugins
    http://www.xulplanet.com/references/objref/Plugin.html
    http://www.xulplanet.com/references/objref/MimeType.html
    http://developer.mozilla.org/en/XEmbed_Extension_for_Mozilla_Plugins
    Totem source code

  Tests:
    http://yansanmo.no-ip.org/test/js/navigator_plugins.html


Totem utilise la fonction gnome_vfs_mime_get_description(mimetype) pour retrouver la description du plugin.
Cette fonction recherche dans le fichier "/usr/share/mime/{mimetype}.xml"

Exemple pour audio/ogg (/usr/share/mime/audio/ogg.xml)

<?xml version="1.0" encoding="utf-8"?>
<mime-type xmlns="http://www.freedesktop.org/standards/shared-mime-info" type="audio/ogg">
  <!--Created automatically by update-mime-database. DO NOT EDIT!-->
  <comment>Ogg Audio</comment>
  <comment xml:lang="fr">audio Ogg</comment>
  <sub-class-of type="application/ogg"/>
  <alias type="audio/x-ogg"/>
</mime-type>

/usr/bin/update-mime-database est un exécutable (paquet shared-mime-info)
Les descriptions en français se retrouve dans: /usr/share/locale/fr/LC_MESSAGES/shared-mime-info.mo
Ou encore: http://translationproject.org/latest/shared-mime-info/fr.po
http://www.freedesktop.org/wiki/Software/shared-mime-info