Building a firefox plugin – part two

May 26, 2009 53 Comments by Richard

Note: For a better way to create a Browser Plugin (that will work on all browsers, not just NPAPI), check out the FireBreath project.

Getting help: For a better place to ask NPAPI-related questions, go to StackOverflow and make sure to use the “npapi” and/or “firebreath” tags.

Recap

Last time, I talked about the fundamentals of implementing a NPAPI plugin.  Today, I’m going to go into a little more detail on how to use the strange callback architecture that firefox exposes.

The NPAPI uses a rather unique (in my experience) methodology for providing an interface between the plugin and browser, but it does have some rather significant advantages.  Instead of providing binaries to be linked against, the browser simply expects you to implement the entry point NP_Initialize in your dll that the browser will call and tell you where the methods that you will be using are located.  In order for the browser to interface with you, it calls NP_GetEntryPoints with a pointer to a structure for your plugin to fill out with function pointers that the browser will be calling on your plugin.

This removes the need for a large number of include and library files in your project.  In fact, in practice I found that I only actually needed 12 include files from the Gecko SDK:

  • jni.h
  • jni_md.h
  • jri.h
  • jritypes.h
  • jri_md.h
  • npapi.h
  • npruntime.h
  • nptypes.h
  • npupp.h
  • obsolete/protypes.h
  • prcpucfg.h
  • prtypes.h

Edit (Sep 16, 2010): Now over a year later there is a better way to do this.  There is a Google Code project called npapi-headers that has just the header files you need — and they actually work on all browsers, all platforms (unlike the ones from the gecko SDK sometimes).

Basic Lifecycle of a plugin:

Jean-Lou Dupont has an excelent visual diagram of this on his blog, which you can find here: http://jldupont.blogspot.com/2009/11/notes-on-npapi-based-plugins.html

The lifecycle of a plugin is actually pretty simple, once you figure it out.  The initial entrypoints NP_Initialize and NP_GetEntryPoints could, according to the documentation, get called in any order; however, in practice, NP_GetEntryPoints seems to get called first on Windows.  With that in mind, here is the order of calls for basic windowed plugin initialization on Windows:

  1. NP_GetEntryPoints – Plugin fills out a function table with the addresses of NPP_NewNPP_DestroyNPP_SetWindow, etc
  2. NP_Initialize – Plugin stores a copy of a function table with the addresses of NPN_CreateObject, NPN_MemAlloc, etc
  3. NPP_New – Plugin creates a new instance of itself and initializes it
  4. *NPP_SetWindow – This is called multiple times for each instance — each time the instance’s window is created, resized, or otherwise changed
  5. NPP_GetValue (Variable = NPPVpluginScriptableNPObject) – Plugin creates a scriptable NPObject and returns a pointer to it (after calling NPN_RetainObject on it)
  6. — Standard Plugin Activity —
  7. NPP_Destroy – Plugin instance is destroyed
  8. NP_Shutdown – All remaining plugin resources are destroyed

It is worth our time to investigate a few of these calls in more detail.

NPError NPP_New(NPMIMEType pluginType, NPP npp, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved)

NPP_New is called once for each instance of the plugin.  Note the difference in terminology: There is one plugin, and there are multiple instances of that plugin.  NP_Initialize is only called once for the plugin — that is, the entire memory space.
From the Gecko SDK source:
/*
 *  NPP is a plug-in's opaque instance handle
 */
typedef struct _NPP
{
  void*pdata;      /* plug-in private data */
  void*ndata;      /* netscape private data */
} NPP_t;

typedef NPP_t*  NPP;

This structure is normally seen as NPP and serves as the handle of a plugin instance. As you can see, there is private data for the plugin and private data for the browser.  When NPP_New is called, you should:

  1. Create some sort of data structure to uniquely identify this instance of the plugin
  2. Assign a pointer to that data structure to npp->pdata

What I like to do is to create a PluginInstance class and use that as my “pdata”.  Then my NPP_New function looks like this:

// Called by the browser to create a new instance of the plugin

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

    NPError rv = NPERR_NO_ERROR;
    PluginInstance* pPluginObj = new PluginInstance(npp);

    if (pPluginObj == NULL)
        return NPERR_OUT_OF_MEMORY_ERROR;

    npp->pdata = pPluginObj;
    return pPluginObj->NpapiNew(pluginType, mode, argc, argn, argv, saved);
}

In this way I can have a PluginInstance class that handles the entire lifecycle of a given instance of a plugin, and I simply create stub static functions to dereference the pdata pointer and call the correct function.

NPP_SetWindow (NPP npp, NPWindow* pNPWindow)

For many, this will be where the real fun starts — this function is called to tell the plugin which window they are in.  From the Gecko SDK (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.			  */
  void * ws_info; /* Platform-dependent additonal data, linux specific */
  NPWindowType type; /* Is this a window or a drawable? */
} NPWindow;

A pointer to this structure is passed in with each call.  On windows, the “void* window” will dereference to an HWND. On other platforms, it will likewise be dereferenced as an appropriate type.

Notice that again NPP npp is the first parameter.  This will be the case on all NPP functions except NPP_New, where the mimetype is also passed in.  Since we created a PluginInstance object and assigned it to npp->pdata in NPP_New, we need to create a small stub function to forward our NPP_New to a method on that object, like so:

// Called by browser whenever the window is changed, including to set up or destroy
NPErrorNPP_SetWindow (NPP npp, NPWindow* pNPWindow)
{
    if (npp == NULL)
        return NPERR_INVALID_INSTANCE_ERROR;
    else if (npp->pdata == NULL)
        return NPERR_GENERIC_ERROR;

    PluginInstance *inst = (PluginInstance *)npp->pdata;
    return inst->NpapiSetWindow(pNPWindow);
}

On windows, when SetWindow is called we need to save the HWND and subclass the window so that we can get our own window event proc.

NPError PluginInstance::NpapiSetWindow (NPWindow* pNPWindow)
{
    NPError rv = NPERR_NO_ERROR;

    if(pNPWindow == NULL)
        return NPERR_GENERIC_ERROR;

    // window just created; in initWindow, set initialized to true
    if(!this->initialized) {
        if(!this->initWindow(pNPWindow)) {
            return NPERR_MODULE_LOAD_FAILED_ERROR;
        }
    }

    // Window was already created; just pass on the updates
    this->updateWindow(pNPWindow);

    return rv;
}

With these functions, we get notified in one function when the window is first set, and another is called each time an update is made.

NPP_Destroy (NPP npp, NPSavedData** save)

When the Browser is ready to shut down a given plugin instance (the user leaves the page or the object tag is removed from the DOM), the browser calls NPP_Destroy.  This call is responsible to free any memory being used by that plugin instance.

// Called by browser to destroy an instance of the plugin
NPError NPP_Destroy (NPP npp, NPSavedData** save)
{
    if (npp == NULL)
        return NPERR_INVALID_INSTANCE_ERROR;
    else if (npp->pdata == NULL)
        return NPERR_GENERIC_ERROR;

    PluginInstance *inst = (PluginInstance *)npp->pdata;
    NPError rv = inst->NpapiDestroy(save);

    delete getPluginObject(npp);
    npp->pdata = NULL;

    return rv;
}

Next Time

That’s all the time I have today.  Next time I will discuss NPObjects and how they are used to provide an interface by which Javascript can interact with your plugin.

Building a firefox plugin – part one

Building a firefox plugin – part two

Building a firefox plugin – part three

Building a firefox plugin – part four

Getting more help

Update May 14, 2011: Many people have been asking questions in the comments; while I don’t mind that, it would probably be more useful if you ask your question on the FireBreath forums. There is a forum there for those just using NPAPI as well!

45 Comments

  1. taxilian
    8 years ago

    You can find other mostly linux-specific information on this at http://inferno-firefox.blogspot.com/

  2. Richard
    8 years ago

    You can find other mostly linux-specific information on this at http://inferno-firefox.blogspot.com/

  3. murat
    8 years ago

    Hi;

    I work on a audio/video player Firefox plugin on Windows, i have some questions about that.

    1) Should i use DirectShow API or MCI ? in DirectShow i found codecs so i can make a player that support lots of formats but in MCI, supporting formats are too few

    2) How can i embed the playing screen on the browser? I mean that I played the file on browser but in another opened window. I want it to make in browser like quicktime or flash or windows media player firefox plugin.

    Thank you

  4. taxilian
    8 years ago

    Well, I'm having a hard time understanding exactly what you're trying to do. Most of it, however, is personal preference.

    You can get a reference to your HWND from NPP_SetWindow. If you haven't already, download the Mozilla source tree and look at the plugin examples in it.

    Once you have your hwnd, you can draw in it however you wish. (This is when using it as a windowed plugin). It will not participate in the DOM z-order unless you make it windowless, but windowless plugins don't draw nearly as quickly and don't work as well for video.

    Personally, I have used it with DX7 and DX9, but I know of people who have drawn using directshow as well. I've never used MCI, but I imagine that you could use it if you want.

    As to embedding it in the browser, just use an object tag; it'll create the window for you and give you the handle in SetWindow.

  5. murat
    8 years ago

    Hi;

    I work on a audio/video player Firefox plugin on Windows, i have some questions about that.

    1) Should i use DirectShow API or MCI ? in DirectShow i found codecs so i can make a player that support lots of formats but in MCI, supporting formats are too few

    2) How can i embed the playing screen on the browser? I mean that I played the file on browser but in another opened window. I want it to make in browser like quicktime or flash or windows media player firefox plugin.

    Thank you

  6. taxilian
    8 years ago

    Well, I'm having a hard time understanding exactly what you're trying to do. Most of it, however, is personal preference.

    You can get a reference to your HWND from NPP_SetWindow. If you haven't already, download the Mozilla source tree and look at the plugin examples in it.

    Once you have your hwnd, you can draw in it however you wish. (This is when using it as a windowed plugin). It will not participate in the DOM z-order unless you make it windowless, but windowless plugins don't draw nearly as quickly and don't work as well for video.

    Personally, I have used it with DX7 and DX9, but I know of people who have drawn using directshow as well. I've never used MCI, but I imagine that you could use it if you want.

    As to embedding it in the browser, just use an object tag; it'll create the window for you and give you the handle in SetWindow.

  7. murat
    8 years ago

    Sorry for misunderstood.

    I want to play video on browser, my basic thing is this. Yes i did what did u advice but it doesn't work. I am giving some of my function here:

    Setwindow func:

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

    NPError rv = NPERR_NO_ERROR;

    if(pNPWindow == NULL)
    return NPERR_GENERIC_ERROR;

    CPlugin * pPlugin = (CPlugin *)instance->pdata;

    if(pPlugin == NULL)
    return NPERR_GENERIC_ERROR;

    // window just created
    if(!pPlugin->isInitialized() && (pNPWindow->window != NULL)) {
    if(!pPlugin->init(pNPWindow)) {
    delete pPlugin;
    pPlugin = NULL;
    return NPERR_MODULE_LOAD_FAILED_ERROR;
    }
    }

    // window goes away
    if((pNPWindow->window == NULL) && pPlugin->isInitialized())
    return NPERR_NO_ERROR;

    // window resized
    if(pPlugin->isInitialized() && (pNPWindow->window != NULL))
    return NPERR_NO_ERROR;

    // this should not happen, nothing to do
    if((pNPWindow->window == NULL) && !pPlugin->isInitialized())
    return NPERR_NO_ERROR;

    return rv;
    }

    CallBack Function
    —————–
    static LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
    //DirectShow

    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEvent *pEvent = NULL;

    // Initialize the COM library.
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
    printf(“ERROR – Could not initialize COM library”);
    return false;
    }

    // Create the filter graph manager and query for interfaces.
    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder, (void **)&pGraph);
    if (FAILED(hr))
    {
    printf(“ERROR – Could not create the Filter Graph Manager.”);
    return false;
    }

    hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
    hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
    hr = pGraph->RenderFile(L”http://www.fakedomain.com/test.m4a”, NULL);

    if (SUCCEEDED(hr))
    {
    // Run the graph.
    hr = pControl->Run();
    if (SUCCEEDED(hr))
    {
    // Wait for completion.
    long evCode;
    pEvent->WaitForCompletion(INFINITE, &evCode);

    // Note: Do not use INFINITE in a real application, because it
    // can block indefinitely.
    }
    }
    pControl->Release();
    pEvent->Release();
    pGraph->Release();
    CoUninitialize();

    switch (msg) {
    case WM_PAINT:
    {
    // draw a frame and display the string
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    RECT rc;
    GetClientRect(hWnd, &rc);
    FrameRect(hdc, &rc, GetStockBrush(BLACK_BRUSH));
    CPlugin * p = (CPlugin *)GetWindowLong(hWnd, GWL_USERDATA);
    if(p) {
    if (p->m_String[0] == 0) {
    strcpy(“foo”, p->m_String);
    }

    DrawText(hdc, p->m_String, strlen(p->m_String), &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
    }
    EndPaint(hWnd, &ps);
    }
    break;
    default:
    break;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);

    Thank you

  8. murat
    8 years ago

    Sorry for misunderstood.

    I want to play video on browser, my basic thing is this. Yes i did what did u advice but it doesn't work. I am giving some of my function here:

    Setwindow func:

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

    NPError rv = NPERR_NO_ERROR;

    if(pNPWindow == NULL)
    return NPERR_GENERIC_ERROR;

    CPlugin * pPlugin = (CPlugin *)instance->pdata;

    if(pPlugin == NULL)
    return NPERR_GENERIC_ERROR;

    // window just created
    if(!pPlugin->isInitialized() && (pNPWindow->window != NULL)) {
    if(!pPlugin->init(pNPWindow)) {
    delete pPlugin;
    pPlugin = NULL;
    return NPERR_MODULE_LOAD_FAILED_ERROR;
    }
    }

    // window goes away
    if((pNPWindow->window == NULL) && pPlugin->isInitialized())
    return NPERR_NO_ERROR;

    // window resized
    if(pPlugin->isInitialized() && (pNPWindow->window != NULL))
    return NPERR_NO_ERROR;

    // this should not happen, nothing to do
    if((pNPWindow->window == NULL) && !pPlugin->isInitialized())
    return NPERR_NO_ERROR;

    return rv;
    }

    CallBack Function
    —————–
    static LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
    //DirectShow

    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEvent *pEvent = NULL;

    // Initialize the COM library.
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
    printf(“ERROR – Could not initialize COM library”);
    return false;
    }

    // Create the filter graph manager and query for interfaces.
    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder, (void **)&pGraph);
    if (FAILED(hr))
    {
    printf(“ERROR – Could not create the Filter Graph Manager.”);
    return false;
    }

    hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
    hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
    hr = pGraph->RenderFile(L”http://www.fakedomain.com/test.m4a”, NULL);

    if (SUCCEEDED(hr))
    {
    // Run the graph.
    hr = pControl->Run();
    if (SUCCEEDED(hr))
    {
    // Wait for completion.
    long evCode;
    pEvent->WaitForCompletion(INFINITE, &evCode);

    // Note: Do not use INFINITE in a real application, because it
    // can block indefinitely.
    }
    }
    pControl->Release();
    pEvent->Release();
    pGraph->Release();
    CoUninitialize();

    switch (msg) {
    case WM_PAINT:
    {
    // draw a frame and display the string
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    RECT rc;
    GetClientRect(hWnd, &rc);
    FrameRect(hdc, &rc, GetStockBrush(BLACK_BRUSH));
    CPlugin * p = (CPlugin *)GetWindowLong(hWnd, GWL_USERDATA);
    if(p) {
    if (p->m_String[0] == 0) {
    strcpy(“foo”, p->m_String);
    }

    DrawText(hdc, p->m_String, strlen(p->m_String), &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
    }
    EndPaint(hWnd, &ps);
    }
    break;
    default:
    break;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);

    Thank you

  9. murat
    8 years ago

    Sorry for misunderstood.

    I want to play video on browser, my basic thing is this. Yes i did what did u advice but it doesn’t work. I am giving some of my function here:

    Setwindow func:

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

    NPError rv = NPERR_NO_ERROR;

    if(pNPWindow == NULL)
    return NPERR_GENERIC_ERROR;

    CPlugin * pPlugin = (CPlugin *)instance->pdata;

    if(pPlugin == NULL)
    return NPERR_GENERIC_ERROR;

    // window just created
    if(!pPlugin->isInitialized() && (pNPWindow->window != NULL)) {
    if(!pPlugin->init(pNPWindow)) {
    delete pPlugin;
    pPlugin = NULL;
    return NPERR_MODULE_LOAD_FAILED_ERROR;
    }
    }

    // window goes away
    if((pNPWindow->window == NULL) && pPlugin->isInitialized())
    return NPERR_NO_ERROR;

    // window resized
    if(pPlugin->isInitialized() && (pNPWindow->window != NULL))
    return NPERR_NO_ERROR;

    // this should not happen, nothing to do
    if((pNPWindow->window == NULL) && !pPlugin->isInitialized())
    return NPERR_NO_ERROR;

    return rv;
    }

    CallBack Function
    —————–
    static LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
    //DirectShow

    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEvent *pEvent = NULL;

    // Initialize the COM library.
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
    printf(“ERROR – Could not initialize COM library”);
    return false;
    }

    // Create the filter graph manager and query for interfaces.
    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder, (void **)&pGraph);
    if (FAILED(hr))
    {
    printf(“ERROR – Could not create the Filter Graph Manager.”);
    return false;
    }

    hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
    hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
    hr = pGraph->RenderFile(L”http://www.fakedomain.com/test.m4a”, NULL);

    if (SUCCEEDED(hr))
    {
    // Run the graph.
    hr = pControl->Run();
    if (SUCCEEDED(hr))
    {
    // Wait for completion.
    long evCode;
    pEvent->WaitForCompletion(INFINITE, &evCode);

    // Note: Do not use INFINITE in a real application, because it
    // can block indefinitely.
    }
    }
    pControl->Release();
    pEvent->Release();
    pGraph->Release();
    CoUninitialize();

    switch (msg) {
    case WM_PAINT:
    {
    // draw a frame and display the string
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    RECT rc;
    GetClientRect(hWnd, &rc);
    FrameRect(hdc, &rc, GetStockBrush(BLACK_BRUSH));
    CPlugin * p = (CPlugin *)GetWindowLong(hWnd, GWL_USERDATA);
    if(p) {
    if (p->m_String[0] == 0) {
    strcpy(“foo”, p->m_String);
    }

    DrawText(hdc, p->m_String, strlen(p->m_String), &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
    }
    EndPaint(hWnd, &ps);
    }
    break;
    default:
    break;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);

    Thank you

  10. murat
    8 years ago

    Sorry for misunderstood.

    I want to play video on browser, my basic thing is this. Yes i did what did u advice but it doesn't work. I am giving some of my function here:

    Setwindow func:

    NPError NPP_SetWindow (NPP instance, NPWindow* pNPWindow)
    {
    // ….
    // window just created
    if(!pPlugin->isInitialized() && (pNPWindow->window != NULL)) {
    if(!pPlugin->init(pNPWindow)) {
    delete pPlugin;
    pPlugin = NULL;
    return NPERR_MODULE_LOAD_FAILED_ERROR;
    }
    }

    // ….
    }

    CallBack Function
    —————–
    static LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
    //DirectShow

    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEvent *pEvent = NULL;

    // Initialize the COM library.
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
    printf(“ERROR – Could not initialize COM library”);
    return false;
    }

    // Create the filter graph manager and query for interfaces.
    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder, (void **)&pGraph);
    if (FAILED(hr))
    {
    printf(“ERROR – Could not create the Filter Graph Manager.”);
    return false;
    }

    hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
    hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
    hr = pGraph->RenderFile(L”http://www.fakedomain.com/test.m4a”, NULL);

    if (SUCCEEDED(hr))
    {
    // Run the graph.
    hr = pControl->Run();
    if (SUCCEEDED(hr))
    {
    // Wait for completion.
    long evCode;
    pEvent->WaitForCompletion(INFINITE, &evCode);

    // Note: Do not use INFINITE in a real application, because it
    // can block indefinitely.
    }
    }
    pControl->Release();
    pEvent->Release();
    pGraph->Release();
    CoUninitialize();

    switch (msg) {
    case WM_PAINT:
    {
    // draw a frame and display the string
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    RECT rc;
    GetClientRect(hWnd, &rc);
    FrameRect(hdc, &rc, GetStockBrush(BLACK_BRUSH));
    CPlugin * p = (CPlugin *)GetWindowLong(hWnd, GWL_USERDATA);
    if(p) {
    if (p->m_String[0] == 0) {
    strcpy(“foo”, p->m_String);
    }

    DrawText(hdc, p->m_String, strlen(p->m_String), &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
    }
    EndPaint(hWnd, &ps);
    }
    break;
    default:
    break;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);

    Thank you

  11. taxilian
    8 years ago

    Well, the first *major* problem I'm seeing is that PluginWinProc gets called about a billion times a second — every time a message is sent to the window, such as a keypress, a mouse movement or click, a refresh message, or any number of others, that function gets called. Therefore, you most certainly should not be using it to initialize your directshow graph.

    I recommend studying up on what a winproc is, and then do some debugging. With that, I will give you some advice: “It doesn't work” is never useful. It doesn't give me any information to work with. I need to know what doesn't work; I need to know what you have tried and what tests you have done. I am willing — happy even — to help you with your problem, but I better see that you are doing your part as well, and that is how everyone else feels too.

    Set some breakpoints; add some logging messages. Find out what is happening, and what isn't. For instance, does your PluginWinProc even get called? If not, you probably missed the part in the example where it subclasses the window (that part of the code wasn't shown here). Are the return values from your COM calls S_OK?

    Do some digging on your own; when you know what is and isn't running, then come back and ask specific, preferably short, questions to get help with those pieces that you don't understand. Also, please try to avoid posting long code fragments in comments =] Once you have done all that you can do, I will be happy to continue to help you; I just need more than “Yes i did what did u advice but it doesn't work.”

    =]

  12. taxilian
    8 years ago

    Well, the first *major* problem I'm seeing is that PluginWinProc gets called about a billion times a second — every time a message is sent to the window, such as a keypress, a mouse movement or click, a refresh message, or any number of others, that function gets called. Therefore, you most certainly should not be using it to initialize your directshow graph.

    I recommend studying up on what a winproc is, and then do some debugging. With that, I will give you some advice: “It doesn't work” is never useful. It doesn't give me any information to work with. I need to know what doesn't work; I need to know what you have tried and what tests you have done. I am willing — happy even — to help you with your problem, but I better see that you are doing your part as well, and that is how everyone else feels too.

    Set some breakpoints; add some logging messages. Find out what is happening, and what isn't. For instance, does your PluginWinProc even get called? If not, you probably missed the part in the example where it subclasses the window (that part of the code wasn't shown here). Are the return values from your COM calls S_OK?

    Do some digging on your own; when you know what is and isn't running, then come back and ask specific, preferably short, questions to get help with those pieces that you don't understand. Also, please try to avoid posting long code fragments in comments =] Once you have done all that you can do, I will be happy to continue to help you; I just need more than “Yes i did what did u advice but it doesn't work.”

    =]

  13. Nicolas.
    8 years ago

    Hi,

    Until now I didn't restrict my Firefox plugin to GeckoAPI, but since I want to be compliant with Chrome, Safari and Opera, I'm working and porting my code to GeckoAPI.
    But I meets a lot of difficulties.

    For example, I didn't find a way to do GET http request with custom headers. (The POST request with custom headers is horrible since it passes custom headers in the POST content…)

    Besides, since HTTP request are asynchronous, is there a way to create a message loop to create a pseudo synchronous request ?

    Thanks for any advice.
    Nicolas.

  14. taxilian
    8 years ago

    Unfortunately, I do not know of a way to do what you're trying to. You can, of course, use NPN_GetUrl and NPN_PostUrl to make page requests, but they are not (as far as I can tell) designed to allow for custom HTTP headers. You could either use query string paramters, or you could write your own TCP handlers (as we have done to handle cases like these and others).

  15. Nicolas.
    8 years ago

    Hi,

    Until now I didn't restrict my Firefox plugin to GeckoAPI, but since I want to be compliant with Chrome, Safari and Opera, I'm working and porting my code to GeckoAPI.
    But I meets a lot of difficulties.

    For example, I didn't find a way to do GET http request with custom headers. (The POST request with custom headers is horrible since it passes custom headers in the POST content…)

    Besides, since HTTP request are asynchronous, is there a way to create a message loop to create a pseudo synchronous request ?

    Thanks for any advice.
    Nicolas.

  16. taxilian
    8 years ago

    Unfortunately, I do not know of a way to do what you're trying to. You can, of course, use NPN_GetUrl and NPN_PostUrl to make page requests, but they are not (as far as I can tell) designed to allow for custom HTTP headers. You could either use query string paramters, or you could write your own TCP handlers (as we have done to handle cases like these and others).

  17. taxilian
    8 years ago

    I would particularly like to encourage all of you who have commented to consider helping out with FireBreath, the open source plugin framework project that I am starting. Even if you don't have a lot of time, a little time here and there can go a long ways.

    http://colonelpanic.net/2009/09/call-for-plugin

  18. taxilian
    8 years ago

    I would particularly like to encourage all of you who have commented to consider helping out with FireBreath, the open source plugin framework project that I am starting. Even if you don't have a lot of time, a little time here and there can go a long ways.

    http://colonelpanic.net/2009/09/call-for-plugin

  19. curtisneeley
    8 years ago

    This plug in programming talk is not quite Mandarin Chinese stuff to me, but it is only slightly more distinct. I have a severe traumatic brain injury and forgot most languages.

    How difficult will it be to create a plug in that offers a click-protest option?
    Suppose I search for something and only land on a parked page that is a Google Inc. fraud? A page where the site is ONLY running ads under license from Google Inc. or another search provider? The user of the plug-in could click the protest button when reaching a parked/cybersquatted page and open and close every page linked on the site without even seeing the sites that run the ads?

    Google might call a click-protest a click-fraud? We will soon address that in my current lawsuit. The protest button would also store the website address for reporting to a repository of such sites as well as providing a customized parked/cybersquatted fraudulent page error screen. I am creating a website where sites that are listed will be listed to send our protest-click visitors. My lawsuit will attempt to make Google's fraudulent policy of licensing parked domains for a profit no longer fiscally sound. I am extremely angry at Google for having a fraudulent business policy of licensing parked/cybersquatted domains also called by Google Inc. as AdSense for Domains.

    I used to run a web reservation site and was well versed in programming. It seems that I now can only ask for anyone's help. I could still write a script to do it in Perl5 but one server IP is too easy to counteract.

    Help. I consider any aid a lien on my lawsuit settlement of $75/hr.

  20. curtisneeley
    8 years ago

    This plugin programming talk is not quite Mandarin Chinese stuff to me, but it is only slightly more distinct. I have a severe traumatic brain injury and forgot most languages.

    How difficult will it be to create a plugin that offers a click-protest option?
    Suppose I search for something and only land on a parked page that is a Google Inc. fraud? A page where the site is ONLY running ads under license from Google Inc or another search provider? The user of the plugin could click the protest button when reaching a parked/cybersquatted page and open and close every page linked on the site without even seeing the sites that ran the ads?

    Google might call a click-protest a click-fraud? We will soon address that in my current lawsuit. The protest button would also store the website address for reporting to a repository of such sites as well as providing a customized parked/cybersquatted fraudulent page error screen. I am creating a website where sites that are reported/discovered will be listed to send our protest-click visitors. My lawsuit will attempt to make Google's fraudulent policy of licensing parked domains for a profit no longer fiscally sound. I am extremely angry at Google for having a fraudulent business policy of licensing parked/cybersquatted domains. This fraud perpetrating policy is called by Google Inc. as AdSense for Domains.

    I used to run a web reservation site and was well versed in programming. It seems that I now can only ask for anyone's help. I could still write a script to do it in Perl5 but one server IP is too easy to counteract.

    Help. I consider any aid a lien on my lawsuit settlement of $75/hr.

  21. taxilian
    8 years ago

    I don't believe that what you want to do can be done with a plugin; I think you need an extension, which will be browser specific (i.e. firefox extensions only work on firefox, etc)

  22. taxilian
    8 years ago

    I don't believe that what you want to do can be done with a plugin; I think you need an extension, which will be browser specific (i.e. firefox extensions only work on firefox, etc)

  23. taxilian
    8 years ago

    I don't believe that what you want to do can be done with a plugin; I think you need an extension, which will be browser specific (i.e. firefox extensions only work on firefox, etc)

  24. Travel Trailers
    8 years ago

    gosh i didn't expect it to be this complicated…

  25. Travel Trailers
    8 years ago

    gosh i didn't expect it to be this complicated…

  26. taxilian
    8 years ago

    That would be why I finally started FireBreath (http://firebreath.googlecode.com). Even with FireBreath it's more complicated than I'd like, but it's a whole lot easier than it is without….

  27. taxilian
    8 years ago

    That would be why I finally started FireBreath (http://firebreath.googlecode.com). Even with FireBreath it's more complicated than I'd like, but it's a whole lot easier than it is without….

  28. Ryan Schipper
    8 years ago

    Hi there,

    I was involved with the development of a windowless Firefox (and IE ActiveX Control as it turns out) plugin during 2007. I continue to maintain the software, although the primary developers have both left the project.

    My experience of plugin lifecycle in NPAPI doesn't exactly match with your description: “quite simple”. At the time, we found that the following actions resulted in slightly different lifecycles (in Firefox 2 at least):
    – refreshing the page
    – navigating from one page containing an active plugin instance to the same URI
    – navigating from one page containing an active plugin instance to a distinct URI that contained an embed of the same plugin
    – opening a window to a distinct URL which embed'd the plugin

    The details are a bit hazy, but do my statements match with your experience?

  29. taxilian
    8 years ago

    Well, yes and no. The lifecycle of a plugin instance is quite simple — but simple becomes more complicated when you realize that there can be multiple instances at one time, and that there is no guarantee of what order they will load or unload in relationship to each other.

    In my experience, for example, if you refresh the page, instance 2 (on the new page) will start up before instance 1 goes away. Sometimes a long time before. Also, you never quite know when the scriptable objects (NPObjects) will be destroyed, since those are reference counted. It gets even more interesting when you add and remove the object tag from the page without refreshing.

    However, the lifecycle itself is still quite simple, just the concurrency of multiple plugin instances makes it a little weird. This can be solved by avoiding the use of static and global variables, etc. Does that meet with your experience? If not, would you care to share some examples?

    Also, if you'd be interested in helping us add windowless support to FireBreath, that's one piece where I don't have much experience, and that would greatly benefit the project.

  30. Nikolai Danylchyk
    8 years ago

    Hello everyone.
    I have a slight problem with my development.
    My plug-in works fine, I'm able to render objects with my ported OpenGL engine without much problems. The thing is.. if I hit F5 to refresh, it won't render anything. If I reload page (click on url and hit enter again) works fine again. I'm not sure but I guess I'm destructing all the instances correctly. By the way, is it possible to create several instances in different tabs and is there any documentation about tab handling?
    Thanks in advance.

  31. taxilian
    8 years ago

    Привет. Most likely this is due to an oddity of the plugin lifecycle; when you refresh the page, it actually loads the next plugin before unloading the previous one.

    Another possibility is that you may be using global variables somewhere; if so, keep in mind that these are global to every instance of the plugin in the browser. If you try to open your plugin in two different firefox windows and experience problems, this is likely the issue. You don't need to do anything special to handle tabs, but you do need to remember to avoid using global variables at all costs!

    I recommend using FireBreath as a simple way to abstract a lot of the complexity involved.

  32. Nikolai Danylchyk
    8 years ago

    Спасибо!

  33. j_rivero
    8 years ago

    Very helpful. Thanks

  34. Thekumar
    8 years ago

    Thanks for this post! I just got thrown right in the middle of a porting project for a plugin, and I had no NPAPI experience, and by far this is the most clear-cut explanation of what’s going on with our code that I can find on the web. Thanks again!!!

  35. Paddre
    8 years ago

    Hi.

    I’ve been building a plugin for Firefox under Linux. The plugin contains a QT-Window (Nearly) everything works fine.
    But there are two problems I’m still facing:

    1. I invoke a JS-function (contained in a HTML-file) from within the plugin. This function fills a select-Box with given information. Now if I open the same HTML in two tabs, the information of the last loaded tab is filled in the select-Box of the first loaded tab. So one select-Box contains twice as much as it should and the other one doesn’t contain anything. I think this is because “NPP_New” is invoked twice, each time with the same npp instance. Is there a solution?

    2. I can’t close Firefox correctly after having played with the plugin. Everytime I close Firefox I get the message that Firefox caused a Segmentation fault. If I open a window with two tabs containing a pluginobject, I even get the Mozilla Crash Reporter. I figured out, that NPP_Shutdown is never been called. I hope some of you came across (and solved) the same problem and want to help me.

    Porting the plugin code to FireBreath is unfortunately not possible (due to time and costs)

    Greetz
    Paddre

  36. Paddre
    8 years ago

    OK I solved problem #1. The mistake was (what else) a static variable containing the Window

    But problem #2 still remains unsolved and I have no idea how I can solve it. I assume, that something is not shut down correctly or some memory remains allocated.

  37. taxilian
    8 years ago

    My guess would be that your function table isn’t being filled out right (NPPluginFuncs). I would recommend attaching gdb and finding out where the segfault is. Problems like this are never fun to debug, but if you dig enough you’ll find it.

  38. jean
    8 years ago

    Hi,
     
    These’re great articles. I am new to NPAPI. I wrote a simple npapi plugin. What I need now is to be able to get the broswser window size and position and update the size and position when broswer window is resized. Could you shed some light on how to achieve this? Thanks.

  39. taxilian
    8 years ago

    Well, first of all I would recommend against implementing your NPAPI plugin from scratch; at this point, I would highly recommend using FireBreath.  I wrote most of it, so I might be a bit biased, but it has a lot of workaround and bugfixes for various browsers as well as working on both IE and NPAPI-based browsers.

    Getting the browser window size *on windows* isn’t bad; you just get the browser window HWND with NPN_GetValue (look up the docs; it’s one of the options) or use getBrowserWindow on the PluginWindowWin object in FireBreath and then use windows API calls to get what you need; I don’t know how you can find out when the browser window moves, however.

  40. jean
    8 years ago

    I got the sample plugin FBtestplugin works. But I am not sure how to build a plugin from scratch. I have a simple plugin not build using firebreath. What it takes to convert to a firebreath plugin? I can deploy that plugin to chrome, but I can’t find an easy way to deploy it on firefox. I was told that I can just copy my plugin to plugins folder, so firefox will show the plugin when I do “about:plugins”. But that does not happen. Thanks.

  41. jean
    8 years ago

    Ignore the above. I found the tutorial and also the video. Very helpful and very cool! 

  42. jean
    8 years ago

    I was able to register the firebreath created plugin. But I was not able to register my own plugin which works find for Chrome. What it takes to register a simple plugin in firefox? Thanks.

  43. taxilian
    8 years ago

    http://forum.firebreath.org, the IRC channel, or the FireBreath mailing list would all be good places to ask this question.  This is not a good place.  All you have to do is run regsvr32 on the dll, though.

  44. jasta ko
    8 years ago

    Hi everyone, and especially you Richard,

    I’ve found your series of articles on FF plugins dev. really useful. To be honest I tried to do some XPCOM stuff and for weeks I was like suprised it is not working with FF 3.6, 6.0, 7.0 and 8.0. Now I know XPCOM is obsolete and have to use npruntime (or sth?) instead.
    Your articles explain this enough detailed, focusing on the different parts of plugin, but I have some difficulties with putting these pieces together. Can you share me a whole project including sources of your sample plugin, used here?

    Many thanks in advance,
    Kuba

  45. taxilian
    8 years ago

    I would love to. It’s called FireBreath and you can find it at http://www.firebreath.org.  Of course, you’d probably be better off *using* firebreath to create your plugin rather than using it just as an example, but all of this is implemented in FireBreath, which is totally open source.

5 Trackbacks

  1. By pre dev wiki: Browser Plugins on July 1, 2009 at 11:49 pm

    […] Building a FireFox plugin: Part One Building a FireFox plugin: Part Two […]

  2. […] you are new to NPAPI plugins, you may want to go back and read part one and part two of this series, which cover the basic architecture and lifecycle of an NPAPI […]

  3. […] Building a firefox plugin – part two […]

  4. By Building a firefox plugin « HeejinDev on September 8, 2009 at 6:42 pm

    […] a firefox plugin – part one Building a firefox plugin – part two Building a firefox plugin – part […]

  5. […] The npruntime Sample Plugin in Visual Studio Building a firefox plugin – part one part 2 part 3 Writing an NPAPI plugin for Mac OS […]

Post a Comment

Your email is never published or shared. Required fields are marked *