<commit_before>/*
 * ClutterMozHeadless; A headless Mozilla renderer
 * Copyright (c) 2009, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Authored by <NAME> <<EMAIL>>
 */

#include <config.h>

#ifdef SUPPORT_PLUGINS
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#endif
#include <glib.h>
#include <glib/gstdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>

#include "clutter-mozheadless.h"
#include "clutter-mozembed-comms.h"
#include "clutter-mozheadless-history.h"
#include "clutter-mozheadless-prefs.h"
#include "clutter-mozheadless-downloads.h"
#include "clutter-mozheadless-cookies.h"
#include "clutter-mozheadless-certs.h"
#include "clutter-mozheadless-login-manager-storage.h"
#include "clutter-mozheadless-permission-manager.h"
#include "clutter-mozheadless-protocol-service.h"
#include "clutter-mozheadless-private-browsing.h"

#include "clutter-mozembed.h"

#include <nsAppDirectoryServiceDefs.h>

G_DEFINE_TYPE (ClutterMozHeadless, clutter_mozheadless, MOZ_TYPE_HEADLESS)

#define MOZHEADLESS_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), CLUTTER_TYPE_MOZHEADLESS, ClutterMozHeadlessPrivate))

enum
{
  PROP_0,

  PROP_INPUT,
  PROP_OUTPUT,
  PROP_SHM,
  PROP_CONNECT_TIMEOUT,
  PROP_PRIVATE
};

enum
{
  CANCEL_DOWNLOAD,

  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0, };

struct _ClutterMozHeadlessPrivate
{
  /* Connection/comms variables */
  GList           *views;
  gint             waiting_for_ack;
  gboolean         pending_resize;
  gchar           *input_file;
  gchar           *output_file;

  /* Shared memory variables */
  int              shm_fd;
  gchar           *shm_name;
  void            *mmap_start;
  size_t           mmap_length;

  /* Surface property variables */
  gint             surface_width;
  gint             surface_height;

  /* Synchronous call variables */
  ClutterMozEmbedCommand  sync_call;
  gchar                  *new_input_file;
  gchar                  *new_output_file;
  gchar                  *new_shm_name;

  /* Connection timeout variables */
  guint            connect_timeout;
  guint            connect_timeout_source;

  /* Page property variables */
  gboolean         private;
  guint            security;
};

static GMainLoop *mainloop;
static gint spawned_heads = 0;

static void block_until_command (ClutterMozHeadless     *moz_headless,
                                 ClutterMozEmbedCommand  command);

static gboolean input_io_func (GIOChannel              *source,
                               GIOCondition             condition,
                               ClutterMozHeadlessView  *view);

static void security_change_cb (ClutterMozHeadless *self,
                                void               *request,
                                guint               state,
                                gpointer            ignored);

void
send_feedback_all (ClutterMozHeadless      *headless,
                   ClutterMozEmbedFeedback  id,
                   ...)
{
  GList *v;
  ClutterMozHeadlessPrivate *priv = headless->priv;

  va_list args;

  for (v = priv->views; v; v = v->next)
    {
      ClutterMozHeadlessView *view = v->data;

      va_start (args, id);
      clutter_mozembed_comms_sendv (view->output, id, args);
      va_end (args);
    }
}

static void
location_cb (ClutterMozHeadless *headless)
{
  gchar *location;
  gboolean status, update;
  ClutterMozHeadlessPrivate *priv = headless->priv;

  location = moz_headless_get_location (MOZ_HEADLESS (headless));
  send_feedback_all (headless, CME_FEEDBACK_LOCATION,
                     G_TYPE_STRING, location,
                     G_TYPE_INVALID);

  status = update = (priv->security & CLUTTER_MOZEMBED_BAD_CERT) ? TRUE : FALSE;
  clutter_mozheadless_update_cert_status (location, &update);

  if (status != update)
    {
      if (update)
        priv->security += CLUTTER_MOZEMBED_BAD_CERT;
      else
        priv->security -= CLUTTER_MOZEMBED_BAD_CERT;
      security_change_cb (headless, NULL, priv->security, NULL);
    }

  g_free (location);
}

static void
title_cb (ClutterMozHeadless *headless)
{
  gchar *title;

  title = moz_headless_get_title (MOZ_HEADLESS (headless));
  send_feedback_all (headless, CME_FEEDBACK_TITLE,
                     G_TYPE_STRING, title,
                     G_TYPE_INVALID);

  g_free (title);
}

static void
icon_cb (ClutterMozHeadless *headless)
{
  gchar *icon;

  icon = moz_headless_get_icon (MOZ_HEADLESS (headless));
  send_feedback_all (headless, CME_FEEDBACK_ICON,
                     G_TYPE_STRING, icon,
                     G_TYPE_INVALID);

  g_free (icon);
}

static void
progress_cb (ClutterMozHeadless *headless,
             gint64              curprogress,
             gint64              maxprogress)
{
  gdouble progress = (curprogress > maxprogress) ?
                       curprogress / (gdouble)maxprogress :
                       -1.0;
  send_feedback_all (headless, CME_FEEDBACK_PROGRESS,
                     G_TYPE_DOUBLE, progress,
                     G_TYPE_INVALID);
}

static void
net_start_cb (ClutterMozHeadless *headless)
{
  send_feedback_all (headless, CME_FEEDBACK_NET_START, G_TYPE_INVALID);
}

static void
net_stop_cb (ClutterMozHeadless *headless)
{
  send_feedback_all (headless, CME_FEEDBACK_NET_STOP, G_TYPE_INVALID);
}

static gboolean
scroll_cb (MozHeadless *headless, MozHeadlessRect *rect, gint dx, gint dy)
{
  /*g_debug ("Scroll %d, %d", dx, dy);*/
  return FALSE;
}

static void
updated_cb (MozHeadless        *headless,
            gint                x,
            gint                y,
            gint                width,
            gint                height)
{
  gint doc_width, doc_height, sx, sy;
  GList *v;

  ClutterMozHeadlessPrivate *priv = CLUTTER_MOZHEADLESS (headless)->priv;

  /* If we're pending a resize, the surface width/height will be incorrect
   * and we could cause a crash in the embedder.
   */
  if (priv->pending_resize)
    return;

  /*g_debug ("Update +%d+%d %dx%d", x, y, width, height);*/

  msync (priv->mmap_start, priv->mmap_length, MS_SYNC);

  moz_headless_get_document_size (headless, &doc_width, &doc_height);
  moz_headless_get_scroll_pos (headless, &sx, &sy);

  /*g_debug ("Doc-size: %dx%d", doc_width, doc_height);*/
  send_feedback_all (CLUTTER_MOZHEADLESS (headless), CME_FEEDBACK_UPDATE,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_INT, width,
                     G_TYPE_INT, height,
                     G_TYPE_INT, priv->surface_width,
                     G_TYPE_INT, priv->surface_height,
                     G_TYPE_INT, sx,
                     G_TYPE_INT, sy,
                     G_TYPE_INT, doc_width,
                     G_TYPE_INT, doc_height,
                     G_TYPE_INVALID);

  for (v = priv->views; v; v = v->next)
    {
      ClutterMozHeadlessView *view = v->data;

      /* This shouldn't happen (because if we're waiting for an ack, we've
       * frozen updates), but put this check in just in case.
       */
      if (view->waiting_for_ack)
        continue;

      priv->waiting_for_ack ++;
      view->waiting_for_ack = TRUE;
    }

  moz_headless_freeze_updates (headless, TRUE);
}

static void
new_window_cb (MozHeadless *headless, MozHeadless **newEmbed, guint chromemask)
{
  ClutterMozHeadless *moz_headless = CLUTTER_MOZHEADLESS (headless);
  ClutterMozHeadlessPrivate *priv = moz_headless->priv;
  ClutterMozHeadlessView *view = (ClutterMozHeadlessView *)priv->views->data;

  clutter_mozembed_comms_send (view->output, CME_FEEDBACK_NEW_WINDOW,
                               G_TYPE_INT, chromemask,
                               G_TYPE_INVALID);
  block_until_command (moz_headless, CME_COMMAND_NEW_WINDOW_RESPONSE);

  if (priv->new_input_file && priv->new_output_file && priv->new_shm_name)
    {
      *newEmbed = g_object_new (CLUTTER_TYPE_MOZHEADLESS,
                                "chromeflags", chromemask,
                                "output", priv->new_output_file,
                                "input", priv->new_input_file,
                                "shm", priv->new_shm_name,
                                "private", priv->private,
                                NULL);
      moz_headless_set_chrome_mask (*newEmbed, chromemask);

      g_free (priv->new_input_file);
      g_free (priv->new_output_file);
      g_free (priv->new_shm_name);

      priv->new_input_file = NULL;
      priv->new_output_file = NULL;
      priv->new_shm_name = NULL;
    }
  else
    *newEmbed = NULL;
}

static void
destroy_browser_cb (ClutterMozHeadless *moz_headless)
{
  send_feedback_all (moz_headless, CME_FEEDBACK_CLOSED, G_TYPE_INVALID);
}

static void
link_message_cb (ClutterMozHeadless *self)
{
  gchar *message = moz_headless_get_link_message (MOZ_HEADLESS (self));
  send_feedback_all (self, CME_FEEDBACK_LINK_MESSAGE,
                     G_TYPE_STRING, message,
                     G_TYPE_INVALID);
  g_free (message);
}

static void
can_go_back_cb (ClutterMozHeadless *self, gboolean can_go_back)
{
  send_feedback_all (self, CME_FEEDBACK_CAN_GO_BACK,
                     G_TYPE_BOOLEAN, can_go_back,
                     G_TYPE_INVALID);
}

static void
can_go_forward_cb (ClutterMozHeadless *self, gboolean can_go_forward)
{
  send_feedback_all (self, CME_FEEDBACK_CAN_GO_FORWARD,
                     G_TYPE_BOOLEAN, can_go_forward,
                     G_TYPE_INVALID);
}

static void
size_to_cb (ClutterMozHeadless *self, gint width, gint height)
{
  ClutterMozHeadlessView *primary_view;

  ClutterMozHeadlessPrivate *priv = self->priv;

  if (!priv->views)
    return;

  primary_view = (ClutterMozHeadlessView *)priv->views->data;
  clutter_mozembed_comms_send (primary_view->output,
                               CME_FEEDBACK_SIZE_REQUEST,
                               G_TYPE_INT, width,
                               G_TYPE_INT, height,
                               G_TYPE_INVALID);
}

static void
security_change_cb (ClutterMozHeadless *self,
                    void               *request,
                    guint               state,
                    gpointer            ignored)
{
  ClutterMozHeadlessPrivate *priv = self->priv;

  priv->security = state | (priv->security & CLUTTER_MOZEMBED_BAD_CERT);
  send_feedback_all (self, CME_FEEDBACK_SECURITY,
                     G_TYPE_INT, priv->security,
                     G_TYPE_INVALID);
}

static void
show_tooltip_cb (ClutterMozHeadless *self,
                 const gchar        *text,
                 gint                x,
                 gint                y)
{
  send_feedback_all (self, CME_FEEDBACK_SHOW_TOOLTIP,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_STRING, text,
                     G_TYPE_INVALID);
}

static void
hide_tooltip_cb (ClutterMozHeadless *self)
{
  send_feedback_all (self, CME_FEEDBACK_HIDE_TOOLTIP, G_TYPE_INVALID);
}

static void
file_changed_cb (GFileMonitor           *monitor,
                 GFile                  *file,
                 GFile                  *other_file,
                 GFileMonitorEvent       event_type,
                 ClutterMozHeadlessView *view)
{
  gint fd;
  gint doc_width, doc_height, sx, sy;

  ClutterMozHeadlessPrivate *priv = view->parent->priv;

  if (event_type != G_FILE_MONITOR_EVENT_CREATED)
    return;

  g_signal_handlers_disconnect_by_func (monitor, file_changed_cb, view);
  g_file_monitor_cancel (monitor);
  g_object_unref (monitor);
  view->monitor = NULL;

  /* Opening input channel */
  fd = open (view->input_file, O_RDONLY | O_NONBLOCK);
  view->input = g_io_channel_unix_new (fd);
  g_io_channel_set_encoding (view->input, NULL, NULL);
  g_io_channel_set_buffered (view->input, FALSE);
  g_io_channel_set_close_on_unref (view->input, TRUE);
  view->watch_id = g_io_add_watch (view->input,
                                   G_IO_IN | G_IO_PRI | G_IO_ERR |
                                   G_IO_NVAL | G_IO_HUP,
                                   (GIOFunc)input_io_func,
                                   view);

  /* Send the shm name and an update to the new view */
  clutter_mozembed_comms_send (view->output, CME_FEEDBACK_SHM_NAME,
                               G_TYPE_STRING, priv->shm_name,
                               G_TYPE_INVALID);

  moz_headless_get_document_size (MOZ_HEADLESS (view->parent),
                                  &doc_width, &doc_height);
  moz_headless_get_scroll_pos (MOZ_HEADLESS (view->parent), &sx, &sy);

  /* If we have an active surface, send an update to this new view */
  if (priv->mmap_start)
    {
      clutter_mozembed_comms_send (view->output, CME_FEEDBACK_UPDATE,
                                   G_TYPE_INT, 0,
                                   G_TYPE_INT, 0,
                                   G_TYPE_INT, priv->surface_width,
                                   G_TYPE_INT, priv->surface_height,
                                   G_TYPE_INT, priv->surface_width,
                                   G_TYPE_INT, priv->surface_height,
                                   G_TYPE_INT, sx,
                                   G_TYPE_INT, sy,
                                   G_TYPE_INT, doc_width,
                                   G_TYPE_INT, doc_height,
                                   G_TYPE_INVALID);

      if (!priv->waiting_for_ack)
        moz_headless_freeze_updates (MOZ_HEADLESS (view->parent), TRUE);

      view->waiting_for_ack = TRUE;
      priv->waiting_for_ack ++;
    }

  /* Inform if we're private */
  /* FIXME: I don't think we need to do this */
  if (priv->private)
    clutter_mozembed_comms_send (view->output, CME_FEEDBACK_PRIVATE,
                                 G_TYPE_BOOLEAN, TRUE,
                                 G_TYPE_INVALID);
}

static void
cursor_changed_cb (MozHeadlessCursorType    type,
                   const MozHeadlessCursor *special,
                   gpointer                 data)
{
  ClutterMozHeadless *mozheadless = data;
  /* TODO - support special cursors */
  send_feedback_all (mozheadless, CME_FEEDBACK_CURSOR,
                     G_TYPE_INT, type,
                     G_TYPE_INVALID);
}

#ifdef SUPPORT_IM
static void
im_reset_cb (ClutterMozHeadless *self)
{
  send_feedback_all(self, CME_FEEDBACK_IM_RESET, G_TYPE_INVALID);
}

static void
im_enable_cb (ClutterMozHeadless *self, gboolean enabled)
{
  send_feedback_all (self, CME_FEEDBACK_IM_ENABLE,
                     G_TYPE_BOOLEAN, enabled,
                     G_TYPE_INVALID);
}

static void
im_focus_change_cb (ClutterMozHeadless *self, gboolean in)
{
  send_feedback_all (self, CME_FEEDBACK_IM_FOCUS_CHANGE,
                     G_TYPE_BOOLEAN, in,
                     G_TYPE_INVALID);
}

static void
im_set_cursor_cb (ClutterMozHeadless *self,
                  gint                x,
                  gint                y,
                  gint                width,
                  gint                height)
{
  send_feedback_all (self, CME_FEEDBACK_IM_SET_CURSOR,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_INT, width,
                     G_TYPE_INT, height,
                     G_TYPE_INVALID);
}
#endif

#ifdef SUPPORT_PLUGINS
static void
plugin_added_cb (ClutterMozHeadless *self,
                 guint plug_id,
                 gint x,
                 gint y,
                 gint width,
                 gint height)

{
  send_feedback_all (self, CME_FEEDBACK_PLUGIN_ADDED,
                     G_TYPE_UINT, plug_id,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_INT, width,
                     G_TYPE_INT, height,
                     G_TYPE_INVALID);
}

static void
plugin_updated_cb (ClutterMozHeadless *self,
                   guint plug_id,
                   gint x,
                   gint y,
                   gint width,
                   gint height)
{
  send_feedback_all (self, CME_FEEDBACK_PLUGIN_UPDATED,
                     G_TYPE_UINT, plug_id,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_INT, width,
                     G_TYPE_INT, height,
                     G_TYPE_INVALID);
}

static void
plugin_visibility_cb (ClutterMozHeadless *self,
                      guint plug_id,
                      gboolean visible)
{
  send_feedback_all (self, CME_FEEDBACK_PLUGIN_VISIBILITY,
                     G_TYPE_UINT, plug_id,
                     G_TYPE_BOOLEAN, visible,
                     G_TYPE_INVALID);
}

#endif

static void
clutter_mozheadless_create_view (ClutterMozHeadless *self,
                                 gchar              *input_file,
                                 gchar              *output_file)
{
  GFile *file;
  gint fd;

  ClutterMozHeadlessPrivate *priv = self->priv;
  ClutterMozHeadlessView *view = g_new0 (ClutterMozHeadlessView, 1);

  priv->views = g_list_append (priv->views, view);

  view->parent = self;
  /* This takes ownership of the input_file and output_file strings */
  view->input_file = input_file;
  view->output_file = output_file;

  mkfifo (view->output_file, S_IWUSR | S_IRUSR);
  fd = open (view->output_file, O_RDWR | O_NONBLOCK);
  view->output = g_io_channel_unix_new (fd);
  g_io_channel_set_encoding (view->output, NULL, NULL);
  g_io_channel_set_buffered (view->output, FALSE);
  g_io_channel_set_close_on_unref (view->output, TRUE);

  file = g_file_new_for_path (view->input_file);
  view->monitor = g_file_monitor_file (file, 0, NULL, NULL);
  g_object_unref (file);
  g_signal_connect (view->monitor, "changed",
                    G_CALLBACK (file_changed_cb), view);

  if (g_file_test (view->input_file, G_FILE_TEST_EXISTS))
    file_changed_cb (view->monitor, NULL, NULL,
                     G_FILE_MONITOR_EVENT_CREATED, view);
}

static gboolean
send_mack (ClutterMozHeadlessView *view)
{
  view->mack_source = 0;
  clutter_mozembed_comms_send (view->output,
                               CME_FEEDBACK_MOTION_ACK,
                               G_TYPE_INVALID);
  return FALSE;
}

static gboolean
send_sack_cb (ClutterMozHeadlessView *view)
{
  view->sack_source = 0;
  clutter_mozembed_comms_send (view->output,
                               CME_FEEDBACK_SCROLL_ACK,
                               G_TYPE_INVALID);
  return FALSE;
}

static void
send_sack (ClutterMozHeadlessView *view)
{
  if (!view->sack_source)
    view->sack_source =
      g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)send_sack_cb, view, NULL);
}

static void
clutter_moz_headless_resize (ClutterMozHeadless *moz_headless)
{
  MozHeadless *headless = MOZ_HEADLESS (moz_headless);
  ClutterMozHeadlessPrivate *priv = moz_headless->priv;

  moz_headless_set_surface (headless, NULL, 0, 0, 0);
  moz_headless_set_size (headless,
                         priv->surface_width,
                         priv->surface_height);

  if (priv->mmap_start)
    munmap (priv->mmap_start, priv->mmap_length);

  priv->mmap_length = priv->surface_width * priv->surface_height * 4;
  ftruncate (priv->shm_fd, priv->mmap_length);
  priv->mmap_start = mmap (NULL, priv->mmap_length, PROT_READ | PROT_WRITE,
                           MAP_SHARED, priv->shm_fd, 0);

  if (priv->mmap_start == MAP_FAILED)
    {
      g_warning ("Unable to mmap region for headless surface\n");
      priv->mmap_start = NULL;
      return;
    }

  moz_headless_set_surface (headless, priv->mmap_start,
                            priv->surface_width, priv->surface_height,
                            priv->surface_width * 4);
}

static void
clutter_moz_headless_unfreeze (ClutterMozHeadless *moz_headless)
{
  MozHeadless *headless = MOZ_HEADLESS (moz_headless);
  ClutterMozHeadlessPrivate *priv = moz_headless->priv;

  moz_headless_freeze_updates (headless, FALSE);

  if (priv->pending_resize)
    {
      clutter_moz_headless_resize (moz_headless);
      priv->pending_resize = FALSE;
    }
}

static void
process_command (ClutterMozHeadlessView *view, ClutterMozEmbedCommand command)
{
  ClutterMozHeadless *moz_headless = view->parent;
  ClutterMozHeadlessPrivate *priv = moz_headless->priv;
  MozHeadless *headless = MOZ_HEADLESS (moz_headless);

  /*g_debug ("Processing command: %d", command);*/

  if (priv->sync_call && (priv->sync_call == command))
    priv->sync_call = 0;

  switch (command)
    {
      case CME_COMMAND_UPDATE_ACK :
        {
          view->waiting_for_ack = FALSE;
          priv->waiting_for_ack --;

          if (priv->waiting_for_ack == 0)
            clutter_moz_headless_unfreeze (moz_headless);

          break;
        }
      case CME_COMMAND_OPEN_URL :
        {
          gchar *url = clutter_mozembed_comms_receive_string (view->input);
          moz_headless_load_url (headless, url);
          g_free (url);
          break;
        }
      case CME_COMMAND_RESIZE :
        {
          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &priv->surface_width,
                                          G_TYPE_INT, &priv->surface_height,
                                          G_TYPE_INVALID);

          if (priv->waiting_for_ack)
            priv->pending_resize = TRUE;
          else
            clutter_moz_headless_resize (moz_headless);

          break;
        }
      case CME_COMMAND_MOTION :
        {
          gint x, y;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &x,
                                          G_TYPE_INT, &y,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_motion (headless, x, y, m);

          /* This is done so that we definitely get to do any redrawing before we
           * send an acknowledgement.
           */
          if (!view->mack_source)
            view->mack_source =
              g_idle_add_full (G_PRIORITY_LOW,
                               (GSourceFunc)send_mack,
                               view,
                               NULL);
          else
            g_warning ("Received a motion event before "
                       "sending acknowledgement");

          break;
        }
      case CME_COMMAND_BUTTON_PRESS :
        {
          gint x, y, button, count;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &x,
                                          G_TYPE_INT, &y,
                                          G_TYPE_INT, &button,
                                          G_TYPE_INT, &count,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_button_press (headless, x, y, button, count, m);

          break;
        }
      case CME_COMMAND_BUTTON_RELEASE :
        {
          gint x, y, button;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &x,
                                          G_TYPE_INT, &y,
                                          G_TYPE_INT, &button,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_button_release (headless, x, y, button, m);

          break;
        }
      case CME_COMMAND_KEY_PRESS :
        {
          MozHeadlessKey key;
          gunichar unicode_char;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &key,
                                          G_TYPE_INT, &unicode_char,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_key_press (headless, key, unicode_char, m);

          break;
        }
      case CME_COMMAND_KEY_RELEASE :
        {
          MozHeadlessKey key;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &key,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_key_release (headless, key, m);

          break;
        }
      case CME_COMMAND_SCROLL :
        {
          gint dx, dy;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &dx,
                                          G_TYPE_INT, &dy,
                                          G_TYPE_INVALID);

          moz_headless_scroll (headless, dx, dy);

          send_sack (view);

          break;
        }
      case CME_COMMAND_SCROLL_TO :
        {
          gint x, y;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &x,
                                          G_TYPE_INT, &y,
                                          G_TYPE_INVALID);

          moz_headless_set_scroll_pos (headless, x, y);

          send_sack (view);

          break;
        }
      case CME_COMMAND_GET_CAN_GO_BACK :
        {
          clutter_mozembed_comms_send (view->output,
                                       CME_FEEDBACK_CAN_GO_BACK,
                                       G_TYPE_BOOLEAN,
                                       moz_headless_can_go_back (headless),
                                       G_TYPE_INVALID);
          break;
        }
      case CME_COMMAND_GET_CAN_GO_FORWARD :
        {
          clutter_mozembed_comms_send (view->output,
                                       CME_FEEDBACK_CAN_GO_FORWARD,
                                       G_TYPE_BOOLEAN,
                                       moz_headless_can_go_back (headless),
                                       G_TYPE_INVALID);
          break;
        }
      case CME_COMMAND_GET_SHM_NAME :
        {
          clutter_mozembed_comms_send (view->output,
                                       CME_FEEDBACK_SHM_NAME,
                                       G_TYPE_STRING, priv->shm_name,
                                       G_TYPE_INVALID);
          break;
        }
      case CME_COMMAND_BACK :
        {
          moz_headless_go_back (headless);
          break;
        }
      case CME_COMMAND_FORWARD :
        {
          moz_headless_go_forward (headless);
          break;
        }
      case CME_COMMAND_STOP :
        {
          moz_headless_stop_load (headless);
          break;
        }
      case CME_COMMAND_REFRESH :
        {
          moz_headless_reload (headless, MOZ_HEADLESS_FLAG_RELOADNORMAL);
          break;
        }
      case CME_COMMAND_RELOAD :
        {
          moz_headless_reload (headless, MOZ_HEADLESS_FLAG_RELOADBYPASSCACHE);
          break;
        }
      case CME_COMMAND_SET_CHROME :
        {
          gint chrome = clutter_mozembed_comms_receive_int (view->input);
          moz_headless_set_chrome_mask (headless, chrome);
          break;
        }
      case CME_COMMAND_TOGGLE_CHROME :
        {
          guint32 chrome = moz_headless_get_chrome_mask (headless);
          chrome ^= clutter_mozembed_comms_receive_int (view->input);
          moz_headless_set_chrome_mask (headless, chrome);
          break;
        }
      case CME_COMMAND_QUIT :
        {
          /* FIXME: Er, will this unref not cause a crash? Must check this... */
          send_feedback_all (moz_headless, CME_FEEDBACK_CLOSED, G_TYPE_INVALID);
          g_object_unref (moz_headless);
          break;
        }
      case CME_COMMAND_NEW_VIEW :
        {
          gchar *input, *output;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_STRING, &input,
                                          G_TYPE_STRING, &output,
                                          G_TYPE_INVALID);

          /* create_view takes ownership of the input and output strings */
          clutter_mozheadless_create_view (moz_headless, input, output);

          break;
        }
      case CME_COMMAND_NEW_WINDOW :
        {
          gchar *input, *output, *shm;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_STRING, &input,
                                          G_TYPE_STRING, &output,
                                          G_TYPE_STRING, &shm,
                                          G_TYPE_INVALID);

          g_object_new (CLUTTER_TYPE_MOZHEADLESS,
                        "input", input,
                        "output", output,
                        "shm", shm,
                        "private", priv->private,
                        NULL);

          break;
        }
      case CME_COMMAND_NEW_WINDOW_RESPONSE :
        {
          if (clutter_mozembed_comms_receive_boolean (view->input))
            {
              clutter_mozembed_comms_receive (view->input,
                                              G_TYPE_STRING, &priv->new_input_file,
                                              G_TYPE_STRING, &priv->new_output_file,
                                              G_TYPE_STRING, &priv->new_shm_name,
                                              G_TYPE_INVALID);
            }
          else
            {
              g_free (priv->new_input_file);
              priv->new_input_file = NULL;
              g_free (priv->new_output_file);
              priv->new_output_file = NULL;
              g_free (priv->new_shm_name);
              priv->new_shm_name = NULL;
            }
          break;
        }
      case CME_COMMAND_FOCUS :
        {
          gboolean focus = clutter_mozembed_comms_receive_boolean (view->input);
          moz_headless_focus (MOZ_HEADLESS (moz_headless), focus);
          break;
        }
      case CME_COMMAND_PURGE_SESSION_HISTORY :
        {
          moz_headless_purge_session_history (MOZ_HEADLESS (moz_headless));
          break;
        }
      case CME_COMMAND_DL_CANCEL :
        {
          gint id = clutter_mozembed_comms_receive_int (view->input);
          g_signal_emit (view->parent, signals[CANCEL_DOWNLOAD], 0, id);
          break;
        }
#ifdef SUPPORT_IM
      case CME_COMMAND_IM_COMMIT :
        {
          gchar *str = clutter_mozembed_comms_receive_string (view->input);

          moz_headless_im_commit (MOZ_HEADLESS (moz_headless), str);
          g_free (str);

          break;
        }
      case CME_COMMAND_IM_PREEDIT_CHANGED :
        {
          gchar *str;
          gint cursor_pos;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_STRING, &str,
                                          G_TYPE_INT, &cursor_pos,
                                          G_TYPE_INVALID);
          moz_headless_im_preedit_changed (MOZ_HEADLESS (moz_headless),
                                           str,
                                           cursor_pos);
          g_free (str);

          break;
        }
#endif
      case CME_COMMAND_SET_SEARCH_STRING :
        {
          gchar *str = clutter_mozembed_comms_receive_string (view->input);
          moz_headless_set_find_string (MOZ_HEADLESS (moz_headless),
                                        str);
          g_free (str);
          break;
        }
      case CME_COMMAND_FIND_NEXT :
        {
          moz_headless_find_next (MOZ_HEADLESS (moz_headless));
          break;
        }<commit_msg>[headless] Update for slightly changed API<commit_after>/*
 * ClutterMozHeadless; A headless Mozilla renderer
 * Copyright (c) 2009, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Authored by <NAME> <<EMAIL>>
 */

#include <config.h>

#ifdef SUPPORT_PLUGINS
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#endif
#include <glib.h>
#include <glib/gstdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>

#include "clutter-mozheadless.h"
#include "clutter-mozembed-comms.h"
#include "clutter-mozheadless-history.h"
#include "clutter-mozheadless-prefs.h"
#include "clutter-mozheadless-downloads.h"
#include "clutter-mozheadless-cookies.h"
#include "clutter-mozheadless-certs.h"
#include "clutter-mozheadless-login-manager-storage.h"
#include "clutter-mozheadless-permission-manager.h"
#include "clutter-mozheadless-protocol-service.h"
#include "clutter-mozheadless-private-browsing.h"

#include "clutter-mozembed.h"

#include <nsAppDirectoryServiceDefs.h>

G_DEFINE_TYPE (ClutterMozHeadless, clutter_mozheadless, MOZ_TYPE_HEADLESS)

#define MOZHEADLESS_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), CLUTTER_TYPE_MOZHEADLESS, ClutterMozHeadlessPrivate))

enum
{
  PROP_0,

  PROP_INPUT,
  PROP_OUTPUT,
  PROP_SHM,
  PROP_CONNECT_TIMEOUT,
  PROP_PRIVATE
};

enum
{
  CANCEL_DOWNLOAD,

  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0, };

struct _ClutterMozHeadlessPrivate
{
  /* Connection/comms variables */
  GList           *views;
  gint             waiting_for_ack;
  gboolean         pending_resize;
  gchar           *input_file;
  gchar           *output_file;

  /* Shared memory variables */
  int              shm_fd;
  gchar           *shm_name;
  void            *mmap_start;
  size_t           mmap_length;

  /* Surface property variables */
  gint             surface_width;
  gint             surface_height;

  /* Synchronous call variables */
  ClutterMozEmbedCommand  sync_call;
  gchar                  *new_input_file;
  gchar                  *new_output_file;
  gchar                  *new_shm_name;

  /* Connection timeout variables */
  guint            connect_timeout;
  guint            connect_timeout_source;

  /* Page property variables */
  gboolean         private;
  guint            security;
};

static GMainLoop *mainloop;
static gint spawned_heads = 0;

static void block_until_command (ClutterMozHeadless     *moz_headless,
                                 ClutterMozEmbedCommand  command);

static gboolean input_io_func (GIOChannel              *source,
                               GIOCondition             condition,
                               ClutterMozHeadlessView  *view);

static void security_change_cb (ClutterMozHeadless *self,
                                void               *request,
                                guint               state,
                                gpointer            ignored);

void
send_feedback_all (ClutterMozHeadless      *headless,
                   ClutterMozEmbedFeedback  id,
                   ...)
{
  GList *v;
  ClutterMozHeadlessPrivate *priv = headless->priv;

  va_list args;

  for (v = priv->views; v; v = v->next)
    {
      ClutterMozHeadlessView *view = v->data;

      va_start (args, id);
      clutter_mozembed_comms_sendv (view->output, id, args);
      va_end (args);
    }
}

static void
location_cb (ClutterMozHeadless *headless)
{
  gchar *location;
  gboolean status, update;
  ClutterMozHeadlessPrivate *priv = headless->priv;

  location = moz_headless_get_location (MOZ_HEADLESS (headless));
  send_feedback_all (headless, CME_FEEDBACK_LOCATION,
                     G_TYPE_STRING, location,
                     G_TYPE_INVALID);

  status = update = (priv->security & CLUTTER_MOZEMBED_BAD_CERT) ? TRUE : FALSE;
  clutter_mozheadless_update_cert_status (location, &update);

  if (status != update)
    {
      if (update)
        priv->security += CLUTTER_MOZEMBED_BAD_CERT;
      else
        priv->security -= CLUTTER_MOZEMBED_BAD_CERT;
      security_change_cb (headless, NULL, priv->security, NULL);
    }

  g_free (location);
}

static void
title_cb (ClutterMozHeadless *headless)
{
  gchar *title;

  title = moz_headless_get_title (MOZ_HEADLESS (headless));
  send_feedback_all (headless, CME_FEEDBACK_TITLE,
                     G_TYPE_STRING, title,
                     G_TYPE_INVALID);

  g_free (title);
}

static void
icon_cb (ClutterMozHeadless *headless)
{
  gchar *icon;

  icon = moz_headless_get_icon (MOZ_HEADLESS (headless));
  send_feedback_all (headless, CME_FEEDBACK_ICON,
                     G_TYPE_STRING, icon,
                     G_TYPE_INVALID);

  g_free (icon);
}

static void
progress_cb (ClutterMozHeadless *headless,
             gint64              curprogress,
             gint64              maxprogress)
{
  gdouble progress = (curprogress > maxprogress) ?
                       curprogress / (gdouble)maxprogress :
                       -1.0;
  send_feedback_all (headless, CME_FEEDBACK_PROGRESS,
                     G_TYPE_DOUBLE, progress,
                     G_TYPE_INVALID);
}

static void
net_start_cb (ClutterMozHeadless *headless)
{
  send_feedback_all (headless, CME_FEEDBACK_NET_START, G_TYPE_INVALID);
}

static void
net_stop_cb (ClutterMozHeadless *headless)
{
  send_feedback_all (headless, CME_FEEDBACK_NET_STOP, G_TYPE_INVALID);
}

static gboolean
scroll_cb (MozHeadless *headless, MozHeadlessRect *rect, gint dx, gint dy)
{
  /*g_debug ("Scroll %d, %d", dx, dy);*/
  return FALSE;
}

static void
updated_cb (MozHeadless        *headless,
            gint                x,
            gint                y,
            gint                width,
            gint                height)
{
  gint doc_width, doc_height, sx, sy;
  GList *v;

  ClutterMozHeadlessPrivate *priv = CLUTTER_MOZHEADLESS (headless)->priv;

  /* If we're pending a resize, the surface width/height will be incorrect
   * and we could cause a crash in the embedder.
   */
  if (priv->pending_resize)
    return;

  /*g_debug ("Update +%d+%d %dx%d", x, y, width, height);*/

  msync (priv->mmap_start, priv->mmap_length, MS_SYNC);

  moz_headless_get_document_size (headless, &doc_width, &doc_height);
  moz_headless_get_scroll_pos (headless, &sx, &sy);

  /*g_debug ("Doc-size: %dx%d", doc_width, doc_height);*/
  send_feedback_all (CLUTTER_MOZHEADLESS (headless), CME_FEEDBACK_UPDATE,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_INT, width,
                     G_TYPE_INT, height,
                     G_TYPE_INT, priv->surface_width,
                     G_TYPE_INT, priv->surface_height,
                     G_TYPE_INT, sx,
                     G_TYPE_INT, sy,
                     G_TYPE_INT, doc_width,
                     G_TYPE_INT, doc_height,
                     G_TYPE_INVALID);

  for (v = priv->views; v; v = v->next)
    {
      ClutterMozHeadlessView *view = v->data;

      /* This shouldn't happen (because if we're waiting for an ack, we've
       * frozen updates), but put this check in just in case.
       */
      if (view->waiting_for_ack)
        continue;

      priv->waiting_for_ack ++;
      view->waiting_for_ack = TRUE;
    }

  moz_headless_freeze_updates (headless, TRUE);
}

static void
new_window_cb (MozHeadless *headless, MozHeadless **newEmbed, guint chromemask)
{
  ClutterMozHeadless *moz_headless = CLUTTER_MOZHEADLESS (headless);
  ClutterMozHeadlessPrivate *priv = moz_headless->priv;
  ClutterMozHeadlessView *view = (ClutterMozHeadlessView *)priv->views->data;

  clutter_mozembed_comms_send (view->output, CME_FEEDBACK_NEW_WINDOW,
                               G_TYPE_INT, chromemask,
                               G_TYPE_INVALID);
  block_until_command (moz_headless, CME_COMMAND_NEW_WINDOW_RESPONSE);

  if (priv->new_input_file && priv->new_output_file && priv->new_shm_name)
    {
      *newEmbed = g_object_new (CLUTTER_TYPE_MOZHEADLESS,
                                "chromeflags", chromemask,
                                "output", priv->new_output_file,
                                "input", priv->new_input_file,
                                "shm", priv->new_shm_name,
                                "private", priv->private,
                                NULL);
      moz_headless_set_chrome_mask (*newEmbed, chromemask);

      g_free (priv->new_input_file);
      g_free (priv->new_output_file);
      g_free (priv->new_shm_name);

      priv->new_input_file = NULL;
      priv->new_output_file = NULL;
      priv->new_shm_name = NULL;
    }
  else
    *newEmbed = NULL;
}

static void
destroy_browser_cb (ClutterMozHeadless *moz_headless)
{
  send_feedback_all (moz_headless, CME_FEEDBACK_CLOSED, G_TYPE_INVALID);
}

static void
link_message_cb (ClutterMozHeadless *self)
{
  gchar *message = moz_headless_get_link_message (MOZ_HEADLESS (self));
  send_feedback_all (self, CME_FEEDBACK_LINK_MESSAGE,
                     G_TYPE_STRING, message,
                     G_TYPE_INVALID);
  g_free (message);
}

static void
can_go_back_cb (ClutterMozHeadless *self, gboolean can_go_back)
{
  send_feedback_all (self, CME_FEEDBACK_CAN_GO_BACK,
                     G_TYPE_BOOLEAN, can_go_back,
                     G_TYPE_INVALID);
}

static void
can_go_forward_cb (ClutterMozHeadless *self, gboolean can_go_forward)
{
  send_feedback_all (self, CME_FEEDBACK_CAN_GO_FORWARD,
                     G_TYPE_BOOLEAN, can_go_forward,
                     G_TYPE_INVALID);
}

static void
size_to_cb (ClutterMozHeadless *self, gint width, gint height)
{
  ClutterMozHeadlessView *primary_view;

  ClutterMozHeadlessPrivate *priv = self->priv;

  if (!priv->views)
    return;

  primary_view = (ClutterMozHeadlessView *)priv->views->data;
  clutter_mozembed_comms_send (primary_view->output,
                               CME_FEEDBACK_SIZE_REQUEST,
                               G_TYPE_INT, width,
                               G_TYPE_INT, height,
                               G_TYPE_INVALID);
}

static void
security_change_cb (ClutterMozHeadless *self,
                    void               *request,
                    guint               state,
                    gpointer            ignored)
{
  ClutterMozHeadlessPrivate *priv = self->priv;

  priv->security = state | (priv->security & CLUTTER_MOZEMBED_BAD_CERT);
  send_feedback_all (self, CME_FEEDBACK_SECURITY,
                     G_TYPE_INT, priv->security,
                     G_TYPE_INVALID);
}

static void
show_tooltip_cb (ClutterMozHeadless *self,
                 const gchar        *text,
                 gint                x,
                 gint                y)
{
  send_feedback_all (self, CME_FEEDBACK_SHOW_TOOLTIP,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_STRING, text,
                     G_TYPE_INVALID);
}

static void
hide_tooltip_cb (ClutterMozHeadless *self)
{
  send_feedback_all (self, CME_FEEDBACK_HIDE_TOOLTIP, G_TYPE_INVALID);
}

static void
file_changed_cb (GFileMonitor           *monitor,
                 GFile                  *file,
                 GFile                  *other_file,
                 GFileMonitorEvent       event_type,
                 ClutterMozHeadlessView *view)
{
  gint fd;
  gint doc_width, doc_height, sx, sy;

  ClutterMozHeadlessPrivate *priv = view->parent->priv;

  if (event_type != G_FILE_MONITOR_EVENT_CREATED)
    return;

  g_signal_handlers_disconnect_by_func (monitor, file_changed_cb, view);
  g_file_monitor_cancel (monitor);
  g_object_unref (monitor);
  view->monitor = NULL;

  /* Opening input channel */
  fd = open (view->input_file, O_RDONLY | O_NONBLOCK);
  view->input = g_io_channel_unix_new (fd);
  g_io_channel_set_encoding (view->input, NULL, NULL);
  g_io_channel_set_buffered (view->input, FALSE);
  g_io_channel_set_close_on_unref (view->input, TRUE);
  view->watch_id = g_io_add_watch (view->input,
                                   G_IO_IN | G_IO_PRI | G_IO_ERR |
                                   G_IO_NVAL | G_IO_HUP,
                                   (GIOFunc)input_io_func,
                                   view);

  /* Send the shm name and an update to the new view */
  clutter_mozembed_comms_send (view->output, CME_FEEDBACK_SHM_NAME,
                               G_TYPE_STRING, priv->shm_name,
                               G_TYPE_INVALID);

  moz_headless_get_document_size (MOZ_HEADLESS (view->parent),
                                  &doc_width, &doc_height);
  moz_headless_get_scroll_pos (MOZ_HEADLESS (view->parent), &sx, &sy);

  /* If we have an active surface, send an update to this new view */
  if (priv->mmap_start)
    {
      clutter_mozembed_comms_send (view->output, CME_FEEDBACK_UPDATE,
                                   G_TYPE_INT, 0,
                                   G_TYPE_INT, 0,
                                   G_TYPE_INT, priv->surface_width,
                                   G_TYPE_INT, priv->surface_height,
                                   G_TYPE_INT, priv->surface_width,
                                   G_TYPE_INT, priv->surface_height,
                                   G_TYPE_INT, sx,
                                   G_TYPE_INT, sy,
                                   G_TYPE_INT, doc_width,
                                   G_TYPE_INT, doc_height,
                                   G_TYPE_INVALID);

      if (!priv->waiting_for_ack)
        moz_headless_freeze_updates (MOZ_HEADLESS (view->parent), TRUE);

      view->waiting_for_ack = TRUE;
      priv->waiting_for_ack ++;
    }

  /* Inform if we're private */
  /* FIXME: I don't think we need to do this */
  if (priv->private)
    clutter_mozembed_comms_send (view->output, CME_FEEDBACK_PRIVATE,
                                 G_TYPE_BOOLEAN, TRUE,
                                 G_TYPE_INVALID);
}

static void
cursor_changed_cb (MozHeadlessCursorType    type,
                   const MozHeadlessCursor *special,
                   gpointer                 data)
{
  ClutterMozHeadless *mozheadless = data;
  /* TODO - support special cursors */
  send_feedback_all (mozheadless, CME_FEEDBACK_CURSOR,
                     G_TYPE_INT, type,
                     G_TYPE_INVALID);
}

#ifdef SUPPORT_IM
static void
im_reset_cb (ClutterMozHeadless *self)
{
  send_feedback_all(self, CME_FEEDBACK_IM_RESET, G_TYPE_INVALID);
}

static void
im_enable_cb (ClutterMozHeadless *self, gboolean enabled)
{
  send_feedback_all (self, CME_FEEDBACK_IM_ENABLE,
                     G_TYPE_BOOLEAN, enabled,
                     G_TYPE_INVALID);
}

static void
im_focus_change_cb (ClutterMozHeadless *self, gboolean in)
{
  send_feedback_all (self, CME_FEEDBACK_IM_FOCUS_CHANGE,
                     G_TYPE_BOOLEAN, in,
                     G_TYPE_INVALID);
}

static void
im_set_cursor_cb (ClutterMozHeadless *self,
                  gint                x,
                  gint                y,
                  gint                width,
                  gint                height)
{
  send_feedback_all (self, CME_FEEDBACK_IM_SET_CURSOR,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_INT, width,
                     G_TYPE_INT, height,
                     G_TYPE_INVALID);
}
#endif

#ifdef SUPPORT_PLUGINS
static void
plugin_added_cb (ClutterMozHeadless *self,
                 guint plug_id,
                 gint x,
                 gint y,
                 gint width,
                 gint height)

{
  send_feedback_all (self, CME_FEEDBACK_PLUGIN_ADDED,
                     G_TYPE_UINT, plug_id,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_INT, width,
                     G_TYPE_INT, height,
                     G_TYPE_INVALID);
}

static void
plugin_updated_cb (ClutterMozHeadless *self,
                   guint plug_id,
                   gint x,
                   gint y,
                   gint width,
                   gint height)
{
  send_feedback_all (self, CME_FEEDBACK_PLUGIN_UPDATED,
                     G_TYPE_UINT, plug_id,
                     G_TYPE_INT, x,
                     G_TYPE_INT, y,
                     G_TYPE_INT, width,
                     G_TYPE_INT, height,
                     G_TYPE_INVALID);
}

static void
plugin_visibility_cb (ClutterMozHeadless *self,
                      guint plug_id,
                      gboolean visible)
{
  send_feedback_all (self, CME_FEEDBACK_PLUGIN_VISIBILITY,
                     G_TYPE_UINT, plug_id,
                     G_TYPE_BOOLEAN, visible,
                     G_TYPE_INVALID);
}

#endif

static void
clutter_mozheadless_create_view (ClutterMozHeadless *self,
                                 gchar              *input_file,
                                 gchar              *output_file)
{
  GFile *file;
  gint fd;

  ClutterMozHeadlessPrivate *priv = self->priv;
  ClutterMozHeadlessView *view = g_new0 (ClutterMozHeadlessView, 1);

  priv->views = g_list_append (priv->views, view);

  view->parent = self;
  /* This takes ownership of the input_file and output_file strings */
  view->input_file = input_file;
  view->output_file = output_file;

  mkfifo (view->output_file, S_IWUSR | S_IRUSR);
  fd = open (view->output_file, O_RDWR | O_NONBLOCK);
  view->output = g_io_channel_unix_new (fd);
  g_io_channel_set_encoding (view->output, NULL, NULL);
  g_io_channel_set_buffered (view->output, FALSE);
  g_io_channel_set_close_on_unref (view->output, TRUE);

  file = g_file_new_for_path (view->input_file);
  view->monitor = g_file_monitor_file (file, 0, NULL, NULL);
  g_object_unref (file);
  g_signal_connect (view->monitor, "changed",
                    G_CALLBACK (file_changed_cb), view);

  if (g_file_test (view->input_file, G_FILE_TEST_EXISTS))
    file_changed_cb (view->monitor, NULL, NULL,
                     G_FILE_MONITOR_EVENT_CREATED, view);
}

static gboolean
send_mack (ClutterMozHeadlessView *view)
{
  view->mack_source = 0;
  clutter_mozembed_comms_send (view->output,
                               CME_FEEDBACK_MOTION_ACK,
                               G_TYPE_INVALID);
  return FALSE;
}

static gboolean
send_sack_cb (ClutterMozHeadlessView *view)
{
  view->sack_source = 0;
  clutter_mozembed_comms_send (view->output,
                               CME_FEEDBACK_SCROLL_ACK,
                               G_TYPE_INVALID);
  return FALSE;
}

static void
send_sack (ClutterMozHeadlessView *view)
{
  if (!view->sack_source)
    view->sack_source =
      g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)send_sack_cb, view, NULL);
}

static void
clutter_moz_headless_resize (ClutterMozHeadless *moz_headless)
{
  MozHeadless *headless = MOZ_HEADLESS (moz_headless);
  ClutterMozHeadlessPrivate *priv = moz_headless->priv;

  moz_headless_set_surface (headless, NULL, 0, 0, 0);
  moz_headless_set_size (headless,
                         priv->surface_width,
                         priv->surface_height);

  if (priv->mmap_start)
    munmap (priv->mmap_start, priv->mmap_length);

  priv->mmap_length = priv->surface_width * priv->surface_height * 4;
  ftruncate (priv->shm_fd, priv->mmap_length);
  priv->mmap_start = mmap (NULL, priv->mmap_length, PROT_READ | PROT_WRITE,
                           MAP_SHARED, priv->shm_fd, 0);

  if (priv->mmap_start == MAP_FAILED)
    {
      g_warning ("Unable to mmap region for headless surface\n");
      priv->mmap_start = NULL;
      return;
    }

  moz_headless_set_surface (headless, priv->mmap_start,
                            priv->surface_width, priv->surface_height,
                            priv->surface_width * 4);
}

static void
clutter_moz_headless_unfreeze (ClutterMozHeadless *moz_headless)
{
  MozHeadless *headless = MOZ_HEADLESS (moz_headless);
  ClutterMozHeadlessPrivate *priv = moz_headless->priv;

  moz_headless_freeze_updates (headless, FALSE);

  if (priv->pending_resize)
    {
      clutter_moz_headless_resize (moz_headless);
      priv->pending_resize = FALSE;
    }
}

static void
process_command (ClutterMozHeadlessView *view, ClutterMozEmbedCommand command)
{
  ClutterMozHeadless *moz_headless = view->parent;
  ClutterMozHeadlessPrivate *priv = moz_headless->priv;
  MozHeadless *headless = MOZ_HEADLESS (moz_headless);

  /*g_debug ("Processing command: %d", command);*/

  if (priv->sync_call && (priv->sync_call == command))
    priv->sync_call = 0;

  switch (command)
    {
      case CME_COMMAND_UPDATE_ACK :
        {
          view->waiting_for_ack = FALSE;
          priv->waiting_for_ack --;

          if (priv->waiting_for_ack == 0)
            clutter_moz_headless_unfreeze (moz_headless);

          break;
        }
      case CME_COMMAND_OPEN_URL :
        {
          gchar *url = clutter_mozembed_comms_receive_string (view->input);
          moz_headless_load_url (headless, url);
          g_free (url);
          break;
        }
      case CME_COMMAND_RESIZE :
        {
          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &priv->surface_width,
                                          G_TYPE_INT, &priv->surface_height,
                                          G_TYPE_INVALID);

          if (priv->waiting_for_ack)
            priv->pending_resize = TRUE;
          else
            clutter_moz_headless_resize (moz_headless);

          break;
        }
      case CME_COMMAND_MOTION :
        {
          gint x, y;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &x,
                                          G_TYPE_INT, &y,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_motion (headless, x, y, m);

          /* This is done so that we definitely get to do any redrawing before we
           * send an acknowledgement.
           */
          if (!view->mack_source)
            view->mack_source =
              g_idle_add_full (G_PRIORITY_LOW,
                               (GSourceFunc)send_mack,
                               view,
                               NULL);
          else
            g_warning ("Received a motion event before "
                       "sending acknowledgement");

          break;
        }
      case CME_COMMAND_BUTTON_PRESS :
        {
          gint x, y, button, count;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &x,
                                          G_TYPE_INT, &y,
                                          G_TYPE_INT, &button,
                                          G_TYPE_INT, &count,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_button_press (headless, x, y, button, count, m);

          break;
        }
      case CME_COMMAND_BUTTON_RELEASE :
        {
          gint x, y, button;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &x,
                                          G_TYPE_INT, &y,
                                          G_TYPE_INT, &button,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_button_release (headless, x, y, button, m);

          break;
        }
      case CME_COMMAND_KEY_PRESS :
        {
          MozHeadlessKey key;
          gunichar unicode_char;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &key,
                                          G_TYPE_INT, &unicode_char,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_key_press (headless, key, unicode_char, m);

          break;
        }
      case CME_COMMAND_KEY_RELEASE :
        {
          MozHeadlessKey key;
          MozHeadlessModifier m;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &key,
                                          G_TYPE_INT, &m,
                                          G_TYPE_INVALID);

          moz_headless_key_release (headless, key, m);

          break;
        }
      case CME_COMMAND_SCROLL :
        {
          gint dx, dy;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &dx,
                                          G_TYPE_INT, &dy,
                                          G_TYPE_INVALID);

          moz_headless_scroll (headless, dx, dy);

          send_sack (view);

          break;
        }
      case CME_COMMAND_SCROLL_TO :
        {
          gint x, y;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_INT, &x,
                                          G_TYPE_INT, &y,
                                          G_TYPE_INVALID);

          moz_headless_set_scroll_pos (headless, x, y);

          send_sack (view);

          break;
        }
      case CME_COMMAND_GET_CAN_GO_BACK :
        {
          clutter_mozembed_comms_send (view->output,
                                       CME_FEEDBACK_CAN_GO_BACK,
                                       G_TYPE_BOOLEAN,
                                       moz_headless_can_go_back (headless),
                                       G_TYPE_INVALID);
          break;
        }
      case CME_COMMAND_GET_CAN_GO_FORWARD :
        {
          clutter_mozembed_comms_send (view->output,
                                       CME_FEEDBACK_CAN_GO_FORWARD,
                                       G_TYPE_BOOLEAN,
                                       moz_headless_can_go_back (headless),
                                       G_TYPE_INVALID);
          break;
        }
      case CME_COMMAND_GET_SHM_NAME :
        {
          clutter_mozembed_comms_send (view->output,
                                       CME_FEEDBACK_SHM_NAME,
                                       G_TYPE_STRING, priv->shm_name,
                                       G_TYPE_INVALID);
          break;
        }
      case CME_COMMAND_BACK :
        {
          moz_headless_go_back (headless);
          break;
        }
      case CME_COMMAND_FORWARD :
        {
          moz_headless_go_forward (headless);
          break;
        }
      case CME_COMMAND_STOP :
        {
          moz_headless_stop_load (headless);
          break;
        }
      case CME_COMMAND_REFRESH :
        {
          moz_headless_reload (headless, MOZ_HEADLESS_FLAG_RELOADNORMAL);
          break;
        }
      case CME_COMMAND_RELOAD :
        {
          moz_headless_reload (headless, MOZ_HEADLESS_FLAG_RELOADBYPASSCACHE);
          break;
        }
      case CME_COMMAND_SET_CHROME :
        {
          gint chrome = clutter_mozembed_comms_receive_int (view->input);
          moz_headless_set_chrome_mask (headless, chrome);
          break;
        }
      case CME_COMMAND_TOGGLE_CHROME :
        {
          guint32 chrome = moz_headless_get_chrome_mask (headless);
          chrome ^= clutter_mozembed_comms_receive_int (view->input);
          moz_headless_set_chrome_mask (headless, chrome);
          break;
        }
      case CME_COMMAND_QUIT :
        {
          /* FIXME: Er, will this unref not cause a crash? Must check this... */
          send_feedback_all (moz_headless, CME_FEEDBACK_CLOSED, G_TYPE_INVALID);
          g_object_unref (moz_headless);
          break;
        }
      case CME_COMMAND_NEW_VIEW :
        {
          gchar *input, *output;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_STRING, &input,
                                          G_TYPE_STRING, &output,
                                          G_TYPE_INVALID);

          /* create_view takes ownership of the input and output strings */
          clutter_mozheadless_create_view (moz_headless, input, output);

          break;
        }
      case CME_COMMAND_NEW_WINDOW :
        {
          gchar *input, *output, *shm;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_STRING, &input,
                                          G_TYPE_STRING, &output,
                                          G_TYPE_STRING, &shm,
                                          G_TYPE_INVALID);

          g_object_new (CLUTTER_TYPE_MOZHEADLESS,
                        "input", input,
                        "output", output,
                        "shm", shm,
                        "private", priv->private,
                        NULL);

          break;
        }
      case CME_COMMAND_NEW_WINDOW_RESPONSE :
        {
          if (clutter_mozembed_comms_receive_boolean (view->input))
            {
              clutter_mozembed_comms_receive (view->input,
                                              G_TYPE_STRING, &priv->new_input_file,
                                              G_TYPE_STRING, &priv->new_output_file,
                                              G_TYPE_STRING, &priv->new_shm_name,
                                              G_TYPE_INVALID);
            }
          else
            {
              g_free (priv->new_input_file);
              priv->new_input_file = NULL;
              g_free (priv->new_output_file);
              priv->new_output_file = NULL;
              g_free (priv->new_shm_name);
              priv->new_shm_name = NULL;
            }
          break;
        }
      case CME_COMMAND_FOCUS :
        {
          gboolean focus = clutter_mozembed_comms_receive_boolean (view->input);
          moz_headless_focus (MOZ_HEADLESS (moz_headless), focus);
          break;
        }
      case CME_COMMAND_PURGE_SESSION_HISTORY :
        {
          moz_headless_purge_session_history (MOZ_HEADLESS (moz_headless));
          break;
        }
      case CME_COMMAND_DL_CANCEL :
        {
          gint id = clutter_mozembed_comms_receive_int (view->input);
          g_signal_emit (view->parent, signals[CANCEL_DOWNLOAD], 0, id);
          break;
        }
#ifdef SUPPORT_IM
      case CME_COMMAND_IM_COMMIT :
        {
          gchar *str = clutter_mozembed_comms_receive_string (view->input);

          moz_headless_im_commit (MOZ_HEADLESS (moz_headless), str);
          g_free (str);

          break;
        }
      case CME_COMMAND_IM_PREEDIT_CHANGED :
        {
          gchar *str;
          gint cursor_pos;

          clutter_mozembed_comms_receive (view->input,
                                          G_TYPE_STRING, &str,
                                          G_TYPE_INT, &cursor_pos,
                                          G_TYPE_INVALID);
          moz_headless_im_preedit_changed (MOZ_HEADLESS (moz_headless),
                                           str,
                                           cursor_pos);
          g_free (str);

          break;
        }
#endif
      case CME_COMMAND_SET_SEARCH_STRING :
        {
          gchar *str = clutter_mozembed_comms_receive_string (view->input);
          moz_headless_find_set_string (MOZ_HEADLESS (moz_headless),
                                        str);
          g_free (str);
          break;
        }
      case CME_COMMAND_FIND_NEXT :
        {
          moz_headless_find_next (MOZ_HEADLESS (moz_headless));
          break;
        }
{'fraction_non_alphanumeric': 0.07597627666949087, 'fraction_numerical': 0.0014172379265192945, 'mean_word_length': 1.6492001305909239, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 377, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 258, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3384072', 'n_tokens_mistral': 20229, 'n_tokens_neox': 19550, 'n_words': 3749}