/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/* 
 *  cl_comp.c - The compositor API
 */


/* On UNIX, child and sibling windows are always clipped, so
   cutout layers are unnecessary. */
#ifdef XP_UNIX
#    define TRIM_UPDATE_REGION_CUTOUTS PR_FALSE
#else
#    define TRIM_UPDATE_REGION_CUTOUTS PR_TRUE
#endif

#include "prtypes.h"
#ifdef XP_MAC
#include "prpriv.h"
#else
#include "private/prpriv.h" /* For PR_NewNamedMonitor */
#endif

#include "xp_core.h"
#include "xpassert.h"
#include "fe_proto.h"		/* For FE_SetTimeout(), et al */
#include "layers.h"
#include "cl_priv.h"
#ifdef CL_ADAPT_FRAME_RATE
#    include "prtime.h"
#endif

#ifndef ABS
#    define ABS(x)  (((x) < 0) ? -(x) : (x))
#endif

static void
cl_compute_update_region(CL_Compositor *compositor);

static PRBool
cl_composite(CL_Compositor *compositor, PRBool cutoutp);

int cl_trace_level = 0;

#if !defined(XP_MAC) && defined(DEBUG) /* FE_HighlightRegion is not currently in the mac build */
void
CL_HighlightRegion(CL_Compositor *compositor, FE_Region region)
{
    extern MWContext *LO_CompositorToContext(CL_Compositor *compositor);

    int time;
    MWContext *context = LO_CompositorToContext(compositor);

    time = 200;
#if defined(XP_UNIX)
    if (getenv("SHOWREGIONS"))
        time = atoi(getenv("SHOWREGIONS"));
#endif

    FE_HighlightRegion(context, region, time);
}
#endif

/* Allocate and initialize a new compositor */
CL_Compositor *
CL_NewCompositor(CL_Drawable *primary_drawable,
                 CL_Drawable *backing_store,
                 int32 x_offset, int32 y_offset,
                 int32 width, int32 height,
                 uint32 frame_rate)
{
    CL_Compositor *compositor;

    compositor = XP_NEW_ZAP(CL_Compositor);

    if (compositor == NULL)
        return NULL;

    CL_TRACE(0, ("Creating new compositor: %x", compositor));

#ifdef CL_THREAD_SAFE
    compositor->monitor = PR_NewNamedMonitor("compositor-monitor");
    if (!compositor->monitor)
        return NULL;
#endif

    compositor->gen_id = 0;
    compositor->primary_drawable = primary_drawable;
    compositor->backing_store = backing_store;
    compositor->x_offset = x_offset;
    compositor->y_offset = y_offset;
    compositor->window_size.left = compositor->window_size.top = 0;
    compositor->window_size.right = width;
    compositor->window_size.bottom = height;
    if (frame_rate)
        compositor->frame_period = 1000000.0F / frame_rate;
    else
        compositor->frame_period = 0.0F;

    compositor->window_region = FE_CreateRectRegion(&compositor->window_size);
    compositor->update_region = FE_CreateRegion();

    compositor->event_containment_lists[0].layer_list = 
        XP_ALLOC(CL_CONTAINMENT_LIST_SIZE * sizeof(CL_Layer *));
    compositor->event_containment_lists[0].list_size = 
        CL_CONTAINMENT_LIST_SIZE;
    compositor->event_containment_lists[0].list_head = -1;
    compositor->event_containment_lists[1].layer_list = 
        XP_ALLOC(CL_CONTAINMENT_LIST_SIZE * sizeof(CL_Layer *));
    compositor->event_containment_lists[1].list_size = 
        CL_CONTAINMENT_LIST_SIZE;
    compositor->event_containment_lists[1].list_head = -1;

    compositor->backing_store_region = FE_CreateRegion();
    if ((compositor->window_region == NULL) ||
        (compositor->update_region == NULL) ||
        (compositor->backing_store_region == NULL) ||
        (compositor->event_containment_lists[0].layer_list == NULL) ||
        (compositor->event_containment_lists[1].layer_list == NULL)) {
        CL_DestroyCompositor(compositor);
        return NULL;
    }
    compositor->offscreen_enabled = PR_FALSE;
    compositor->back_to_front_only = PR_FALSE;

    compositor->mouse_event_grabber = NULL;
    compositor->key_event_grabber = NULL;
    compositor->last_mouse_event_grabber = NULL;
    compositor->focus_policy = CL_FOCUS_POLICY_CLICK;

    primary_drawable->compositor = compositor;
    if (backing_store)
        backing_store->compositor = compositor;

    cl_InitDrawable(compositor->primary_drawable);

    return compositor;
}

static int
cl_group_list_destroy(PRHashEntry *he,
                      int i,
                      void *arg)
{
    XP_List *list = (XP_List *)he->value;
    
    XP_ListDestroy(list);
    
    return HT_ENUMERATE_REMOVE;
}


/* Free an existing compositor */
void 
CL_DestroyCompositor(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);

    if (!compositor)
        return;

    CL_TRACE(0, ("Destroying compositor: %x", compositor));

    if (compositor->composite_timeout)
        FE_ClearTimeout(compositor->composite_timeout);

    if (compositor->root)
        CL_DestroyLayerTree(compositor->root);

    if (compositor->window_region)
        FE_DestroyRegion(compositor->window_region);
  
    if (compositor->update_region)
        FE_DestroyRegion(compositor->update_region);

    if (compositor->backing_store_region)
        FE_DestroyRegion(compositor->backing_store_region);

    if (compositor->group_table) {
        /* Delete all the group lists in the table */
        PR_HashTableEnumerateEntries(compositor->group_table,
                                     cl_group_list_destroy,
                                     NULL);
        PR_HashTableDestroy(compositor->group_table);
    }

    if (compositor->primary_drawable) {
        cl_RelinquishDrawable(compositor->primary_drawable);
        CL_DestroyDrawable(compositor->primary_drawable);
    }

    if (compositor->backing_store) {
        if (compositor->offscreen_initialized)
            cl_RelinquishDrawable(compositor->backing_store);
        CL_DestroyDrawable(compositor->backing_store);
    }

    if (compositor->event_containment_lists[0].layer_list)
        XP_FREE(compositor->event_containment_lists[0].layer_list);

    if (compositor->event_containment_lists[1].layer_list)
        XP_FREE(compositor->event_containment_lists[1].layer_list);
    
#ifdef CL_THREAD_SAFE
    if (compositor->monitor)
        PR_DestroyMonitor(compositor->monitor);
#endif
  
    XP_FREE(compositor);
}

void
CL_IncrementCompositorGeneration(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return;
    
    compositor->gen_id++;
}

/* Find layer by name. This carries out a breadth-first search */
/* of the layer tree. Returns NULL if no such layer exists.    */
CL_Layer *
CL_FindLayer(CL_Compositor *compositor, char *name)
{
    XP_List *list;
    CL_Layer *layer, *child;

    XP_ASSERT(compositor);
    XP_ASSERT(name);

    if (!compositor || !name || !compositor->root)
        return NULL;

    /* BUGBUG This is rather an expensive way to maintain a list */
    /* since a little struct is allocated for each element added */
    /* to the list.                                              */
    list = XP_ListNew();

    if (list == NULL)
        return NULL;

    LOCK_COMPOSITOR(compositor);
  
    XP_ListAddObject(list, compositor->root);

    /* While the list is not empty */
    while ((layer = XP_ListRemoveTopObject(list))) {
	
        /* Check if the name of this layer matches */
        if (layer->name && strcmp(layer->name, name) == 0) {
            XP_ListDestroy(list);
            UNLOCK_COMPOSITOR(compositor);
            return layer;
        }
	
        /* Add each of the children to the end of the list */
        for (child = layer->top_child; child; child = child->sib_below)
            XP_ListAddObjectToEnd(list, child);
    }

    XP_ListDestroy(list);
    UNLOCK_COMPOSITOR(compositor);
    return NULL;
}

static void
cl_refresh_window_region_common(CL_Compositor *compositor, 
                                FE_Region refresh_region,
                                PRBool copy_region)
{
    FE_Region draw_region, save_update_region, copy_refresh_region=0;
    CL_Drawable *backing_store;
    PRBool save_offscreen_inhibited;
    
    XP_ASSERT(compositor);
    XP_ASSERT(refresh_region);

    if (!compositor || !refresh_region)
        return;
    
    CL_TRACE(0, ("Refreshing window region"));

    LOCK_COMPOSITOR(compositor);

#if 0
    /* Subtract out the cutout layers, since they are out-of-bounds
       for compositor drawing. */
    if (compositor->enabled) {
	cl_prepare_to_draw(compositor, refresh_region, PR_FALSE);
    }
#endif

/*    if (compositor->root)
        CL_HighlightRegion(compositor, refresh_region);*/

    if (compositor->enabled)
        cl_compute_update_region(compositor);

   /* Invalidate any parts of the backing store for which there are
       update requests. */
    FE_SubtractRegion(compositor->backing_store_region,
                      compositor->update_region,
                      compositor->backing_store_region);

    /* Figure out what part of the refresh region has already been
       composited into the backing store and blit that onto the
       primary drawable. */
    if (compositor->backing_store) {
        backing_store = cl_LockDrawableForRead(compositor->backing_store);
        if (backing_store) {
            draw_region = FE_CreateRegion();
            if (! draw_region) {
                UNLOCK_COMPOSITOR(compositor);
                return;
            }
            
            FE_IntersectRegion(compositor->backing_store_region, refresh_region, draw_region);
            cl_CopyPixels(backing_store, compositor->primary_drawable,
                          draw_region);
            FE_SubtractRegion(refresh_region, draw_region, refresh_region);
            FE_DestroyRegion(draw_region);
            cl_UnlockDrawable(backing_store);
        }
    }

    if (compositor->enabled) {
        /* Save out the current update region, since we only want to draw
           the area that we got a refresh request for.  We can't simply
           subtract out the refresh area from the update area because
           of Windows' behavior: In between the BeginPaint() and EndPaint()
           calls, the clip is constrained to be that of the OS's update
           region intersected with the clip that we set.  But, we don't know
           what the resulting cascaded clip. */
        save_update_region = compositor->update_region;

        /* We might have to copy the refresh region, since compositing will
           modify it. */
        if (copy_region) {
            copy_refresh_region = FE_CreateRegion();
            FE_CopyRegion(refresh_region, copy_refresh_region);
            compositor->update_region = copy_refresh_region;
        }
        else
            compositor->update_region = refresh_region;

        /* Subtract out the area that isn't in the composited region */
        FE_IntersectRegion(compositor->window_region, 
                           compositor->update_region,
                           compositor->update_region);

        /* For speedier scrolling and exposures, we always composite
           on-screen for refresh. */
        save_offscreen_inhibited = compositor->offscreen_inhibited;
        compositor->offscreen_inhibited = PR_TRUE;

        /* 
         * Note that we don't subtract out the cutout region when we're
         * going through this entry point, since it can be a performance
         * hog, especially if we're scrolling. This works for Windows and
         * X, but I'm not sure it will work across platforms.
         */
        cl_composite(compositor, PR_FALSE);
        compositor->offscreen_inhibited = save_offscreen_inhibited;

        /* 
         * If a layer has changed during the compositing, the update
         * region will reflect the changes and will need to be dealt
         * with in the next compositing pass. Copy it back into the
         * saved update region.
         */
        FE_UnionRegion(save_update_region, compositor->update_region,
                       save_update_region);

        if (copy_region)
            FE_DestroyRegion(copy_refresh_region);

        compositor->update_region = save_update_region;
    }
    else
        FE_UnionRegion(compositor->update_region, refresh_region,
                       compositor->update_region);

    UNLOCK_COMPOSITOR(compositor);
}


/*
 * Inform the compositor that some part of the window needs to be
 * refreshed, e.g. in response to a window expose event.  The region
 * to be refreshed is expressed in window coordinates.
 */
void
CL_RefreshWindowRegion(CL_Compositor *compositor, FE_Region refresh_region)
{
    cl_refresh_window_region_common(compositor, refresh_region, PR_TRUE);
}

/*
 * Inform the compositor that some part of the window needs to be
 * refreshed, e.g. in response to a window expose event.  The region
 * to be refreshed is expressed in window coordinates.
 */
void
CL_RefreshWindowRect(CL_Compositor *compositor, XP_Rect *refresh_rect)
{
    FE_Region refresh_region = FE_CreateRectRegion(refresh_rect);

    if (! refresh_region)       /* OOM */
        return;
    
    cl_refresh_window_region_common(compositor, refresh_region, PR_FALSE);
    FE_DestroyRegion(refresh_region);
}

/*
 * Inform the compositor that some part of a layer has changed and
 * needs to be redrawn. The region to be updated is expressed in the
 * layer's coordinate system. For efficiency, only the part of the
 * passed in region that is not clipped by ancestor layers is actually
 * updated.  If update_now is PR_TRUE, the composite is done
 * synchronously. Otherwise, it is done at the next timer callback.
 */
void 
CL_UpdateLayerRegion(CL_Compositor *compositor, CL_Layer *layer,
                     FE_Region layer_region, PRBool update_now)
{
    int32 x_offset, y_offset;
    XP_Rect win_clipped_bbox;
    FE_Region update_region, win_clipped_bbox_region;
    
    XP_ASSERT(compositor);
    XP_ASSERT(layer);
    XP_ASSERT(layer_region);

    if (!compositor || !layer || !layer_region)
        return;

    /* Oddball predicate below is used to combat the case where the
       layer was made invisible, updated and made visible again, all
       between two composite cycles.  In that case, we still want
       to repaint the composited area. */
    if (!layer->prev_visible && !layer->visible)
        return;

    CL_TRACE(0, ("Updating layer region"));

    LOCK_COMPOSITOR(compositor);

    /* Compute offsets to convert layer coordinates to window coordinates */
    x_offset = layer->x_origin - compositor->x_offset;
    y_offset = layer->y_origin - compositor->y_offset;
    
    /* 
     * If the offsets are outside the coordinate range we can express
     * for regions, we can assume that the region to be updated is
     * outside the window rectangle.
     */
    if ((ABS(x_offset) > FE_MAX_REGION_COORDINATE) ||
        (ABS(y_offset) > FE_MAX_REGION_COORDINATE)) {
        UNLOCK_COMPOSITOR(compositor);
        return;
    }
    
    /* Convert input region from layer coordinates into window coordinates */
    update_region = FE_CreateRegion();
    
    XP_ASSERT(update_region);
    if (! update_region) {
        UNLOCK_COMPOSITOR(compositor);
        return;
    }
    
    FE_CopyRegion(layer_region, update_region);
    FE_OffsetRegion(update_region, x_offset, y_offset);

    /* Convert clipping bbox of layer from document coordinates to
       window coordinates and clip to window bounds. */
    XP_CopyRect(&layer->clipped_bbox, &win_clipped_bbox);
    XP_OffsetRect(&win_clipped_bbox,
                  -compositor->x_offset, -compositor->y_offset);
    XP_IntersectRect(&compositor->window_size,
                     &win_clipped_bbox, 
                     &win_clipped_bbox);

    /* Make the clipping bbox into a region, so we can do intersection */
    win_clipped_bbox_region = FE_CreateRectRegion(&win_clipped_bbox);
    XP_ASSERT(win_clipped_bbox_region);
    if (! win_clipped_bbox_region) {
        FE_DestroyRegion(update_region);
        UNLOCK_COMPOSITOR(compositor);
        return;
    }

    /* Subtract out the area that isn't visible in the layer */
    FE_IntersectRegion(win_clipped_bbox_region, update_region, update_region);
    FE_DestroyRegion(win_clipped_bbox_region);

    if (FE_IsEmptyRegion(update_region)) {
        FE_DestroyRegion(update_region);
        UNLOCK_COMPOSITOR(compositor);
        return;
    }
    
    /* Add the altered region to the region to update at the next composite */
    FE_UnionRegion(compositor->update_region, update_region, 
                   compositor->update_region);

    FE_DestroyRegion(update_region);

    if (compositor->enabled) {
        if (update_now)
            cl_composite(compositor, TRIM_UPDATE_REGION_CUTOUTS);
        else
            cl_start_compositor_timeouts(compositor);
    }
    UNLOCK_COMPOSITOR(compositor);
}

/*
 * Inform the compositor that some rectangular part of a layer has
 * changed and needs to be redrawn. The region to be updated is
 * expressed in the layer's coordinate system. For efficiency, only
 * the part of the passed in region that is not clipped by ancestor
 * layers is actually updated.  If update_now is PR_TRUE, the
 * composite is done synchronously. Otherwise, it is done at the next
 * timer callback.
 */
void
CL_UpdateLayerRect(CL_Compositor *compositor, CL_Layer *layer,
                   XP_Rect *layer_rect, PRBool composite_now)
{
    int32 x_offset, y_offset;
    XP_Rect update_rect;

    XP_ASSERT(compositor);
    XP_ASSERT(layer);
    XP_ASSERT(layer_rect);

    if (!compositor || !layer || !layer_rect)
        return;

    /* Oddball predicate below is used to combat the case where the
       layer was made invisible, updated and made visible again, all
       between two composite cycles.  In that case, we still want
       to repaint the composited area. */
    if (!layer->prev_visible && !layer->visible)
        return;

    CL_TRACE(0, ("Updating layer rect"));

    XP_CopyRect(layer_rect, &update_rect);
    
    LOCK_COMPOSITOR(compositor);
    
    /* Compute offsets to convert layer coordinates to document coordinates */
    x_offset = layer->x_origin;
    y_offset = layer->y_origin;

    /* Convert the update_rect into document coordinates */
    XP_OffsetRect(&update_rect, x_offset, y_offset);
    
    /* Don't update parts of the layer that are clipped by ancestor layers. */
    XP_IntersectRect(&update_rect, &layer->clipped_bbox, &update_rect);

    CL_UpdateDocumentRect(compositor, &update_rect, composite_now);

    UNLOCK_COMPOSITOR(compositor);
}

/*
 * Inform the compositor that some rectangular part of a document has
 * changed and needs to be redrawn. The region to be updated is
 * expressed in the document's coordinate system.  If update_now is
 * PR_TRUE, the composite is done synchronously. Otherwise, it is done
 * at the next timer callback.
 */
void
CL_UpdateDocumentRect(CL_Compositor *compositor,
                      XP_Rect *doc_rect, PRBool update_now)
{
    int32 x_offset, y_offset;
    XP_Rect update_rect;
    FE_Region update_region;
    
    XP_ASSERT(compositor);
    XP_ASSERT(doc_rect);

    if (!compositor || !doc_rect)
        return;
    
    CL_TRACE(0, ("Updating document rect"));

    XP_CopyRect(doc_rect, &update_rect);

    LOCK_COMPOSITOR(compositor);

    /* Compute offsets to convert document coordinates to window coordinates */
    x_offset = - compositor->x_offset;
    y_offset = - compositor->y_offset;
    
    /* Convert the update_rect into window coordinates */
    XP_OffsetRect(&update_rect, x_offset, y_offset);

    /* 
     * If there's no overlap between the update_rect and the
     * composited area, there's nothing to draw.
     */
    if (!XP_RectsOverlap(&compositor->window_size, &update_rect)) {
        UNLOCK_COMPOSITOR(compositor);
        return;
    }
    
    /* Subtract out the area that isn't in the composited rect */
    XP_IntersectRect(&compositor->window_size, &update_rect,
                     &update_rect);

    update_region = FE_CreateRectRegion(&update_rect);

    XP_ASSERT(update_region);
    if (!update_region) {
        UNLOCK_COMPOSITOR(compositor);
        return;
    }
    
    /* 
     * Add the altered region to the region to update at 
     * the next composite 
     */
    FE_UnionRegion(compositor->update_region, update_region, 
                   compositor->update_region);

    FE_DestroyRegion(update_region);

    if (compositor->enabled) {
        if (update_now)
            cl_composite(compositor, TRIM_UPDATE_REGION_CUTOUTS);
        else
            cl_start_compositor_timeouts(compositor);
    }
    UNLOCK_COMPOSITOR(compositor);
}

/* Update the entire contents of a layer and its children. */
void
cl_UpdateLayer(CL_Layer *layer, PRBool update_now)
{
    if (! layer->compositor)
        return;

    /* Update the layer */
    CL_UpdateDocumentRect(layer->compositor, &layer->clipped_bbox, PR_FALSE);

    /* Update the layer's children, but only if there's a possibility
       that some children are not clipped by this layer, otherwise
       we've already updated with the statement above. */
    if (!layer->clip_children) {
        CL_Layer *child;
        for (child = layer->top_child; child; child = child->sib_below) {
            cl_UpdateLayer(child, PR_FALSE);
        }
    }

    if (layer->compositor->enabled) {
        if (update_now)
            cl_composite(layer->compositor, TRIM_UPDATE_REGION_CUTOUTS);
        else
            cl_start_compositor_timeouts(layer->compositor);
    }
}

/*
The decision whether to draw onscreen or offscreen is made once for
every composite cycle using these rules:

 1) If compositor->offscreen_inhibited is set, drawing is always
    done onscreen.
 2) If compositor->offscreen_enabled is set, drawing is always done
    offscreen.
 3) Otherwise, the offscreen mode depends on the drawing-mode flags
    set for all the layers that fall within the update-region.

    A layer is either neutral or biased towards drawing onscreen or
    drawing offscreen, as indicated by the CL_PREFER_DRAW_ONSCREEN and
    CL_PREFER_DRAW_OFFSCREEN flags.

    For rule 3, the set of all layers that falls within the bounding box
    of the update-region is examined to determine the drawing mode:

    3a) If no layer has the CL_PREFER_DRAW_OFFSCREEN flag set, all drawing
	is performed onscreen.
    3b) Otherwise, bottom-up drawing is performed offscreen until reaching
	a layer that has CL_PREFER_DRAW_ONSCREEN set.  Then the offscreen
	damaged area is BLTed onscreen and remaining layers are drawn onscreen.
    
Top-down drawing is handled differently: It always occurs onscreen unless
CL_PREFER_DRAW_OFFSCREEN is set for an individual layer.
*/

static void
cl_setup_offscreen(CL_Compositor *compositor)
{
    if (!compositor->backing_store)
        return;
    
    if (compositor->offscreen_enabled && !compositor->offscreen_inhibited) {
        if (!compositor->offscreen_initialized) {
            int width = compositor->window_size.right-
                compositor->window_size.left;
            int height = compositor->window_size.bottom-
                compositor->window_size.top;
            cl_InitDrawable(compositor->backing_store);
            cl_SetDrawableDimensions(compositor->backing_store, width, height);
            compositor->offscreen_initialized = PR_TRUE;
        }
    } else if (compositor->offscreen_initialized) {
#ifdef THRASH_OFFSCREEN
        cl_RelinquishDrawable(compositor->backing_store);
        compositor->offscreen_initialized = PR_FALSE;
#endif
    }
}

static void
cl_compute_update_region_recurse(CL_Compositor *compositor,
                                 CL_Layer *layer,
                                 FE_Region update_region)
{
    CL_Layer *child;
    int32 x_offset, y_offset;
    FE_Region layer_update_region;
    XP_Rect *win_clipped_bbox;
    XP_Rect prev_win_clipped_bbox;
    
    PRBool visibility_changed, bbox_changed, position_changed;

    /* Find out what properties of the layer have changed since the
       last time compositing occurred. */
    visibility_changed = layer->visible != layer->prev_visible;
    bbox_changed = !XP_EqualRect(&layer->clipped_bbox,
                                 &layer->prev_clipped_bbox);
    x_offset = layer->x_origin - layer->prev_x_origin;
    y_offset = layer->y_origin - layer->prev_y_origin;
    position_changed = (x_offset || y_offset);

    /* If layer bbox changed, call client-defined callback. */
    if (bbox_changed && layer->vtable.bbox_changed_func) {
        XP_Rect bbox_copy;
        XP_CopyRect(&layer->bbox, &bbox_copy);
        (*layer->vtable.bbox_changed_func)(layer, &bbox_copy);
    }

    /* If visibility changed, call client-defined callback. */
    if (visibility_changed && layer->vtable.visibility_changed_func)
        (*layer->vtable.visibility_changed_func)(layer, layer->visible);

    /* If layer origin changed, call client-defined callback. */
    if (position_changed && layer->vtable.position_changed_func)
        (*layer->vtable.position_changed_func)(layer, x_offset, y_offset);

    /* Compute the bounding box, in window coordinates, of the part of
       the layer that isn't clipped by ancestors and which lies within
       the bounding box of the window. */
    win_clipped_bbox = &layer->win_clipped_bbox;
    XP_CopyRect(&layer->clipped_bbox, win_clipped_bbox);
    XP_OffsetRect(win_clipped_bbox,
                  -compositor->x_offset, -compositor->y_offset);
    XP_IntersectRect(win_clipped_bbox, &compositor->window_size,
                     win_clipped_bbox);

    XP_CopyRect(&layer->prev_clipped_bbox, &prev_win_clipped_bbox);
    XP_OffsetRect(&prev_win_clipped_bbox,
                  -compositor->x_offset, -compositor->y_offset);
    XP_IntersectRect(&prev_win_clipped_bbox, &compositor->window_size,
                     &prev_win_clipped_bbox);

    layer_update_region = FE_NULL_REGION;
    if (visibility_changed) {
        if (layer->visible) {
            if (!layer->cutout) {
                layer_update_region = FE_CreateRectRegion(win_clipped_bbox);
                XP_ASSERT(layer_update_region);
            }
        } else {
            layer_update_region = FE_CreateRectRegion(&prev_win_clipped_bbox);
            XP_ASSERT(layer_update_region);
        }

    } else if (layer->visible && (bbox_changed || position_changed)) {
        FE_Region win_clipped_region, prev_win_clipped_region;

        win_clipped_region = FE_CreateRectRegion(win_clipped_bbox);
        prev_win_clipped_region = FE_CreateRectRegion(&prev_win_clipped_bbox);

        if (win_clipped_region && prev_win_clipped_region) {
            layer_update_region = FE_CreateRegion();

            if (layer->cutout)
                FE_SubtractRegion(prev_win_clipped_region,
                                  win_clipped_region,
                                  layer_update_region);
            else if (bbox_changed && !position_changed)
                cl_XorRegion(win_clipped_region,
                             prev_win_clipped_region,
                             layer_update_region);
            else
                FE_UnionRegion(win_clipped_region,
                               prev_win_clipped_region,
                               layer_update_region);
        }
                               
        if (win_clipped_region)
            FE_DestroyRegion(win_clipped_region);
        if (prev_win_clipped_region)
            FE_DestroyRegion(prev_win_clipped_region);
        XP_ASSERT(layer_update_region);
    }
        
    if (layer_update_region != FE_NULL_REGION) {
        FE_UnionRegion(update_region, layer_update_region, update_region);
        FE_DestroyRegion(layer_update_region);
    }
    
    /* Set layer properties for subsequent composite cycle */
    layer->prev_visible = layer->visible;
    XP_CopyRect(&layer->clipped_bbox, &layer->prev_clipped_bbox);
    layer->prev_x_origin = layer->x_origin;
    layer->prev_y_origin = layer->y_origin;

    for (child = layer->top_child; child; child = child->sib_below)
        cl_compute_update_region_recurse(compositor, child, update_region);
}

static void
cl_compute_update_region(CL_Compositor *compositor)
{
    /* We may need to recompute the update region iteratively since
       layer callbacks may result in changes to layer properties, such
       as bbox, visibility and position. */
    while (compositor->recompute_update_region) {
        compositor->recompute_update_region = PR_FALSE;
        cl_compute_update_region_recurse(compositor, compositor->root,
                                         compositor->update_region);
    }
}

/* Pre-drawing layer initialization in front-to-back order.  (This
   could be folded into cl_draw_front_to_back, but then we would need
   to duplicate this functionality when in back_to_front_only mode. */
static int
cl_prepare_to_draw_recurse(CL_Compositor *compositor,
                           CL_Layer *layer, PRBool cutoutp,
                           XP_Rect *update_bbox,
                           PRBool *prefer_draw_offscreen_p)
{
    CL_Layer *child;
    XP_Rect *win_clipped_bbox;
    int descendant_draw_needed = 0;

    /* Debugging assert to make sure that these regions are
       destroyed at the end of the composite cycle */
    XP_ASSERT(layer->draw_region == FE_NULL_REGION);
    
    /* Check if the layer is eligible for compositing.  A layer won't
       be composited if it is hidden, or it is entirely clipped by
       ancestors. */
    if (! layer->visible) {
        layer->draw_needed = FALSE;
        if (!layer->descendant_visible || layer->cutout) {
            layer->descendant_draw_needed = FALSE;
            return FALSE;
        }
    } else {
        layer->draw_needed = (layer->vtable.painter_func != NULL);
    }

    /* Compute the bounding box, in window coordinates, of the part of
       the layer that isn't clipped by ancestors and which lies within
       the bounding box of the update region. */
    win_clipped_bbox = &layer->win_clipped_bbox;
    XP_CopyRect(&layer->clipped_bbox, win_clipped_bbox);
    XP_OffsetRect(win_clipped_bbox,
                  -compositor->x_offset, -compositor->y_offset);
    XP_IntersectRect(win_clipped_bbox, update_bbox, win_clipped_bbox);

    if (XP_IsEmptyRect(win_clipped_bbox)) {
        layer->draw_needed = PR_FALSE;
        if (!layer->descendant_visible || layer->cutout) {
            layer->descendant_draw_needed = PR_FALSE;
            return FALSE;
        }
    }

    /* If this is a cutout layer, add the layer's area to the
       off-limits drawing region for the compositor */
    if (layer->cutout) {
        if (cutoutp) {
            FE_Region win_clipped_region = FE_CreateRectRegion(win_clipped_bbox);
            
            if (! win_clipped_region)	/* OOM check */
                return 0;
            
            FE_UnionRegion(compositor->cutout_region,
                           win_clipped_region,
                           compositor->cutout_region);
            FE_DestroyRegion(win_clipped_region);
        }

        /* Cutout-layers can't have children and don't draw themselves. */
        layer->draw_needed = PR_FALSE;
        layer->descendant_draw_needed = PR_FALSE;
        return 0;
    }

    /* Recurse for children.
       First get the children to draw in front to back order. */
    for (child = layer->top_child; child; child = child->sib_below) {
        descendant_draw_needed |=
            cl_prepare_to_draw_recurse(compositor, child, cutoutp, update_bbox,
                                       prefer_draw_offscreen_p);
    }
    layer->descendant_draw_needed = (PRBool)descendant_draw_needed;

    /* If the layer must draw offscreen then do the entire composite
       offscreen. */
    if ((layer->draw_needed || descendant_draw_needed) && layer->prefer_draw_offscreen)
        *prefer_draw_offscreen_p = PR_TRUE;


    return (int)layer->draw_needed | descendant_draw_needed;
}

static void
cl_prepare_to_draw(CL_Compositor *compositor, FE_Region update_region, 
                   PRBool cutoutp, PRBool *prefer_draw_offscreen_p)
{
    XP_Rect update_bbox;

    compositor->cutout_region = FE_CreateRegion();
    if (!compositor->cutout_region) /* OOM */
	return;

    FE_GetRegionBoundingBox(update_region, &update_bbox);

    cl_prepare_to_draw_recurse(compositor, compositor->root, cutoutp,
                               &update_bbox, prefer_draw_offscreen_p);
    
    if (cutoutp)
        FE_SubtractRegion(update_region, compositor->cutout_region,
                          update_region);
    FE_DestroyRegion(compositor->cutout_region);
}

/* The front-to-back drawing phase
   Yech!  Let's get rid of all these arguments. */
static PRBool
cl_draw_front_to_back_recurse(CL_Compositor *compositor,
                              CL_Layer *layer,
                              CL_Layer **top_undrawn_layerp,
                              CL_Layer **bottom_undrawn_layerp,
                              CL_Drawable *backing_store,
                              PRBool *offscreen_layer_drawn_above,
                              FE_Region update_region,
                              FE_Region transparent_above,
                              FE_Region unobscured_by_opaque,
                              FE_Region offscreen_region)
{
    FE_Region overlap;
    CL_Layer *child;
    PRBool layer_is_unobscured_by_transparent_layers, done;
    CL_Drawable *drawable;
    int32 old_x_origin, old_y_origin;

    if (layer->descendant_draw_needed) {

        /* First get the children to draw in front to back order. */
        done = PR_FALSE;
        for (child = layer->top_child; child && !done; child = child->sib_below) {
            done = cl_draw_front_to_back_recurse(compositor, child,
                                                 top_undrawn_layerp,
                                                 bottom_undrawn_layerp,
                                                 backing_store,
                                                 offscreen_layer_drawn_above,
                                                 update_region,
                                                 transparent_above,
                                                 unobscured_by_opaque,
                                                 offscreen_region);
        }

        /* Have we reached the last layer in the front-to-back pass ? */
        if (done)
            return PR_TRUE;
    }

    if (!layer->draw_needed)
        return PR_FALSE;

    /* Compute the layer's clipping region for drawing. */
    layer->draw_region = FE_CreateRectRegion(&layer->win_clipped_bbox);
    FE_IntersectRegion(unobscured_by_opaque, 
                       layer->draw_region, 
                       layer->draw_region);

    /* If the layer is wholly obscured by opaque layers in front of it,
       there's nothing to draw. */
    if (FE_IsEmptyRegion(layer->draw_region)) {
        layer->draw_needed = PR_FALSE;
        if (layer->clip_children) {
            layer->descendant_draw_needed = PR_FALSE;
        }
        FE_DestroyRegion(layer->draw_region);
        layer->draw_region = FE_NULL_REGION;
        return PR_FALSE;
    }

    if (layer->opaque) {

        overlap = FE_CreateRegion();
        FE_IntersectRegion(transparent_above, layer->draw_region, overlap);
        layer_is_unobscured_by_transparent_layers = FE_IsEmptyRegion(overlap);
        FE_DestroyRegion(overlap);

        /* Are there any non-opaque layers which must draw on top of this
           layer ? */
        if (layer_is_unobscured_by_transparent_layers) {
            
            /* Figure out if we're drawing to the backing store or not */
            if (layer->prefer_draw_onscreen || !backing_store) {
                drawable = compositor->primary_drawable;
            } else {
                drawable = backing_store;
                FE_UnionRegion(layer->draw_region, 
                               offscreen_region, 
                               offscreen_region);
            }

            /* Set the drawing origin and clip for this layer. */
            cl_GetDrawableOrigin(drawable, &old_x_origin, &old_y_origin);
            cl_SetDrawableOrigin(drawable, layer->x_origin, layer->y_origin);
            cl_SetDrawableClip(drawable, layer->draw_region);
 
            /* Draw the layer's contents */
            (*layer->vtable.painter_func)(drawable,
                                          layer, layer->draw_region);

            cl_SetDrawableOrigin(drawable, old_x_origin, old_y_origin);
            
            /* No other layers either above or below this one need to
               update the screen area occupied by this layer.  Any
               layers are either opaque overlapping layers that have
               already been drawn in the front-to-back phase, or they
               are below this layer and will therefore be obscured by it. */
            FE_SubtractRegion(update_region,
                              layer->draw_region, update_region);

            /* We don't need to draw this layer in the back-to-front phase,
               since we've already drawn it. */
            layer->draw_needed = PR_FALSE;
        } else {

            /* This is the bottommost layer that needs drawing, and which
               has not yet been drawn */
            *bottom_undrawn_layerp = layer;

            if (! *top_undrawn_layerp)
                *top_undrawn_layerp = layer;

            layer->offscreen_layer_drawn_above = *offscreen_layer_drawn_above;
            *offscreen_layer_drawn_above |= (int)layer->prefer_draw_offscreen;
        }

        FE_SubtractRegion(unobscured_by_opaque, layer->draw_region,
                          unobscured_by_opaque);

        /* If we've already drawn the layer, get rid of the draw region */
        if (layer_is_unobscured_by_transparent_layers) {
            FE_DestroyRegion(layer->draw_region);
            layer->draw_region = FE_NULL_REGION;
        }
        
        /* When the entire remaining update region is obscured by
           opaque layers above it, there's nothing left to do. */
        if (FE_IsEmptyRegion(unobscured_by_opaque)) {
            return PR_TRUE;
        }
    } else {
        FE_UnionRegion(transparent_above, layer->draw_region, 
                       transparent_above);
        *bottom_undrawn_layerp = layer;
        if (! *top_undrawn_layerp)
            *top_undrawn_layerp = layer;
        layer->offscreen_layer_drawn_above = *offscreen_layer_drawn_above;
        *offscreen_layer_drawn_above |= (int)layer->prefer_draw_offscreen;
    }

    return PR_FALSE;
}

/* The front-to-back drawing phase */
static void
cl_draw_front_to_back(CL_Compositor *compositor,
                      CL_Layer **top_undrawn_layerp,
                      CL_Layer **bottom_undrawn_layerp,
                      CL_Drawable *backing_store,
                      FE_Region update_region,
                      FE_Region offscreen_region)
{ 
    FE_Region transparent_above, unobscured_by_opaque;   
    PRBool offscreen_layer_drawn_above = PR_FALSE;

    transparent_above = FE_CreateRegion();
    if (! transparent_above) {
        XP_ASSERT(0);      /* OOM */
        return;
    }
         
    unobscured_by_opaque = FE_CopyRegion(update_region, NULL);
    if (! unobscured_by_opaque) {
        XP_ASSERT(0);      /* OOM */
        FE_DestroyRegion(transparent_above);
        return;
    }
    cl_draw_front_to_back_recurse(compositor, compositor->root,
                                  top_undrawn_layerp, bottom_undrawn_layerp,
                                  backing_store,
                                  &offscreen_layer_drawn_above,
                                  update_region,
                                  transparent_above,
                                  unobscured_by_opaque,
                                  offscreen_region);
    FE_DestroyRegion(transparent_above);
    FE_DestroyRegion(unobscured_by_opaque);
}

typedef struct cl_back_to_front_state 
{
    CL_Compositor *compositor;
    CL_Layer *top_undrawn_layer;
    CL_Layer *bottom_undrawn_layer;
    PRBool *onscreen_layer_drawn_below;
    FE_Region update_region;
    CL_Drawable *backing_store;
    FE_Region offscreen_region;
} cl_back_to_front_state;

/* The back-to-front drawing phase */
static CL_BackToFrontStatus
cl_draw_back_to_front_recurse(CL_Layer *layer,
                              cl_back_to_front_state *s,
                              CL_BackToFrontStatus status)
{
    CL_Layer *child;
    CL_Drawable *drawable;
    int32 old_x_origin, old_y_origin;
    PRBool *onscreen_layer_drawn_below = s->onscreen_layer_drawn_below;
    CL_Drawable *backing_store = s->backing_store;
    CL_Compositor *compositor = s->compositor;

    /* Only draw if a draw is necessary and we've already reached
     * the bottommost layer that needs to be drawn.
     */
    if (layer->draw_needed && ((status == CL_REACHED_BOTTOM_UNDRAWN) || 
                               (layer == s->bottom_undrawn_layer))) {

        /* Clip drawing to the bounds of itself and any ancestors */
        if (!layer->draw_region) {
            layer->draw_region = FE_CreateRectRegion(&layer->win_clipped_bbox);
            FE_IntersectRegion(s->update_region, layer->draw_region, 
                               layer->draw_region);
        }

        /* If any layers beneath this one were drawn onscreen, then this
           layer needs to be drawn there also. */
        if (!backing_store || *onscreen_layer_drawn_below) {
            drawable = compositor->primary_drawable;
            *onscreen_layer_drawn_below = PR_TRUE;
        } else {
            FE_Region offscreen_region = s->offscreen_region;
            
            /* If this layer and layers above this one are drawn
               onscreen, but all the layers beneath were drawn
               offscreen, we need to copy what we've painted so far
               from the backing store to the primary drawable. */
            if (layer->prefer_draw_onscreen &&
                !layer->offscreen_layer_drawn_above &&
                !*onscreen_layer_drawn_below) {
                
                /* The clip could have been modified during the
                   front-to-back phase, so clear it. */
                cl_SetDrawableClip(compositor->primary_drawable, NULL);
                cl_CopyPixels(backing_store, compositor->primary_drawable,
                              offscreen_region);
                FE_CLEAR_REGION(offscreen_region);
                drawable = compositor->primary_drawable;
                *onscreen_layer_drawn_below = PR_TRUE;
            } else {
                drawable = backing_store;
                FE_UnionRegion(offscreen_region, layer->draw_region, 
                               offscreen_region);
            }
        }

        /* Set the drawing origin and clip for this layer. */
        cl_GetDrawableOrigin(drawable, &old_x_origin, &old_y_origin);
        cl_SetDrawableOrigin(drawable, layer->x_origin, layer->y_origin);
        cl_SetDrawableClip(drawable, layer->draw_region);

        /* Draw the layer */
        (*layer->vtable.painter_func)(drawable, layer, layer->draw_region);

        cl_SetDrawableOrigin(drawable, old_x_origin, old_y_origin);

        FE_DestroyRegion(layer->draw_region);
        layer->draw_region = FE_NULL_REGION;

        /* If this layer is uniformly-colored, push it on the
           front-to-back stack of uniformly-colored layers. */
        if (layer->uniform_color) {
            XP_ASSERT(layer->opaque);
            layer->uniformly_colored_layer_below = 
                compositor->uniformly_colored_layer_stack;
            compositor->uniformly_colored_layer_stack = layer;
        }
        
        /* If we've reached the last layer that couldn't draw in the
           front to back phase, it's time to stop.              */
        if (layer == s->top_undrawn_layer)
            return CL_REACHED_TOP_UNDRAWN;

        /* If we've reached the bottommost (first) layer that needs to be
         * drawn, the rest of the layers can start drawing. */
        if (layer == s->bottom_undrawn_layer)
            status = CL_REACHED_BOTTOM_UNDRAWN;
    }

    if (layer->descendant_draw_needed) {
        
        /* Get the children to draw in back to front order. 
           Stop if we've reached the last layer to draw.      */
        for (child = layer->bottom_child; 
             child && (status != CL_REACHED_TOP_UNDRAWN) ; 
             child = child->sib_above) {
            status = cl_draw_back_to_front_recurse(child, s, status);
        }
        return status;
    }
    
    return status;
}

/* The back-to-front drawing phase */
static void
cl_draw_back_to_front(CL_Compositor *compositor, 
                      CL_Layer *top_undrawn_layer,
                      CL_Layer *bottom_undrawn_layer,
					  FE_Region update_region,
                      CL_Drawable *backing_store,
                      FE_Region offscreen_region)
{
    PRBool onscreen_layer_drawn_below = PR_FALSE;
    
    cl_back_to_front_state state;
    
    state.compositor = compositor;
    state.top_undrawn_layer = top_undrawn_layer;
    state.bottom_undrawn_layer = bottom_undrawn_layer;
    state.onscreen_layer_drawn_below = &onscreen_layer_drawn_below;
    state.update_region = update_region;
    state.backing_store = backing_store;
    state.offscreen_region = offscreen_region;

    if (compositor->back_to_front_only)
    cl_draw_back_to_front_recurse(compositor->root, &state, 
                                  CL_REACHED_BOTTOM_UNDRAWN);
    else
        cl_draw_back_to_front_recurse(compositor->root, &state, CL_REACHED_NOTHING);
}
    

/* Redraws all regions changed since last call to cl_composite().
 * This is called by the timer callback. It can be also be directly
 * called to force a synchronous composite.
 */
static PRBool
cl_composite(CL_Compositor *compositor, PRBool cutoutp)
{
    CL_Layer *top_undrawn_layer, *bottom_undrawn_layer;
    CL_Drawable *backing_store;
    FE_Region offscreen_region=0, update_region;
    PRBool save_offscreen_enabled=PR_FALSE, prefer_draw_offscreen = PR_FALSE;
    
    XP_ASSERT(compositor);

    if (!compositor || !compositor->root)
        return PR_FALSE;

    LOCK_COMPOSITOR(compositor);

    /* Check if there's a region to be drawn... */
    cl_compute_update_region(compositor);
    if (FE_IsEmptyRegion(compositor->update_region)) {
        UNLOCK_COMPOSITOR(compositor);
        return PR_FALSE;
    }

    {
        XP_Rect bbox;
        FE_GetRegionBoundingBox(compositor->update_region, &bbox);
        CL_TRACE(0, ("Compositing rectangle [%d, %d, %d, %d]",
                     bbox.left, bbox.top, bbox.right, bbox.bottom));
    }

    XP_ASSERT(!compositor->composite_in_progress);
    compositor->composite_in_progress = PR_TRUE;

     /* 
      * Copy the current update region and then clear it out. The layer
      * painter funcs might actually perform operations that add to the
      * update regions and we don't want to lose those updates.
      */
     update_region = FE_CopyRegion(compositor->update_region, NULL);
     FE_CLEAR_REGION(compositor->update_region);

     /* Initialize temporary per-layer drawing variables.  Also determine
        whether this composite must be done offscreen. */
     cl_prepare_to_draw(compositor, update_region, cutoutp,
                        &prefer_draw_offscreen);

     if (prefer_draw_offscreen) {
         save_offscreen_enabled = compositor->offscreen_enabled;
         compositor->offscreen_enabled = PR_TRUE;
     }
     cl_setup_offscreen(compositor);

     /* Determine whether or not we'll use a backing store */
     if (compositor->offscreen_inhibited || !compositor->offscreen_enabled ||
         !compositor->backing_store) {
         /* FIXME - what happens if we've disabled the backing store,
            but some layers have prefer_draw_offscreen set ? */
         backing_store = NULL;
         offscreen_region = FE_NULL_REGION;
         
         /* Invalidate the parts of the backing store for which there are
            update requests. */
         FE_SubtractRegion(compositor->backing_store_region,
                           compositor->update_region,
                           compositor->backing_store_region);
     } else {
         backing_store = cl_LockDrawableForReadWrite(compositor->backing_store);
         if (!backing_store) {
             /* Couldn't read the backing store.  Invalidate its contents. */
             FE_CLEAR_REGION(compositor->backing_store_region);
             backing_store = cl_LockDrawableForWrite(compositor->backing_store);
         }

         /* Assume that there is a painter for every area of the drawable to 
            be updated. */
         if (backing_store)
             offscreen_region = FE_CreateRegion();
         
     }

     compositor->uniformly_colored_layer_stack = NULL;
     top_undrawn_layer = bottom_undrawn_layer = NULL;

     /* Front-to-back phase */
     if (!compositor->back_to_front_only) {
         cl_draw_front_to_back(compositor,
                               &top_undrawn_layer, &bottom_undrawn_layer,
                               backing_store,
                               update_region,
                               offscreen_region);
     }

     /* Back-to-front phase */
     if (top_undrawn_layer || compositor->back_to_front_only) {
         cl_draw_back_to_front(compositor,
                               top_undrawn_layer,
                               bottom_undrawn_layer,
                               update_region,
                               backing_store,
                               offscreen_region);
     }
    
     /* Transfer the composited pixels from the backing store to
        the onscreen drawable. */
	 cl_SetDrawableClip(compositor->primary_drawable, NULL);
     if (backing_store) {
         if (!FE_IsEmptyRegion(offscreen_region)) {
             /* The clip could have been modified during the
                front-to-back phase, so clear it. */
             cl_CopyPixels(backing_store, compositor->primary_drawable,
                           offscreen_region);
         }

         cl_UnlockDrawable(backing_store);

         FE_DestroyRegion(offscreen_region);
     }

     /* If this composite was forced offscreen, restore the previous state */
     if (prefer_draw_offscreen)
         compositor->offscreen_enabled = save_offscreen_enabled;

     /* Get rid of the temporary copy */
     FE_DestroyRegion(update_region);

     /* Set the clip region back to its old value */
     cl_RestoreDrawableClip(compositor->primary_drawable);
     
     compositor->composite_in_progress = PR_FALSE;
     
     UNLOCK_COMPOSITOR(compositor);

	 return PR_TRUE;
}

/* External API */
void
CL_CompositeNow(CL_Compositor *compositor)
{
    cl_composite(compositor, TRIM_UPDATE_REGION_CUTOUTS);
}

/* 
 * Indicates that the window the compositor is drawing to has been resized.
 * We make the assumption that any refreshing will be carried out by 
 * calling CL_RefreshWindowRect.
 */
void 
CL_ResizeCompositorWindow(CL_Compositor *compositor, int32 width, int32 height)
{
    XP_ASSERT(compositor);
  
    if (!compositor)
        return;
    
    LOCK_COMPOSITOR(compositor);

    compositor->window_size.right = compositor->window_size.left + width;
    compositor->window_size.bottom = compositor->window_size.top + height;

    if (compositor->window_region) 
        FE_DestroyRegion(compositor->window_region);

    compositor->window_region = FE_CreateRectRegion(&compositor->window_size);

    if (compositor->offscreen_initialized && compositor->backing_store)
        cl_SetDrawableDimensions(compositor->backing_store, width, height);

    UNLOCK_COMPOSITOR(compositor);
}

/* 
 * Called when the compositor's window scrolls. We make the assumption 
 * that any refreshing will be carried out by calling CL_RefreshWindowRect
 */
void 
CL_ScrollCompositorWindow(CL_Compositor *compositor, 
                          int32 x_origin, int32 y_origin)
{
    int32 delta_x, delta_y;
    
    XP_ASSERT(compositor);
    
    if (!compositor)
        return;
    
    LOCK_COMPOSITOR(compositor);

#if defined(XP_WIN) && _MSC_VER == 1100
    /* Hack to avoid optimizer bug in VC++ v5 */
    delta_x = (compositor->x_offset != x_origin ) ||
              (compositor->y_offset != y_origin );
    if ( delta_x ) {
#else
    if ((compositor->x_offset != x_origin) ||
        (compositor->y_offset != y_origin)) {
#endif
        
        /* Invalidate backing store.
           For better performance, perhaps we should be scrolling it, instead */
        FE_CLEAR_REGION(compositor->backing_store_region);

        delta_x = compositor->x_offset - x_origin;
        delta_y = compositor->y_offset - y_origin;

        /* Are we scrolling a small enough amount that some part of
           the current screen contents may remain onscreen ? */
        if ((ABS(delta_x) < FE_MAX_REGION_COORDINATE) &&
            (ABS(delta_y) < FE_MAX_REGION_COORDINATE)) {
            FE_Region update_region = compositor->update_region;
            FE_Region window_region = compositor->window_region;
            
            /* Scroll compositor update region */
            FE_OffsetRegion(update_region, delta_x, delta_y);
            
            FE_IntersectRegion(window_region, update_region, update_region);
        }

        compositor->x_offset = x_origin;
        compositor->y_offset = y_origin;
        
    }
    UNLOCK_COMPOSITOR(compositor);
}

/* Sets whether a compositor offscreen-drawing should occur or not */
/* If enabled is PR_TRUE, the compositor will use offscreen        */
/* drawing if applicable.                                          */
void
CL_SetCompositorOffscreenDrawing(CL_Compositor *compositor, 
                                 CL_OffscreenMode mode)
{
    XP_ASSERT(compositor);
    if (!compositor)
        return;

    switch (mode) {
    case CL_OFFSCREEN_ENABLED:
        compositor->offscreen_enabled = PR_TRUE;
        compositor->offscreen_inhibited = PR_FALSE;
        break;
    case CL_OFFSCREEN_DISABLED:
        compositor->offscreen_inhibited = PR_TRUE;
        break;
    case CL_OFFSCREEN_AUTO:
        compositor->offscreen_enabled = PR_FALSE;
        compositor->offscreen_inhibited = PR_FALSE;
        break;
    }
}

CL_OffscreenMode
CL_GetCompositorOffscreenDrawing(CL_Compositor *compositor)
{
    PRBool enabled;
    XP_ASSERT(compositor);
    if (! compositor)
        return PR_FALSE;

    if (compositor->offscreen_inhibited)
        return CL_OFFSCREEN_DISABLED;

    enabled = compositor->offscreen_enabled;
    return enabled ? CL_OFFSCREEN_ENABLED : CL_OFFSCREEN_AUTO;
}

/* Frame-rate excursion limits */
#define MAX_FRAME_PERIOD      (1000000.0F / CL_FRAME_RATE_MIN)
#define MIN_FRAME_PERIOD      (1000000.0F / CL_FRAME_RATE_MAX)
#define INITIAL_FRAME_PERIOD  (1000000.0F / CL_FRAME_RATE_INITIAL)

/* Timeout callback for time-based compositing.

   Adaptively adjust the frame-rate at which compositing is done based
   on the CPU load.  (CPU load is estimated by measuring the ability
   of recent composite timeouts to meet their deadlines.)
 */
static void
cl_compositor_callback(void *closure)
{
#ifdef CL_ADAPT_FRAME_RATE
    unsigned int i, index, filter_length, delay_line_index;
    int64 slack64, frame_period64, delay64, nominal_deadline64, now64;
    int32 slack, delay, slip, clamped_slack;
    double earliness, smoothed_slack, frame_period, adjust;
    
    CL_Compositor *compositor = (CL_Compositor *)closure;
    frame_period = compositor->frame_period;

	XP_ASSERT(compositor->frame_period != 0); /* should never be here in static case */
	if (compositor->frame_period == 0)
		return; /* to avoid divide by zero, since fundamentally should never be here */

    /* First, do the actual drawing work */
    if (cl_composite(compositor, TRIM_UPDATE_REGION_CUTOUTS) == PR_FALSE) {

		compositor->nothing_to_do_count++;

		if (compositor->nothing_to_do_count > 5) {

			/*
			 *    Clear the timeout, because we are in the closure
			 *    function, and we don't want another dangling timer.
			 */
			compositor->composite_timeout = NULL; /* VERY IMPORTANT */
			compositor->nothing_to_do_count = 0;
			return;
		}
	} else {
		compositor->nothing_to_do_count = 0;
	}

    /* Compute the "slack", which indicates how late (or early) the
       compositor was meeting its deadline.  Positive slack numbers
       indicate earliness.  Negative numbers lateness. */
    now64 = PR_Now();
    nominal_deadline64 = compositor->nominal_deadline64;
    LL_SUB(slack64, nominal_deadline64, now64);
    LL_L2I(slack, slack64);

    slack -= (int32)((1.0 - CL_CPU_LOAD) * frame_period);

    /* Clamp slack to maximum permitted amount of frame-period adjustment. */
    slip = 0;
    if (frame_period - slack > MAX_FRAME_PERIOD) {
        clamped_slack = (int32)(frame_period - MAX_FRAME_PERIOD);
        slip = clamped_slack - slack;
    }

    /* Store latest slack value in the delay line, a circular array
       that keeps track of the most recent slack values. */
    delay_line_index = compositor->delay_line_index;
    if (delay_line_index == DELAY_LINE_LENGTH) {
        /* First time we're running the timeout.  Initialize the delay line */
        for (i = 0; i < DELAY_LINE_LENGTH; i++)
            compositor->delay_line[i] = slack;
    } else {
        /* Push most recent slack value into circular delay line */
        compositor->delay_line[delay_line_index] = slack;
    }

    /* Compute the number of filter taps corresponding to the
       requested duration of the filter, assuming that the
       compositor's frame-rate changes slowly. */

	/* This should never happen, since we check for this up top. But just in case */	
	/* something changes in the future in the above code, this is here */	
	/* just to make sure we find a potential divide by zero quickly, and don't do it. 	
	XP_ASSERT(frame_period != 0); 

	if(frame_period == 0)
        filter_length = DELAY_LINE_LENGTH;
	else
	*/
	
	filter_length = (unsigned int) ((SLOW_FILTER_DURATION * 1000) / frame_period);

    if (filter_length > DELAY_LINE_LENGTH)
        filter_length = DELAY_LINE_LENGTH;
    if (filter_length < 4)
        filter_length = 4;

    /* Average the last <filter_length> values in the circular delay
       line to create a smoothed slow-response filtering of the slack
       values.  Create a "fast-response" filter that uses half as many
       elements as the "slow" one. */
    smoothed_slack = 0;
    earliness = 0;
    for (i = 0; i < filter_length; i++) {
        int32 slack_val;

        index = delay_line_index - i;
        index &= (DELAY_LINE_LENGTH - 1);
        slack_val = compositor->delay_line[index];
        smoothed_slack += slack_val;
        if (slack_val > 0)
            earliness += slack_val;
    }
    smoothed_slack /= filter_length;
    earliness /= filter_length;

    /* Update next index in circular delay-line */
    delay_line_index++;
    delay_line_index &= (DELAY_LINE_LENGTH - 1);
    compositor->delay_line_index = delay_line_index;

    adjust = earliness + ((smoothed_slack > 0) ? 0 : smoothed_slack * 0.35);

    /* Adjust the frame rate, but don't allow the frame rate to excur
       outside its preset limits. */
    if (adjust < 0) {
        /* Slack is decreasing, Let's decrease the CPU load by
           decreasing the frame rate. */
        frame_period -= 0.10 * adjust;
        if (frame_period > MAX_FRAME_PERIOD)
            frame_period = MAX_FRAME_PERIOD;
    } else {
        /* Slack is negative and decreasing.  (Really, we're
           finishing earlier and getting more so) ; Increase
           frame-rate to make use of available CPU. */
        frame_period -= 0.20 * adjust;
        if (frame_period < MIN_FRAME_PERIOD)
            frame_period = MIN_FRAME_PERIOD;
    }
    compositor->frame_period = (float)frame_period;

    /* Compute delay from now until start of next composite timeout */
    LL_SUB(delay64, nominal_deadline64, now64);
    LL_L2I(delay, delay64);
    delay = (delay + 500) / 1000; /* Convert from microseconds to ms */

    /* Compute nominal completion time for next composite timeout */
    LL_I2L(frame_period64, (int32)frame_period + slip);
    LL_ADD(nominal_deadline64, nominal_deadline64, frame_period64);
    compositor->nominal_deadline64 = nominal_deadline64;

    /* Impose minimum delay to prevent timeouts from being scheduled
       back-to-back */
    if (delay < CL_MIN_TIMEOUT)
        delay = CL_MIN_TIMEOUT;

    /* Schedule the next compositor timeout. */
    compositor->composite_timeout = 
        FE_SetTimeout((TimeoutCallbackFunction)cl_compositor_callback,
                      (void *)compositor,
                      (uint32)delay);

#else  /* !CL_ADAPT_FRAME_RATE */
    CL_Compositor *compositor = (CL_Compositor *)closure;
    cl_composite(compositor, TRIM_UPDATE_REGION_CUTOUTS);
    compositor->composite_timeout = 
        FE_SetTimeout((TimeoutCallbackFunction)cl_compositor_callback,
                      (void *)compositor, 0);
#endif /* CL_ADAPT_FRAME_RATE */
}

PRBool 
CL_GetCompositorEnabled(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return PR_FALSE;
    
    return compositor->enabled;
}


void
cl_start_compositor_timeouts(CL_Compositor *compositor)
{
    int64 frame_period64, now64;

	XP_ASSERT(compositor);
	if (compositor == NULL)
		return;

	if (compositor->frame_period == 0) /* indicates static case, no timeouts... */
		return;

    /* No need to start timeouts if already started */
    if (compositor->composite_timeout)
        return;

    if (!compositor->enabled)
        return;

    /* Compute nominal time for next composite timeout */
    now64 = PR_Now();
    LL_I2L(frame_period64, (int32)compositor->frame_period);
    LL_ADD(compositor->nominal_deadline64, now64, frame_period64);

    /* Special value indicates empty delay line */
    compositor->delay_line_index = DELAY_LINE_LENGTH;
    compositor->composite_timeout = 
        FE_SetTimeout((TimeoutCallbackFunction)cl_compositor_callback,
                      (void *)compositor, 0);
}


/* Sets a compositor's enabled state. Disabling a compositor will  */
/* stop timed composites (but will not prevent explicit composites */
/* from happening). Enabling a compositor restarts timed draws.    */
void 
CL_SetCompositorEnabled(CL_Compositor *compositor, PRBool enabled)
{
    XP_ASSERT(compositor);

    if (!compositor)
        return;
    
    CL_TRACE(0, ("Setting compositor enabled state to %d ", enabled));

    if (enabled && (compositor->enabled == PR_FALSE) && compositor->frame_period) {
        cl_composite(compositor, TRIM_UPDATE_REGION_CUTOUTS);
        
        CL_TRACE(0, ("Starting compositor timeout"));
        cl_start_compositor_timeouts(compositor);
    }
    else if (!enabled && (compositor->enabled == PR_TRUE)) {
        if (compositor->composite_timeout)
            FE_ClearTimeout(compositor->composite_timeout);
        compositor->composite_timeout = NULL;
        if (compositor->offscreen_initialized) {
            cl_RelinquishDrawable(compositor->backing_store);
            compositor->offscreen_initialized = PR_FALSE;
        }
    }
	
    compositor->enabled = enabled;
}
    
CL_Drawable *
CL_GetCompositorDrawable(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return NULL;
    
    return compositor->primary_drawable;
}

void 
CL_SetCompositorDrawable(CL_Compositor *compositor, 
                         CL_Drawable *drawable)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return;
    
    compositor->primary_drawable = drawable;
    drawable->compositor = compositor;
}

CL_Layer *
CL_GetCompositorRoot(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return NULL;

    return compositor->root;
}

/* Set a layer as the root of the layer tree */
void 
CL_SetCompositorRoot(CL_Compositor *compositor, CL_Layer *root)
{
    XP_ASSERT(compositor);

    if (!compositor)
        return;

    CL_TRACE(0,("Setting root of compositor %x to layer %x", compositor, root));

    LOCK_COMPOSITOR(compositor);
    
    compositor->root = root;

    if (root) {
        cl_SetCompositorRecursive(root, compositor);
        
        cl_LayerAdded(compositor, root);
    }
    
    UNLOCK_COMPOSITOR(compositor);
}

uint32 
CL_GetCompositorFrameRate(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return 0;

    if (compositor->frame_period == 0)
        return 0;
    else
        return (uint32)((1000000.0 / compositor->frame_period) + 0.5);
}

void 
CL_SetCompositorFrameRate(CL_Compositor *compositor, uint32 frame_rate)
{
    XP_ASSERT(compositor);
  
    if (!compositor)
        return;

    LOCK_COMPOSITOR(compositor);

    if (frame_rate)
        compositor->frame_period = 1000000.0F / frame_rate;
    else
        compositor->frame_period = 0.0F;

    /* BUGBUG This is probably the wrong behavior. If we had better */
    /* timing services, we could do better.                         */
    if (compositor->enabled && compositor->frame_period) {
        if (compositor->composite_timeout) {
            FE_ClearTimeout(compositor->composite_timeout);
            compositor->composite_timeout = NULL;
        }
	
        cl_start_compositor_timeouts(compositor);
    }

    UNLOCK_COMPOSITOR(compositor);
}
    
int32 
CL_GetCompositorXOffset(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return 0;

    return compositor->x_offset;
}

int32 
CL_GetCompositorYOffset(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return 0;

    return compositor->y_offset;
}

void CL_GetCompositorWindowSize(CL_Compositor *compositor,
                                XP_Rect *window_size)
{
    XP_ASSERT(compositor);
    XP_ASSERT(window_size);
    
    if (!compositor || !window_size)
        return;

    LOCK_COMPOSITOR(compositor);
    *window_size = compositor->window_size;
    UNLOCK_COMPOSITOR(compositor);
}

static PRBool
cl_build_containment_list(CL_Compositor *compositor, CL_Layer *layer,
                          int32 which_list)
{
    int32 index = 0;
    CL_EventContainmentList *list = 
        &compositor->event_containment_lists[which_list];

    while (layer) {
        /* 
         * XXX We're assuming here that we don't have to do anything
         * special for Win16, since the depth of the layer tree will
         * hopefully not be > 8k (i.e. 32K segment boundary/4 bytes 
         * per pointer). Watch me eat my words one day.
         */
        if (index >= list->list_size) {
            list->list_size += CL_CONTAINMENT_LIST_INCREMENT;
            list->layer_list = XP_REALLOC(list->layer_list, list->list_size);
            if (list->layer_list == NULL)
                return PR_FALSE;
        }

        list->layer_list[index++] = layer;
        layer = CL_GetLayerParent(layer);
    } 

    list->list_head = index-1;

    return PR_TRUE;
}

/*
 * This function checks for a change in either mouse or keyboard focus.
 * For mouse events, the focus has changed if the last layer to get
 * mouse events is different from the current one. In that case, we
 * build a containment list for the two layers. A containment list is
 * just a glorified name for a list consisting of the path from the
 * root of the layer tree down to the layer. Consider the following
 * example where layer C is the old mouse grabber and layer E the new
 * one.
 *                           root
 *                            /\
 *                           /  ...
 *                          A
 *                          |
 *                          B 
 *                         / \
 *                        C   D
 *                            |
 *                            E
 *            
 * In this case the containment list for layer C is root-A-B-C and the
 * containment list for layer E is root-A-B-D-E. We trace down both 
 * lists till we reach a point where there is a divergence. In this 
 * example, this point is below layer B. For the sub-tree rooted at B
 * that holds the old mouse grabber (layer C) we send MOUSE_LEAVE events
 * and for the sub-tree that holds the new mouse grabber (layer E) we
 * send MOUSE_ENTER events. This ensures that MOUSE_LEAVE and MOUSE_ENTER
 * events are sent for to all layers for which leaving and entering has
 * happened.
 */
static void
cl_check_focus_change(CL_Compositor *compositor, CL_Layer *new_grabber, 
                      CL_Event *mouse_event)
{    
    PRBool mouse_enter_occured = PR_FALSE;

    if (new_grabber != compositor->last_mouse_event_grabber) {
        CL_Event crossing_event;
        CL_Layer *last_grabber = compositor->last_mouse_event_grabber;
        int32 old_list_num = compositor->last_containment_list;
        int32 new_list_num = CL_NEXT_CONTAINMENT_LIST(old_list_num);
        int32 old_list_index, new_list_index;
        CL_EventContainmentList *old_list, *new_list;

        old_list = &compositor->event_containment_lists[old_list_num];
        new_list = &compositor->event_containment_lists[new_list_num];
        
        /* Build the containment list for the new mouse grabber */
        if (!cl_build_containment_list(compositor, 
                                       new_grabber, 
                                       new_list_num))
            return;
        
        old_list_index = old_list->list_head;
        new_list_index = new_list->list_head;

        /* 
         * Starting from the root, we trace downward in the tree, until
         * we find a divergence in the path.
         */
        while ((old_list_index >= 0) && (new_list_index >= 0) &&
               (old_list->layer_list[old_list_index] == 
                new_list->layer_list[new_list_index])) {
            old_list_index--;
            new_list_index--;
        }
               
        crossing_event.x = crossing_event.y = 0;
        
        /* 
         * Send a mouse leave event to all layers that the mouse
         * has moved out of. 
         */
        while (old_list_index >= 0) {
            CL_Layer *layer = old_list->layer_list[old_list_index];
            
            if (layer && (layer->vtable.event_handler_func != NULL)) {
                crossing_event.type = CL_EVENT_MOUSE_LEAVE;
                crossing_event.which = (uint32)new_grabber;
                crossing_event.fe_event = NULL;
                crossing_event.x = mouse_event->x;
                crossing_event.y = mouse_event->y;
                crossing_event.modifiers = 0;
                (*layer->vtable.event_handler_func)(layer, &crossing_event);
            }
            old_list_index--;
        }
        
        
        /* 
         * Send a mouse enter to all the new layers into which the mouse
         * has moved.
         */
        while (new_list_index >= 0) 
        {
            CL_Layer *layer = new_list->layer_list[new_list_index];
            
            if (layer && (layer->vtable.event_handler_func != NULL)) {
                crossing_event.type = CL_EVENT_MOUSE_ENTER;
                crossing_event.which = (uint32)last_grabber;
                crossing_event.fe_event = NULL;
                crossing_event.x = mouse_event->x;
                crossing_event.y = mouse_event->y;
                crossing_event.modifiers = 0;
                (*layer->vtable.event_handler_func)(layer, &crossing_event);
                mouse_enter_occured = PR_TRUE;
            }
            new_list_index--;
        }

        compositor->last_containment_list = new_list_num;
        compositor->last_mouse_event_grabber = new_grabber;
    }
  
    /* 
     * Check for a keyboard focus change, depending on the policy
     */
    if ((((compositor->focus_policy == CL_FOCUS_POLICY_CLICK) &&
          (mouse_event->type == CL_EVENT_MOUSE_BUTTON_DOWN) &&
          (mouse_event->which == 1)) ||
         ((compositor->focus_policy == CL_FOCUS_POLICY_MOUSE_ENTER) &&
          mouse_enter_occured))  &&
        (new_grabber != compositor->key_event_grabber)) {
        CL_Event keyboard_focus_event;
        CL_Layer *last_grabber = compositor->key_event_grabber;
        PRBool accepted = PR_TRUE;
       
        keyboard_focus_event.x = keyboard_focus_event.y
				= keyboard_focus_event.modifiers = 0;
        
        /* Tell the new guy that he's gained focus */
        if (new_grabber &&
            (new_grabber->vtable.event_handler_func != NULL)){
            keyboard_focus_event.type = CL_EVENT_KEY_FOCUS_GAINED;
            keyboard_focus_event.which = (uint32)last_grabber;
            keyboard_focus_event.fe_event = NULL;
            accepted = (*new_grabber->vtable.event_handler_func)(new_grabber,
                                                      &keyboard_focus_event);
        }

        if (accepted) {
            /* Tell the old event focus holder that it's lost focus */
            if (last_grabber &&
                (last_grabber->vtable.event_handler_func != NULL)){
                keyboard_focus_event.type = CL_EVENT_KEY_FOCUS_LOST;
                keyboard_focus_event.which = (uint32)new_grabber;
                keyboard_focus_event.fe_event = NULL;
                (*last_grabber->vtable.event_handler_func)(last_grabber, 
                                                        &keyboard_focus_event);
            }

            compositor->key_event_grabber = new_grabber;
        }
    }
}


static PRBool 
cl_front_to_back_event_dispatch(CL_Compositor *compositor, CL_Layer *layer,
                                CL_Event *event)
{
    CL_Layer *child;
    PRBool event_grabbed = PR_FALSE;
    uint32 old_gen_id;

    old_gen_id = compositor->gen_id;
    
    if (!layer->hidden) {
        /* 
         * First we ask the children if they're interested.
         * Stop if one of them grabs the event.
         */
        for (child = layer->top_child; 
             child && !event_grabbed; 
             child = child->sib_below) {
            event_grabbed = cl_front_to_back_event_dispatch(compositor, 
                                                            child, event);
        }
        
        /* 
         * Now check if the event occurred within this layer.
         * If so, call the event handler if one exists 
         */
        if (!event_grabbed &&
            XP_PointInRect(&layer->clipped_bbox, event->x, event->y) &&
            (layer->vtable.event_handler_func != NULL)) {

            event->x -= layer->x_origin;
            event->y -= layer->y_origin;

            event_grabbed = (*layer->vtable.event_handler_func)(layer, event);

            /*
             * If something in the event handler caused the gen_id to
             * fail, then we just bail out of here and hope that we
             * do nothing else that will access the old state of the
             * compositor.
             */
            if (compositor->gen_id != old_gen_id)
                return PR_TRUE;
            
            /* 
             * Check for mouse entering/leaving for layers. If
             * the current event grabber (for mouse events) is not the 
             * same as the last one, we synthesize the appropriate event.
             */
            if (event_grabbed && CL_IS_MOUSE_EVENT(event))
                cl_check_focus_change(compositor, layer, event);

            event->x += layer->x_origin;
            event->y += layer->y_origin;
        }

        return event_grabbed;
    }
    
    return PR_FALSE;
}

/* Dispatch an event to the correct layer */
PRBool
CL_DispatchEvent(CL_Compositor *compositor, CL_Event *event)
{
    CL_Layer *layer;
    uint32 old_gen_id;

    XP_ASSERT(compositor);
    XP_ASSERT(event);

    if (!compositor || !event || !compositor->root)
        return PR_FALSE;

    if (event->type == CL_EVENT_MOUSE_MOVE) {
        compositor->last_mouse_x = event->x;
        compositor->last_mouse_y = event->y;
        compositor->last_mouse_button = event->which;
    }
    else if (event->type == CL_EVENT_MOUSE_BUTTON_DOWN) 
        compositor->last_mouse_button = event->which;

    old_gen_id = compositor->gen_id;
           
    if (compositor->mouse_event_grabber && CL_IS_MOUSE_EVENT(event)) {
        layer = compositor->mouse_event_grabber;
        if (layer->vtable.event_handler_func) {
            PRBool ret;
            
            event->x -= layer->x_origin;
            event->y -= layer->y_origin;
            ret = ((*layer->vtable.event_handler_func)(layer, event));

            if (compositor->gen_id != old_gen_id)
                return ret;

            /* Check for a focus change */
            if (ret)
                cl_check_focus_change(compositor, layer, event);

            return ret;
        }
    }
    else if (compositor->key_event_grabber && CL_IS_KEY_EVENT(event)) {
        layer = compositor->key_event_grabber;
        if (layer->vtable.event_handler_func) {
            event->x -= layer->x_origin;
            event->y -= layer->y_origin;
            return ((*layer->vtable.event_handler_func)(layer, event));
        }
    }
    else
        return cl_front_to_back_event_dispatch(compositor,
                                               compositor->root,
                                               event);

    return PR_FALSE;
}

/* All mouse events go to this layer. If layer is NULL, stop grabbing */
PRBool
CL_GrabMouseEvents(CL_Compositor *compositor, CL_Layer *layer)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return PR_FALSE;

    compositor->mouse_event_grabber = layer;
    return PR_TRUE;
}

/* All key events go to this layer. If layer is NULL, stop grabbing */
PRBool
CL_GrabKeyEvents(CL_Compositor *compositor, CL_Layer *layer)
{
    CL_Event keyboard_focus_event;
    CL_Layer *last_grabber, *new_grabber;
    PRBool accepted = PR_TRUE;

    XP_ASSERT(compositor);
    
    if (!compositor)
        return PR_FALSE;

    keyboard_focus_event.x = keyboard_focus_event.y = 0;
    
    last_grabber = compositor->key_event_grabber;
    new_grabber = layer;

    if (new_grabber &&
        (new_grabber->vtable.event_handler_func != NULL)){
        keyboard_focus_event.type = CL_EVENT_KEY_FOCUS_GAINED;
        keyboard_focus_event.which = (uint32)last_grabber;
        keyboard_focus_event.fe_event = NULL;
        accepted = (*new_grabber->vtable.event_handler_func)(new_grabber,
                                                       &keyboard_focus_event);
    }

    if (accepted) {
        if (last_grabber &&
            (last_grabber->vtable.event_handler_func != NULL)){
            keyboard_focus_event.type = CL_EVENT_KEY_FOCUS_LOST;
            keyboard_focus_event.which = (uint32)new_grabber;
            keyboard_focus_event.fe_event = NULL;
            (*last_grabber->vtable.event_handler_func)(last_grabber, 
                                                       &keyboard_focus_event);
        }

        compositor->key_event_grabber = layer;
        return PR_TRUE;
    }
    else
        return PR_FALSE;
}

/* Returns PR_TRUE if layer is the mouse event grabber. */
PRBool
CL_IsMouseEventGrabber(CL_Compositor *compositor, CL_Layer *layer)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return PR_FALSE;
    
    return (PRBool)(compositor->mouse_event_grabber == layer);
}

/* Returns PR_TRUE if layer is the key event grabber. */
PRBool
CL_IsKeyEventGrabber(CL_Compositor *compositor, CL_Layer *layer)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return PR_FALSE;
    
    return (PRBool)(compositor->key_event_grabber == layer);
}

/* Returns PR_TRUE if layer is the key event grabber. */
CL_Layer *
CL_GetKeyEventGrabber(CL_Compositor *compositor)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return NULL;
    
    return (compositor->key_event_grabber);
}

void
CL_SetKeyboardFocusPolicy(CL_Compositor *compositor, 
                          CL_KeyboardFocusPolicy focus_policy)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return;
    
    compositor->focus_policy = focus_policy;
}

void 
CL_SetCompositorDrawingMethod(CL_Compositor *compositor,
                              CL_DrawingMethod method)
{
    XP_ASSERT(compositor);
    
    if (!compositor)
        return;
    
    compositor->back_to_front_only = 
        (method == CL_DRAWING_METHOD_BACK_TO_FRONT_ONLY);
}


/* Called when a layer is added to the layer tree */
void
cl_LayerAdded(CL_Compositor *compositor, CL_Layer *layer)
{
    XP_ASSERT(compositor);
    XP_ASSERT(layer);

    cl_ParentChanged(layer);

    /* If this child is visible, ask the compositor to update its area */
    if (layer->visible)
        cl_UpdateLayer(layer, PR_FALSE);
}

void
cl_LayerDestroyed(CL_Compositor *compositor, CL_Layer *layer)
{
    XP_ASSERT(compositor);
    XP_ASSERT(layer);

    if (!compositor || !layer)
        return;
    
    if (layer == compositor->mouse_event_grabber)
        compositor->mouse_event_grabber = NULL;
    if (layer == compositor->key_event_grabber)
        compositor->key_event_grabber = NULL;
    if (layer == compositor->last_mouse_event_grabber) {
		compositor->event_containment_lists[compositor->last_containment_list].list_head = -1;
        compositor->last_mouse_event_grabber = NULL;
    }

    compositor->gen_id++;
}

/* Called when a layer is removed from the layer tree */
void
cl_LayerRemoved(CL_Compositor *compositor, CL_Layer *layer)
{
    XP_ASSERT(compositor);
    XP_ASSERT(layer);

    if (!compositor || !layer)
        return;

    /* If this child was visible, ask the compositor
       to update the area it formerly occupied. */
    if (layer->visible)
        cl_UpdateLayer(layer, PR_FALSE);

    if (layer == compositor->last_mouse_event_grabber) {
        compositor->event_containment_lists[compositor->last_containment_list].list_head = -1;
        compositor->last_mouse_event_grabber = NULL;
    }
}

void
cl_LayerMoved(CL_Compositor *compositor, CL_Layer *layer)
{

    /* The auto-generation of mousemoves is playing havoc with the
     * REAL mouse moves.  Commented out until further notice. */
    if (0) {
	/*
	 * If the layer that grabbed mouse events has moved, the
	 * movement is equivalent to a mouse move.
	 */
	if (compositor && compositor->mouse_event_grabber == layer) {
	    CL_Event event;
        
	    event.type = CL_EVENT_MOUSE_MOVE;
	    event.fe_event = NULL;
	    event.x = compositor->last_mouse_x - layer->x_offset;
	    event.y = compositor->last_mouse_y - layer->y_offset;
	    event.which = compositor->last_mouse_button;

	    if (layer->vtable.event_handler_func)
		(*layer->vtable.event_handler_func)(layer, &event);
	}
    }
}
