Gecko:Frame Painting

From MozillaWiki
Jump to: navigation, search

Currently our handling of stacking contexts and z-ordering is deficient because nsIFrame::Paint can't separate the painting of a frame's background from the painting of its children. See bug #78087.

We also have some problems because currently a frame with a view paints all its descendant frames-without-views in one go. Sometimes that's not correct. e.g.:

     F (V) 
      / \\  

  F1(V1)  F2 

we'll build a display list "V V1"; painting V paints F and F2. So the rendered order is F F2 F1, which is wrong.

I think the right approach is to move all the display list stuff out of the view system and into frames. Display list elements will be instructions like "paint border of frame X", "paint background of frame Y", "paint text in frame Z" and whatever other paint commands are required by various frame types. Elements would only be needed for frames that actually have borders, backgrounds etc. (Except that for event handling there should always be a background element to catch events for the frame.)

The frame display list can be built by with code similar to [ http://www.w3.org/TR/CSS21/zindex.html]. We should be able to avoid of the trickery of display list construction in the view system.

One question is whether we really need a display list if we could paint everything in one recursive traversal. Given that it lets us unify event handling, bitblit analysis, and painting, and also lets us optimize cases where lower frames are covered by opaque higher ones, and serves the needs of various embedders, I think it's worth having. Also I think that we can construct it in just one traversal of the frame tree, which could be more effecient than doing several passes over the frame tree if we directly followed Hixie's z-order spec. (E.g., doing separate passes over large frame subtrees just to see if there are any 'outline's seems very slow and wasteful). Also we can do some useful postprocessing of the display list such as outline merging.

More Details

For speed we want only one pass over the frame tree. Here's how I suggest this be implemented. A display list is a doubly-linked list of display items (for O(1) concatenation); some display items can contain their own display lists (when the item corresponds to a stacking context), so a display list is really a tree.

class nsDisplayList {
public:
  nsDisplayList(PLArenaPool* pool);
  void AppendToTop(nsDisplayItem* item);
  // appends contents of 'list'
  void AppendToTop(nsDisplayList* list);
private:
  nsDisplayItem* top;
  nsDisplayItem* bottom;
};
class nsDisplayItem {
public:
  nsRect GetAbsoluteBounds();
  // will return an empty area for non-opaque items
  nsRect GetAbsoluteOpaqueArea();
  // painting should be offset by absoluteFrameOrigin, so there is
  // no need to put a translation in 'ctx' as we go from frame to frame
  virtual void Paint(nsIRenderingContext* ctx, const nsRect& dirtyArea) = 0;
  // point in absolute coordinates; return PR_TRUE if the item should
  // receive the event
  virtual PRBool HitTest(nsPoint pt);
  virtual nsDisplayList* GetEnclosedList();
  nsIFrame* GetFrame();
private:
  nsIFrame* frame;
  nsDisplayItem* above;
  nsDisplayItem* below;
  nsPoint absoluteFrameOrigin;
};
class nsDisplayBackground;
class nsDisplayBorder;
class nsDisplayFieldSetBorder;
class nsDisplayTableCollapsedBorders;
class nsDisplayText; // includes underline/overline
class nsDisplayImage;
class nsDisplayStrikeThrough;
class nsDisplayOutline;
// plus others ... e.g., MathML, XUL, SVG whatever currently has'
// custom painting
// This is where we support recursion
class nsDisplayStackingContext : public nsDisplayItem {
private:
  nsDisplayList list;
  nsRect bounds;
  nsRect opaqueArea;
  float opacity;
  PRPackedBool clipToBounds;
};

Then some methods on nsIFrame:

// append to display list 'dl', treating this frame as the root
// of a stacking context (i.e., start at the top of algorithm E.2).
// the rect is given in frame coordinates, and only we need display items whose
// bounds intersect r need be given. absPt is the absolute offset of the frame
// origin relative to the origin of the drawing context (or widget or
// whatever reference point we choose)
BuildDisplayListForStackingContext(const nsPoint& absPt, const nsRect& dirty, const nsPoint& nsDisplayList* dl);
// append to display list 'dl', treating this frame as a psuedo-
// stacking-context (i.e., algorithm E.2 except that descendants
// that create a genuine stacking context are placed on the auxiliar
// display list)
BuildDisplayListForPseudoStackingContext(const nsPoint& absPt, const nsRect& r, nsDisplayList* dl,
   nsDisplayList* descendantStackingContexts);
// append to display list 'dl', recursively descending through block-level
// elements, accumulating the results of step 4 in
// 'backgroundsAndBorders', the results of step 5 in 'floats', the results
// of step 6 in 'content', the results of step 9 in 'outlines', and any
// discovered (psuedo)stacking-contexts in 'descendantStackingContexts'
BuildDisplayListForBlockLevelElement(const nsPoint& absPt, const nsRect& r,
  nsDisplayList* backgroundsAndBorders, nsDisplayList* floats,
  nsDisplayList* content, nsDisplayList* outlines,
  nsDisplayList* descendantStackingContexts);
// append to display list 'dl', recursively descending through inline-level
// elements, accumulating the results in 'content' except that outlines go
// in 'outlines' and any discovered (psuedo)stacking-contexts in
// 'descendantStackingContexts'
BuildDisplayListForInlineLevelElement(const nsPoint& absPt, const nsRect& r,
  nsDisplayList* content, nsDisplayList* outlines,
  nsDisplayList* descendantStackingContexts);