/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : PlugIn.c                               */
/*                                                 */
/* Purpose: Supporting the generic RISC OS browser */
/*          Plug-In interface.                     */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 05-Oct-97: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "swis.h"
#include "flex.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "toolbox.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "Utils.h"

#include "Browser.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "FetchHTML.h"
#include "FetchPage.h"
#include "Filetypes.h"
#include "Frames.h"
#include "Handlers.h"
#include "Images.h"
#include "MimeMap.h"
#include "MiscDefs.h"
#include "Object.h"
#include "Protocols.h"
#include "Redraw.h"
#include "Reformat.h"
#include "RMA.h"
#include "TokenUtils.h"
#include "Toolbars.h"
#include "URLveneer.h"
#include "Windows.h"

#include "PlugIn.h"

/**************************************************************************************************/
/*                                                                                                */
/* Allocation of browser handles for the Plug-In protocol                                         */
/* ======================================================                                         */
/*                                                                                                */
/* In the Plug-In protocol, a browser supplies a private word (the browser instance handle) and   */
/* the Plug-In supplies a private word (the Plug-In instance handle). Each is considered an       */
/* opaque value to the other.                                                                     */
/*                                                                                                */
/* Because the browser, in this case, needs to know both the browser_data structure owning a      */
/* Plug-In and the HStream structure representing it, the plugin_insts structure is used to form  */
/* a linked list containing information on a Plug-In. The browser points to items in this list    */
/* to create the browser instance handle.                                                         */
/*                                                                                                */
/* When streaming data, two new handles are used, one for the Plug-In's side of the stream, and   */
/* one for the browser's side. In this case, we only need to know the browser_data struct that is */
/* handling the fetching of data, so the browser stream instance handle is a pointer to that      */
/* structure. This structure has a 'pstream' field which points to a plugin_stream structure.     */
/* This is used to hold a lot of information on the fetch, and is an externally visible structure */
/* unlike plugin_insts.                                                                           */
/*                                                                                                */
/*                                                                                                */
/* Other structures                                                                               */
/* ================                                                                               */
/*                                                                                                */
/* Less related to the protocol are two other important structures - the plugin_files struct and  */
/* the plugin_queue. The plugin_queue is a simple structure added to whenever the browser finds   */
/* a token representing a Plug-In and wants to try launching that Plug-In. The head item of the   */
/* queue is launched and removed when the protocol completes (successfully or otherwise),         */
/* allowing the next item to rise to the head and be launched, and so-forth.                      */
/*                                                                                                */
/* The plugin_files struct is used in place of a browser cache. Since the core browser has no     */
/* cache itself, and given that the Plug-In protocol expects one (no method for deleting          */
/* temporary files is provided other than implicit cache expiry), an alternative is needed.       */
/* Whenever a temporary file is created as part of Plug-In streaming (i.e. everything except the  */
/* Params file used at launch time), an entry holding the URL relating to the file, and its       */
/* pathname, is placed in the list. Instead of fetching new data as soon as asked to by a Plug-In */
/* the browser will first scan the list, and if a matching URL is found, use the existing file    */
/* instead.                                                                                       */
/*                                                                                                */
/* Files are cached for the duration of a session, and are deleted on browser shutdown.           */
/**************************************************************************************************/

/* Local structures */

typedef struct plugin_files
{
  struct plugin_files * next;     /* When a Plug-In fetch to a file has successfully completed,  */
                                  /* this records the filename so it can be deleted on shutdown. */
  char                * url;
  char                * pathname;
}
plugin_files;

typedef struct plugin_insts
{
  struct plugin_insts * next;     /* This is what a browser instance handle is actually a pointer to */
                                  /* (but not a browser stream instance handle; see comments above). */
  browser_data        * browser;
  HStream             * token;

  unsigned int          plugin_task_handle;
}
plugin_insts;

/* Statics */

static plugin_queue * plugin_queue_head           = NULL;
static plugin_files * plugin_files_head           = NULL;
static plugin_insts * plugin_insts_head           = NULL;

static int            plugin_open_reference       = 0;
static int            plugin_open_tried_once      = 0;
static char         * plugin_open_filename        = NULL;

static int            plugin_stream_new_reference = 0;

/* Static function prototypes */

static browser_data    * plugin_return_browser        (unsigned int browser_instance_handle);

static _kernel_oserror * plugin_write_params_word     (int fh, int word);
static _kernel_oserror * plugin_write_params_string   (int fh, const char * string);
static _kernel_oserror * plugin_write_params_entry    (int fh, int type, const char * name, const char * data, const char * mime);

static _kernel_oserror * plugin_remove_head_item      (void);

static _kernel_oserror * plugin_broadcast_open        (void);

static int               plugin_close_stream          (int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle);

static _kernel_oserror * plugin_add_file_entry        (const char * pathname, const char * url);
static const char      * plugin_find_file             (const char * url);
static void              plugin_flush_files           (void);

static _kernel_oserror * plugin_add_instance_entry    (browser_data * b, HStream * t, unsigned int task);
static plugin_insts    * plugin_find_instance_entry   (browser_data * b, HStream * t);
static void              plugin_remove_instance_entry (plugin_insts * remove);

/*************************************************/
/* plugin_return_string()                        */
/*                                               */
/* Takes a message block and string_value from   */
/* within it, and returns the string it points   */
/* to.                                           */
/*                                               */
/* Parameters: Pointer to a WimpMessage struct   */
/*             holding the string_value field;   */
/*                                               */
/*             Pointer to the string_value field */
/*             inside that message.              */
/*                                               */
/* Returns:    Pointer to the string the         */
/*             string_value references, or NULL  */
/*             if the value seems invalid.       */
/*************************************************/

const char * plugin_return_string(WimpMessage * m, string_value * sv)
{
  int offset = ((char *) sv) - ((char *) m);

  if (!m || !sv || !sv->offset) return NULL;

  /* Is the offset of the string_value field within the message */
  /* block out of range (i.e. the string_value does not appear  */
  /* to lie in the given message block)?                        */

  if (offset >= m->hdr.size || offset < sizeof(m->hdr)) return NULL;

  /* Now read the string_value field itself */

  if (sv->offset < 256)
  {
    if (sizeof(m->hdr) + offset >= m->hdr.size) return NULL;

    else return (const char *) (((char *) m) + sizeof(m->hdr) + sv->offset);
  }
  else return (const char *) sv->offset;
}

/*************************************************/
/* plugin_write_params_word()                    */
/*                                               */
/* Writes a word to a given parameters file,     */
/* in little-endian order.                       */
/*                                               */
/* Parameters: RISC OS file handle of the file;  */
/*                                               */
/*             Word to write.                    */
/*************************************************/

static _kernel_oserror * plugin_write_params_word(int fh, int word)
{
  char integer[4];

  /* #define Brain_Off... */

  integer[0] = (word & 0xff);
  integer[1] = ((word & 0xff00) >> 8);
  integer[2] = ((word & 0xff0000) >> 16);
  integer[3] = ((word & 0xff000000) >> 24);

  RetError(_swix(OS_BPut,
                 _INR(0,1),

                 integer[0],
                 fh));

  RetError(_swix(OS_BPut,
                 _INR(0,1),

                 integer[1],
                 fh));

  RetError(_swix(OS_BPut,
                 _INR(0,1),

                 integer[2],
                 fh));

  RetError(_swix(OS_BPut,
                 _INR(0,1),

                 integer[3],
                 fh));

  return NULL;
}

/*************************************************/
/* plugin_return_browser()                       */
/*                                               */
/* Converts a browser instance handle to a       */
/* browser_data structure pointer.               */
/*                                               */
/* Parameters: A browser instance handle.        */
/*                                               */
/* Returns:    Pointer to a browser_data struct  */
/*             represented by the instance       */
/*             handle, or NULL if none is found. */
/*************************************************/

static browser_data * plugin_return_browser(unsigned int browser_instance_handle)
{
  browser_data * b;

  if (!browser_instance_handle) return NULL;

  b = ((plugin_insts *) browser_instance_handle)->browser;

  if (!is_known_browser(b)) return NULL;

  return b;
}

/*************************************************/
/* plugin_write_params_string()                  */
/*                                               */
/* Writes a string to a given parameters file,   */
/* padding with zeros to a length which is a     */
/* multiple of 4. If the string length, not      */
/* including terminator, is already a multiple   */
/* of four, no extra bytes are written.          */
/*                                               */
/* Parameters: RISC OS file handle of the file;  */
/*                                               */
/*             Pointer to the string.            */
/*************************************************/

static _kernel_oserror * plugin_write_params_string(int fh, const char * string)
{
  const char * s;
  int          i = 0;
  int          pad;

  /* Write the main string */

  s = string ? string : "";

  while (s[i])
  {
    RetError(_swix(OS_BPut,
                   _INR(0,1),

                   s[i],
                   fh));

    i++;
  }

  /* Pad if necessary */

  pad = 4 - (strlen(s) % 4);

  if (pad < 4)
  {
    for (i = 0; i < pad; i ++)
    {
      RetError(_swix(OS_BPut,
                     _INR(0,1),

                     '\0',
                     fh));
    }
  }

  return NULL;
}

/*************************************************/
/* plugin_write_params_entry()                   */
/*                                               */
/* Writes an entry to the given parameters file. */
/*                                               */
/* Parameters: RISC OS file handle of the file;  */
/*                                               */
/*             Type of the entry (see            */
/*             PlugIn.h);                        */
/*                                               */
/*             Pointer to the name string;       */
/*                                               */
/*             Pointer to the data string;       */
/*                                               */
/*             Pointer to the Mime string.       */
/*                                               */
/* Assumes:    Any of the pointers may be NULL   */
/*             or a zero length string to write  */
/*             a zero length field for that      */
/*             item.                             */
/*************************************************/

static _kernel_oserror * plugin_write_params_entry(int fh, int type, const char * name, const char * data, const char * mime)
{
  int          length;
  const char * n;
  const char * d;
  const char * m;

  /* Make life easier */

  n = name ? name : "";
  d = data ? data : "";
  m = mime ? mime : "";

  /* Write the type */

  RetError(plugin_write_params_word(fh, type));

  /* Calculate and write the entry length */

  length = 4 + strlen(n);
  length = (int) WordAlign(length);

  length += 4 + strlen(d);
  length = (int) WordAlign(length);

  length += 4 + strlen(m);
  length = (int) WordAlign(length);

  RetError(plugin_write_params_word(fh, length));

  /* The name field */

  RetError(plugin_write_params_word(fh, strlen(n)));
  RetError(plugin_write_params_string(fh, n));

  /* The data field */

  RetError(plugin_write_params_word(fh, strlen(d)));
  RetError(plugin_write_params_string(fh, d));

  /* The mime type field */

  RetError(plugin_write_params_word(fh, strlen(m)));
  RetError(plugin_write_params_string(fh, m));

  return NULL;
}

/*************************************************/
/* plugin_write_params()                         */
/*                                               */
/* Writes a parameters file to a unique filename */
/* based on the given HStream.                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the HStream;          */
/*                                               */
/*             Pointer to the HStream struct     */
/*             representing the OBJECT, APPLET   */
/*             or EMBED tag that the parameters  */
/*             file is for;                      */
/*                                               */
/*             Buffer for the filename that was  */
/*             used, or if the buffer size given */
/*             is zero, pointer to the filename  */
/*             to use;                           */
/*                                               */
/*             Size of the buffer, or zero if    */
/*             the above parameter is a pointer  */
/*             to the filename to use instead of */
/*             a pointer to a buffer to receive  */
/*             a locally generated system unique */
/*             filename;                         */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             size of the Plug-In window.       */
/*************************************************/

_kernel_oserror * plugin_write_params(browser_data * b, HStream * t, char * buffer, int buffer_size, BBox * position)
{
  _kernel_oserror * e  = NULL;
  int               fh = 0;
  char            * data;
  char              version[5];
  char              number [64];

  if (buffer_size) protocols_util_make_unique_name(buffer, buffer_size);

  RetError(_swix(OS_Find,
                 _INR(0,1) | _OUT(0),

                 0x8c, /* Create empty file with r/w access, return errors */
                 buffer,

                 &fh));

  /* Write the browser special fields */

  data = browser_current_url(b);
  if (!data) data = browser_fetch_url(b);

  e = plugin_write_params_entry(fh,
                                PlugIn_ParamType_BrowserSpecial,
                                "BASEHREF",
                                data,
                                NULL);

  if (e) goto plugin_write_params_exit;

  data = lookup_token("_TaskName", 1, 0);

  e = plugin_write_params_entry(fh,
                                PlugIn_ParamType_BrowserSpecial,
                                "USERAGENT",
                                data,
                                NULL);

  if (e) goto plugin_write_params_exit;

  data = lookup_token("Version", 1, 0);
  StrNCpy0(version, data);

  e = plugin_write_params_entry(fh,
                                PlugIn_ParamType_BrowserSpecial,
                                "UAVERSION",
                                version,
                                NULL);

  if (e) goto plugin_write_params_exit;

  e = plugin_write_params_entry(fh,
                                PlugIn_ParamType_BrowserSpecial,
                                "APIVERSION",
                                "1.10",
                                NULL);

  if (e) goto plugin_write_params_exit;

  if (redraw_backcol(b) != -1)
  {
    char word[9];

    sprintf(word, "%08x", redraw_backcol(b));

    e = plugin_write_params_entry(fh,
                                  PlugIn_ParamType_BrowserSpecial,
                                  "BGCOLOR",
                                  word,
                                  NULL);
  }

  /* Now write details based on the Object type */

  if (
       (
         HtmlOBJECTclassid(t)   &&
         *HtmlOBJECTclassid(t)
       )
     )
  {
    /* A code object, e.g. Java applet */

    const char * cis = HtmlOBJECTclassid(t);
    const char * cie;

    /* Get details from the CLASSID attribute, giving just the leafname */

    cie = cis + strlen(cis) - 1;

    while (cie > cis && *cie != '/') cie --;
    if (*cie == '/') cie++;

    e = plugin_write_params_entry(fh,
                                  PlugIn_ParamType_URLFromOBJECT,
                                  "CLASSID",
                                  cie,
                                  HtmlOBJECTcodetype(t));

    if (e) goto plugin_write_params_exit;

    /* CODEBASE, if we have it */

    if (HtmlOBJECTcodebase(t) && *HtmlOBJECTcodebase(t))
    {
      e = plugin_write_params_entry(fh,
                                    PlugIn_ParamType_DataFromOBJECT,
                                    "CODEBASE",
                                    HtmlOBJECTcodebase(t),
                                    NULL);

      if (e) goto plugin_write_params_exit;
    }
  }
  else if (HtmlOBJECTdata(t) && *HtmlOBJECTdata(t))
  {
    /* A data object, e.g. Shockwave movie */

    e = plugin_write_params_entry(fh,
                                  PlugIn_ParamType_URLFromOBJECT,
                                  "DATA",
                                  HtmlOBJECTdata(t),
                                  HtmlOBJECTtype(t));
  }

  /* Some generic bits */

  if (HtmlOBJECTstandby(t) && *HtmlOBJECTstandby(t))
  {
    e = plugin_write_params_entry(fh,
                                  PlugIn_ParamType_DataFromOBJECT,
                                  "STANDBY",
                                  HtmlOBJECTstandby(t),
                                  NULL);

    if (e) goto plugin_write_params_exit;
  }

  /* Width and height */

  sprintf(number, "%d", (position->ymax - position->ymin) / 2);

  e = plugin_write_params_entry(fh,
                                PlugIn_ParamType_DataFromOBJECT,
                                "HEIGHT",
                                number,
                                NULL);

  if (e) goto plugin_write_params_exit;

  sprintf(number, "%d", (position->xmax - position->xmin) / 2);

  e = plugin_write_params_entry(fh,
                                PlugIn_ParamType_DataFromOBJECT,
                                "WIDTH",
                                number,
                                NULL);

  if (e) goto plugin_write_params_exit;

  /* If we have a child token stream, look for PARAM tags */

  if (HtmlOBJECTstream(t))
  {
    HStream * child = HtmlOBJECTstream(t);

    while (child)
    {
      if (child->tagno == TAG_PARAM)
      {
        int type;

        /* Get the entry type from the valuetype field of the tag */

        switch (HtmlPARAMvaluetype(child))
        {
          default:
          case paramtype_DATA:   type = PlugIn_ParamType_DataFromPARAM;   break;
          case paramtype_OBJECT: type = PlugIn_ParamType_ObjectFromPARAM; break;
          case paramtype_REF:    type = PlugIn_ParamType_URLFromPARAM;    break;
        }

        /* Write the entry */

        e = plugin_write_params_entry(fh,
                                      type,
                                      HtmlPARAMname(child),
                                      HtmlPARAMvalue(child),
                                      HtmlPARAMtype(child));

        if (e) goto plugin_write_params_exit;
      }

      child = child->next;
    }
  }

  /* Write the terminating entry */

  RetError(plugin_write_params_word(fh, PlugIn_ParamType_Terminator));

plugin_write_params_exit:

  /* Exit by closing the file and returning any */
  /* generated error.                           */

  if (fh)
  {
    _swix(OS_Find,
     _INR(0,1),

     0, /* Close file */
     fh);

    /* Set the filetype */

    _swix(OS_File,
     _INR(0,2),

     18,
     buffer,
     FileType_DATA);
  }

  return e;
}

/*************************************************/
/* plugin_add_queue_item()                       */
/*                                               */
/* Add details of a new Plug-In to the queue of  */
/* Plug-Ins. If there were none already in the   */
/* queue, then start this new one immediately.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to the HStream struct     */
/*             representing the OBJECT, APPLET   */
/*             or EMBED tag (Object) that the    */
/*             message is for;                   */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             where the Plug-In should open, in */
/*             parent window coordinates.        */
/*************************************************/

_kernel_oserror * plugin_add_queue_item(browser_data * b, HStream * t, BBox * position)
{
  plugin_queue * new;
  int            start_now = 0;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_add_queue_item: Called for %p, %p\n",b,t);
  #endif

  /* Allocate memory for the item */

  new = malloc(sizeof(plugin_queue));
  if (!new) return make_no_memory_error(21);

  if (plugin_queue_head)
  {
    /* Insert the new item in between the existing first and second items */

    new->next               = plugin_queue_head->next;
    plugin_queue_head->next = new;
  }
  else
  {
    new->next               = NULL;
    plugin_queue_head       = new;

    /* This is the only item in the queue, so try to start a Plug-In */
    /* for it as soon as possible.                                   */

    start_now = 1;
  }

  /* Fill in the rest of the structure */

  new->browser  = b;
  new->token    = t;
  new->position = *position;

  /* If required, try to start the Plug-In */

  if (start_now)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_add_queue_item: Exitting through plugin_broadcast_open\n");
    #endif

    return plugin_broadcast_open();
  }

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_add_queue_item: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_remove_item()                          */
/*                                               */
/* Remove an item from the Plug-In queue.        */
/*                                               */
/* Parameters: Pointer to the plugin_queue       */
/*             struct to remove.                 */
/*************************************************/

_kernel_oserror * plugin_remove_item(plugin_queue * remove)
{
  plugin_queue * prev;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_remove_item: Called for %p\n",remove);
  #endif

  /* Can't do anything if there are no items */

  if (!plugin_queue_head || !remove)
  {
    #ifdef TRACE

      erb.errnum  = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,
              "NULL queue head (0x%08x) or remove item (0x%08x)in plugin_remove_item",
              (int) plugin_queue_head,
              (int) remove);
      return &erb;

    #endif

    return NULL;
  }

  /* Find the previous item */

  prev = plugin_queue_head;

  while (prev && prev->next != remove) prev = prev->next;

  /* If we've not found the item we were asked to remove, and */
  /* the remove item isn't at the queue head, 'remove' must   */
  /* be invalid - so exit.                                    */

  if (!prev && remove != plugin_queue_head)
  {
    #ifdef TRACE

      erb.errnum  = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,
              "Can't find item that points to given structure %0x08x in plugin_remove_item",
              (int) remove);
      return &erb;

    #endif

    return NULL;
  }

  /* OK, unlink it */

  if (prev) prev->next   = remove->next;
  else plugin_queue_head = remove->next;

  /* Free the item - finished. */

  free(remove);

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_remove_item: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_remove_head_item()                     */
/*                                               */
/* Remove the head item from the Plug-In queue,  */
/* launching the next item if there is one.      */
/*************************************************/

static _kernel_oserror * plugin_remove_head_item(void)
{
  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_remove_head_item: Called\n");
  #endif

  RetError(plugin_remove_item(plugin_queue_head));

  if (plugin_queue_head)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_remove_head_item: Exitting through plugin_broadcast_open\n");
    #endif

    return plugin_broadcast_open();
  }
  else
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_remove_head_item: Successful\n");
    #endif

    return NULL;
  }
}

/*************************************************/
/* plugin_flush_queue()                          */
/*                                               */
/* Remove selected, or all Plug-Ins in the       */
/* queue.                                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Plug-Ins to       */
/*             remove, or NULL for all items;    */
/*                                               */
/*             If one of the removed items was   */
/*             at the head of the queue, 1 to    */
/*             launch the new head item Plug-In, */
/*             else 0.                           */
/*************************************************/

_kernel_oserror * plugin_flush_queue(browser_data * b, int start_now)
{
  plugin_queue * old_head = plugin_queue_head;
  plugin_queue * current;
  plugin_queue * next;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_flush_queue: Called for %p\n",b);
  #endif

  current = plugin_queue_head;

  /* For each item, remove it only if it belongs to the given */
  /* browser, or the given browser is NULL.                   */

  while (current)
  {
    next = current->next;

    if (!b || (b && next->browser == b)) RetError(plugin_remove_item(current));

    current = next;
  }

  /* If there are still queue items, the head item has changed and */
  /* the start_now parameter passed in says to do so, launch the   */
  /* new head item Plug-In.                                        */

  if (
       plugin_queue_head             &&
       plugin_queue_head != old_head &&
       start_now
     )
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_flush_queue: Exitting through plugin_broadcast_open\n");
    #endif

    return plugin_broadcast_open();
  }
  else
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_flush_queue: Successful\n");
    #endif

    return NULL;
  }
}

/*************************************************/
/* plugin_broadcast_open()                       */
/*                                               */
/* Where it all starts - broadcast a             */
/* Message_PlugIn_Open for the Plug-In at the    */
/* head of the queue.                            */
/*                                               */
/* External functions should invoke this by      */
/* adding to the Plug-In queue, not by a direct  */
/* call here.                                    */
/*************************************************/

static _kernel_oserror * plugin_broadcast_open(void)
{
  _kernel_oserror * e;
  char              params[Limits_OS_Pathname];
  const char      * type;
  WimpMessage       m;
  MPlugIn_Open    * open = (MPlugIn_Open *) &m.data;
  browser_data    * b;
  unsigned int      browser_instance;
  HStream         * t;
  BBox            * position;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_broadcast_open: Called, head item is %p\n", plugin_queue_head);
  #endif

  /* Can't do anything if there are no queue items */

  if (!plugin_queue_head)
  {
    #ifdef TRACE

      erb.errnum  = Utils_Error_Custom_Normal;
      strcpy(erb.errmess,
             "NULL queue head in plugin_broadcast_open");
      return &erb;

    #endif

    return NULL;
  }

  /* Make life a bit less verbose... */

  b = plugin_queue_head->browser;
  t = plugin_queue_head->token;

  position = &plugin_queue_head->position;

  #ifdef TRACE
    if (tl & (1u<<30))
    {
      char debugbuf[1024];
      sprintf(debugbuf,"plugin_broadcast_open: Open at BBox %d, %d, %d, %d",position->xmin,position->ymin,position->xmax,position->ymax);
      Printf("%s\n",debugbuf);
    }
  #endif

  /* Write the parameters file */

  RetError(plugin_write_params(b, t, params, sizeof(params), position));

  // For debugging, this may be useful...
  //
  // plugin_write_params(b, t, "ADFS::4.$.DebugParam", 0, position);

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_broadcast_open: Params file is at '%s'\n",params);
  #endif

  /* Fill in the header */

  m.hdr.size        = sizeof(MPlugIn_Open) + sizeof(m.hdr);
  m.hdr.your_ref    = 0;
  m.hdr.action_code = Message_PlugIn_Open;

  /* Get an instance handle */

  RetError(plugin_obtain_instance(b, t, &browser_instance));

  /* Fill in the body */

  open->flags                   = 0;
  open->reserved                = 0;
  open->browser_instance_handle = browser_instance;
  open->parent_window_handle    = b->window_handle;
  open->parent_area             = *position;

  open->file_type = 0;

  /* Get the Mime type */

  type = HtmlOBJECTcodetype(t);
  if (!type || !*type) type = HtmlOBJECTtype(t);

  /* If we've got one, ask the Mime Mapper what RISC OS filetype this is */

  if (type && *type)
  {
    if (mimemap_mime_to_riscos(type, &open->file_type)) open->file_type = FileType_DATA;
  }

  /* Otherwise get it from the filename extension */

  else
  {
    const char * data = HtmlOBJECTclassid(t);
    const char * ext;

    open->file_type = FileType_DATA;

    if (!data || !*data) data = HtmlOBJECTdata(t);

    if (data && * data)
    {
      ext = data + strlen(data) - 1;

      while (ext >= data && *ext != '.') ext--;

      if (*ext == '.')
      {
        if (mimemap_extension_to_riscos(ext, &open->file_type)) open->file_type = FileType_DATA;
      }
    }
  }

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_broadcast_open: Filetype to use: 0x%03X\n", open->file_type);
  #endif

  /* If we already have RMA space for the filename, free it */

  if (plugin_open_filename)
  {
    rma_release(NULL, plugin_open_filename);
    plugin_open_filename = NULL;
  }

  /* (Re)claim RMA space for the filename */

  e = rma_claim(NULL, strlen(params) + 1, (void **) &plugin_open_filename);

  if (e) return e;
  if (!plugin_open_filename) return NULL;

  /* Copy it in and fill in the message field */

  strcpy(plugin_open_filename, params);

  open->file_name.ptr = plugin_open_filename;

  /* Broadcast the message */

  RetError(wimp_send_message(Wimp_EUserMessageRecorded, &m, 0, 0, NULL));

  /* Record my_ref in case it bounces */

  plugin_open_reference  = m.hdr.my_ref;
  plugin_open_tried_once = 0; /* We haven't tried once until we get the first bounce */

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_broadcast_open: Successful\n",b,t);
  #endif

  return NULL;
}

/*************************************************/
/* plugin_open_bounced()                         */
/*                                               */
/* Handle a Message_PlugIn_Open bounce.          */
/*                                               */
/* Parameters: Pointer to a Wimp_Message struct  */
/*             holding the bounced message.      */
/*************************************************/

_kernel_oserror * plugin_open_bounced(WimpMessage * m)
{
  browser_data * b;
  MPlugIn_Open * open = (MPlugIn_Open *) &m->data;

  #ifdef TRACE
    if (!plugin_open_tried_once)
    {
      if (tl & (1u<<30)) Printf("plugin_open_bounced: Called\n");
    }
    else
    {
      if (tl & (1u<<30)) Printf("plugin_open_bounced: Called again\n");
    }
  #endif

  /* For a bounce, we get the same message back - so the message reference */
  /* is still in my_ref, it hasn't been copied to your_ref by a receiver.  */

  if (m->hdr.my_ref == plugin_open_reference)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_open_bounced: Message reference %x is known\n", m->hdr.my_ref);
    #endif

    if (plugin_open_tried_once)
    {
      /* Yuk, everything collapsed in a nasty great heap */

      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_open_bounced: Plug-In launch failed\n");
      #endif

      /* Delete the parameters file */

      remove(plugin_open_filename);

      /* Need to free up the RMA space used for the filename */

      rma_release(NULL, plugin_open_filename);

      plugin_open_filename   = NULL;

      plugin_open_reference  = 0;
      plugin_open_tried_once = 0;

      /* Remove the item from the queue, launching a new one */
      /* if there is anything else there.                    */

      RetError(plugin_remove_head_item());
    }
    else
    {
      char             combuf[16]; /* Size of '@PlugInType_xxx' plus terminator */
      char             sysvar[22]; /* As above, preceeded by 'Alias$'           */
      _kernel_swi_regs r;
      int              required;
      BBox             new;

      /* Try launching the Plug-In through Wimp_StartTask */

      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_open_bounced: Attempting Plug-In launch\n");
      #endif

      plugin_open_tried_once = 1;

      if (open->file_type >= 0 && open->file_type <= 4095) sprintf(combuf, "@PlugInType_%03x", open->file_type);
      else                                                 sprintf(combuf, "@PlugInType_%03x", FileType_DATA);

      strcpy(sysvar, "Alias$");
      strcat(sysvar, combuf);

      /* Does the system variable exist? */

      r.r[0] = (int) sysvar;
      r.r[1] = (int) NULL;
      r.r[2] = -1;
      r.r[3] = 0;
      r.r[4] = 0;

      /* _swix will not work correctly for this particular SWI if */
      /* requiring the returned R2 value. Something to do with    */
      /* the call relying on generating an error, but _swix spots */
      /* it and pulls out earlier than the call expects. Or some  */
      /* such thing...                                            */

      _kernel_swi(OS_ReadVarVal, &r, &r);

      required = -r.r[2];

      if (required)
      {
        show_error_ret(_swix(Wimp_StartTask,
                             _IN(0),

                             combuf));
      }

      #ifdef TRACE

        else
        {
          if (tl & (1u<<30)) Printf("plugin_open_bounced: System variable '%s' is not set\n", sysvar);
        }

      #endif

      /* Now rebroadcast the message. Note we can't use the same position, */
      /* as between the two redrawing may have occurred that has changed   */
      /* where the Object now sits.                                        */

      b = plugin_return_browser(open->browser_instance_handle);

      if (!b)
      {
        #ifdef TRACE
          if (tl & (1u<<30)) Printf("plugin_open_bounced: \0211Invalid browser instance handle suspected.\0217\n");
        #endif

        return NULL;
      }

      if (
           !object_get_token_object_box(b,
                                        ((plugin_insts *) open->browser_instance_handle)->token,
                                        &new)
         )
         open->parent_area = new;

      #ifdef TRACE
        if (tl & (1u<<30))
        {
          char debugbuf[1024];
          sprintf(debugbuf,"plugin_open_bounced: New BBox %d, %d, %d, %d",new.xmin,new.ymin,new.xmax,new.ymax);
          Printf("%s\n",debugbuf);

          if (tl & (1u<<30)) Printf("plugin_open_bounced: Rebroadcasting Message_PlugIn_Open\n");
        }
      #endif

      m->hdr.my_ref = m->hdr.your_ref = 0;

      RetError(wimp_send_message(Wimp_EUserMessageRecorded, m, 0, 0, NULL));

      /* Record my_ref in case it bounces for a second time */

      plugin_open_reference = m->hdr.my_ref;
    }
  }

  #ifdef TRACE

    else
    {
      if (tl & (1u<<30)) Printf("plugin_open_bounced: Message reference %x is not known\n", m->hdr.my_ref);

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Unknown message reference %x in plugin_open_bounced",
              m->hdr.my_ref);

      return &erb;
    }

    if (tl & (1u<<30)) Printf("plugin_open_bounced: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_got_opening()                          */
/*                                               */
/* Handle reception of a Message_PlugIn_Opening. */
/*                                               */
/* Parameters: Pointer to a Wimp_Message struct  */
/*             relevant to the message.          */
/*************************************************/

_kernel_oserror * plugin_got_opening(WimpMessage * m)
{
  _kernel_oserror * e       = NULL;
  browser_data    * b;
  MPlugIn_Opening * opening = (MPlugIn_Opening *) &m->data;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_got_opening: Called\n");
  #endif

  if (m->hdr.your_ref == plugin_open_reference)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_opening: Message reference %x is known\n", m->hdr.your_ref);
    #endif

    if (plugin_open_filename)
    {
      if (!(opening->flags & MPlugIn_Opening_WillDeleteParamsItself))
      {
        #ifdef TRACE
          if (tl & (1u<<30)) Printf("plugin_got_opening: Removing parameters file\n");
        #endif

        remove(plugin_open_filename);
      }

      /* Need to free up the RMA space used for the filename */

      rma_release(NULL, plugin_open_filename);
      plugin_open_filename = NULL;
    }

    b = plugin_return_browser(opening->browser_instance_handle);

    if (!b)
    {
      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_got_opening: \0211Invalid browser instance handle suspected.\0217\n");
      #endif

      return NULL;
    }

    /* Tell the Object the PlugIn details */

    object_set_token_object_plugin(b,
                                   ((plugin_insts *) opening->browser_instance_handle)->token,
                                   opening->plugin_instance_handle,
                                   m->hdr.sender);

    /* Deal with cases where the Plug-In wants stuff fetching */

    if (opening->flags & MPlugIn_Opening_WantsDataResourceFetched)
    {
      const char * url = NULL;

      if (b) url = HtmlRelativiseURL(browser_current_url(b),
                                     HtmlOBJECTdata(((plugin_insts *) opening->browser_instance_handle)->token),
                                     b->stream);

      if (!url) url = HtmlOBJECTdata(((plugin_insts *) opening->browser_instance_handle)->token);

      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_got_opening: Plug-In wants data resource fetched\n");
      #endif

      e = plugin_start_new_stream(opening->browser_instance_handle,
                                  url,
                                  opening->plugin_instance_handle,
                                  m->hdr.sender);
    }

    plugin_open_reference  = 0;
    plugin_open_tried_once = 0;
    plugin_open_filename   = NULL;

    if (e) return e;

    /* Remove the item from the queue, launching a new one */
    /* if there is anything else there.                    */

    RetError(plugin_remove_head_item());
  }

  #ifdef TRACE

    else
    {
      if (tl & (1u<<30)) Printf("plugin_got_opening: Message reference %x is not known\n", m->hdr.my_ref);

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Unknown message reference %x in plugin_got_opening",
              m->hdr.your_ref);

      return &erb;
    }

    if (tl & (1u<<30)) Printf("plugin_got_opening: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_send_close()                           */
/*                                               */
/* Tell a Plug-In to close down.                 */
/*                                               */
/* Parameters: Browser instance handle for the   */
/*             browser owning the Plug-In;       */
/*                                               */
/*             Task handle for the Plug-In;      */
/*                                               */
/*             Plug-In instance handle.          */
/*************************************************/

_kernel_oserror * plugin_send_close(unsigned int b, unsigned int task, unsigned int instance)
{
  _kernel_oserror * e;
  WimpMessage       m;
  MPlugIn_Close   * close = (MPlugIn_Close *) &m.data;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_send_close: Called\n");
  #endif

  /* Fill in the header... */

  m.hdr.size        = sizeof(MPlugIn_Close) + sizeof(m.hdr);
  m.hdr.your_ref    = 0;
  m.hdr.action_code = Message_PlugIn_Close;

  /* ...and the body... */

  close->flags                   = 0;
  close->plugin_instance_handle  = instance;
  close->browser_instance_handle = b;

  /* ...then send it. */

  e = wimp_send_message(Wimp_EUserMessageRecorded,
                        &m,
                        task,
                        0,
                        NULL);

  #ifdef TRACE
    if (tl & (1u<<30))
    {
      if (e) Printf("plugin_send_close: Exitting with error\n");
      else   Printf("plugin_send_close: Successful\n");
    }
  #endif

  return e;
}

/*************************************************/
/* plugin_got_reshape_request()                  */
/*                                               */
/* Handle reception of a                         */
/* Message_PlugIn_ReshapeRequest.                */
/*                                               */
/* Parameters: Pointer to a Wimp_Message struct  */
/*             relevant to the message.          */
/*************************************************/

_kernel_oserror * plugin_got_reshape_request(WimpMessage * m)
{
  MPlugIn_ReshapeRequest * request = (MPlugIn_ReshapeRequest *) &m->data;
  browser_data           * b;
  HStream                * t;
  unsigned int             plugin_task;
  int                      line;
  BBox                     size;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_got_reshape_request: Called\n");
  #endif

  /* Does this look valid? */

  if (!request->browser_instance_handle)
  {
    /* No; do nothing */

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_reshape_request: Message held a NULL browser instance handle\n");
    #endif

    return NULL;
  }

  /* Find the Object representing this Plug-In */

  b = plugin_return_browser(request->browser_instance_handle);

  if (
       !is_known_browser(b) ||
       !object_return_info(b,
                           request->plugin_instance_handle,
                           &t,
                           &plugin_task)
     )
  {
    /* Can't find the Object / not a valid browser */

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_reshape_request: %p isn't a known browser, or can't find an Object to match the given handle\n", b);
    #endif

    return NULL;
  }

  /* Does the task handle match? */

  if (plugin_task != (unsigned int) m->hdr.sender)
  {
    /* No, so exit */

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_reshape_request: Senders task handle of %08x doens't match Object's handle, %08x\n", m->hdr.sender, plugin_task);
    #endif

    return NULL;
  }

  /* Right, this looks like a valid request. Find out */
  /* the ancestor line it lies in so we can reformat  */
  /* the page from here.                              */

  line = tokenutils_find_ancestor_line(b, t);

  /* Get the object's current size */

  RetError(object_get_token_object_size(b, t, &size));

  /* Only proceed if the size has changed */

  if (
       size.xmax - size.xmin != request->width  ||
       size.ymax - size.ymin != request->height
     )
  {
    size.xmin = 0;
    size.ymin = 0;
    size.xmax = request->width;
    size.ymax = request->height;

    RetError(object_set_token_object_size(b, t, &size));

    /* Do a reformat if we had a valid line */

    if (line >= 0) RetError(reformat_format_from(b,
                                                 line - 1,
                                                 1,
                                                 0));
  }

  return NULL;
}

/*************************************************/
/* plugin_send_original_reshape()                */
/*                                               */
/* Tell a Plug-In to reposition or resize itself */
/* - not as part of a Plug-In request to resize. */
/*                                               */
/* Parameters: Browser instance handle for the   */
/*             browser owning the Plug-In;       */
/*                                               */
/*             Plug-In task handle;              */
/*                                               */
/*             Plug-In instance handle;          */
/*                                               */
/*             New bounding box for the Plug-In  */
/*             child window (in (parent) window  */
/*             coordinates).                     */
/*************************************************/

_kernel_oserror * plugin_send_original_reshape(unsigned int b, unsigned int task, unsigned int instance, BBox * position)
{
  _kernel_oserror * e;
  WimpMessage       m;
  MPlugIn_Reshape * reshape = (MPlugIn_Reshape *) &m.data;

  #ifdef TRACE
    if (tl & (1u<<30))
    {
      char debugbuf[1024];
      sprintf(debugbuf,"plugin_send_original_reshape: Called for %p to move instance 0x%08x to %d, %d, %d, %d",(plugin_insts *) b,instance,position->xmin,position->ymin,position->xmax,position->ymax);
      Printf("%s\n",debugbuf);
    }
  #endif

  /* Fill in the header... */

  m.hdr.size        = sizeof(MPlugIn_Reshape) + sizeof(m.hdr);
  m.hdr.your_ref    = 0;
  m.hdr.action_code = Message_PlugIn_Reshape;

  /* ...and the body... */

  reshape->flags                   = 0;
  reshape->plugin_instance_handle  = instance;
  reshape->browser_instance_handle = b;
  reshape->parent_window_handle    = ((plugin_insts *) b)->browser->window_handle;
  reshape->parent_area             = *position;

  /* ...then send it. */

  e = wimp_send_message(Wimp_EUserMessageRecorded, &m, task, 0, NULL);

  #ifdef TRACE
    if (tl & (1u<<30))
    {
      if (e) Printf("plugin_send_original_reshape: Exitting with error\n");
      else   Printf("plugin_send_original_reshape: Successful\n");
    }
  #endif

  return e;
}

/*************************************************/
/* plugin_got_url_access()                       */
/*                                               */
/* Handle reception of a                         */
/* Message_PlugIn_URLAccess                      */
/*                                               */
/* Parameters: Pointer to a Wimp_Message struct  */
/*             relevant to the message.          */
/*************************************************/

_kernel_oserror * plugin_got_url_access(WimpMessage * m)
{
  MPlugIn_URLAccess * access = (MPlugIn_URLAccess *) &m->data;
  const char        * url;
  const char        * target;
  browser_data      * targetted;
  browser_data      * b = plugin_return_browser(access->browser_instance_handle);
  browser_data      * ancestor = utils_ancestor(b);

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_got_url_access: Called\n");
  #endif

  url    = plugin_return_string(m, &access->url);
  target = plugin_return_string(m, &access->target);

  #ifdef TRACE
    if (tl & (1u<<30))
    {
      if (url)    Printf("plugin_got_url_access: URL '%s'\n", url);
      else        Printf("plugin_got_url_access: No URL\n");
      if (target) Printf("plugin_got_url_access: Target '%s'\n", target);
      else        Printf("plugin_got_url_access: No Target\n");
    }
  #endif

  if (url)
  {
    /* Relativise the URL if possible */

    if (b)
    {
      char * new_url = HtmlRelativiseURL(browser_current_url(b), url, b->stream);

      if (new_url) url = new_url;
    }

    if (target)
    {
      /* If a target is given, this is a standard URL fetch in a window */

      if (target) targetted = frames_find_target(b, target);
      else        targetted = NULL;

      /* Don't want to ever open a new window if configured */
      /* to run full screen.                                */

      if (targetted || choices.full_screen)
      {
        /* If a named target was found, open in that. Otherwise we must */
        /* be running full screen, so can't open a new window; in this  */
        /* case, open in the ancestor.                                  */

        RetError(fetchpage_new(targetted ? targetted : ancestor,
                               url,
                               1,
                               1));
      }
      else
      {
        /* If we've reached here, a named target wasn't found but the */
        /* browser isn't running full screen either, so open a new    */
        /* window with the name specified in the link.                */

        RetError(windows_create_browser((char *) url,
                                        NULL,
                                        NULL,
                                        (char *) target,
                                        Windows_CreateBrowser_Normal));
      }
    }
    else
    {
      /* If a target is not given, we're supposed to fetch a file */
      /* on behalf of a Plug-In.                                  */

      RetError(plugin_start_new_stream(access->browser_instance_handle,
                                       url,
                                       access->plugin_instance_handle,
                                       m->hdr.sender));
    }
  }

  return NULL;
}

/*************************************************/
/* plugin_got_status()                           */
/*                                               */
/* Handle reception of a Message_PlugIn_Status.  */
/*                                               */
/* Parameters: Pointer to a Wimp_Message struct  */
/*             relevant to the message.          */
/*************************************************/

_kernel_oserror * plugin_got_status(WimpMessage * m)
{
  MPlugIn_Status * status   = (MPlugIn_Status *) &m->data;
  browser_data   * b        = plugin_return_browser(status->browser_instance_handle);
  browser_data   * ancestor = utils_ancestor(b);
  const char     * message;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_got_status: Called\n");
  #endif

  if (!b || !ancestor)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_status: \0211Can't find a valid [ancestor] browser\0217\n");
    #endif

    return NULL;
  }

  message = plugin_return_string(m, &status->status);

  if (!message || !*message)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_status: Null message string or pointer; cancelling Plug-in message status\n");
    #endif

    free(ancestor->plugin_status);
    ancestor->plugin_status = NULL;

    return toolbars_cancel_status(ancestor, Toolbars_Status_PlugIn);
  }

  /* Otherwise, we need to allocate space for the message in */
  /* the browser_data structure so the Toolbar routines can  */
  /* get at it.                                              */

  free(ancestor->plugin_status);
  ancestor->plugin_status = malloc(strlen(message) + 1);

  if (ancestor->plugin_status)
  {
    /* Allocation successful - copy the string over and update */
    /* the status bar.                                         */

    strcpy(ancestor->plugin_status, message);

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_status: Successful with '%s'\n", message);
    #endif

    return toolbars_update_status(ancestor, Toolbars_Status_PlugIn);
  }
  else
  {
    /* Allocation failed - cancel any existing Plug-In message */

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_status: Allocation for message string failed, cancelling status\n");
    #endif

    return toolbars_cancel_status(ancestor, Toolbars_Status_PlugIn);
  }
}

/*************************************************/
/* plugin_got_busy()                             */
/*                                               */
/* Handle reception of a Message_PlugIn_Busy.    */
/*                                               */
/* Parameters: Pointer to a Wimp_Message struct  */
/*             relevant to the message.          */
/*************************************************/

_kernel_oserror * plugin_got_busy(WimpMessage * m)
{
  MPlugIn_Busy * busy     = (MPlugIn_Busy *) &m->data;
  browser_data * b        = plugin_return_browser(busy->browser_instance_handle);
  browser_data * ancestor = utils_ancestor(b);
  int            active;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_got_busy: Called\n");
  #endif

  if (!b || !ancestor)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_busy: \0211Can't find a valid [ancestor] browser\0217\n");
    #endif

    return NULL;
  }

  /* Are we setting or clearing a busy state? */

  active = busy->flags & MPlugIn_Busy_IsBusy;

  if (ancestor->plugin_active != active)
  {
    ancestor->plugin_active = active;

    /* Are we turning it off? */

    if (!active)
    {
      /* If we've only got a handler because of the Plug-In, turn it off */
      /* and if required go to the drift handler instead.                */

      if (!b->anim_handler)
      {
        deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation,b);
        b->anim_handler = 0;

        /* If the Controls say to install the 'drift' handler to ensure   */
        /* the animation finishes on the first frame, and that handler is */
        /* not already installed, install it.                             */

        if (controls.anim_drift && !b->anim_drift)
        {
          register_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation_drift,b);
          b->anim_drift = 1;
        }
      }
    }

    /* The animation should be turned on / kept on */

    else
    {
      /* Deregister any existing drift handler */

      if (b->anim_drift)
      {
        deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation_drift,b);
        b->anim_drift = 0;
      }

      /* Register the full time animation handler, if it isn't already registered */

      if (!b->anim_handler)
      {
        /* Is there an appropriate gadget in the status bar? */

        ObjectId lower = toolbars_get_lower(b);
        BBox     box;

        if (!gadget_get_bbox(0, lower, StatusBarAnimAnim, &box))
        {
          /* Yes, so install an animation handler. Don't set the */
          /* anim_handler flag! The fact that anim_handler is    */
          /* unset and plugin_active is set is used by other     */
          /* bits of code.                                       */

          register_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation,b);
        }
      }
    }
  }

  return NULL;
}

/*************************************************/
/* plugin_setup_stream()                         */
/*                                               */
/* Sets up a plugin_stream structure in a new    */
/* browser so it may fetch data for a plugin     */
/* owned by another browser.                     */
/*                                               */
/* Not all fields are filled in. The browser     */
/* and token details are, and the URL field is   */
/* filled in. The Mime type is also filled in    */
/* on the basis of a filename extension from the */
/* URL, if possible. Other fields are zeroed.    */
/*                                               */
/* All string_value values are allocated in the  */
/* RMA. It is therefore essential to ensure that */
/* these are always completely freed.            */
/*                                               */
/* Parameters: Browser instance handle for the   */
/*             browser owning the Plug-In;       */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             for the browser that will fetch   */
/*             the data;                         */
/*                                               */
/*             URL to fetch.                     */
/*************************************************/

_kernel_oserror * plugin_setup_stream(unsigned int owner, browser_data * fetcher, const char * url)
{
  plugin_stream * stream;
  HStream       * object = ((plugin_insts *) owner)->token;

  if (!url) return NULL;

  /* Allocate memory for the item */

  stream = calloc(1, sizeof(plugin_stream));
  if (!stream) return make_no_cont_memory_error(11);

  /* Now fill it in */

  fetcher->pstream = stream;

  stream->browser_instance_handle = owner;
  stream->token                   = object;

  /* Claim RMA space for the URL */

  RetError(rma_claim(fetcher,
                     strlen(url) + 1,
                     (void **) &stream->url.ptr));

  strcpy(stream->url.ptr, url);

  /* Fill in the Mime type if there's a filename extension */

  if (strrchr(url, '.'))
  {
    char mimetype[MimeMap_MaximumBufferSizeRequired];

    if (!mimemap_extension_to_mime(strrchr(url, '.'),
                                   mimetype,
                                   sizeof(mimetype)))
    {
      RetError(rma_claim(fetcher,
                         strlen(mimetype) + 1,
                         (void **) &stream->mime.ptr));

      strcpy(stream->mime.ptr, mimetype);
    }
    else stream->mime.ptr = NULL;
  }
  else stream->mime.ptr = NULL;

  /* Finished */

  return NULL;
}

/*************************************************/
/* plugin_start_new_stream()                     */
/*                                               */
/* Begin a fetch to a temporary file. When we    */
/* have enough data to know the data type,       */
/* the fetcher will start the stream message     */
/* protocol, by calling                          */
/* plugin_send_original_stream_new.              */
/*                                               */
/* Parameters: Browser instance handle for the   */
/*             browser owning the Plug-In;       */
/*                                               */
/*             URL to fetch;                     */
/*                                               */
/*             Plug-In instance handle;          */
/*                                               */
/*             Plug-In task handle.              */
/*************************************************/

_kernel_oserror * plugin_start_new_stream(unsigned int b, const char * data, unsigned int instance, unsigned int task)
{
  plugin_stream * pstream;
  const char    * file;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_start_new_stream: Called for %p, instance 0x%08x\n",(plugin_insts *) b,instance);
  #endif

  if (!data || !*data)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_start_new_stream: No URL to fetch - exitting\n");
    #endif

    return NULL;
  }

  /* See if we've already got a temporary file in Scrap relating to this URL */

  file = plugin_find_file(data);

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_start_new_stream: Initiate fetch for '%s'\n",data);
  #endif

  /* Initiate a fetch for this item. If we've got a file holding */
  /* the data already, give a null URL - windows_create_browser  */
  /* knows what this means (in the context of an object fetch).  */

  RetError(windows_create_browser(file ? NULL : (char *) data,
                                  NULL,
                                  NULL,
                                  NULL,
                                  Windows_CreateBrowser_ForPlugIn));

  /* Add stream related information to the browser */

  RetError(plugin_setup_stream(b,
                               last_browser,
                               data));

  /* Fill in more information for the stream we're going to */
  /* try and establish                                      */

  pstream = last_browser->pstream;

  pstream->browser_instance_handle = b;
  pstream->token                   = ((plugin_insts *) b)->token; /* (Because this is externally visible, whereas plugin_insts structures aren't) */

  pstream->plugin_instance_handle  = instance;
  pstream->plugin_task_handle      = task;

  if (file)
  {
    /* If we've got a file already, need to remember its filename and */
    /* start the message protocol to tell the Plug-In about it.       */

    RetError(rma_claim(last_browser,
                       strlen(file) + 1,
                       (void **) &pstream->filename.ptr));

    strcpy(pstream->filename.ptr, file);

    RetError(plugin_send_original_stream_new(last_browser));
  }

  /* OK, finished. The fetcher will now be called on Nulls (since we */
  /* did windows_create_browser with a URL to start fetching on and  */
  /* set the flag to say 'for a Plug-In'), so the fetcher will now   */
  /* deal with things from here for a while.                         */

  return NULL;
}

/*************************************************/
/* plugin_send_original_stream_new()             */
/*                                               */
/* Sent out a Message_PlugIn_StreamNew to start  */
/* a fetch on a Plug-Ins behalf.                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the stream - its      */
/*             'pstream' field should point to a */
/*             valid plugin_stream struct.       */
/*************************************************/

_kernel_oserror * plugin_send_original_stream_new(browser_data * b)
{
  WimpMessage         m;
  MPlugIn_StreamNew * stream  = (MPlugIn_StreamNew *) &m.data;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_send_original_stream_new: Called for %p\n",b);
  #endif

  if (!b->pstream)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_send_original_stream_new: No plugin_stream - exitting\n");
    #endif

    return NULL;
  }

  /* Fill in the header... */

  m.hdr.size        = sizeof(MPlugIn_StreamNew) + sizeof(m.hdr);
  m.hdr.your_ref    = 0;
  m.hdr.action_code = Message_PlugIn_StreamNew;

  /* ...and the body... */

  stream->flags                          = 0;

  stream->plugin_instance_handle         = b->pstream->plugin_instance_handle;
  stream->browser_instance_handle        = (unsigned int) b->pstream->browser_instance_handle;

  stream->browser_stream_instance_handle = (unsigned int) b;

  stream->url.ptr                        = b->pstream->url.ptr;

  stream->eos                            = 0;
  stream->last_modified                  = 0;
  stream->notify                         = 0;

  stream->mime_type.ptr                  = b->pstream->mime.ptr;
  stream->window_target.ptr              = b->pstream->target.ptr;

  /* Now send it. The reply should give us details on the flags (type */
  /* of stream) and the plugin's stream instance handle.              */
  /*                                                                  */
  /* Of course, if it bounces we need to cancel the fetch, free up    */
  /* all the various bits and pieces assocated with the stream        */
  /* including the RMA space, and so-forth.                           */

  RetError(wimp_send_message(Wimp_EUserMessageRecorded,
                             &m,
                             b->pstream->plugin_task_handle,
                             -1,
                             NULL));

  plugin_stream_new_reference = m.hdr.my_ref;

  return NULL;
}

/*************************************************/
/* plugin_got_stream_new()                       */
/*                                               */
/* Handles reception of a                        */
/* Message_PlugIn_StreamNew. Will consequently   */
/* continue a fetch on behalf of a Plug-In,      */
/* issuing Message_PlugIn_StreamAsFile when the  */
/* fetch is complete.                            */
/*                                               */
/* True streaming is not supported yet.          */
/*                                               */
/* Parameters: Pointer to a WimpMessage struct   */
/*             holding the message details.      */
/*************************************************/

_kernel_oserror * plugin_got_stream_new(WimpMessage * m)
{
  MPlugIn_StreamNew * stream = (MPlugIn_StreamNew *) &m->data;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_got_stream_new: Called\n");
  #endif

  if (m->hdr.your_ref == plugin_stream_new_reference)
  {
    browser_data * b;

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_got_stream_new: Message reference %x is known\n", m->hdr.your_ref);
    #endif

    /* Get the browser that is fetching - this is stored as the */
    /* stream instance handle.                                  */

    b = (browser_data *) stream->browser_stream_instance_handle;

    if (!is_known_browser(b))
    {
      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_got_stream_new: Browser %p is not known...\n", b);
      #endif

      return NULL;
    }

    b->pstream->plugin_stream_handle = stream->plugin_stream_instance_handle;
    b->pstream->stream_flags         = stream->flags;

    /* We should take different action according to the flags, but at */
    /* the moment only 'fetch as file' is supported.                  */

    if (b->pstream->stream_flags & MPlugIn_StreamNew_StreamTypeMask != MPlugIn_StreamNew_StreamTypeAsFileOnly)
    {
      #ifdef TRACE

        if (tl & (1u<<30)) Printf("plugin_got_stream_new: Can't handle stream type %d\n", b->pstream->stream_flags & MPlugIn_StreamNew_StreamTypeMask);

        erb.errnum = Utils_Error_Custom_Message;

        sprintf(erb.errmess,
                "Can't handle stream type %d in plugin_got_stream_new",
                b->pstream->stream_flags & MPlugIn_StreamNew_StreamTypeMask);

        show_error_ret(&erb);

      #endif

      return plugin_send_original_stream_destroy(b,
                                                 MPlugIn_StreamDestroy_Reason_Error);
    }

    /* OK, we can handle the stream type. In that case, just exit;  */
    /* the fetcher will keep fetching, and when complete, will give */
    /* the file to the Plug-In.                                     */
    /*                                                              */
    /* So that it knows to do this, mark the stream as active.      */

    b->pstream->active = 1;
  }

  #ifdef TRACE

    else
    {
      if (tl & (1u<<30)) Printf("plugin_got_stream_new: Message reference %x is not known\n", m->hdr.your_ref);

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Unknown message reference %x in plugin_got_stream_new",
              m->hdr.your_ref);

      return &erb;
    }

    if (tl & (1u<<30)) Printf("plugin_got_stream_new: Successful\n");

  #endif

  return NULL;
}

/*************************************************/
/* plugin_stream_new_bounced()                   */
/*                                               */
/* Handles a Message_PlugIn_StreamNew bounce,    */
/* aborting any associated fetch.                */
/*                                               */
/* Parameters: Pointer to a WimpMessage struct   */
/*             holding the message details.      */
/*************************************************/

_kernel_oserror * plugin_stream_new_bounced(WimpMessage * m)
{
  MPlugIn_StreamNew * stream = (MPlugIn_StreamNew *) &m->data;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_stream_new_bounced: Called\n");
  #endif

  /* Do we recognise the message (checking my_ref not your_ref since this */
  /* is a bounce, not a reply)?                                           */

  if (m->hdr.my_ref == plugin_stream_new_reference)
  {
    browser_data * b;

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_stream_new_bounced: Message reference %x is known\n", m->hdr.my_ref);
    #endif

    /* Get the browser that is fetching - this is stored as the */
    /* stream instance handle.                                  */

    b = (browser_data *) stream->browser_stream_instance_handle;

    if (!is_known_browser(b))
    {
      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_stream_new_bounced: Browser %p is not known...\n", b);
      #endif

      return NULL;
    }

    /* The browser is known (current), so close it down. This takes */
    /* care of everything, including memory management issues.      */

    windows_close_browser(b);
  }

  #ifdef TRACE

    else
    {
      if (tl & (1u<<30)) Printf("plugin_stream_new_bounced: Message reference %x is not known\n", m->hdr.my_ref);

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Unknown message reference %x in plugin_stream_new_bounced",
              m->hdr.my_ref);

      return &erb;
    }

    if (tl & (1u<<30)) Printf("plugin_stream_new_bounced: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_send_original_stream_destroy()         */
/*                                               */
/* Send a Message_PlugIn_StreamDestroy to a      */
/* Plug-In, to close down a stream between a     */
/* browser and a Plug-In.                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             that was fetching data for the    */
/*             Plug-In, not the one that owns    */
/*             the actual Plug-In instance.      */
/*                                               */
/*             Reason for shutting down the      */
/*             stream (see reason code           */
/*             definitions in PlugIn.h).         */
/*************************************************/

_kernel_oserror * plugin_send_original_stream_destroy(browser_data * b, int reason)
{
  WimpMessage             m;
  MPlugIn_StreamDestroy * destroy = (MPlugIn_StreamDestroy *) &m.data;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_send_original_stream_destroy: Called for %p\n",b);
  #endif

  /* Can't do anything if we don't have a browser with an */
  /* associated plugin_stream structure                   */

  if (!b || !b->pstream)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_send_original_stream_destroy: Null browser or plugin_stream pointer; exitting\n");
    #endif

    return NULL;
  }

  /* Fill in the header... */

  m.hdr.size        = sizeof(MPlugIn_StreamDestroy) + sizeof(m.hdr);
  m.hdr.your_ref    = 0;
  m.hdr.action_code = Message_PlugIn_StreamDestroy;

  /* ...and the body... */

  destroy->flags                          = 0;

  destroy->plugin_instance_handle         = b->pstream->plugin_instance_handle;
  destroy->browser_instance_handle        = b->pstream->browser_instance_handle;

  destroy->plugin_stream_instance_handle  = b->pstream->plugin_stream_handle;
  destroy->browser_stream_instance_handle = (unsigned int) b;

  destroy->url.ptr                        = b->pstream->url.ptr;

  destroy->eos                            = b->pstream->eos;
  destroy->last_modified                  = b->pstream->last_modified;
  destroy->notify                         = b->pstream->notify;

  destroy->reason                         = reason;

  /* Send it */

  return wimp_send_message(Wimp_EUserMessage,
                           &m,
                           b->pstream->plugin_task_handle,
                           -1,
                           NULL);
}

/*************************************************/
/* plugin_abort_stream()                         */
/*                                               */
/* Stop any fetch on behalf of a Plug-In, and    */
/* free all associated structures (with the      */
/* exception of the fetching browser_data        */
/* struct itself).                               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which was dealing with the data   */
/*             streaming.                        */
/*************************************************/

_kernel_oserror * plugin_abort_stream(browser_data * b)
{
  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_abort_stream: Called for browser %p\n", b);
  #endif

  if (!b) return NULL;

  /* Do we have an active stream? If so, must tell the Plug-In */
  /* that the stream is being destroyed.                       */

  if (b->pstream->active)
  {
    RetError(plugin_send_original_stream_destroy(b, MPlugIn_StreamDestroy_Reason_User));

    /* If we have a temporary file, remove it */

    if (b->pstream->filename.ptr) remove(b->pstream->filename.ptr);
  }

  /* Now free the various RMA components */

  if (b->pstream->url.ptr)      rma_release(b, b->pstream->url.ptr);
  if (b->pstream->mime.ptr)     rma_release(b, b->pstream->mime.ptr);
  if (b->pstream->target.ptr)   rma_release(b, b->pstream->target.ptr);
  if (b->pstream->filename.ptr) rma_release(b, b->pstream->filename.ptr);

  /* Finally, free the plugin_stream structure itself */

  free(b->pstream);
  b->pstream = NULL;

  /* Finished */

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_abort_stream: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_fetch_completed()                      */
/*                                               */
/* Called when a fetch for a Plug-In has been    */
/* completed (e.g. by fetchpage_fetch).          */
/*                                               */
/* Parameters: Pointer to the browser_data       */
/*             struct doing the fetch, with its  */
/*             'pstream' field pointing to a     */
/*             valid plugin_stream struct.       */
/*************************************************/

_kernel_oserror * plugin_fetch_completed(browser_data * b)
{
  WimpMessage            m;
  MPlugIn_StreamAsFile * stream = (MPlugIn_StreamAsFile *) &m.data;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_fetch_completed: Called for %p\n", b);
  #endif

  if (!b || !b->pstream) return NULL;

  if (!b->pstream->active)
  {
    #ifdef TRACE

      if (tl & (1u<<30)) Printf("plugin_fetch_completed: Stream is inactive, exitting\n");

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Stream associated with browser %p is not active in plugin_fetch_completed",
              b);

      show_error_ret(&erb);

    #endif

    return NULL;
  }

  /* First, tell the Plug-In about the stream */

  m.hdr.size        = sizeof(MPlugIn_StreamAsFile) + sizeof(m.hdr);
  m.hdr.your_ref    = 0;
  m.hdr.action_code = Message_PlugIn_StreamAsFile;

  /* Fill in the body */

  stream->flags                          = 0;

  stream->plugin_instance_handle         = b->pstream->plugin_instance_handle;
  stream->browser_instance_handle        = b->pstream->browser_instance_handle;

  stream->plugin_stream_instance_handle  = b->pstream->plugin_stream_handle;
  stream->browser_stream_instance_handle = (int) b;

  stream->url.ptr                        = b->pstream->url.ptr;

  stream->eos                            = b->pstream->eos;
  stream->last_modified                  = b->pstream->last_modified;
  stream->notify                         = b->pstream->notify;

  stream->pathname.ptr                   = b->pstream->filename.ptr;

  /* Send the message */

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_fetch_completed: Sending Message_PlugIn_StreamAsFile\n");
  #endif

  RetError(wimp_send_message(Wimp_EUserMessage,
                             &m,
                             b->pstream->plugin_task_handle,
                             -1,
                             NULL));

  /* Install a null handler to close the stream down. Must do this */
  /* to give the Plug-In time to deal with the message we've sent  */
  /* before we free all the strings it points to!                  */
  /*                                                               */
  /* Mark that this has been installed in the plugin_stream struct */
  /* so fetcher functions know not to close the window themselves. */

  b->pstream->will_close_itself = 1;

  register_null_claimant(Wimp_ENull, (WimpEventHandler *) plugin_close_stream, b);

  /* OK, finished. */

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_fetch_completed: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_close_stream()                         */
/*                                               */
/* Closes down a stream for a browser by marking */
/* it as inactive and calling                    */
/* windows_close_browser.                        */
/*                                               */
/* Parameters are as for a standard Wimp event   */
/* handler (this is called on null events).      */
/* The handle should point to a browser_data     */
/* struct that is dealing with the stream.       */
/*************************************************/

static int plugin_close_stream(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_close_stream: Called for %p\n",handle);
  #endif

  /* Only proceed if this is a known browser */

  if (is_known_browser(handle))
  {
    handle->pstream->active            = 0;
    handle->pstream->will_close_itself = 0;

    /* We've flagged the stream as inactive, so windows_close_browser */
    /* (see below) won't end up sending Message_PlugIn_StreamDestroy  */
    /* to the Plug-In. So we must send it now (quoting a reason code  */
    /* of success, rather than error or user, as would have been sent */
    /* via. windows_close_browser if we'd left the stream flagged as  */
    /* active).                                                       */

    plugin_send_original_stream_destroy(handle,
                                        MPlugIn_StreamDestroy_Reason_Success);

    /* Record the temporary file details so it can be removed on shutdown. */
    /* Only do this if the browser had a URL to fetch, since otherwise,    */
    /* we went through an existing file entry.                             */

    plugin_add_file_entry(handle->pstream->filename.ptr, handle->pstream->url.ptr);

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_close_stream: Calling windows_close_browser\n");
    #endif

    /* Now shut the fetch down, freeing browser allocated memory */
    /* associated with the stream in passing.                    */

    windows_close_browser(handle);
  }

  #ifdef TRACE

    else
    {
      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_close_stream: This is not a recognised current browser...\n");
      #endif
    }

  #endif

  /* Deregister the handler */

  deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) plugin_close_stream, handle);

  /* Finished */

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_close_stream: Successful\n");
  #endif

  return 0;
}

/*************************************************/
/* plugin_add_file_entry()                       */
/*                                               */
/* Adds a record of a Plug-In temporary file to  */
/* the list of such records. Such files are      */
/* deleted later, for example, on calling        */
/* plugin_shutdown.                              */
/*                                               */
/* Parameters: Pointer to the pathname to        */
/*             record;                           */
/*                                               */
/*             Pointer to the URL this relates   */
/*             to, or NULL if unknown.           */
/*************************************************/

static _kernel_oserror * plugin_add_file_entry(const char * pathname, const char * url)
{
  plugin_files * entry;
  int            plen, ulen;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_add_file_entry: Called\n");
  #endif

  if (!pathname || !*pathname)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_add_file_entry: Null pathname or null string; exitting\n");
    #endif

    return NULL;
  }

  /* Is this entry already present? */

  entry = plugin_files_head;

  while (entry)
  {
    /* If so, just exit */

    if (entry->url && url && !strcmp(entry->url, url)) return NULL;

    entry = entry->next;
  }

  /* Find out string lengths */

  plen = strlen(pathname);
  ulen = url ? strlen(url) : 0;

  /* Allocate memory for the structure itself */

  entry = malloc(sizeof(plugin_files));
  if (!entry) return make_no_memory_error(23);

  /* Allocate memory for the strings and copy them in */

  if (plen)
  {
    entry->pathname = malloc(plen + 1);
    if (!entry->pathname) return make_no_memory_error(23);

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_add_file_entry: Adding pathname '%s'\n", pathname);
    #endif

    strcpy(entry->pathname, pathname);
  }
  else entry->pathname = NULL;

  if (ulen)
  {
    entry->url = malloc(ulen + 1);
    if (!entry->url) return make_no_memory_error(23);

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_add_file_entry: Adding URL '%s'\n", url);
    #endif

    strcpy(entry->url, url);
  }
  else entry->url = NULL;

  /* Link the item in */

  entry->next       = plugin_files_head;
  plugin_files_head = entry;

  /* Finished */

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_add_file_entry: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_find_file()                            */
/*                                               */
/* Checks the records of Plug-In temporary files */
/* to see if one matches the given URL, and if   */
/* so returns a pointer to the filename.         */
/*                                               */
/* Parameters: Pointer to the URL to check.      */
/*                                               */
/*                                               */
/* Returns:    Pointer to the filename relates   */
/*             to, or NULL if unknown.           */
/*************************************************/

static const char * plugin_find_file(const char * url)
{
  plugin_files * entry = plugin_files_head;

  while (entry)
  {
    if (entry->url && !strcmp(entry->url, url)) return entry->pathname;
    else entry = entry->next;
  }

  return NULL;
}

/*************************************************/
/* plugin_flush_files()                          */
/*                                               */
/* Removes all records of Plug-In temporary      */
/* files, deleteing (sp?) the files as it goes.  */
/*************************************************/

static void plugin_flush_files(void)
{
  plugin_files * entry = plugin_files_head;
  plugin_files * next;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_flush_files: Called\n");
  #endif

  while (entry)
  {
    next = entry->next;

    if (entry->url)
    {
      /* Free the URL string */

      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_flush_files: Free URL '%s'\n", entry->url);
      #endif

      free(entry->url);
    }

    if (entry->pathname)
    {
      /* Delete the temporary file and free the pathname string */

      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_flush_files: Remove and free pathname '%s'\n", entry->pathname);
      #endif

      remove(entry->pathname);
      free  (entry->pathname);
    }

    /* Free the structure itself */

    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_flush_files: Free entry '%p'\n", entry);
    #endif

    free(entry);

    /* Move to the next item */

    entry = next;
  }

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_flush_files: Successful\n");
  #endif

  plugin_files_head = NULL;

  return;
}

/*************************************************/
/* plugin_add_instance_entry()                   */
/*                                               */
/* Adds a record of a Plug-In to the list of     */
/* records, which the browser points to in the   */
/* browser instance handles quoted back to those */
/* Plug-Ins. This allows a one word handle to    */
/* give back information on the browser, token,  */
/* Plug-In task handle, etc.                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which owns the Plug-In;           */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the Plug-In;         */
/*                                               */
/*             The Plug-In task handle, if       */
/*             known (else 0).                   */
/*************************************************/

static _kernel_oserror * plugin_add_instance_entry(browser_data * b, HStream * t, unsigned int task)
{
  plugin_insts * entry;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_add_instance_entry: Called for %p, %p, 0x%08x\n",b,t,task);
  #endif

  /* Allocate space for the entry */

  entry = malloc(sizeof(plugin_insts));
  if (!entry) return make_no_memory_error(24);

  /* Fill the entry in */

  entry->browser            = b;
  entry->token              = t;
  entry->plugin_task_handle = task;

  entry->next               = plugin_insts_head;
  plugin_insts_head         = entry;

  /* Finished */

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_add_instance_entry: Successful (new entry %p)\n",entry);
  #endif

  return NULL;
}

/*************************************************/
/* plugin_find_instance_entry()                  */
/*                                               */
/* Finds an entry in the list of structures      */
/* holding information on Plug-Ins, based on the */
/* owning browser and representing HStream       */
/* struct.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which owns the Plug-In;           */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the Plug-In.         */
/*                                               */
/* Returns:    Pointer to the entry, or NULL if  */
/*             not found.                        */
/*************************************************/

static plugin_insts * plugin_find_instance_entry(browser_data * b, HStream * t)
{
  plugin_insts * entry = plugin_insts_head;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_find_instance_entry: Called for %p, %p\n",b,t);
  #endif

  while (entry)
  {
    if (entry->browser == b && entry->token == t)
    {
      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_find_instance_entry: Found one, returning %p\n",entry);
      #endif

      return entry;
    }

    entry = entry->next;
  }

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_find_instance_entry: No entry found, returning NULL\n");
  #endif

  return NULL;
}

/*************************************************/
/* plugin_obtain_instance()                      */
/*                                               */
/* Returns an instance handle for a Plug-In      */
/* based on the owning browser and representing  */
/* HStream struct.                               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which owns the Plug-In;           */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the Plug-In;         */
/*                                               */
/*             Pointer to an unsigned int, in    */
/*             which the handle is written.      */
/*                                               */
/* Assumes:    The int pointer may *not* be      */
/*             NULL.                             */
/*************************************************/

_kernel_oserror * plugin_obtain_instance(browser_data * b, HStream * t, unsigned int * instance)
{
  plugin_insts * browser_instance = NULL;

  *instance = 0;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_obtain_instance: Called for %p, %p\n",b,t);
  #endif

  browser_instance = plugin_find_instance_entry(b, t);

  if (!browser_instance)
  {
    #ifdef TRACE
      if (tl & (1u<<30)) Printf("plugin_obtain_instance: No existing item, creating a new one\n");
    #endif

    RetError(plugin_add_instance_entry(b, t, 0));

    browser_instance = plugin_insts_head;
  }

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_obtain_instance: Returning %p\n",b,t);
  #endif

  *instance = (unsigned int) browser_instance;

  return NULL;
}

/*************************************************/
/* plugin_remove_instance_entry()                */
/*                                               */
/* Removes a record of a Plug-In from the list   */
/* of such records.                              */
/*                                               */
/* Parameters: Pointer to the entry (i.e. the    */
/*             browser instance handle quoted to */
/*             the Plug-In to which the entry    */
/*             refers).                          */
/*************************************************/

static void plugin_remove_instance_entry(plugin_insts * remove)
{
  plugin_insts * entry = plugin_insts_head;
  plugin_insts * prev  = NULL;

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_remove_instance_entry: Called for %p\n",remove);
  #endif

  /* Find the item */

  while (entry)
  {
    if (entry == remove)
    {
      /* Found item, so remove it, linking the previous */
      /* item to its next item as required.             */

      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_remove_instance_entry: Found it, removing item\n");
      #endif

      if (prev) prev->next   = entry->next;
      else plugin_insts_head = entry->next;

      free(entry);

      break;
    }

    /* Not found it, so move on to the next item */

    prev  = entry;
    entry = entry->next;
  }

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_remove_instance_entry: Successful\n");
  #endif

  return;
}

/*************************************************/
/* plugin_flush_instance_entries()               */
/*                                               */
/* Removes all Plug-In instance entries for a    */
/* given browser, or unconditionally, all items. */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for which items will be removed,  */
/*             or NULL for all of them.          */
/*************************************************/

void plugin_flush_instance_entries(browser_data * b)
{
  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_flush_instance_entries: Called for %p\n",b);
  #endif

  if (!b)
  {
    /* Simple case first */

    while (plugin_insts_head)
    {
      #ifdef TRACE
        if (tl & (1u<<30)) Printf("plugin_flush_instance_entries: Removing entry %p\n", plugin_insts_head);
      #endif

      plugin_remove_instance_entry(plugin_insts_head);
    }
  }
  else
  {
    plugin_insts * entry = plugin_insts_head;
    plugin_insts * next;

    /* Slightly more complex case for a specific browser */

    while (entry)
    {
      next = entry->next;

      if (entry->browser == b)
      {
        #ifdef TRACE
          if (tl & (1u<<30)) Printf("plugin_flush_instance_entries: Removing entry %p\n", entry);
        #endif

        plugin_remove_instance_entry(entry);
      }

      entry = next;
    }
  }

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_flush_instance_entries: Successful %p\n",b);
  #endif

  return;
}

/*************************************************/
/* plugin_shutdown()                             */
/*                                               */
/* Closes down all activities, flushing the      */
/* Plug-In queue and freeing any RMA associated  */
/* with string_value fields for in-transit       */
/* messages.                                     */
/*************************************************/

void plugin_shutdown(void)
{
  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_shutdown: Called\n");
  #endif

  /* In transit Message_PlugIn_Open broadcasts */

  if (plugin_open_filename)
  {
    rma_release(NULL, plugin_open_filename);
    plugin_open_filename = NULL;
  }

  /* Clear other message fields */

  plugin_open_reference  = 0;
  plugin_open_tried_once = 0;

  /* For now that's all we have to do for messages. Now */
  /* flush out the various queues and lists.            */

  plugin_flush_queue(NULL, 0);
  plugin_flush_files();
  plugin_flush_instance_entries(NULL);

  /* Finished */

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("plugin_shutdown: Successful\n");
  #endif

  return;
}