/* 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   : Object.c                               */
/*                                                 */
/* Purpose: Handling OBJECT, APPLET and EMBED.     */
/*                                                 */
/* 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 "ChoiceDefs.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "FetchHTML.h"
#include "Filetypes.h"
#include "Images.h"
#include "Memory.h"
#include "MimeMap.h"
#include "MiscDefs.h"
#include "PlugIn.h"
#include "Redraw.h"
#include "Reformat.h"
#include "TokenUtils.h"
#include "Toolbars.h"
#include "URLveneer.h"

#include "Object.h"

/* Static function prototypes */

static int               object_get_token_object    (browser_data * b, HStream * t);
static _kernel_oserror * object_get_object_size     (browser_data * b, int object, BBox * size);
static _kernel_oserror * object_set_object_size     (browser_data * b, int object, BBox * size);
static int               object_get_object_position (browser_data * b, int object, int * x, int * y);
static _kernel_oserror * object_get_object_plugin   (browser_data * b, int object, unsigned int * plugin_instance, unsigned int * plugin_task);
static _kernel_oserror * object_set_object_plugin   (browser_data * b, int object, unsigned int plugin_instance, unsigned int plugin_task);

/*************************************************/
/* object_new_object()                           */
/*                                               */
/* Adds a structure for a new Object to a        */
/* browser's array of Objects.                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to add to;                        */
/*                                               */
/*             Pointer to a token representing   */
/*             the Object (OBJECT, EMBED or      */
/*             APPLET tag).                      */
/*************************************************/

_kernel_oserror * object_new_object(browser_data * b, HStream * t)
{
  const char * data;
  const char * type;

  /* If this isn't an Object token, do nothing (well, */
  /* complain about it in TRACE builds).              */

  if (!t || !ISOBJECT(t))
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Token %08x passed to object_new_object does not represent an OBJECT, APPLET or EMBED tag",
              (int) t);

      return &erb;

    #endif

    return NULL;
  }

  /* Allocate memory for the item */

  RetError(memory_set_chunk_size(b,
                                 NULL,
                                 CK_OBJB,
                                 (b->nobjects + 1) * sizeof(object_info)));

  /* Fill in the new item */

  b->odata[b->nobjects].plugin_instance_handle = 0;
  b->odata[b->nobjects].plugin_task_handle     = 0;
  b->odata[b->nobjects].token                  = t;

  /* Until this is seen by the user, open it off-screen to avoid */
  /* having to send out lots of reposition calls during the main */
  /* page reformat.                                              */

  b->odata[b->nobjects].x                      = -1;
  b->odata[b->nobjects].y                      = -1;

  /* Initially set invalid widths / heights so that it must be */
  /* worked out properly later.                                */

  b->odata[b->nobjects].w                      = -1;
  b->odata[b->nobjects].h                      = -1;

  /* Set up initial flags values */

  b->odata[b->nobjects].isimage                = 0;
  b->odata[b->nobjects].isplugin               = 0;
  b->odata[b->nobjects].broadcast_sent         = 0;

  /* Finally, increment the objects counter */

  b->nobjects++;

  /* Now deal with data types we can handle inline */

  data = HtmlOBJECTdata(t);
  type = HtmlOBJECTtype(t);

  if (data && *data)
  {
    int filetype;

    /* If we have a Mime type, use it */

    if (type && *type)
    {
      if (mimemap_mime_to_riscos(type, &filetype)) filetype = FileType_DATA;
    }

    /* Otherwise, try to work it out from the filename extension */

    else
    {
      const char * ext;

      ext = data + strlen(data) - 1;

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

      if (*ext == '.')
      {
        if (mimemap_extension_to_riscos(ext, &filetype)) filetype = FileType_DATA;
      }
      else filetype = FileType_DATA;
    }

    /* Is it an image we can handle? */

    if (
         filetype == FileType_PNG  ||
         filetype == FileType_GIF  ||
         filetype == FileType_JPEG ||
         filetype == FileType_TIFF ||
         filetype == FileType_XBM  ||
         filetype == FileType_BMP
       )
    {
      const char * current;
      const char * newdata;

      /* Must relativise the data URL... */

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

      if (current)
      {
        newdata = HtmlRelativiseURL(current, data, t);

        if (newdata)
        {
          /* If we've managed all that, call image_new_image */

          RetError(image_new_image(b,
                                   newdata,
                                   t,
                                   0));

          b->odata[b->nobjects - 1].isimage = 1;

          return NULL;
        }
      }
    }
  }

  /* Deal with external objects */

  else data = HtmlOBJECTclassid(t);

  if (data && *data)
  {
    int cannot_handle = 0;

    /* Well OK, this looks like a Plug-In. But can we handle it? */
    /* First, check for Active-X.                                */

    if (!strncmp(data, "clsid:", 6)) cannot_handle = 1;

    /* Otherwise, check a Plug-In exists to handle the data type */

    else
    {
      const char * type = HtmlOBJECTcodetype(t);
      const char * ext  = strrchr(data, '.');

      /* Can't handle it if there's no extension or code type */

      if (!ext && (!type || (type && !*type))) cannot_handle = 1;
      else
      {
        int filetype;

        /* Can't handle it if we don't have a Mime map entry for it */

        if (type)
        {
          if (mimemap_mime_to_riscos(type, &filetype)) cannot_handle = 1;
        }
        else
        {
          if (mimemap_extension_to_riscos(ext, &filetype)) cannot_handle = 1;
        }

        if (!cannot_handle)
        {
          char             var[32];
          _kernel_swi_regs r;

          /* Final check is to see if there's an Alias$@PlugInType_xxx variable */

          sprintf(var, "Alias$@PlugInType_%03X", filetype);

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

          /* *Not* _swix, we want the R2 value of 0 for 'unset'... */

          _kernel_swi(OS_ReadVarVal, &r, &r);

          if (!r.r[2]) cannot_handle = 1;
        }
      }
    }

    /* If we can handle it, flag this as a Plug-In */

    if (!cannot_handle) b->odata[b->nobjects - 1].isplugin = 1;

    /* Otherwise, use the alternative HTML stream here */

    else
    {
      int          depth, line, chunk;
      token_path * path = NULL;

      // For now - security blanket...

      /* Can't do anything if the next token has been preprocessed [somehow] */

      if (t->next && t->next->flags == HFlags_DealtWithToken)
      {
        #ifdef TRACE

          erb.errnum = Utils_Error_Custom_Normal;

          StrNCpy0(erb.errmess,
                   "Can't replace this object tag as later tokens have already been preprocessed, in object_new_object");

          show_error_ret(&erb);

        #else

          t = t;

        #endif
      }
      else
      {
        /* What line was the token in? */

        depth = tokenutils_line_range(b,
                                      t,
                                      &line,
                                      &chunk,
                                      NULL,
                                      NULL,
                                      &path);

        if (line >= 0)
        {
          /* If the line was found and we have a depth, the line */
          /* is in a table - find the base parent line of the    */
          /* table, so we can reformat it.                       */

          if (depth) line = path[depth - 1].line;
        }

        if (path) free(path);

        /* Right, only have to reformat if the token was in the */
        /* line list.                                           */

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

        /* We now know that the object is no longer in any line */
        /* list, so it's safe to preprocess the alternate HTML  */
        /* stream and place that under the token.               */

        t->flags |= HFlags_IgnoreObject;

        /* Discard the new object */

        b->nobjects --;

        memory_set_chunk_size(b,
                              NULL,
                              CK_OBJB,
                              (b->nobjects + 1) * sizeof(object_info));

        if (HtmlOBJECTstream(t))
        {

// Eeek! Recursive calls back to this routine!
//
//            HStream * alt = HtmlOBJECTstream(t);
//
//            /* Preprocess the stream */
//
//            // Of course, this probably breaks forms if extra form fields
//            // are present as part of an ongoing FORM tag.
//
//            while (alt)
//            {
//              fetch_preprocess_token(b, alt);
//
//              alt = alt->next;
//            }
//
          /* Move it into the main token list */

          HtmlReplaceOBJECT(t);

          /* That's it; any subsequent reformatting takes care of the rest */

          return NULL;
        }
      }
    }
  }

  /* If this is a Plug-In, and we're supposed to launch them */
  /* as soon as possible, then launch it now.                */

  if (
       b->odata[b->nobjects - 1].isplugin &&
       choices.support_object    &&
       choices.plugin_control == Choices_PlugIns_ASAP
     )
  {
    BBox position;

    RetError(object_get_object_size(b, b->nobjects - 1, &position));

    /* Object position is currently invalid, so hide the Plug-In */
    /* by moving it off the top of the work area.                */

    position.xmax = (position.xmax - position.xmin);
    position.xmin = 0;
    position.ymax = (position.ymax - position.ymin) + 0x1000; /* 0x1000 to be (very) safe */
    position.ymin = 0x1000;

    b->odata[b->nobjects - 1].broadcast_sent = 1; /* Don't try over and over if the first gives an error */

    RetError(plugin_add_queue_item(b, t, &position));
  }

  return NULL;
}

/*************************************************/
/* object_discard()                              */
/*                                               */
/* Discards all Objects held by a given browser. */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Objects.          */
/*************************************************/

_kernel_oserror * object_discard(browser_data * b)
{
  _kernel_oserror * e = NULL;
  _kernel_oserror * local;
  int               i;

  /* Close down any Plug-Ins */

  for (i = 0; i < b->nobjects; i++)
  {
    if (
         b->odata[i].isplugin       &&
         b->odata[i].broadcast_sent &&
         b->odata[i].plugin_task_handle
       )
    {
      unsigned int instance;

      e = plugin_obtain_instance(b, b->odata[i].token, &instance);

      if (!e)
      {
        e = plugin_send_close(instance,
                              b->odata[i].plugin_task_handle,
                              b->odata[i].plugin_instance_handle);
      }
    }
  }

  plugin_flush_instance_entries(b);

  /* Only set 'e' from the queue flush if the above call didn't */
  /* generate an error.                                         */

  local = plugin_flush_queue(b, 1);
  if (!e) e = local;

  /* Remove the Objects themselves. */

  b->nobjects = 0;

  memory_set_chunk_size(b, NULL, CK_OBJB, 0);

  return e;
}

/*************************************************/
/* object_get_token_object()                     */
/*                                               */
/* Return the number of the Object represented   */
/* in the given browser by the given token.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to a token representing   */
/*             the Object (OBJECT, EMBED or      */
/*             APPLET tag).                      */
/*                                               */
/* Returns:    Number of the Object from 0 to    */
/*             number of Objects minus 1, or -1  */
/*             if the Object cannot be found.    */
/*************************************************/

static int object_get_token_object(browser_data * b, HStream * t)
{
  int found = -1;
  int i;

  /* If this isn't an Object token, do nothing (well, */
  /* complain about it in TRACE builds).              */

  if (!t || !ISOBJECT(t))
  {
    #ifdef TRACE

      if (!ISOBJECT(t))
      {
        erb.errnum = Utils_Error_Custom_Normal;

        sprintf(erb.errmess,
                "Token %08x passed to object_get_token_object does not represent an OBJECT, APPLET or EMBED tag",
                (int) t);

        show_error_ret(&erb);
      }

    #endif

    return -1;
  }

  /* Otherwise, try to find the item */

  for (i = 0; i < b->nobjects; i++)
  {
    if (b->odata[i].token == t)
    {
      found = i;
      break;
    }
  }

  return found;
}

/*************************************************/
/* object_get_object_size()                      */
/*                                               */
/* Returns the size of a given Object.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to add to;                        */
/*                                               */
/*             Number of the Object, from 0 to   */
/*             number of Objects minus 1;        */
/*                                               */
/*             Pointer to a BBox, in which the   */
/*             size of the Object will be        */
/*             written.                          */
/*************************************************/

static _kernel_oserror * object_get_object_size(browser_data * b, int object, BBox * size)
{
  HStream * tp;

  /* Can't do anything without a bounding box! */

  if (!size)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess, "Null bounding box pointer passed to object_get_object_size");
      return &erb;

    #endif

    return NULL;
  }

  /* Fill in zeros to start with */

  size->xmin = size->ymin = 0;
  size->xmax = size->ymax = 0;

  /* Is this a valid object number? */

  if (object < 0 || object >= b->nobjects)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid object number %d for passed to object_get_object_size (browser %08x, nobjects = %d)",
              object,
              (int) b,
              b->nobjects);

      return &erb;

    #endif

    return NULL;
  }

  /* If this is an image, return through the image sizing routines */

  if (b->odata[object].isimage)
  {
    return image_get_token_image_size(b,
                                      b->odata[object].token,
                                      size);
  }

  /* Find its size */

  tp = b->odata[object].token;

  /* If we haven't already, work this out */

  if (b->odata[object].w < 0 || b->odata[object].h < 0)
  {
    int w, h;
    int available_w = redraw_display_width (b, b->cell);
    int available_h = redraw_display_height(b, b->cell);

    /* Deal with % specifiers as well as pixels */

    if (OBJECT_HAS_WIDTH(tp))
    {
      switch (OBJECT_WIDTH_UNITS(tp))
      {
        default:
        case UNITS_PIXELS:  w = OBJECT_WIDTH(tp) * 2; break;
        case UNITS_PERCENT: w = available_w * OBJECT_WIDTH(tp) / 100; break;
      }
    }
    else w = 0;

    if (OBJECT_HAS_HEIGHT(tp))
    {
      switch (OBJECT_HEIGHT_UNITS(tp))
      {
        default:
        case UNITS_PIXELS:  h = OBJECT_HEIGHT(tp) * 2; break;
        case UNITS_PERCENT: h = available_h * OBJECT_HEIGHT(tp) / 100; break;
      }
    }
    else h = 0;

    size->xmax = w;
    size->ymax = h;

    b->odata[object].w = w;
    b->odata[object].h = h;
  }

  /* Otherwise, get the prestored value */

  else
  {
    size->xmax = b->odata[object].w;
    size->ymax = b->odata[object].h;
  }

  /* Don't allow zero in any direction */

  if (size->xmax == 0 || size->ymax == 0)
  {
    const char * text = HtmlOBJECTstandby(b->odata[object].token);

    reformat_get_placeholder_size(b, b->odata[object].token, text, size);

    b->odata[object].w = size->xmax - size->xmin;
    b->odata[object].h = size->ymax - size->ymin;
  }

  return NULL;
}

/*************************************************/
/* object_get_token_object_size()                */
/*                                               */
/* Returns the size of a given Object.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to a token representing   */
/*             the Object (OBJECT, EMBED or      */
/*             APPLET tag);                      */
/*                                               */
/*             Pointer to a BBox, in which the   */
/*             size of the Object will be        */
/*             written.                          */
/*************************************************/

_kernel_oserror * object_get_token_object_size(browser_data * b, HStream * t, BBox * size)
{
  return object_get_object_size(b, object_get_token_object(b, t), size);
}

/*************************************************/
/* object_set_object_size()                      */
/*                                               */
/* Sets the size of a given Object.              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Number of the Object, from 0 to   */
/*             number of Objects minus 1;        */
/*                                               */
/*             Pointer to a BBox, from which the */
/*             xmax - xmin and ymax - ymin       */
/*             values are used to set the size.  */
/*************************************************/

static _kernel_oserror * object_set_object_size(browser_data * b, int object, BBox * size)
{
  int width, height;

  /* Can't do anything without a bounding box! */

  if (!size)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess, "Null bounding box pointer passed to object_get_object_size");
      return &erb;

    #endif

    return NULL;
  }

  /* Work out the width and height */

  width  = size->xmax - size->xmin;
  height = size->ymax - size->ymin;

  /* Is this a valid object number? */

  if (object < 0 || object >= b->nobjects)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid object number %d for passed to object_get_object_size (browser %08x, nobjects = %d)",
              object,
              (int) b,
              b->nobjects);

      return &erb;

    #endif

    return NULL;
  }

  /* If this is an image, need to set the image's size */

  if (b->odata[object].isimage) RetError(image_set_token_image_size(b,
                                                                    b->odata[object].token,
                                                                    size));

  /* Set also the width and height values for the Object */

  b->odata[object].w = width;
  b->odata[object].h = height;

  return NULL;
}

/*************************************************/
/* object_set_token_object_size()                */
/*                                               */
/* Sets the size of a given Object.              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to a token representing   */
/*             the Object (OBJECT, EMBED or      */
/*             APPLET tag);                      */
/*                                               */
/*             Pointer to a BBox, from which the */
/*             xmax - xmin and ymax - ymin       */
/*             values are used to set the size.  */
/*************************************************/

_kernel_oserror * object_set_token_object_size(browser_data * b, HStream * t, BBox * size)
{
  return object_set_object_size(b, object_get_token_object(b, t), size);
}

/*************************************************/
/* object_get_object_position()                  */
/*                                               */
/* Returns the x and y fields of the object_info */
/* structure for a given Object.                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Number of the Object;             */
/*                                               */
/*             Pointer to an int, in which the   */
/*             X coordinate is returned;         */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Y coordinate is returned.         */
/*                                               */
/* Returns:    1 if the object number is invalid */
/*             or 0 for success.                 */
/*                                               */
/* Assumes:    Neither pointer is NULL.          */
/*************************************************/

static int object_get_object_position(browser_data * b, int object, int * x, int * y)
{
  if (object < 0 || object >= b->nobjects) return 1;

  *x = b->odata[object].x;
  *y = b->odata[object].y;

  return 0;
}

/*************************************************/
/* object_get_token_object_position()            */
/*                                               */
/* Returns the x and y fields of the object_info */
/* structure for a given Object.                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to the token representing */
/*             the Object;                       */
/*                                               */
/*             Pointer to an int, in which the   */
/*             X coordinate is returned;         */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Y coordinate is returned.         */
/*                                               */
/* Returns:    1 if the object could not be      */
/*             found from the given token, or 0  */
/*             for success.                      */
/*                                               */
/* Assumes:    Neither pointer is NULL.          */
/*************************************************/

int object_get_token_object_position(browser_data * b, HStream * t, int * x, int * y)
{
  int object = object_get_token_object(b, t);

  return object_get_object_position(b, object, x, y);
}

/*************************************************/
/* object_set_token_object_position()            */
/*                                               */
/* Sets the x and y fields of the object_info    */
/* structure for a given Object, so that it may  */
/* be [partially] plotted during a fetch.        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the object;           */
/*                                               */
/*             Pointer to the token representing */
/*             the Object;                       */
/*                                               */
/*             X coordinate (window coords);     */
/*                                               */
/*             Y coordinate (window coords).     */
/*                                               */
/* Returns:    Number of the Object that was     */
/*             changed, or -1 if none could be   */
/*             found for the given token.        */
/*************************************************/

int object_set_token_object_position(browser_data * b, HStream * t, int x, int y)
{
  int object = object_get_token_object(b, t);

  #ifdef TRACE
    if (tl & (1u<<30))
    {
      char debugbuf[1024];
      sprintf(debugbuf,"object_set_token_object_positon: Called for %p, %p at %d, %d",b,t,x,y);
      Printf("%s\n",debugbuf);
    }
  #endif

  /* If an object was found, set the x and y coordinates */

  if (object >= 0)
  {
    int oldx, oldy;

    oldx = b->odata[object].x;
    oldy = b->odata[object].y;

    /* *Don't* invalidate x and y positions for Objects; this causes */
    /* too many reshape messages for some PlugIns and can lead to    */
    /* items left with out-of-date position information.             */
    /*                                                               */
    /* Objects are initialised to be in an invalidated position, of  */
    /* course, so still need to check for that later in the code.    */

    if (x != -1 && y != -1)
    {
      b->odata[object].x = x;
      b->odata[object].y = y;
    }

    /* If this is is acting as an image, need to update */
    /* the image structure too.                         */

    if (b->odata[object].isimage) image_set_token_image_position(b, t, x, y);

    /* If this item is a Plug-In, this could be the first time we've */
    /* known for sure what position the item should be plotted at;   */
    /* pr we may want to tell the Plug-In to move to a new position. */

    if (b->odata[object].isplugin)
    {
      BBox position;

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

      show_error_ret(object_get_object_size(b, object, &position));

      /* If this is the first time we've tried to set a position for */
      /* the Object and a broadcast to start up an associated        */
      /* Plug-In has not yet been sent, add the Plug-In to the       */
      /* 'ready to launch' queue.                                    */

      if (!b->odata[object].broadcast_sent)
      {
        int start_now = 0;

        /* Only do this for valid Object positions */

        if (x >= 0)
        {
          /* Object position is valid, we can place it in the right position initially */

          position.xmin += x;
          position.ymin += y;
          position.xmax += x;
          position.ymax += y;

          if (choices.plugin_control != Choices_PlugIns_Never && choices.support_object) start_now = 1;
        }

        if (start_now)
        {
          b->odata[object].broadcast_sent = 1; /* Don't try over and over if the first gives an error */

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

          show_error_ret(plugin_add_queue_item(b, t, &position));
        }

        #ifdef TRACE

          else if (tl & (1u<<30)) Printf("object_set_token_object_position: Choices say not to start Plug-In yet\n");

        #endif
      }

      /* If the Plug-In has been started, and the position of the Object */
      /* has changed, tell the Plug-In to move.                          */

      else if (
                (
                  oldx != x ||
                  oldy != y
                )
                && b->odata[object].plugin_task_handle
              )
      {
        unsigned int instance;

        if (x < 0)
        {
          /* Object position is invalid */

          position.xmax = (position.xmax - position.xmin);
          position.xmin = 0;
          position.ymax = (position.ymax - position.ymin) + 0x1000; /* 0x1000 to be (very) safe */
          position.ymax = 0x1000;
        }
        else
        {
          /* Object position has been updated */

          position.xmin += x;
          position.ymin += y;
          position.xmax += x;
          position.ymax += y;
        }

        /* Send the reshape message */

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

        if (!plugin_obtain_instance(b, b->odata[object].token, &instance))
        {
          show_error_ret(plugin_send_original_reshape(instance,
                                                      b->odata[object].plugin_task_handle,
                                                      b->odata[object].plugin_instance_handle,
                                                      &position));
        }
      }
    }
  }

  #ifdef TRACE
    if (tl & (1u<<30)) Printf("object_set_token_object_positon: Successful, returning Object number %d\n",object);
  #endif

  return object;
}

/*************************************************/
/* object_return_info()                          */
/*                                               */
/* Return information on an Object identified by */
/* a Plug-In instance handle.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             holding the Objects to search;    */
/*                                               */
/*             Plug-In instance handle;          */
/*                                               */
/*             Pointer to an HStream *, in which */
/*             the token the Object is represen- */
/*             ting is written;                  */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Plug-In task handle for this      */
/*             Object is written.                */
/*                                               */
/* Returns:    Returns 1 if the item was found,  */
/*             else 0. If 0, the contents of the */
/*             given pointers are not changed.   */
/*                                               */
/* Assumes:    Either of the last two pointers   */
/*             may be NULL.                      */
/*************************************************/

int object_return_info(browser_data * b, unsigned int plugin_instance_handle, HStream ** token, unsigned int * plugin_task)
{
  int i;

  for (i = 0; i < b->nobjects; i++)
  {
    if (b->odata[i].plugin_instance_handle == plugin_instance_handle)
    {
      /* Found it */

      if (token)       *token       = b->odata[i].token;
      if (plugin_task) *plugin_task = b->odata[i].plugin_task_handle;

      return 1;
    }
  }

  /* Found nothing */

  return 0;
}

/*************************************************/
/* object_get_token_object_box()                 */
/*                                               */
/* Returns a bounding box filled in with parent  */
/* window work area coordinates for a given      */
/* Object.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to the token representing */
/*             the Object;                       */
/*                                               */
/*             Pointer to a BBox to return the   */
/*             work area coordinates describing  */
/*             the Object's size.                */
/*                                               */
/* Returns:    1 for failure, 0 for success.     */
/*************************************************/

int object_get_token_object_box(browser_data * b, HStream * t, BBox * box)
{
  int object = object_get_token_object(b, t);
  int x, y;

  if (object < 0 || object >= b->nobjects || !box) return 1;

  /* Get the Object bounding box */

  if (object_get_object_size(b, object, box)) return 1;

  /* Get the Object position */

  if (object_get_object_position(b, object, &x, &y)) return 1;

  /* Return if the Object position is invalidated at the moment */

  if (x < 0) return 1;

  /* Otherwise add the coordinates */

  box->xmin += x;
  box->ymin += y;
  box->xmax += x;
  box->ymax += y;

  return 0;
}

/*************************************************/
/* object_get_object_plugin()                    */
/*                                               */
/* Returns details of any Plug-In associated     */
/* with a given Object (referenced by number).   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Number of the Object;             */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Plug-In instance handle will be   */
/*             written;                          */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Plug-In task handle will be       */
/*             written, or 0 if the Object       */
/*             does not have an associated       */
/*             Plug-In or can't be found.        */
/*                                               */
/* Assumes:    Either pointer may be NULL.       */
/*************************************************/

static _kernel_oserror * object_get_object_plugin(browser_data * b, int object, unsigned int * plugin_instance, unsigned int * plugin_task)
{
  /* Fill in zeros to start with */

  if (plugin_instance) *plugin_instance = 0;
  if (plugin_task)     *plugin_task     = 0;

  /* Is this a valid object number? */

  if (object < 0 || object >= b->nobjects)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid object number %d passed to object_get_token_object_plugin (browser %08x, nobjects = %d)",
              object,
              (int) b,
              b->nobjects);

      return &erb;

    #endif

    return NULL;
  }

  /* If not associated with a Plug-In, exit */

  if (!b->odata[object].isplugin) return NULL;

  /* Otherwise, return the details */

  if (plugin_instance) *plugin_instance = b->odata[object].plugin_instance_handle;
  if (plugin_task)     *plugin_task     = b->odata[object].plugin_task_handle;

  /* Finished */

  return NULL;
}

/*************************************************/
/* object_get_token_object_plugin()              */
/*                                               */
/* Returns details of any Plug-In associated     */
/* with a given Object (referenced by token).    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to the token representing */
/*             the Object;                       */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Plug-In instance handle will be   */
/*             written;                          */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Plug-In task handle will be       */
/*             written, or 0 if the Object       */
/*             does not have an associated       */
/*             Plug-In or can't be found.        */
/*                                               */
/* Assumes:    Either pointer may be NULL.       */
/*************************************************/

_kernel_oserror * object_get_token_object_plugin(browser_data * b, HStream * t, unsigned int * plugin_instance, unsigned int * plugin_task)
{
  return object_get_object_plugin(b,
                                  object_get_token_object(b, t),
                                  plugin_instance,
                                  plugin_task);
}

/*************************************************/
/* object_set_object_plugin()                    */
/*                                               */
/* Sets details of any Plug-In associated with   */
/* a given Object (referenced by number).        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Number of the Object;             */
/*                                               */
/*             Plug-In instance handle to set;   */
/*                                               */
/*             Plug-In task handle to set.       */
/*                                               */
/* Assumes:    Either pointer may be NULL.       */
/*************************************************/

static _kernel_oserror * object_set_object_plugin(browser_data * b, int object, unsigned int plugin_instance, unsigned int plugin_task)
{
  /* Is this a valid object number? */

  if (object < 0 || object >= b->nobjects)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid object number %d for passed to object_set_token_object_plugin (browser %08x, nobjects = %d)",
              object,
              (int) b,
              b->nobjects);

      return &erb;

    #endif

    return NULL;
  }

  /* If not associated with a Plug-In, exit */

  if (!b->odata[object].isplugin) return NULL;

  /* Otherwise, set the details */

  b->odata[object].plugin_instance_handle = plugin_instance;
  b->odata[object].plugin_task_handle     = plugin_task;

  /* Finished */

  return NULL;
}

/*************************************************/
/* object_set_token_object_plugin()              */
/*                                               */
/* Sets details of any Plug-In associated with   */
/* a given Object (referenced by token).         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to the token representing */
/*             the Object (usually have to       */
/*             remember this when the initial    */
/*             Message_PlugIn_Open is sent);     */
/*                                               */
/*             Plug-In instance handle to set;   */
/*                                               */
/*             Plug-In task handle to set.       */
/*                                               */
/* Assumes:    Either pointer may be NULL.       */
/*************************************************/

_kernel_oserror * object_set_token_object_plugin(browser_data * b, HStream * t, unsigned int plugin_instance, unsigned int plugin_task)
{
  return object_set_object_plugin(b,
                                  object_get_token_object(b, t),
                                  plugin_instance,
                                  plugin_task);
}

/*************************************************/
/* object_redraw()                               */
/*                                               */
/* Does a high level redraw of an Object, using  */
/* an outline to show where the Object should be */
/* if it isn't plotted by some other method.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the Object;           */
/*                                               */
/*             Pointer to a RedrawWindowBlock    */
/*             struct which holds information    */
/*             about the current redraw session; */
/*                                               */
/*             Address of the token representing */
/*             the OBJECT, APPLET or EMBED tag;  */
/*                                               */
/*             The X offset in window coords (so */
/*             OS units) of the left hand edge   */
/*             of the Object;                    */
/*                                               */
/*             The Y offset in window coords (so */
/*             OS units) of the bottom edge of   */
/*             the Object.                       */
/*************************************************/

_kernel_oserror * object_redraw(browser_data * b, WimpRedrawWindowBlock * r, HStream * token, int x, int y)
{
  _kernel_oserror * e       = NULL;
  int               object  = object_get_token_object(b, token);
  int               plotted = 0;

  if (b->odata[object].isimage)
  {
    e = image_redraw(b, r, token, x, y);
    if (!e) plotted = 1;
  }

  if (!plotted)
  {
    /* Plot a placeholder */

    BBox box;

    object_get_object_size(b, object, &box);

    box.xmin += x;
    box.ymin += y;
    box.xmax += x - box.xmin;
    box.ymax += y - box.ymin;

    box.xmin &= ~(wimpt_dx() - 1);
    box.ymin &= ~(wimpt_dy() - 1);

    redraw_draw_placeholder(b,
                            r,
                            &box,
                            token,
                            HtmlOBJECTstandby(token));
  }

  return e;
}

/*************************************************/
/* object_token_is_image()                       */
/*                                               */
/* Finds out whether an Object is acting as an   */
/* an inline image or not.                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the object;           */
/*                                               */
/*             Pointer to the token representing */
/*             the Object.                       */
/*                                               */
/* Returns:    1 if the Object is acting as an   */
/*             inline image, else 0.             */
/*************************************************/

int object_token_is_image(browser_data * b, HStream * t)
{
  int object = object_get_token_object(b, t);

  if (object < 0 || object >= b->nobjects) return 0;
  else                                     return !!b->odata[object].isimage;
}