Click here to Skip to main content
Click here to Skip to main content

Working on an NPAPI-browser plugin

, 8 Jul 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
Extending the NPAPI-runtime to a Firefox plugin.

Screenshot.png

Introduction

A browser plug-in has the same rights as a window application on the PC. Plug-ins empower websites with rich features like as graphics, or extended computation, or system access. But it also opens the doors for criminals. So everyone has to be aware which browser plug-in gets installed in their systems. As some Google guys wrote, NPAPI is a real hammer.

This sample is based on a sample npruntime of the Gecko SDK. So all credits go there; I have only extended in the way I want to show the power and share my knowledge; my additions to the original files are all marked with kk (I hope so).

Background

The power of NPAPI is that it can work in Mozilla Firefox, Google Chrome, and Apple Safari, and is also cross platform. But that doesn't come for free; there are some installation steps to do. My plug-in now only works in Firefox on Windows. To compile the code, you need the Gecko SDK.

Using the sample

You need to copy the DLL in the plug-ins directory of Mozilla Firefox and than open Codeproject.html in the project with Firefox.

Using the code

The code is, as mentioned, based on the npruntime sample, and extended in some ways. The skeleton is setting up and initializing a plug-in which can accessed from websites.

To get a plug-in running, you have to deal with at least three languages: HTML, JavaScript, and C++.

To include a plug-in in a website, you only need this HMTL-code:

<embed type="application/npcodeproject" width=800 height=200 id="plugin">

To use the plug-in, the plug-in must be carefully installed on a PC that includes all the required stuff like DLLs. So the best thing to do is to link statically. Mozilla Firefox installs plug-ins in its "plugins" directory.

To access the plug-in from JavaScript, you need a reference to the instance of the plug-in. If it doesn't work, then something has went wrong. Dealing with that case is really a pain.

var PLUGIN = document.getElementById('plugin');

Writing the plug-in is done in C++. At first, you need to understand the startup of the plug-in: getting function pointers from the Netscape Plug-in API to work. Callback communications are done via these functions.

NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs)
{
  if(pFuncs == NULL)
    return NPERR_INVALID_FUNCTABLE_ERROR;

  if(pFuncs->size < sizeof(NPPluginFuncs))
    return NPERR_INVALID_FUNCTABLE_ERROR;

  pFuncs->version       = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
  pFuncs->newp          = NPP_New;
  pFuncs->destroy       = NPP_Destroy;
  pFuncs->setwindow     = NPP_SetWindow;
  pFuncs->newstream     = NPP_NewStream;
  pFuncs->destroystream = NPP_DestroyStream;
  pFuncs->asfile        = NPP_StreamAsFile;
  pFuncs->writeready    = NPP_WriteReady;
  pFuncs->write         = NPP_Write;
  pFuncs->print         = NPP_Print;
  pFuncs->event         = NPP_HandleEvent;
  pFuncs->urlnotify     = NPP_URLNotify;
  pFuncs->getvalue      = NPP_GetValue;
  pFuncs->setvalue      = NPP_SetValue;
  pFuncs->javaClass     = NULL;

  return NPERR_NO_ERROR;
}

NPError OSCALL
NP_Initialize(NPNetscapeFuncs* pFuncs
#ifdef XP_UNIX
              , NPPluginFuncs* pluginFuncs
#endif
              )
{
  if(pFuncs == NULL)
    return NPERR_INVALID_FUNCTABLE_ERROR;

  if(HIBYTE(pFuncs->version) > NP_VERSION_MAJOR)
    return NPERR_INCOMPATIBLE_VERSION_ERROR;

  if(pFuncs->size < sizeof(NPNetscapeFuncs))
    return NPERR_INVALID_FUNCTABLE_ERROR;

  NPNFuncs.size                    = pFuncs->size;
  NPNFuncs.version                 = pFuncs->version;
  NPNFuncs.geturlnotify            = pFuncs->geturlnotify;
  NPNFuncs.geturl                  = pFuncs->geturl;
  NPNFuncs.posturlnotify           = pFuncs->posturlnotify;
  NPNFuncs.posturl                 = pFuncs->posturl;
  NPNFuncs.requestread             = pFuncs->requestread;
  NPNFuncs.newstream               = pFuncs->newstream;
  NPNFuncs.write                   = pFuncs->write;
  NPNFuncs.destroystream           = pFuncs->destroystream;
  NPNFuncs.status                  = pFuncs->status;
  NPNFuncs.uagent                  = pFuncs->uagent;
  NPNFuncs.memalloc                = pFuncs->memalloc;
  NPNFuncs.memfree                 = pFuncs->memfree;
  NPNFuncs.memflush                = pFuncs->memflush;
  NPNFuncs.reloadplugins           = pFuncs->reloadplugins;
  NPNFuncs.getJavaEnv              = NULL;
  NPNFuncs.getJavaPeer             = NULL;
  NPNFuncs.getvalue                = pFuncs->getvalue;
  NPNFuncs.setvalue                = pFuncs->setvalue;
  NPNFuncs.invalidaterect          = pFuncs->invalidaterect;
  NPNFuncs.invalidateregion        = pFuncs->invalidateregion;
  NPNFuncs.forceredraw             = pFuncs->forceredraw;
  NPNFuncs.getstringidentifier     = pFuncs->getstringidentifier;
  NPNFuncs.getstringidentifiers    = pFuncs->getstringidentifiers;
  NPNFuncs.getintidentifier        = pFuncs->getintidentifier;
  NPNFuncs.identifierisstring      = pFuncs->identifierisstring;
  NPNFuncs.utf8fromidentifier      = pFuncs->utf8fromidentifier;
  NPNFuncs.intfromidentifier       = pFuncs->intfromidentifier;
  NPNFuncs.createobject            = pFuncs->createobject;
  NPNFuncs.retainobject            = pFuncs->retainobject;
  NPNFuncs.releaseobject           = pFuncs->releaseobject;
  NPNFuncs.invoke                  = pFuncs->invoke;
  NPNFuncs.invokeDefault           = pFuncs->invokeDefault;
  NPNFuncs.evaluate                = pFuncs->evaluate;
  NPNFuncs.getproperty             = pFuncs->getproperty;
  NPNFuncs.setproperty             = pFuncs->setproperty;
  NPNFuncs.removeproperty          = pFuncs->removeproperty;
  NPNFuncs.hasproperty             = pFuncs->hasproperty;
  NPNFuncs.hasmethod               = pFuncs->hasmethod;
  NPNFuncs.releasevariantvalue     = pFuncs->releasevariantvalue;
  NPNFuncs.setexception            = pFuncs->setexception;
  
  //end of snippet

If these functions get called, the plug-in runs, and the special implementation does stuff like getting and setting properties. But before you can do that, check if the "Has" callback is implemented.

if( !strcmp( "Name", pProp ) )
{
    //allocating Memory for the string with invocation of Browser-API
    char *p = (char*) NPN_MemAlloc( m_pszName );
    STRINGZ_TO_NPVARIANT( p, *result);
    return true;
}

The interesting point here is that allocating memory with the NPAPI is needed to create the result strings. It took me some time to figure that out. The handy macro STRINGZ_TO_NPVARIANT helps in the output.

The real functionality is done via the Invoke interface. As seen, it is possible to call my "Add" function with strings and numbers mixed and also a varying count of arguments. But that is done in the tuning of the interface.

bool ScriptablePluginObject::Invoke(NPIdentifier name, 
     const NPVariant *args, uint32_t argCount, NPVariant *result)
{
    //kk
    char *pFunc = NPN_UTF8FromIdentifier(name);

    if( !strcmp( "Add", pFunc ) )
    {
        int sum = 0;

        for( unsigned int i = 0; i < argCount; i++ )
        {
            if( args[i].type == NPVariantType_Int32 )
            {
                sum += args[i].value.intValue;
            }
            else if( args[i].type == NPVariantType_String )
            {
                CNPString s(args[i].value.stringValue);
                sum += atoi( s );
            }
            else return false;//an error happenend

        }

Points of interest

It is really interesting to see how the plug-ins work under the hood. Sometimes it gets mysterious when it doesn't work. I am not happy that it doesn't work with Google Chrome and Apple Safari, but I do not have the time to figure it out now.

Internet Explorer doesn't support the NPAPI, so you will need ActiveX for it. But this should be considered in the architecture of the code.

If you write a plug-in, don't forget to set the accurate MIMEType in NPP_GetMIMEDescription() and the rc file, or you will have a headache for hours. If you can't compile the code because a lot of Windows types are unknown, include the Windows headers first!!!

The output DLL has to be named in the "np*.dll" format to be a Firefox plug-in, and goes to to the plugins directory of Firefox: "C:\Program Files\Mozilla Firefox\plugins".

Some interesting links

History

  • 08/07/2010: Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

KarstenK
Software Developer
Germany Germany
I am living in germany and now living from programming for some Years. In my spare time I like sports as jogging, playing football (soccer) and basketball.
 
We must take care for our planet, because we and our family has no other. And everybody has to do something for it.

Comments and Discussions

 
Questionis this code working? PinmemberJayShin15-Jan-12 15:25 
AnswerRe: is this code working? PinmemberKarstenK15-Jan-12 22:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 8 Jul 2010
Article Copyright 2010 by KarstenK
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid