/** 
 * Determines whether at least one of the row header and databody will have a horizontal 
 * scrollbar, and whether at least one of the column header and databody will have a vertical 
 * scrollbar, assuming that space is allocated optimally between the headers and databody so 
 * that no unnecessary scrollbars are introduced.  
 * 
 * @param ptAvailableSize        [ptAvailableWidth, ptAvailableHeight]
 * @param totalCanvasSize        [totalCanvasWidth, totalCanvasHeight]
 * @param headerBlockPixelDepth  [colHeaderBlockHeight, rowHeaderBlockWidth]  "Depth" means size layer-wise, not slice-wise 
 * @param headerExtraPixelDepth  [colHeaderExtraHeight, rowHeaderExtraWidth]  "Depth" means size layer-wise, not slice-wise 
 * @param scrollbarSize  the [width, height] of scrollbars in the current browser.  Both always positive.
 */
AdfPivotTableUtils.prototype.determineScrollbarStatus = function(
            ptAvailableSize, totalCanvasSize, headerBlockPixelDepth, headerExtraPixelDepth, scrollbarSize) {
    var hasOverallScrollbar = []; // [has horizontal scrollbar, has vertical scrollbar].  hOS[0] = whether at least one of the row header and databody has a horizontal scrollbar.  hOS[1] = whether at least one of the col header and databody has a vertical scrollbar.
    
    for (var edge=0; edge<2; ++edge) {
        var totalContentSize = headerBlockPixelDepth[1-edge] + totalCanvasSize[edge];
        
/////////////////////////////////////////////////////////////////////////////////////////////////////////////// TODO: (only) when panes are split, do we need to subtract a pixel from totalInnerSize for the border between the panes?
        
        var totalInnerSize = ptAvailableSize[edge] - headerExtraPixelDepth[1-edge]; // Note that it is the scroller size, not databody size, that matters here, so we do not subtract databodyExtraWidth/Height.
        
        if (totalContentSize > totalInnerSize) { 
            hasOverallScrollbar[edge] = true; // Even if there is no vertical (horiz) scrollbar taking up space, the pivot table still is not wide (tall) enough to display the rowHeader and databody contents (colHeader and databody contents) without at least one horizontal (vertical) scrollbar.
        } else if (totalContentSize <= totalInnerSize - scrollbarSize[edge]) { 
            hasOverallScrollbar[edge] = false; // Even if there is a vertical (horiz) scrollbar taking up space, the pivot table still is wide (tall) enough to display the rowHeader and databody contents (colHeader and databody contents) with no horizontal (vertical) scrollbars.
        } else {
            hasOverallScrollbar[edge] = AdfPivotTableUtils.MAYBE; // The pivot table is wide (tall) enough to display the rowHeader and databody contents (colHeader and databody contents) with no horizontal (vertical) scrollbars iff there are no vertical (horiz) scrollbars taking up space.
        }
    }
    
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TODO: update comment for split panes.  It still holds, right?
    // Now we resolve the maybes.  The only subtle case is when both vars are maybe, in which case each dimension needs 
    // at least one scrollbar iff the other dimension has at least one scrollbar.  There are two consistent resolutions 
    // to this: (no scrollbars), or (at least one horiz scrollbar AND at least one vert scrollbar).  We choose the 
    // former, so that (maybe, maybe) resolves to (false, false).  This causes subsequent code to make the headers big 
    // enough not to need a scrollbar, leaving a databody scroller for which each dimension is big enough to display the 
    // canvas without a scrollbar iff the other dimension lacks a scrollbar.  Thus, there are again two consistent 
    // choices, but to a simpler problem: 0 scroller scrollbars (i.e. no scrollbars overall) or 2 scroller scrollbars 
    // (i.e. at least one horiz scrollbar overall AND at least one vert scrollbar overall).  Our (false, false) choice 
    // causes subsequent code to choose 0 (no scrollbars overall), which is what the browser would choose if overflow 
    // were set to auto, and which is consistent with our initial choice of (false, false).  
    for (var edge=0; edge<2; ++edge) {
        if (hasOverallScrollbar[edge] === AdfPivotTableUtils.MAYBE) {
            hasOverallScrollbar[edge] = (hasOverallScrollbar[1-edge] === true);
        }
    }
    
    return hasOverallScrollbar;
}

/**
 * Static method so PrintablePage code can use it.
 * 
 * Figures out how to allocate space between 2 regions based on the total space available 
 * for the 2 regions to share, and on the space needs of the 2 regions.  The algorithm is 
 * to give the specified amount of the space to the first region, unless that 
 * would create whitespace because it gives at least one region more space than it needs, 
 * in which case the allocation is adjusted to give that region exactly what it needs, 
 * thus avoiding both whitespace (too much space) and a scrollbar (too little space) in 
 * that region.  If whitespace is unavoidable because the total space desired by both 
 * regions is less than the space available, the first region is given exactly what it 
 * wants so that the whitespace appears after the 2nd region, and neither region has scrollbars.  
 *
 * For one caller, the regions are the header and (all panes of) the databody region, and the containing region is the PT.
 * For another caller, the regions are the 2 panes of the databody, and the containing region is the databody region.
 * 
 * @param totalSize           the "available size" of the containing region, which excludes the containing region's borders, 
 *                            scrollbars, etc., and excludes any space inside that region that is unavailable for use by 
 *                            regions 1 and 2, e.g. because it is being used for a status bar.  See further comments below.
 * @param desiredRegion1Size  the desired outer width (height) of the first region, which includes its borders, width (height) 
 *                            of vertical (horizontal) scrollbar if present, etc.  See further comments below.
 * @param desiredRegion2Size  the desired outer size of the second region, as defined above.  See further comments below.
 * @param region1Allocation   the amount of the available space (in % or px) to which the first region is entitled if it needs it.
 * @param unit                the unit ('%' or 'px') for region1Allocation
 * @return                    the computed outer size of the first region, and booleans indicating 
 *                            whether the 2 regions will have scrollbars.  See further comments below.
 * 
 * Optionally, the first 3 params can be tweaked as long as they remain consistent.  E.g., starting with the 3 params as defined above, 
 * if desiredRegion2Size includes a 17px scrollbar, and desiredRegion1Size includes a 1px border, then those params can be specified 
 * net of those values if totalSize is also reduced by 17+1=18px.  Then in the return value, the size of first region will be the size 
 * net of that 1px border (i.e. inner not outer size), and the 2 scrollbar booleans will be correct (as long as return value is treated 
 * as inner, not outer, size).
 * 
 * This is how callers currently use this method.  The benefit is that the *usable* space (net of scrollbar) is allocated 50/50, rather 
 * than giving the 2nd region 17px less usable space because the scrollbar was part of its 50%.  
 */
AdfPivotTableUtils.allocateSpace = function(totalSize, desiredRegion1Size, desiredRegion2Size, region1Allocation, unit) {
    // We don't try to be "smart" about reining in extreme app-supplied allocations, e.g. 1px or 99%.  We've never had a problem with the non-extreme allocations we compute ourselves, and the app is responsible for supplying valid parameters.
    // We do ensure that the values are in-bounds (e.g. that px values are between 0 and totalSize).
    if (unit=='%') {
        // internal callers always supply %'s between 0 and 100.  Server validates app-supplied %'s are in this range.  So no need to bounds-check here.
        region1Allocation = Math.floor(region1Allocation * totalSize); // convert from % to px.  the first region is guaranteed this many pixels of space if it needs it
    } else { // unit is 'px'.  px units only come from the app.  Bounds check:
        region1Allocation = Math.min(region1Allocation, totalSize-1);
        region1Allocation = Math.max(region1Allocation, 0);
    }
    
    // now region1Allocation is in px units, and is in-bounds (between 0 and totalSize). The first region is guaranteed this many pixels of space if it needs it
    var region2Allocation = totalSize - region1Allocation; // the second region is guaranteed this many pixels of space if it needs it
    
    var region1Size, hasRegion1Scrollbar, hasRegion2Scrollbar;

      // if both regions' contents fit in containing region, then must give the first region exactly the space it wants, so that any whitespace goes at end of 2nd region.  Less would create unnecessary scrollbar in first region (that our other logic isn't expecting). More would create whitespace in first region, and possibly create unnecessary scrollbar in 2nd region (that our other logic isn't expecting).
    if(totalSize >= desiredRegion1Size + desiredRegion2Size) {
        // plenty of space, so give region1 all the space it wants (which leaves enough to give region2 all the space it wants)
        region1Size = desiredRegion1Size; 
        // region2Size = (totalSize - desiredRegion1Size), which is >= desiredRegion2Size
        hasRegion1Scrollbar = false;
        hasRegion2Scrollbar = false;

      // Else at least one region must have a scrollbar, so at most one region can have whitespace.  If one does, allocate space so that that region is exactly the size of its contents, which eliminates the whitespace without risk of creating whitespace in the other region.
    } else if (desiredRegion1Size <= region1Allocation) {
        // region1 wants less space than its allocation (or exactly equal to its allocation), so give it what it wants and give region2 the rest (which is >= its allocation but strictly less than it wanted)
        region1Size = desiredRegion1Size; 
        // region2Size = totalSize - desiredRegion1Size;
        hasRegion1Scrollbar = false;
        hasRegion2Scrollbar = true;
    } else if (desiredRegion2Size <= region2Allocation) { 
        // region2 wants less space than its allocation (or exactly equal to its allocation), so give it what it wants and give region1 the rest (which is >= its allocation but strictly less than it wanted)
        region1Size = totalSize - desiredRegion2Size; 
        // region2Size = desiredRegion2Size; 
        hasRegion1Scrollbar = true;
        hasRegion2Scrollbar = false;
    } else {
        // both regions want strictly more space than their respective allocations, so we give them each their allocation exactly
        region1Size = region1Allocation; 
        // region2Size = region2Allocation;
        hasRegion1Scrollbar = true;
        hasRegion2Scrollbar = true;
    }
    
    // In all cases, region2Size can be calculated from the return value: region2Size = totalSize - region1Size;
    return [region1Size, hasRegion1Scrollbar, hasRegion2Scrollbar];
}

AdfPivotTableUtils.prototype.displayScrollbarSpacer = function(spacerRenderInfo) {
    var spacer = spacerRenderInfo.spacer;
    spacer.style.display="inline"; // TBD - Is block correct??
    spacer.style.position="absolute";
    this.setPos(AdfPivotTableUtils.COLUMN_EDGE, spacerRenderInfo.left, spacer, true);
    this.setPos(AdfPivotTableUtils.ROW_EDGE,    spacerRenderInfo.top, spacer);
    spacer.style.zIndex = "3"; 
    this.setCachedSize(spacer, spacerRenderInfo.innerWidth, AdfPivotTableUtils.COLUMN_EDGE);  
    this.setCachedSize(spacer, spacerRenderInfo.innerHeight, AdfPivotTableUtils.ROW_EDGE);
}

/**
 * @export
 * Sizes and positions the major divs of the pivot table:  header divs, databody, scroller, canvas, 
 * corner, scrollbar spacers.  Does not size or position blocks.
 * 
 * Since this method can resize both the scroller div and its canvas, it's possible that the maximum 
 * possible scroll position (horizontally or vertically) will become smaller than the existing scroll 
 * position, in which case the scroll position is automatically decreased by the browser.  Thus, the 
 * caller must restore the canvasPos = pos + scrollPos invariant, by changing one or 
 * more of the three variables.  
 * 
 * Usually the right thing is for the caller to call scrollHTMLBlocks().  It changes pos only, setting 
 * it to (canvasPos - scrollPos), so that blocks move (jump) relative to viewport, the scrollbar doesn't 
 * jump, and blocks' position relative to the canvas doesn't change.  This is usually the right choice, since 
 * we need the blocks to jump so that the last block is aligned with the end of the viewport, which is 
 * what would happen automatically if the blocks actually lived on the canvas.
 * 
 * If the caller is already calling a method that restores the invariant, like positionInitialBlocks, 
 * that is a valid alternative to scrollHTMLBlocks.  
 */
AdfPivotTableUtils.prototype.sizePivotTable = function(outerWidth, outerHeight)
{
this.startTimer(0, 2);
  var rightToLeft = this._rightToLeft;
  var hasStatusBar = this._hasStatusBar; 
  var bAutosize = this.isShrinkToFit();

  // -- get the pivot table divs and sizing information about the pivot table frame --   TBD: determine the outer widths/heights from CSS on the server rather than using JS constants
  
  var colHeaders = this._colHeaders;
  var rowHeaders = this._rowHeaders;
  var headers = this._headers;
  var databodies  = this._databodies;
  var scrollers = this._databodyScrollers;
  var corner = this._corner;
  var statusBar = this._statusBar;
  var scrollbarSpacers = this._scrollbarSpacers;
  var pt = this._innerContainer;
  var scrollbarTester = this._scrollbarTester;

  // - InnerWidth/Height is the size of the CSS content box, i.e. net of padding/borders/margin, but including scrollbar 
  //   width/height.  It is the value set on the style property and on the _width/_height expandos.
  // - ExtraWidth/Height is the width/height taken up by borders (and padding and margin, but currently not using those).
  // - OuterWidth/Height = InnerWidth/Height + ExtraWidth/Height = the amount of space the div takes up in the parent div.
  // - UsableWidth/Height is the InnerWidth/Height net of scrollbarWidth/Height.  It's the amount of space actually 
  //   available for viewing the contents of the div.
  // - Scrollbar calcs:
  //   clientWidth = innerWidth + padding - scrollbarWidth
  //   offsetWidth = innerWidth + padding + border
  //   so, scrollbarWidth = innerWidth + padding - clientWidth
  //                      = offsetWidth - border - clientWidth
  //   Likewise for heights.

  var containerExtraSize = AdfPivotTableUtils.CONTAINER_EXTRA_SIZE;          // [containerExtraWidth, containerExtraHeight]
  var databodyExtraSize = AdfPivotTableUtils.DATABODY_EXTRA_SIZE;            // [databodyExtraWidth,  databodyExtraHeight]
  var headerExtraPixelExtent = AdfPivotTableUtils.HEADER_EXTRA_PIXEL_EXTENT; // [colHeaderExtraWidth, rowHeaderExtraHeight].  "Extent" means size slice-wise, not layer-wise.
  var headerExtraPixelDepth = AdfPivotTableUtils.HEADER_EXTRA_PIXEL_DEPTH;   // [colHeaderExtraHeight, rowHeaderExtraWidth].  "Depth" means size layer-wise, not slice-wise.
  var scrollbarSpacerExtraLength  = AdfPivotTableUtils.SCROLLBAR_SPACER_EXTRA_LENGTH;  // [horizScrollbarSpacerExtraWidth, vertScrollbarSpacerExtraHeight]
  var scrollbarSpacerExtraBreadth = AdfPivotTableUtils.SCROLLBAR_SPACER_EXTRA_BREADTH; // [horizScrollbarSpacerExtraHeight, vertScrollbarSpacerExtraWidth]
  var cornerScrollbarSpacerExtraSize = [scrollbarSpacerExtraBreadth[1], scrollbarSpacerExtraBreadth[0]]; // [vertSpacerExtraWidth, horizScrollbarSpacerExtraHeight]
  
  var ptInnerSize = [outerWidth - containerExtraSize[0], outerHeight - containerExtraSize[1]]; // [ptInnerWidth, ptInnerHeight]
  
this.checkpointTimer(0, "get pivot table divs and sizing constants");

  // -- generally useful arrays --  
  
  var sizeAttr   = this._sizeAttr;   // ["_width", "_height"]
  var splitSlice = this._splitSlice; // [splitCol, splitRow]
  var startPane  = this._startPane;  // [colEdgeStartPane, rowEdgeStartPane]. For each edge, start pane is the first pane in use for that edge, i.e. 0 if edge split, 1 otherwise.  
  var paneExtent = this._paneExtent; // # of slices in pane.  Indexed by edge/pane, i.e. [[leftPaneColCount, rightPaneColCount],
                                     //                                                   [topPaneRowCount, bottomPaneRowCount]]
  
  var pagingControlHeight = [[0,0], [0,0]];
  if (this._pivotTablePeer._isPaging)
  {
    for (var edge = 0; edge < 2; edge++)
    {
      for (var pane = this._startPane[edge]; pane < 2; pane++)
      {
        // TBD: See if there is a way to not get / set / get heights here...
        pagingControlHeight[edge][pane] = this._pagingControl[edge][pane].childNodes[0].offsetHeight;
        this._pagingControl[edge][pane].parentNode.style.height = pagingControlHeight[edge][pane] + 'px';
        pagingControlHeight[edge][pane] = this._pagingControl[edge][pane].parentNode.offsetHeight;
      }
    }
    
    ptInnerSize[1] = ptInnerSize[1] - pagingControlHeight[0][1] - pagingControlHeight[1][1] - pagingControlHeight[1][0];
  }

  // -- get scrollbar sizes, get status bar size --
  
  // [widthOfVerticalScrollbar, heightOfHorizontalScrollbar].  Tester always has scrollbars.  Tester has no borders, so no need to subtract border width 
  // We recalc the scrollbar sizes on every call to sizePivotTable, so that we can recover in the unlikely event that they changed mid-session (e.g. user changed an OS property).
  if (!this._pivotTablePeer._isPaging)
    var scrollbarSize = [scrollbarTester.offsetWidth  - scrollbarTester.clientWidth,
                         scrollbarTester.offsetHeight - scrollbarTester.clientHeight];
  else
    scrollbarSize = [0, 0];
  
  var statusBarHeight = (hasStatusBar) ? statusBar.offsetHeight : 0;
  
  // [ptAvailableWidth, ptAvailableHeight].  Available size is size net of status bar
  var ptAvailableSize = [ptInnerSize[0], ptInnerSize[1] - statusBarHeight];
  
this.checkpointTimer(0, "get scrollbar sizes and status bar size");

  
  // -- Get header/databody block sizes, calc canvas size, and allocate space between the headers and databody (but don't set sizes on DOM yet) --
    
  var canvasSize = [[],[]]; // indexed by edge/pane, not by row/col.  So is [[leftPaneWidth, rightPaneWidth], 
                            //                                               [topPaneHeight, bottomPaneHeight]]
  var totalCanvasSize = [0,0]; // indexed by edge, i.e. [totalCanvasWidth, totalCanvasHeight]
      
  // these 2 vars are indexed in the useful order, which means that one has the horiz scrollbar first (horiz then vert), and the other has it second (width then height)
  var hasOverallScrollbar  = []; // [hasAtLeastOneHorizontalScrollbar, hasAtLeastOneVerticalScrollbar].
  var overallScrollbarSize = []; // [widthOfVerticalScrollbarIfExists, heightOfHorizontalScrollbarIfExists]

  // desiredFooSize is just big enough to display the contents of Foo without a scrollbar:
  var desiredHeaderOuterPixelDepth = []; // [desiredColHeaderOuterHeight, desiredRowHeaderOuterWidth].  Note is in edge order (colHeader then rowHeader, NOT size order (height is before width, which is backwards).  "Depth" means size layer-wise, not slice-wise 
  var desiredDatabodyRegionSize = []; // [desiredDatabodyRegionWidth, desiredDatabodyRegionHeight]
  
  var emptyTextDiv = this._emptyTextDiv;
  
  if (emptyTextDiv) { // if we're displaying the no-data message b/c there's no data
      for (var edge=0; edge<2; ++edge) {
          for (var pane=startPane[edge]; pane<2; ++pane) {
              canvasSize[edge][pane] = 0; // totalCanvasSize is already [0,0] as desired
          }
      }
      
      hasOverallScrollbar = [false, false];
      overallScrollbarSize = [0, 0];
      
      var totalHorizMargin = AdfAgent.AGENT.getIntAttribute(emptyTextDiv,"_totHorizMargin");
      var totalVertMargin  = AdfAgent.AGENT.getIntAttribute(emptyTextDiv,"_totVertMargin");
      var emptyTextInnerDiv = AdfDhtmlPivotTablePeer._getChildById(emptyTextDiv, emptyTextDiv.id + "Inner");
      
      desiredHeaderOuterPixelDepth = AdfPivotTableUtils.HEADER_OUTER_PIXEL_DEPTH_NO_DATA;
      desiredDatabodyRegionSize = [emptyTextInnerDiv.offsetWidth  + totalHorizMargin,
                                   emptyTextInnerDiv.offsetHeight + totalVertMargin];
  } else {
      var headerBlockPixelDepth = []; // [colHeaderBlockHeight, rowHeaderBlockWidth].  "Depth" means size layer-wise, not slice-wise.
      var headerDatabodyBlockPixelExtent; // "Extent" means size slice-wise, not layer-wise.  Var name reflects the fact that header and databody blocks have the same size slice-wise (e.g. row header blocks and databody blocks in the same row of blocks have the same height).
      
      for (var edge=0; edge<2; ++edge) {
          for (var pane=startPane[edge]; pane<2; ++pane) {
              headerDatabodyBlockPixelExtent = this.getHeaderBlocksSize(this._buffers2[edge][pane], sizeAttr[edge]);
              var numSlicesWeDontHave = paneExtent[edge][pane] - this.getSliceCountInBuffer(edge, pane);
              canvasSize[edge][pane] = headerDatabodyBlockPixelExtent + (numSlicesWeDontHave * this._defaultSliceSize[edge]); // (known width of columns we have) + (estimated width of columns we don't)
              totalCanvasSize[edge] += canvasSize[edge][pane];
          }
          headerBlockPixelDepth[edge] = this.getFirstHeaderBlockSize(this._buffers2[edge][1], sizeAttr[1-edge]); // all blocks in both panes of the row header should have the same width, so just check one of them (the first one from the pane that always exists)
      }
      
      // To determine how much horizontal (vertical) space is available in the viewport, we need to 
      // determine whether there will be at least one vertical (horizontal) scrollbar taking up space--i.e. 
      // whether at least one of the column (row) header and databody has a vertical (horizontal) scrollbar.
      if (this._pivotTablePeer._isPaging)
          hasOverallScrollbar = [false, false];
      else
          hasOverallScrollbar = this.determineScrollbarStatus(ptAvailableSize, totalCanvasSize, headerBlockPixelDepth, headerExtraPixelDepth, scrollbarSize);
      
      for (var edge=0; edge<2; ++edge) {
        overallScrollbarSize[edge] = (hasOverallScrollbar[1-edge]) ? scrollbarSize[edge] : 0;
        desiredHeaderOuterPixelDepth[edge] = headerBlockPixelDepth[edge] + headerExtraPixelDepth[edge];  // Derivation: (1) desired rowHeaderUsableWidth = rowHeaderBlockWidth, the minimum usable width necessary to avoid a rowHeader scrollbar.  (2) rowHeaderInnerWidth = rowHeaderUsableWidth, since the rowHeader never has a vertical scrollbar.  (3) rowHeaderOuterWidth = rowHeaderInnerWidth + rowHeaderExtraWidth by definition.  (4) Thus, desiredRowHeaderOuterWidth = rowHeaderBlockWidth + rowHeaderExtraWidth.
        desiredDatabodyRegionSize[edge] = totalCanvasSize[edge] + overallScrollbarSize[edge];  // Derivation: desired scrollerUsableWidth = canvasWidth, the minimum usable width necessary to avoid a databody horizontal scrollbar.  Since databodyRegionWidth = scrollerUsableWidth + overallScrollbarWidth,  we have desiredDatabodyRegionWidth = canvasWidth + overallScrollbarWidth.
      }
  }

  var headerInnerPixelDepth  = []; // [colHeaderInnerHeight, rowHeaderInnerWidth].  Note is in edge order (colHeader then rowHeader, NOT size order (height is before width, which is backwards).  "Depth" means size layer-wise, not slice-wise 
  var headerOuterPixelDepth  = []; // [colHeaderOuterHeight, rowHeaderOuterWidth].  Note is in edge order (colHeader then rowHeader, NOT size order (height is before width, which is backwards).  "Depth" means size layer-wise, not slice-wise 
  
  // these vars are indexed in the most useful order, which means that the hasScrollbar vars have the row header horiz scrollbar first, and the scrollbarSize vars have it second.
  var hasHeaderScrollbar     = [];      // [hasRowHeaderHorizScrollbar, hasColHeaderVertScrollbar].  Note that scroll direction (horiz then vert) drives the order of this array, not the headers, which are backwards (RH then CH).
  var hasDatabodyScrollbar   = [[],[]]; // Indexed by scroll direction (horiz then vert), and then pane (0 then 1), not by row/col of panes, i.e.:
                                        // [[leftPaneHasHorizScrollbar, rightPaneHasHorizScrollbar],
                                        //  [topPaneHasVertScrollbar,   bottomPaneHasVertScrollbar]]
                                        // Note that it is really the scroller, not the databody, that has scrollbars
  var headerScrollbarSize    = [];      // [colHeaderScrollbarWidth, rowHeaderScrollbarHeight].  Note that the headers (CH then RH) and size dimension (width then height) drive the order of this array, not scroll direction, which is backwards (vert then horiz).
                                        // Each size is 0 if that scrollbar doesn't exist.  
  var databodyScrollbarSize  = [[],[]]; // Indexed by size dimension (width then height), and then pane (0 then 1), i.e.: 
                                        // [[widthOfVerticalScrollbarInTopPane,     widthOfVerticalScrollbarInBottomPane],
                                        //  [heightOfHorizontalScrollbarInLeftPane, heightOfHorizontalScrollbarInRightPane]]
                                        // Each size is 0 if that scrollbar doesn't exist.  

  for (var edge=0; edge<2; ++edge) {
    var ptAvailableContentSize = ptAvailableSize[edge] - headerExtraPixelDepth[1-edge] - overallScrollbarSize[edge];
    var desiredHeaderInnerPixelDepth = desiredHeaderOuterPixelDepth[1-edge] - headerExtraPixelDepth[1-edge];
    var desiredDatabodyRegionUsableSize = desiredDatabodyRegionSize[edge] - overallScrollbarSize[edge];
    
    if (emptyTextDiv) { // if we're displaying the no-data message b/c there's no data 
        var headerAllocation = 0.15; // the amount of pivot table space (in % or px) to which the header is entitled if it needs it.  The headers are just empty space when we're displaying the "no data" message, so we give them much lower priority (than the default 0.5) in that case, but we don't want them to disappear altogether.
        var headerAllocUnit = '%';
    } else {
        headerAllocation = this._headerSizeAllocation[1-edge]; // 50% by default (if app didn't set it)
        headerAllocUnit  = this._headerSizeAllocUnit[1-edge]; // '%' or 'px'
    }
    
    var headerDatabodyAllocationInfo = AdfPivotTableUtils.allocateSpace(ptAvailableContentSize, desiredHeaderInnerPixelDepth, desiredDatabodyRegionUsableSize, headerAllocation, headerAllocUnit);
    headerInnerPixelDepth[1-edge] = headerDatabodyAllocationInfo[0]; // the index into headerDatabodyAllocationInfo does NOT represent an edge!
    headerOuterPixelDepth[1-edge] = headerInnerPixelDepth[1-edge] + headerExtraPixelDepth[1-edge];
    
    if (emptyTextDiv || this._pivotTablePeer._isPaging) { // if we're displaying the no-data message b/c there's no data, or this is paging mode
        hasHeaderScrollbar[edge]      = false;
        hasDatabodyScrollbar[edge][1] = false; // if edge isn't split, this is the correct hasDatabodyScrollbar value for pane 1, and pane 0's unused value can remain null.  If edge is split, both pane's values will be overwritten with the result of the next call to allocateSpace().
    } else {
        hasHeaderScrollbar[edge]      = headerDatabodyAllocationInfo[1]; // the index into headerDatabodyAllocationInfo does NOT represent an edge!
        hasDatabodyScrollbar[edge][1] = headerDatabodyAllocationInfo[2];  // the index into headerDatabodyAllocationInfo does NOT represent an edge!  If edge isn't split, this is the correct hasDatabodyScrollbar value for pane 1, and pane 0's unused value can remain null.  If edge is split, both pane's values will be overwritten with the result of the next call to allocateSpace().
    }
//    console.log("edge "+ edge + " has db scrollbar? " + hasDatabodyScrollbar[edge][1]);
    headerScrollbarSize[1-edge]      = (hasHeaderScrollbar[edge])      ? scrollbarSize[1-edge] : 0;
    databodyScrollbarSize[1-edge][1] = (hasDatabodyScrollbar[edge][1]) ? scrollbarSize[1-edge] : 0; // See above comment on hasDatabodyScrollbar[edge][1]
  }
  
  AdfAssert.assert(AdfPivotTableUtils.assertsOff || 
                   (overallScrollbarSize[1] == Math.max(headerScrollbarSize[1], databodyScrollbarSize[1][1]) &&
                    overallScrollbarSize[0] == Math.max(headerScrollbarSize[0], databodyScrollbarSize[0][1]))); 

this.checkpointTimer(0, "get block sizes, calc canvas size, and allocate space between headers/databody");


  // -- shrink to fit; calc databody region sizes (but don't set them on DOM yet) --
  
  // In autosize mode, if none of the divs need scrollbars (case 1 of allocateSpace), then shrink the PT so 
  // that it is just big enough to hold its contents without introducing scrollbars (so that it still would have 
  // entered case 1 of allocateSpace).  All else (databody size, etc.) will derive properly from the new size.
  // TBD: would be a little cleaner to move this *before* the call to allocateSpace()...
  if(bAutosize) {
      for (var edge=0; edge<2; ++edge) {
          var desiredTotalSize  = desiredHeaderOuterPixelDepth[1-edge]  + desiredDatabodyRegionSize[edge]; 
          ptAvailableSize[edge] = Math.min(ptAvailableSize[edge],  desiredTotalSize);
      }
      
      ptInnerSize[0] = ptAvailableSize[0];
      ptInnerSize[1] = ptAvailableSize[1] + statusBarHeight;
  }
  
  // The "databody region" is the area taken up by all panes of the databody, including all panes' databody divs, scroller divs, and any databody 
  // scrollbar spacers.  It is not a real div, and thus has no extraWidth/Height or scrollbars, so that its outerSize = innerSize = usableSize.  
  // Thus we simply refer to "the" width/height.  Even when panes are not split, the databody region is a more constant point of reference for sizing 
  // purposes than the databody or scroller, both of which can shrink by the size of a scrollbar.  
  // Likewise, databodyPaneRegionSize is the area taken up by a given databody pane, including its databody div, scroller div, and any databody 
  // scrollbar spacer.
  var databodyRegionSize     = [];      // [databodyRegionWidth, databodyRegionHeight]
  var databodyPaneRegionSize = [[],[]]; // indexed by edge (width/height), then by pane, i.e.:
                                        // [[leftPaneWidth, rightPaneWidth],
                                        //  [topPaneHeight, bottomPaneHeight]]

  for (var edge=0; edge<2; ++edge) {
    databodyRegionSize[edge] = ptAvailableSize[edge] - headerOuterPixelDepth[1-edge];

    if (splitSlice[edge]!=-1) {
      var paneAllocationInfo = AdfPivotTableUtils.allocateSpace(databodyRegionSize[edge]-overallScrollbarSize[edge], canvasSize[edge][0], canvasSize[edge][1], this._splitPercentage[edge], '%');

      databodyPaneRegionSize[edge][0] = paneAllocationInfo[0]; // the index into paneAllocationInfo does NOT represent an edge!
      databodyPaneRegionSize[edge][1] = databodyRegionSize[edge] - databodyPaneRegionSize[edge][0];
      
      if (emptyTextDiv) { // if we're displaying the no-data message b/c there's no data 
          hasDatabodyScrollbar[edge][0] = false;
          hasDatabodyScrollbar[edge][1] = false;
      } else {
          hasDatabodyScrollbar[edge][0] = paneAllocationInfo[1]; // the index into paneAllocationInfo does NOT represent an edge!
          hasDatabodyScrollbar[edge][1] = paneAllocationInfo[2];  // the index into paneAllocationInfo does NOT represent an edge!  
      }
      
      for (var pane=startPane[edge]; pane<2; ++pane) { // startPane always 0 since we're inside an "if split" block, but keep this version to avoid copy-paste errors.
        databodyScrollbarSize[1-edge][pane] = (hasDatabodyScrollbar[edge][pane]) ? scrollbarSize[1-edge] : 0;
      }
    } else { // else pane 1 takes up entire viewport.  In this case, all the scrollbar vars were already set after previous call to allocateSpace
      databodyPaneRegionSize[edge][1] = databodyRegionSize[edge];
    }
  }
this.checkpointTimer(0, "shrink to fit; calc databody region sizes");


  // -- If some but not all of the in-use panes capable of having a vertical scrollbar have a vertical scrollbar, then place a         
  //    spacer div where the absent scrollbars would have gone (but don't set on DOM yet).  Likewise for horizontal. -- 
  
  var spacerRenderInfo = [[],[]]; // spacerRenderInfo[edge][pane] = (shouldRenderThatSpacerForThatDatabodyPaneOrForHeader) ? {spacer, left, top, innerWidth, innerHeight} : null;
                                  // Indexed by edge (spacers for horiz then vertical scrollbars), then by pane, just like indexes of this._scrollbarSpacers, i.e.:
                                  // [[spacerForLeftPaneHorizScrollbar, spacerForRightPaneHorizScrollbar], 
                                  //  [spacerForTopPaneVertScrollbar,   spacerForBottomPaneVertScrollbar]]
                                  // An unused spacer from an edge will be used for the header if the header needs a spacer.
  
  var headerSpacerPane = []; // indexed by edge, contains pane number.  For a given edge, if header needs a spacer, then at least one databody pane does not, so header will use that pane's spacer
  
///////////////////////////// TODO: are the 2 header arrays ever used?  (Expect to use the DB ones: spacerSize for positioning scroller in bidi (when edge is 1, for both panes), and hasSpacer to decide whether to show corner spacer (both edges, last pane only) )
  // Note: the 2 databody arrays indicate whether that databody pane has a spacer, and contain false/0 if that pane's spacer is used in the header.  To find out whether a pane's spacer is used at all, for that databody pane or for the header, use spacerRenderInfo[edge][pane]!=null.
  var hasHeaderSpacer     = [];      // [hasRowHeaderHorizSpacer, hasColHeaderVertSpacer].  Note that scroll direction (horiz then vert) drives the order of this array, not the headers, which are backwards (RH then CH).
  var hasDatabodySpacer   = [[],[]]; // Indexed by scroll direction (horiz then vert), and then pane (0 then 1), not by row/col of panes, i.e.:
                                     // [[leftPaneHasHorizSpacer, rightPaneHasHorizSpacer],
                                     //  [topPaneHasVertSpacer,   bottomPaneHasVertSpacer]]
  var headerSpacerSize    = [];      // [colHeaderSpacerWidth, rowHeaderSpacerHeight].  Note that the headers (CH then RH) and size dimension (width then height) drive the order of this array, not scroll direction, which is backwards (vert then horiz).
                                     // These are the outer sizes.  Each size is 0 if that spacer doesn't exist.  
  var databodySpacerSize  = [[],[]]; // Indexed by size dimension (width then height), and then pane (0 then 1), i.e.: 
                                     // [[widthOfVerticalSpacerInTopPane,     widthOfVerticalSpacerInBottomPane],
                                     //  [heightOfHorizontalSpacerInLeftPane, heightOfHorizontalSpacerInRightPane]]
                                     // These are the outer sizes.  Each size is 0 if that spacer doesn't exist.  
  
  // when edge==0, we're handling the spacers for horizontal scrollbars, including the one in the opposite edge (the rowHeader horiz scrollbar).  And vice versa for edge==1.
  for (var edge=0; edge<2; ++edge) {
    var scrollbarCount = 0;
    var paneCount = (splitSlice[edge]==-1) ? 2 : 3;
    
    if (hasHeaderScrollbar[edge]) {
        ++scrollbarCount;
    }
    
    for (var pane=startPane[edge]; pane<2; ++pane) {
      if (hasDatabodyScrollbar[edge][pane]) {
        headerSpacerPane[edge] = pane; 
        ++scrollbarCount;
      }
    }
    
    // need spacers iff some panes have scrollbars and some don't
    var needSpacers = (this._pivotTablePeer._isPaging ? false : (scrollbarCount>0 && scrollbarCount<paneCount));
    
    if (needSpacers) {
      // These 3 vars are the same for all 3 panes on the edge (header and both databody panes)
      var breadthStart = (edge==1 && rightToLeft) 
                         ? 0 
                         : ptAvailableSize[1-edge] - overallScrollbarSize[1-edge];
      var outerBreadth = overallScrollbarSize[1-edge];
      var innerBreadth = outerBreadth - scrollbarSpacerExtraBreadth[edge];
      
      // Define and set these outside of if-block since they're used to set lengthStart of next pane.
      var lengthStart = 0;
      var outerLength = headerOuterPixelDepth[1-edge];
      
      if (!hasHeaderScrollbar[edge]) {
        // Note: Technically we should shrink the header by the breadth of the scrollbar spacer, but it turns out that 
        // it doesn't harm anything to leave it the same size, so that the "spacer" sits on top of the header and obscures 
        // a portion of it, rather than sitting beside it like a true spacer.  This is because the colHeader (rowHeader) is used for neither horizontal (vertical)
        // scrolling (like the scroller) nor viewport-satisfied logic (like the databody), because the spacer appears at the end, 
        // not the beginning, of the header in both LTR and RTL (so that it doesn't mess up positioning), and since the bidi 
        // logic doesn't require the header to be any particular size (so that it still doesn't mess up positioning).

        var pane = headerSpacerPane[edge];
        var spacer = scrollbarSpacers[edge][pane];
        var innerLength = outerLength - scrollbarSpacerExtraLength[edge];
        
        if (edge==1) {
          spacerRenderInfo[edge][pane] = {spacer:spacer, left:breadthStart, top:lengthStart, innerWidth:innerBreadth, innerHeight:innerLength};
        } else {
          // in RTL for edge 0, the lengthStart used to position the spacer is a function of the regular lengthStart, but we still need the regular value as an input to that function for the next pane, so we need 2 lengthStart vars.
          var lengthStartFinal = (rightToLeft) ? ptAvailableSize[edge] - outerLength - lengthStart : lengthStart;
          spacerRenderInfo[edge][pane] = {spacer:spacer, left:lengthStartFinal, top:breadthStart, innerWidth:innerLength, innerHeight:innerBreadth};
        }
        
        hasHeaderSpacer[edge] = true;
        headerSpacerSize[1-edge] = overallScrollbarSize[1-edge];
      } else {
        hasHeaderSpacer[edge] = false;
        headerSpacerSize[1-edge] = 0;
      }
      
      for (var pane=startPane[edge]; pane<2; ++pane) {
        
        // Set these outside of if-block since they're used to set lengthStart of next pane.
        lengthStart += outerLength; // (start of pane n) = (start of pane n-1) + (length of pane n-1)
        outerLength = databodyPaneRegionSize[edge][pane];
        
        if (!hasDatabodyScrollbar[edge][pane]) {
        
          spacer = scrollbarSpacers[edge][pane];
          if (pane==1) 
            outerLength -= overallScrollbarSize[edge]; // when last pane of DB has a vert (horiz) SB spacer, overallScrollbarHeight (Width) is the height (width) of the corner spacer if it is visible, and zero otherwise.  We want to decrease height (width) by height (width) of corner spacer if it is visible.
          innerLength = outerLength - scrollbarSpacerExtraLength[edge];
          
          if (edge==1) {
            spacerRenderInfo[edge][pane] = {spacer:spacer, left:breadthStart, top:lengthStart, innerWidth:innerBreadth, innerHeight:innerLength};
          } else {
            // in RTL for edge 0, the lengthStart used to position the spacer is a function of the regular lengthStart, but we still need the regular value as an input to that function for the next pane, so we need 2 lengthStart vars.
            lengthStartFinal = (rightToLeft) ? ptAvailableSize[edge] - outerLength - lengthStart : lengthStart;
            spacerRenderInfo[edge][pane] = {spacer:spacer, left:lengthStartFinal, top:breadthStart, innerWidth:innerLength, innerHeight:innerBreadth};
          }
          
          hasDatabodySpacer[edge][pane] = true;
          databodySpacerSize[1-edge][pane] = overallScrollbarSize[1-edge];
        } else {
          hasDatabodySpacer[edge][pane] = false;
          databodySpacerSize[1-edge][pane] = 0;
        }
      }
    } else {
      hasHeaderSpacer[edge] = false;
      headerSpacerSize[1-edge] = 0;
      for (var pane=startPane[edge]; pane<2; ++pane) {
        hasDatabodySpacer[edge][pane] = false;
        databodySpacerSize[1-edge][pane] = 0;
      }
    }
  }
  
  // if bottom right databody pane (the one that can have both horiz and vert scrollbars) has 2 scrollbar spacers, or 1 spacer and 1 scrollbar, then show corner spacer
  if ((hasDatabodySpacer[0][1] && hasOverallScrollbar[1]) || 
      (hasDatabodySpacer[1][1] && hasOverallScrollbar[0]))
  {
      var cornerSpacerLeft = (rightToLeft) ? 0 : ptAvailableSize[0] - overallScrollbarSize[0];
      var cornerSpacerTop = ptAvailableSize[1] - overallScrollbarSize[1];
      var cornerSpacerOuterWidth = overallScrollbarSize[0];
      var cornerSpacerInnerWidth = cornerSpacerOuterWidth - cornerScrollbarSpacerExtraSize[0]; 
      var cornerSpacerOuterHeight = overallScrollbarSize[1];
      var cornerSpacerInnerHeight = cornerSpacerOuterHeight - cornerScrollbarSpacerExtraSize[1];
      
      var cornerSpacerRenderInfo = {spacer:this._cornerScrollbarSpacer, left:cornerSpacerLeft, top:cornerSpacerTop, innerWidth:cornerSpacerInnerWidth, innerHeight:cornerSpacerInnerHeight};
      var hasCornerSpacer = true; // display a scrollbar spacer in the bottom right corner of the bottom right databody pane where the horiz and vert scrollbar/ scrollbar spacers meet
  } else {
      hasCornerSpacer = false;
  }
this.checkpointTimer(0, "Scrollbar spacer calcs");
  
  
  // -- position the PT divs (on DOM) --
  
  var top = headerOuterPixelDepth[0]; // rowHeader and databody top is where colHeader ends
  for (var rowPane=startPane[1]; rowPane<2; ++rowPane) { 
    this.setPos(AdfPivotTableUtils.ROW_EDGE, top, rowHeaders[rowPane]);
    for (var colPane=startPane[0]; colPane<2; ++colPane) { 
      this.setPos(AdfPivotTableUtils.ROW_EDGE, top, databodies[rowPane][colPane]);
      if (rowPane+colPane>0) // top left databody pane has no scroller
        this.setPos(AdfPivotTableUtils.ROW_EDGE, top, scrollers[rowPane][colPane]);
    }
/////////////// TODO: DO WE NEED TO INCREMENT BY AN "OUTER" HEIGHT INSTEAD, I.E. 1 PIXEL MORE?
    top += databodyPaneRegionSize[1][rowPane] + pagingControlHeight[1][0]; // this will be used iff there is a second pass of outer loop, in which case that second pass is for rowPane 1, for which we increment "top" by height of top pane.
  }
  
  if (hasStatusBar) {
    this.setPos(AdfPivotTableUtils.ROW_EDGE, ptAvailableSize[1], statusBar);
    this.setPos(AdfPivotTableUtils.COLUMN_EDGE, 0, statusBar, true);
  }
  
  // Note: the setPos calls in this block must set left, not horizPos, so pass true to 4th arg when edge is column edge.
  if (rightToLeft) {
    var colHeaderLeft = 0;                                                    // b/c we don't shrink the colHeader when it has a scrollbar spacer
    var databodyLeft  = overallScrollbarSize[0];                              // b/c we shrink the databody when there's a databody scrollbar OR spacer
    var scrollerLeft  = [databodySpacerSize[0][0], databodySpacerSize[0][1]]; // b/c we shrink the scroller iff it has a scrollbar spacer
    for (var colPane=1; colPane>=startPane[0]; --colPane) { // iterate backwards, which is left to right
      this.setPos(AdfPivotTableUtils.COLUMN_EDGE, colHeaderLeft, colHeaders[colPane], true);
      for (var rowPane=startPane[1]; rowPane<2; ++rowPane) { 
        this.setPos(AdfPivotTableUtils.COLUMN_EDGE, databodyLeft, databodies[rowPane][colPane], true);
        if (rowPane+colPane>0) // top left databody pane (which is top right in RTL) has no scroller
          this.setPos(AdfPivotTableUtils.COLUMN_EDGE, scrollerLeft[rowPane], scrollers[rowPane][colPane], true);
      }
/////////////// TODO: ARE WE OFF BY 1PX DUE TO "INNER" VS. "OUTER" WIDTH ISSUES?
      colHeaderLeft += databodyPaneRegionSize[0][colPane]; // this will be used iff there is a second pass of outer loop, in which case that second pass is for colPane 0, for which all "lefts" are the size of pane 1.
      databodyLeft = colHeaderLeft;
      scrollerLeft[0] = colHeaderLeft;
      scrollerLeft[1] = colHeaderLeft;
    }

    for (var rowPane=startPane[1]; rowPane<2; ++rowPane) { 
      rowHeaders[rowPane]._left = databodyRegionSize[0];  // don't bother setting the style, since it already has the correct horizontal position
    }
  } else {
    var left = headerOuterPixelDepth[1]; // colHeader and databody left is where rowHeader ends
    for (var colPane=startPane[0]; colPane<2; ++colPane) { 
      this.setPos(AdfPivotTableUtils.COLUMN_EDGE, left, colHeaders[colPane], true);
      for (var rowPane=startPane[1]; rowPane<2; ++rowPane) { 
        this.setPos(AdfPivotTableUtils.COLUMN_EDGE, left, databodies[rowPane][colPane], true);
        if (rowPane+colPane>0) // top left databody pane has no scroller
          this.setPos(AdfPivotTableUtils.COLUMN_EDGE, left, scrollers[rowPane][colPane], true);
      }
/////////////// TODO: DO WE NEED TO INCREMENT BY AN "OUTER" WIDTH INSTEAD, I.E. 1 PIXEL MORE?
      left += databodyPaneRegionSize[0][colPane]; // this will be used iff there is a second pass of outer loop, in which case that second pass is for colPane 1, for which we increment "left" by width of left pane.
    }

    for (var rowPane=startPane[1]; rowPane<2; ++rowPane) { 
      // TBD: seems that rowHeader.offsetLeft is -1, not 0, in FF but not IE.  Should we set this expando to offsetLeft instead, or is 0 actually better?  
      rowHeaders[rowPane]._left = 0;  // don't bother setting the style, since it already has the correct horizontal position
    }
  }

  //AdfAssert.assert(AdfPivotTableUtils.domAssertsOff || (rowHeaders[1]._left == rowHeaders[1].offsetLeft));
  //AdfLogger.LOGGER.severe("rowHeaders[1].offsetLeft = " + rowHeaders[1].offsetLeft + ", rowHeaders[1]._left = " + rowHeaders[1]._left);
this.checkpointTimer(0, "position pt divs");


  // -- size the PT divs (on DOM), including showing/hiding spacers --
  
  for (var edge=0; edge<2; ++edge) {
    var scroller;
    var scrollerSize;
    var scrollerUsableSize;
    var usableSizeAttr;
  
    if (splitSlice[edge] != -1) {
      // width for the horizontal-scrolling-only scroller and height for the vertical-scrolling-only scroller is just the width/height of that databody pane.
      // Each of these scrollers is used only if the corresponding edge is split
      // edge  (pane)  scroller     size    canvasSize           maxScrollPos
      // 0     0       bottomLeft   width   leftPaneCanvasWidth  leftPaneMaxScrollLeft
      // 1     0       topRight     height  topPaneCanvasHeight  topPaneMaxScrollTop
      scroller = this._databodyScrollersByEdgeAndPane[edge][0];
      scrollerSize = databodyPaneRegionSize[edge][0]; // For both widths and heights, scrollerOuterSize = scrollerInnerSize, since scrollerExtraSize = 0, so just call it scrollerSize.
      this.setCachedSize(scroller, scrollerSize, edge);

      scrollerUsableSize  = scrollerSize;
      usableSizeAttr = this._usableSizeAttr[edge];   // "_usableWidth" or "_usableHeight" 
      scroller[usableSizeAttr]  = scrollerUsableSize; // TODO: is this ever used for this size dimension?
      
      this._maxScrollPos[edge][0]  = Math.max(0, canvasSize[edge][0] - scrollerUsableSize); // maxScrollPos is indexed by scroll direction (horiz then vert), and then pane (0 then 1), i.e. 
                                                                                            // [[leftPaneMaxScrollLeft, rightPaneMaxScrollLeft],
                                                                                            //  [topPaneMaxScrollTop,   bottomPaneMaxScrollTop]]
    }
    
    // width (height) for a scroller that can scroll vertically (horizontally), is the width (height) of that databody pane minus the width (height) of any vertical (horizontal) spacer in that pane:
    for (var pane=startPane[edge]; pane<2; ++pane) {
      // edge  pane  scroller     size    canvasSize              maxScrollPos
      // 0     0     bottomLeft   height  
      // 0     1     bottomRight  height  bottomPaneCanvasHeight  bottomPaneMaxScrollTop
      // 1     0     topRight     width   
      // 1     1     bottomRight  width   rightPaneCanvasWidth    rightPaneMaxScrollLeft
      scroller = this._databodyScrollersByEdgeAndPane[edge][pane];
      scrollerSize = databodyPaneRegionSize[1-edge][1] - databodySpacerSize[1-edge][pane];  // make room for the scrollbar spacer.  Unlike the header, it is important to shrink the databody scroller to avoid various problems.  For both widths and heights, scrollerOuterSize = scrollerInnerSize, since scrollerExtraSize = 0, so just call it scrollerSize.
      this.setCachedSize(scroller, scrollerSize, 1-edge);

      scrollerUsableSize  = scrollerSize - databodyScrollbarSize[1-edge][pane];
      usableSizeAttr = this._usableSizeAttr[1-edge];   // "_usableHeight" or "_usableWidth"
      scroller[usableSizeAttr]  = scrollerUsableSize;
      
      if (pane==1)
        this._maxScrollPos[1-edge][1]  = Math.max(0, canvasSize[1-edge][1] - scrollerUsableSize); // maxScrollPos is indexed by scroll direction (horiz then vert), and then pane (0 then 1), i.e. 
                                                                                                  // [[leftPaneMaxScrollLeft, rightPaneMaxScrollLeft],
                                                                                                  //  [topPaneMaxScrollTop,   bottomPaneMaxScrollTop]]
    }
  }
  
  for (var edge=0; edge<2; ++edge) {
    for (var pane=startPane[edge]; pane<2; ++pane) {
      this.setCanvasSizeByEdgeAndPane(edge, pane, canvasSize[edge][pane]);
    }
  }
  
  //console.log("corner width/ht: "+ headerOuterPixelDepth[1] + ", " + headerOuterPixelDepth[0]);

  this.setCachedSize(corner, headerOuterPixelDepth[1], AdfPivotTableUtils.COLUMN_EDGE); // rowHeaderOuterWidth  = cornerOuterWidth  = cornerInnerWidth,  since cornerExtraWidth  = 0.
  this.setCachedSize(corner, headerOuterPixelDepth[0], AdfPivotTableUtils.ROW_EDGE); // colHeaderOuterHeight = cornerOuterHeight = cornerInnerHeight, since cornerExtraHeight = 0.

///////////// TODO: which of these arrays can just be scalar vars declared inside the loop, b/c they're never used outside the loop?
  var headerOuterPixelExtent = databodyPaneRegionSize; // Alias for the databody array, whose sizes are the same.  Indexed by edge (width/height), then by pane, i.e.:
                                                       // [[colHeaderLeftPaneOuterWidth, colHeaderRightPaneOuterWidth],
                                                       //  [rowHeaderTopPaneOuterHeight, rowHeaderBottomPaneOuterHeight]]
  var headerInnerPixelExtent = [[], []]; // Indexed like headerOuterPixelExtent.
  
  for (var edge=1; edge>=0; --edge) { // iterate backwards to match order we had before this was in a for-loop
    for (var pane=startPane[edge]; pane<2; ++pane) {
      headerInnerPixelExtent[edge][pane] = headerOuterPixelExtent[edge][pane] - headerExtraPixelExtent[edge];
      
//      console.log((edge ? "row header width/ht" : "col header ht/width") + " for pane " + pane + ": " + headerInnerPixelDepth[edge] + ", " + headerInnerPixelExtent[edge][pane]);

      this.setCachedSize(headers[edge][pane], headerInnerPixelDepth[edge]       , 1-edge);
      this.setCachedSize(headers[edge][pane], headerInnerPixelExtent[edge][pane], edge);
    }
  }

  // For each pane, make databody outer size the same size as databody region, except decreased by scrollbar size (which is 0 iff 
  // there are no scrollbars on that edge) so that the scroller's scrollbars or scrollbar spacers are visible if they are present.  
  for (var edge=0; edge<2; ++edge) { // iterate backwards to match order we had before this was in a for-loop
    for (var pane=startPane[edge]; pane<2; ++pane) {
      var paneScrollbarSize = (pane) ? overallScrollbarSize[edge] : 0; // top (left) pane has no horiz (vert) scrollbars
      var databodyOuterSize = databodyPaneRegionSize[edge][pane]  - paneScrollbarSize;
      var databodyInnerSize  = databodyOuterSize - databodyExtraSize[edge];
      for (var otherPane=startPane[1-edge]; otherPane<2; ++otherPane) {
        var databody = edge ? databodies[pane][otherPane] : databodies[otherPane][pane];
        this.setCachedSize(databody, databodyInnerSize, edge);
      }
    }
  }
  
  if (hasStatusBar) {
      this.setCachedSize(statusBar, ptInnerSize[0], AdfPivotTableUtils.COLUMN_EDGE); // don't change the height the browser calced for it
  }
  
  // Note that spacers for unused panes (first pane of an unsplit edge) have a null entry in the array and so are getting hidden as desired.
  for (var edge=0; edge<2; ++edge) {
    for (var pane=0; pane<2; ++pane) { // loop starts at 0, not startPane[edge], so that spacers for unused panes are hidden.  Don't copy this elsewhere.
      if (spacerRenderInfo[edge][pane]) {
        this.displayScrollbarSpacer(spacerRenderInfo[edge][pane]);
      } else { 
        scrollbarSpacers[edge][pane].style.display="none";
      }
    }
  }

  if (hasCornerSpacer) {
      this.displayScrollbarSpacer(cornerSpacerRenderInfo);
  } else {
      this._cornerScrollbarSpacer.style.display="none";
  }
this.checkpointTimer(0, "size PT divs, show/hide spacers");


  // -- Set scrollability of divs, and size inner (and if sizing==auto, outer) container --

  // This code accomplishes 2 things.  (1) It handles the "scroller special case" (see below) by forcing 2 scrollbars on the bottom right scroller 
  // in that case.  (2) When this case doesn't apply, it sets the scrollbar status to the one that would have been chosen by a 
  // correctly behaving browser if overflow:auto were selected.  We don't actually use overflow:auto, because IE seems to mess 
  // up when overflow:auto is programmatically set after overflow:scroll has been in effect.
  // "Scroller special case" explanation:  If, in both dimensions, the bottom right scroller (the one with both a vertical and 
  // horizontal scrollbar) is between 0 and scrollbarSize px bigger than the canvas, then there are two logically consistent 
  // choices: 0 scroller scrollbars, or 2 scroller scrollbars.  When overflow is set to auto, the browser always chooses 0.  
  // When none of the other header or databody panes has a scrollbar, this is the correct choice.  However, if at least one 
  // other pane has a scrollbar, then we need the scroller to have 2 scrollbars, not 0, meaning that the browser makes the 
  // wrong choice.  In this case, we force the scrollbars to appear by setting "overflow: scroll" on the scroller.
  if (!AdfPivotTableUtils.isMobileTouchDevice() && !this._pivotTablePeer._isPaging) {
    for (var edge=0; edge<2; ++edge) {
      var overflowAttr = edge ? "overflowY" : "overflowX";
      for (var pane=startPane[edge]; pane<2; ++pane) {
        var overflow = hasDatabodyScrollbar[edge][pane] ? "scroll" : "hidden";
        var scroller = this._databodyScrollersByEdgeAndPane[edge][pane];
        scroller.style[overflowAttr] = overflow;
      }
    }
  }
  
/////////////////////////////////////// TODO: do this init on server instead, just like Jay did w/ databody div for iOS:
  if (!this._scrollerInit) {
    if (!AdfPivotTableUtils.isMobileTouchDevice()) {
      // Top pane's scroller should not have a horiz scrollbar, and left pane's scroller should not have a vert scrollbar.
      // Also, pane 0 of each header should not have a scrollbar
      scrollers[0][1].style.overflowX = "hidden";
      scrollers[1][0].style.overflowY = "hidden";
      this._headers[0][0].style.overflow = "hidden";
      this._headers[1][0].style.overflow = "hidden";
    }
    this._scrollerInit = true;
  }
  
  this.setCachedSize(pt, ptInnerSize[0], 0);
  if (splitSlice[AdfPivotTableUtils.ROW_EDGE] == -1)
    this.setCachedSize(pt, ptInnerSize[1], 1);
  else
    this.setCachedSize(pt, ptInnerSize[1] + pagingControlHeight[1][0], 1);
  
  if (this._pivotTablePeer._isPaging)
  {
    // TBD: See if any of these styles can be cleaned up / removed in the future
    if (splitSlice[AdfPivotTableUtils.COLUMN_EDGE] == -1)
    {
      this._pagingControl[0][1].style.position = 'absolute';
      this._pagingControl[0][0].style.visibility = 'hidden';
      this._pagingControl[0][1].style.width = databodyPaneRegionSize[0][1] + 'px';
      if (!this._rightToLeft)
        this._pagingControl[0][1].style.marginLeft = (corner.offsetWidth) + 'px';
      else
        this._pagingControl[0][1].style.marginRight = (corner.offsetWidth) + 'px';
    }
    else
    {
      this._pagingControl[0][0].style.position = 'absolute';
      this._pagingControl[0][0].style.width = databodyPaneRegionSize[0][0] + 'px';
      if (!this._rightToLeft)
        this._pagingControl[0][0].style.marginLeft = (corner.offsetWidth) + 'px';
      else
        this._pagingControl[0][0].style.marginRight = (corner.offsetWidth) + 'px';
      this._pagingControl[0][0].style.visibility = 'visible';
      
      this._pagingControl[0][1].style.position = 'absolute';
      this._pagingControl[0][1].style.width = databodyPaneRegionSize[0][1] + 'px';
      if (!this._rightToLeft)
        this._pagingControl[0][1].style.marginLeft = (corner.offsetWidth + databodyPaneRegionSize[0][0]) + 'px';
      else
        this._pagingControl[0][1].style.marginRight = (corner.offsetWidth + databodyPaneRegionSize[0][0]) + 'px';
    }
    if (splitSlice[AdfPivotTableUtils.ROW_EDGE] == -1)
    {
        this._pagingControl[1][0].parentNode.style.visibility = 'hidden';
    }
    else
    {
      top = headerOuterPixelDepth[0] + databodyPaneRegionSize[1][0] + pagingControlHeight[1][0] + 1;
      var pagingControl = this._pagingControl[1][0].parentNode;
      pagingControl.style.top = top + 'px';
      pagingControl.style.visibility = 'visible';
    }
  }
  
  if (bAutosize)
  {
    this.setCachedSize(this._container, ptInnerSize[0] + containerExtraSize[0], AdfPivotTableUtils.COLUMN_EDGE);  // the outer frame is 2 px bigger than the inner, due to borders on the inner
    this.setCachedSize(this._container, ptInnerSize[1] + containerExtraSize[1] + pagingControlHeight[0][1] + pagingControlHeight[1][0] + pagingControlHeight[1][1], AdfPivotTableUtils.ROW_EDGE); // the outer frame is 2 px bigger than the inner, due to borders on the inner
  }
this.stopTimer(0, "Set overflow, size containers", "All sizing"); // stop timer here because remainder is just testing code (which is expensive and won't run in real life)


  // -- compare predicted to actual scrollbar sizes --
  
// TODO: COMMENTED-OUT ASSERT BLOCK IS UPDATED FOR NEW VAR NAMES, BUT NOT TESTED, AND DIDN'T ADD LOOPS TO TEST THE OTHER PANES
/*
  if (!AdfPivotTableUtils.domAssertsOff) {
    // TBD - Check if there are scrollbars before fetching sizes!
      var databodyScrollbarWidth2  = scrollers[1][1].offsetWidth  - scrollers[1][1].clientWidth;  // is 0 iff there's no scrollbar.  Scroller has no borders, so no need to subtract border width
      var databodyScrollbarHeight2 = scrollers[1][1].offsetHeight - scrollers[1][1].clientHeight; // is 0 iff there's no scrollbar.  Scroller has no borders, so no need to subtract border height
      var colHeaderScrollbarWidth2  = headerInnerPixelExtent[0][1] - colHeaders[1].clientWidth; // don't need to add padding because it is 0
      var rowHeaderScrollbarHeight2 = headerInnerPixelExtent[1][1] - rowHeaders[1].clientHeight; // don't need to add padding because it is 0
      if(databodyScrollbarSize[0][1]  != databodyScrollbarWidth2  || databodyScrollbarSize[1][1]  != databodyScrollbarHeight2 || 
         headerScrollbarSize[0] != colHeaderScrollbarWidth2 || headerScrollbarSize[1] != rowHeaderScrollbarHeight2) 
         AdfAssert.assert(AdfPivotTableUtils.domAssertsOff || false); //AdfLogger.LOGGER.severe("PROBLEM!!!"); 
    
      // Header has a scrollbar iff it is smaller than the desired size, which was just big enough to hold the contents 
      // without needing a scrollbar.  (We never make it bigger than the desired size.)  The calc is this simple because 
      // the row (col) header never has a vertical (horizontal) scrollbar taking up space.  We use this calc rather than 
      // fetching offset-client, which forces sizing.  
      var hasRowHeaderScrollbar2 = (headerOuterPixelDepth[1] != desiredHeaderOuterPixelDepth[1]);
      var hasColHeaderScrollbar2 = (headerOuterPixelDepth[0] != desiredHeaderOuterPixelDepth[0]);
      if (hasHeaderScrollbar[0] != hasRowHeaderScrollbar2 || hasHeaderScrollbar[1] != hasColHeaderScrollbar2) 
          AdfAssert.assert(AdfPivotTableUtils.domAssertsOff || false); 
  }
  
  AdfAssert.assert(AdfPivotTableUtils.assertsOff || scrollers[1][1]._width  - databodyScrollbarSize[0][1] == this._databodies[1][1]._width  + databodyExtraSize[0]);
  AdfAssert.assert(AdfPivotTableUtils.assertsOff || scrollers[1][1]._height - databodyScrollbarSize[1][1] == this._databodies[1][1]._height + databodyExtraSize[1]);
*/
}

/**
 * See JSDoc for ResizeNotify.
 * 
 * @return true iff sizing="auto" and we are not being stretched by our parent.
 */
AdfPivotTableUtils.prototype.isShrinkToFit = function() {   
    return AdfPivotTableUtils.isShrinkToFit(this._pivotTable.getSizing(), this._container);
}

/**
 * This is the static version for use by PrintablePage.  Other callers should find the regular version more convenient.
 * See JSDoc for ResizeNotify.
 * 
 * @return true iff sizing="auto" and we are not being stretched by our parent.
 */
AdfPivotTableUtils.isShrinkToFit = function(sizing, ptContainer) {   
    var bAutosize = sizing=="auto";
    
        // Don't auto-size (shrink to fit) if we were stretched by a splitter, panelStretchLayout, or other component.
        // Per email with Matt Cooper:  This way of asking "is parent stretching us" should be fine because we tell people in our layout basics that they should never specify "position" as an inlineStyle.  Even if someone knew what they were doing and they specified position:absolute, your code would then be compatible with it.
        // 
        // Strange browser bug: [this happened back when the parentStretchesItsChildren expression was used in the condition of an if-stmt rather than in the return stmt] sometimes the if-clause is not entered even though this boolean condition is true.  (While debugger is stopped at this line, you can paste the condition verbatim to the Firebug console, which confirms it's true, but control doesn't enter the if-clause.)
        // To hopefully avoid this browser bug, I'm changing several things:
        // First, I'm setting the condition on a var, and referencing that var in the if-condition, rather than having the logic live directly in the if-condition. 
        // Second, I'm removing all line-breaks from the condition (at the expense of readability).  
        // Third, I'm adding lots of parentheses.  
        // Fourth, I'm adding braces around the single line body of the if stmt.
    if (bAutosize) {
        var containerStyle = ptContainer['style']; // formerly used AdfAgent.AGENT.getComputedStyle(this._container), but I confirmed with Matt Cooper on 2/24/12 that the much cheaper inlineStyle is fine.
        var parentStretchesItsChildren = ((containerStyle.position == "absolute") && (containerStyle.top) && (containerStyle.top.length>0) && (containerStyle.bottom) && (containerStyle.bottom.length > 0));
        return !parentStretchesItsChildren;
    } else {
        return false;
    }
}

// ByEdgeAndPane naming is for consistency with the ByEdgeAndPane naming convention used by this._databodyScrollersByEdgeAndPane, etc.
AdfPivotTableUtils.prototype.getCanvasSizeByEdgeAndPane = function(edge, pane) {
    return (edge == AdfPivotTableUtils.ROW_EDGE) 
           ? (pane == 1) ? this._canvasHeight1 : this._canvasHeight0
           : (pane == 1) ? this._canvasWidth1 : this._canvasWidth0;
}

// ByEdgeAndPane naming is for consistency with the ByEdgeAndPane naming convention used by this._databodyScrollersByEdgeAndPane, etc.
AdfPivotTableUtils.prototype.setCanvasSizeByEdgeAndPane = function(edge, pane, size) {
    var sizeString = size + "px";
    var startPane = this._startPane;
    
    if (edge == AdfPivotTableUtils.ROW_EDGE) {
        if (pane == 1) {
            this._canvasHeight1 = size;
        } else {
            this._canvasHeight0 = size;
        }
        
        // Note: For non-touchscreen, we probably only need to set the size on the last pane's canvas.  Setting width on top pane's canvas, or setting height on left pane's canvas, shouldn't be necessary to make scrolling work, since top pane's scroller only scrolls vertically, and left pane's scroller only scrolls horizontally.  Currently setting size of both canvases for consistency, to avoid any unanticipated problems.  
        // For touchscreen, we do need to set the size in all panes.
        // TBD: as a performance tweak, we could try not setting the first pane's canvas size for non-touchscreen.
        for (var otherPane=startPane[1-edge]; otherPane<2; ++otherPane) { // iterate over other edge's panes
            if (pane+otherPane>0 || AdfPivotTableUtils.isMobileTouchDevice()) // top left databody pane has no scroller, but for touchscreen it does have a databody canvas
                this.setCachedSize(this.getScrollerCanvas(pane, otherPane), size, edge);
        }
    } else { 
        if (pane == 1) {
            this._canvasWidth1 = size;
        } else {
            this._canvasWidth0 = size;
        }
        
        // TBD: same as in row edge case.
        for (var otherPane=startPane[1-edge]; otherPane<2; ++otherPane) { // iterate over other edge's panes
            if (pane+otherPane>0 || AdfPivotTableUtils.isMobileTouchDevice()) // top left databody pane has no scroller, but for touchscreen it does have a databody canvas
                this.setCachedSize(this.getScrollerCanvas(otherPane, pane), size, edge);
        }
    }
}

AdfPivotTableUtils.prototype.getHeaderBlocksSize = function(headerBuffer, sizeAttr) {
    var length = headerBuffer.length;
    var size = 0;
    for (var i=0;i<length;i++) {
        size += headerBuffer[i][sizeAttr];
    }
    return size;
}

AdfPivotTableUtils.prototype.getFirstHeaderBlockSize = function(headerBuffer, sizeAttr) {
    return headerBuffer[0][sizeAttr];
}

/**
 * Precond: Column header buffer must be non-empty (without which it doesn't make much 
 * sense to ask whether there is a scrollbar).
 */
AdfPivotTableUtils.prototype.hasColHeaderScrollbar = function() {
    return (this._colHeaders[1]['_height'] < this._colHeaderBuffers[1][0]['_height']); // the calc is this simple because the col header can't have a horizontal scrollbar
}

/**
 * Returns width of col header scrollbar if it has one, and 0 otherwise
 * 
 * Precond: Column header buffer must be non-empty (without which it doesn't make much sense to ask about its scrollbar).
 */
// TODO: Can't we just have sizePT() cache this value, and have this method refer to it, to avoid getting offsetWidths each time?  
AdfPivotTableUtils.prototype.getColHeaderScrollbarWidth = function() {
    if (this.hasColHeaderScrollbar()) {
//        if (!this._scrollbarWidth) { // only access the DOM to calc this at most once per session
//            this._scrollbarWidth = this._colHeaders[1].offsetWidth - this._colHeaders[1].clientWidth - AdfPivotTableUtils.HEADER_EXTRA_PIXEL_EXTENT[0]; // actually should subtract only the border, not the entire extraWidth, but in this case the extraWidth consists of only border.  
//        }
//        return this._scrollbarWidth;
        // TBD: we commented out the code that caches the scrollbarWidth just in case it changes underneath us--e.g. user changes OS setting.  How does this affect performance?  How paranoid do we want to be here?  
        return this._colHeaders[1].offsetWidth - this._colHeaders[1].clientWidth - AdfPivotTableUtils.HEADER_EXTRA_PIXEL_EXTENT[0]; // TODO: actually should subtract only the border, not the entire extraWidth, but in this case the extraWidth consists of only border.  
    } else {
        return 0;
    }
}

// size the blocks and shift the tables in the blocks by 1px, so the placeholder cells don't show.  
AdfPivotTableUtils.prototype.sizeNewHTMLBlocks = function() { ///////////////////////////////////////// DONE INCLUDING CALLEES.
    this.coordinateNewBlockSizes();
    this.setBlockTableStyles(true);

    var buffer, block, length;
    var isIE7 = (AdfAgent.AGENT.getPlatform()==AdfAgent.IE_PLATFORM) && !(AdfAgent.AGENT.getVersion() >= 8);
    
    if (isIE7) {
        this.shiftAllPlaceholderCells(true);
    } else {
        // Original comment: In Mozilla, we need to set the size of the row header block to be equal to the size of the table in the row header block
        // New comment: Webkit has always used this code path too.  Previously IE8+ used neither code path, which caused bug 9532207, since sizePivotTable uses rowHeaderBlock._width, which was undefined since this block was skipped.  To fix this, we're routing IE8+ thru this code path too.  It's unclear why IE7 doesn't also need this code to avoid the same bug, but there may be a simple explanation.  In any case, it works so I'm not going to change it after all this time.
        for (var pane=this._startPane[1]; pane<2; ++pane) {
            buffer = this._rowHeaderBuffers[pane]
            length = buffer.length;
            for (i=0; i<length; i++) {
                block = buffer[i]; 
                if (!block['_placeholder'] && block['_new'])
                    this.setCachedSize(block, block.childNodes[0].offsetWidth, AdfPivotTableUtils.COLUMN_EDGE); 
            }
        }
    }
}

AdfPivotTableUtils.prototype.setBlockTableStyles = function(bNewBlocksOnly) { //////////////////// DONE
    var colHeaderBlock = this._colHeaderBuffers[1][0];
    var blockTableClass = AdfAgent.AGENT.getAttribute(colHeaderBlock.childNodes[0], "_className");
    
    // mark new header tables as fixed
    for (var edge=0; edge<2; ++edge) {
        var edgeBuffers = this._buffers2[edge];
        for (var pane=this._startPane[edge]; pane<2; ++pane) {
            var buffer = edgeBuffers[pane];
            var length = buffer.length;
            for (i=0; i<length; i++) {
                block = buffer[i]; 
                if (!block['_placeholder'] && (block['_new'] || !bNewBlocksOnly)) {
                     block.childNodes[0].className = blockTableClass;
                }
            }
        }
    }

    // mark new databody tables as fixed
    for (var rowPane=this._startPane[1]; rowPane<2; ++rowPane) {
        var rowOfBuffers = this._databodyBuffers[rowPane];
        for (var colPane=this._startPane[0]; colPane<2; ++colPane) {
            buffer = rowOfBuffers[colPane];
            var numBlockRows = buffer.length;
            for (var i=0; i<numBlockRows; i++) {
                var rowOfBlocks = buffer[i];
                var numBlocks = rowOfBlocks.length;
                for (var j=0; j<numBlocks; j++) {
                    block = rowOfBlocks[j];
                    if (block && (block['_new'] || !bNewBlocksOnly)) {
                        block.childNodes[0].className = blockTableClass;
                    }
                }
            }
        }
    }
    
    // if persistent layer labels enabled, mark layer label table as fixed
    if (this._layerLabelBlock && (this._layerLabelBlock['_new'] || !bNewBlocksOnly))
        this._layerLabelBlock.childNodes[0].className = blockTableClass;
}

// this is called when width of a column header has changed
AdfPivotTableUtils.prototype.coordinateColumnBlockSizes = function() 
{
    // coordinate the widths of new blocks
    var colHeaderBlocks = [];
    for (var colPane=this._startPane[0]; colPane<2; ++colPane) 
    {
        var colHeaderBuffer = this._colHeaderBuffers[colPane];
        var length = colHeaderBuffer.length;
        for (var iCol=0; iCol<length; iCol++) {
            var colHeaderBlock = colHeaderBuffer[iCol]; 
            if (!colHeaderBlock['_placeholder']) {
                colHeaderBlocks.push(colHeaderBlock);
            }
        }

        this.coordinateColumnHeaderRowHeights(colHeaderBlocks, false);
    }
}

// this is called when width of a row header has changed
AdfPivotTableUtils.prototype.coordinateRowBlockSizes = function() 
{
    var existingDatabodyBlocks = [];    
    for (var rowPane=this._startPane[1]; rowPane<2; ++rowPane) 
    {
        var rowHeaderBuffer = this._rowHeaderBuffers[rowPane];
        var length = rowHeaderBuffer.length;
        for (var iRow=0; iRow<length; iRow++) 
        {
            var rowHeaderBlock = rowHeaderBuffer[iRow]; 
            for (var colPane=this._startPane[0]; colPane<2; ++colPane) 
            {
                var databodyBuffer = this._databodyBuffers[rowPane][colPane];
                this.coordinateHTMLBlockRowHeights(rowHeaderBlock, databodyBuffer[iRow], existingDatabodyBlocks, iRow);            
            }
        }
    }
}

AdfPivotTableUtils.prototype.coordinateNewBlockSizes = function() { /////////////////////// DONE
    var isIE = AdfAgent.AGENT.getPlatform()==AdfAgent.IE_PLATFORM;
    
    // coordinate the widths of new blocks
    var colHeaderBlocks = [];
    for (var colPane=this._startPane[0]; colPane<2; ++colPane) {
        var colHeaderBuffer = this._colHeaderBuffers[colPane]
        var length = colHeaderBuffer.length;
        for (var iCol=0; iCol<length; iCol++) {
            var colHeaderBlock = colHeaderBuffer[iCol]; 
            if (!colHeaderBlock['_placeholder']) {
                var databodyBlocks = [];
                for (var rowPane=this._startPane[1]; rowPane<2; ++rowPane) {
                    var databodyBuffer = this._databodyBuffers[rowPane][colPane];
                    var numBlockRows = databodyBuffer.length;
                    for (var iRow=0; iRow<numBlockRows; iRow++) {
                        var block = databodyBuffer[iRow][iCol];
                        if (block && block['_new'])
                            databodyBlocks.push(block);
                    }
                }
                if (colHeaderBlock['_new']) { // this should only happen when all blocks in the column are new. 
                    this.coordinateHTMLBlockColumnWidths(colHeaderBlock, databodyBlocks);
                } else { 
                    // For vertical short scrolls and fill-viewports, where a new databody block is added to an existing column of blocks (so that colHeaderBlock._new is false), we don't need to coordinate widths for that column of blocks, because the server set already the existing column widths on all the columns of the new block so it will match.
                    // Bug 13840806: However, we do need to set the _width expando on the new databody block, because the RTL logic uses it.  Normally the coord-width method takes care of this. Technically we could just set the expando for RTL only, but since we always assume that these _height/_width expandos are in place, continuing to omit it would just invite further bugs.
                    this.setWidthExpandosOnDatabodyBlocks(colHeaderBlock, databodyBlocks);
                }
                colHeaderBlocks.push(colHeaderBlock);
            }
        }
    }

    for (var rowPane=this._startPane[1]; rowPane<2; ++rowPane) {
        var rowHeaderBuffer = this._rowHeaderBuffers[rowPane];
        var length = rowHeaderBuffer.length;
        for (var iRow=0; iRow<length; iRow++) {
            var rowHeaderBlock = rowHeaderBuffer[iRow]; 
            var databodyBlocks = [];
            var existingDatabodyBlocks = [];    
            for (var colPane=this._startPane[0]; colPane<2; ++colPane) {
                var databodyBuffer = this._databodyBuffers[rowPane][colPane];
                var numBlockCols = databodyBuffer[iRow].length;
                for (var iCol=0; iCol<numBlockCols; iCol++) {
                    var block = databodyBuffer[iRow][iCol];
                    if (block && block['_new']) {
                        // HACK ALERT - in RTL, in order to have the databody block show up in the databody, we 
                        // set the position of the block's table to be absolute.  Somehow, the position needs to be 
                        // absolute before fetching heights during coordinateHTMLBlockRowHeights
                        // TBD: is this hack still needed in IE8??
                        if(isIE && this._rightToLeft)
                            block.childNodes[0].style.position = "absolute";              
                        databodyBlocks.push(block);
                    } else
                        existingDatabodyBlocks.push(block);
                }
            }
            if (rowHeaderBlock['_new'] || databodyBlocks.length>0) // rowHeaderBlock._new should only be set when all blocks in the row are new
                this.coordinateHTMLBlockRowHeights(rowHeaderBlock, databodyBlocks, existingDatabodyBlocks, iRow);            
        }
    }

    // coordinate the heights of new column header blocks. 
    this.coordinateColumnHeaderRowHeights(colHeaderBlocks, false);
}

AdfPivotTableUtils.prototype.setWidthExpandosOnDatabodyBlocks = function(colHeaderBlock, databodyBlocks) {
    var width = colHeaderBlock._width;
    var length = databodyBlocks.length;
    for (var i=0; i<length; ++i) {
        databodyBlocks[i]._width = width;
    }
}

/**
 * Size the blocks and shift the tables in the blocks by 1px, so the placeholder cells don't show.  
 *
 * @param bSetDefaultSizes for cases when we don't resize the canvas and we don't want to reset the default sizes. 
 * @return an object with widthBeforeViewport and heightBeforeViewport properties, indicating 
 *         the number of pixels before the viewport horizontally and vertically
 */
AdfPivotTableUtils.prototype.sizeInitialHTMLBlocks = function(bSetDefaultSizes) {
//    console.log(100);

    var isIE = AdfAgent.AGENT.getPlatform()==AdfAgent.IE_PLATFORM;
    var isIE7= isIE && !(AdfAgent.AGENT.getVersion() >= 8);

//    console.log(125);

    var sizeBeforeViewport = {widthBeforeViewport:[],heightBeforeViewport:[]};
    
//    console.log(150);

    var startPane = this._startPane;
  
//    console.log(200);

    for (var i=startPane[0]; i<2; ++i) {
      var colHeaderBlock = this._colHeaderBuffers[i][0];
      var databodyBlocks = (this._splitSlice[1] > 0) ? [this._databodyBuffers[0][i][0][0], this._databodyBuffers[1][i][0][0]] : [this._databodyBuffers[1][i][0][0]];
//      console.log(210);
      var defColWidth = Math.round(this.coordinateHTMLBlockColumnWidths(colHeaderBlock, databodyBlocks, sizeBeforeViewport)); // fine to just keep the return value from the last iteration 
//      console.log(220);
      /////////////////////////////////////// DONE
    }
    
//    console.log(250);

    var rowHeaderBlocks = (this._splitSlice[1] > 0) ? [this._rowHeaderBuffers[0][0], this._rowHeaderBuffers[1][0]] : [this._rowHeaderBuffers[1][0]];
    this.coordinateRowHeaderColumnWidths(rowHeaderBlocks);//////////////////////////////////////////////////////////////////////////////////////////////////// DONE
    
//    console.log(300);

    var colHeaderBlocks = (this._splitSlice[0] > 0) ? [this._colHeaderBuffers[0][0], this._colHeaderBuffers[1][0]] : [this._colHeaderBuffers[1][0]];
    this.coordinateColumnHeaderRowHeights(colHeaderBlocks, true);//////////////////////////////////////////////////////////////////////////////////// DONE
    
    // HACK ALERT - in RTL, in order to have the databody block show up in the databody, we 
    // set the position of the block's table to be absolute.  Somehow, the position needs to be 
    // absolute before fetching heights during coordinateHTMLBlockRowHeights
    // TBD: is this hack still needed in IE8??  If IE7 only, then decomp to own method in an IE7.js file and use JS partioning to only send for IE7.  (Or should we just get rid of it once IE7 desupported?)
    if(isIE && this._rightToLeft) {
      var buffers = this._databodyBuffers;
      for (var rowPane=startPane[1]; rowPane<2; ++rowPane) {
        var rowOfBuffers = buffers[rowPane];
        for (var colPane=startPane[0]; colPane<2; ++colPane) {
          var buffer = rowOfBuffers[colPane];
          var databodyBlock = buffer[0][0];
          databodyBlock.childNodes[0].style.position = "absolute";///////////////////////////////////////////// DONE
        }
      }
    }
    
//    console.log(500);

    for (var i=startPane[1]; i<2; ++i) {
      rowHeaderBlock = this._rowHeaderBuffers[i][0];
      var databodyBlocks = (this._splitSlice[0] > 0) ? [this._databodyBuffers[i][0][0][0], this._databodyBuffers[i][1][0][0]] : [this._databodyBuffers[i][1][0][0]];
      var defRowHeight = Math.round(this.coordinateHTMLBlockRowHeights(rowHeaderBlock, databodyBlocks, undefined, undefined, sizeBeforeViewport)); // fine to just keep the return value from the last iteration 
      ///////////////////////////////////////////////// DONE
    }
    
//    console.log(600);

    this.setBlockTableStyles(false);
    
//    console.log(900);

    if (bSetDefaultSizes) {
        this._defaultSliceSize = [defColWidth, defRowHeight];
    }
    
    if(isIE7) {
        this.shiftAllPlaceholderCells(false);
    }
    
//    console.log(1000);

    return sizeBeforeViewport;
}

AdfPivotTableUtils.prototype.coordinateRowHeaderColumnWidths = function(rowHeaderBlocks)
{
  var numBlocks = rowHeaderBlocks.length;
  var numCols = rowHeaderBlocks[0].childNodes[0].rows[0].cells.length;
  var widthArray = new Array(numCols);      
  var totalWidth = 0;
  
  var component = this._pivotTablePeer.getComponent();
  var rowHeaderStartLayer = component.getProperty(AdfRichPivotTable.__ROW_HEADER_START_LAYER_KEY);
  var lastLayerBeforeViewport = rowHeaderStartLayer - 1;
  var widthBeforeViewport = 0; // If rowHeaderStartLayer<=0 (in which case we treat it as 0 even if it's negative), then lastLayerBeforeViewport<0, so that it never ==i in the loop below, so that widthBeforeViewport remains 0 (plus the startPixel if any) as desired.
  
  for (var iCol=1; iCol<numCols; iCol++) // note that there is no iteration for column 0, the column of sizing cells
  {
    var colWidth = AdfDhtmlPivotTablePeer.MIN_ROW_HEADER_WIDTH;
    
    for (var iBlock=0; iBlock<numBlocks; iBlock++) {
        colWidth = this.getRowHeaderBlockColumnWidth(rowHeaderBlocks[iBlock], iCol, colWidth);
    }
    
    // We debated whether to have the layer label block be (a) only a size taker or (b) also a size contributor.  The difference is seen when the layer label's row height or col width is larger than the corresponding header layer, in which case either (a) the layer label is truncated to avoid devoting screen real estate to the layer labels, or (b) the header layer gets bigger to accommodate the layer label.  We chose (b) size contributor.  To make it (a) a size taker only, delete this line of code in coordinateRowHeaderColumnWidths and/or coordinateColumnHeaderRowHeights:
    // Note: the preceding truncation comment is probably relevant only to column widths.  For row heights, since browser insists on making the row tall enough, we probably have to make it a size contributor.  
    // this._layerLabelBlock is null if persistent layer labels not enabled, in which case callee does nothing and colWidth is not changed.
    colWidth = this.getRowHeaderBlockColumnWidth(this._layerLabelBlock, iCol, colWidth);

    widthArray[iCol] = colWidth;
    totalWidth += colWidth;
    
    var layer = iCol-1; // since first column is sizing column
    if (layer == lastLayerBeforeViewport) {
      // set widthBeforeViewport to the total width of all the layers of the block that should precede the viewport:
      widthBeforeViewport = totalWidth;
    }
  }
  
  // The last RH layer spans the last 2 columns of the layer label block, which contain the labels for the innermost RH layer and the innermost CH layer.  
  // Get the proportion of that column width that should be given to the innermost RH layer label.
  if (this._layerLabelBlock) {
      var sizingCells = this._layerLabelBlock.childNodes[0].rows[0].cells;
      var rhLabelSizingCell = sizingCells[numCols-1]; // numCols is the RH block column count.  Layer label block has 1 more column.
      var chLabelSizingCell = sizingCells[numCols];
      var rhLabelOrigWidth = rhLabelSizingCell.offsetWidth
      var chLabelOrigWidth = chLabelSizingCell.offsetWidth
      this._innerRhLayerLabelPercentage = rhLabelOrigWidth / (rhLabelOrigWidth + chLabelOrigWidth);
  }
  
  for (var iBlock=0; iBlock<numBlocks; iBlock++) {
      this.setRowHeaderBlockColumnWidths(rowHeaderBlocks[iBlock], widthArray, totalWidth) 
  }
  
  // this._layerLabelBlock is null if persistent layer labels not enabled, in which case callee does nothing.
  this.setRowHeaderBlockColumnWidths(this._layerLabelBlock, widthArray, totalWidth) 
  
  // Unlike the corresponding colHeader methods, we don't check isInit here because this method is called only in the init case.
  // TBD: if this ever changes, add the isInit check.
  
  // We set this here, and not in positionInitialBlocks with the databody scrollPos's, to hopefully minimize the visibility 
  // of the jump of the header scrollbars to the beginning and then back down to the correct value.  This doesn't help very much.  
  // TODO: Fix this per the todo in positionInitialBlocks().  At that time, move the set-header-scrollpos calls back to there if possible.  
  if (rowHeaderStartLayer >= this._rowLayerCount) { // the _rowLayerCount==0 case goes to the scroll-to-end case, which is the right thing.  This has no impact unless the empty row header has a scrollbar, which can only happen if (a) the PT is too miniscule to be usable or (b) if persistent layer labels are enabled and the CH labels are long relative to the width of the PT, in which case scrolling to the end is the best thing since the default skin end-aligns (e.g. in LTR right-aligns) the CH labels.  
    var scrollPos = 999999; // startLayer>=layerCount is sentinel for "scroll to the end"
  } else {
    widthBeforeViewport += component.getProperty(AdfRichPivotTable.__ROW_HEADER_START_PIXEL_KEY);
    scrollPos = widthBeforeViewport;
  }
  
  for (var pane=this._startPane[1]; pane<2; ++pane) {
    this._rowHeaders[pane].scrollLeft = scrollPos; // TODO: RTL!! (here and for corner below)
  }
  
  if (this._layerLabelBlock) // if persistent layer labels enabled
    this._corner.scrollLeft = scrollPos;
}

// Also used for layer label block column widths 
AdfPivotTableUtils.prototype.getRowHeaderBlockColumnWidth = function(block, iCol, colWidth) {
    if (block) {
        var cells = block.childNodes[0].rows[0].cells;        
        var cell = cells[iCol];        
        var _width = cell.offsetWidth;
        
        var isLayerLabel = (block==this._layerLabelBlock);
        
        // if this is the layer label block, and this is the last RH column, which spans the last 2 columns of the layer label block, then get total width of both those layer label columns
        if (isLayerLabel && iCol==cells.length-2)
            _width += cells[iCol+1].offsetWidth;
        
        if (colWidth < _width)
            colWidth = _width; // keep the biggest column width
        
        if (!isLayerLabel) {
            // only apply the override width the first time the block is downloaded!!
            var widthOverride = AdfAgent.getAgent().getIntAttribute(cell,"_winW");
            if (widthOverride!=undefined)
            {
                if (widthOverride < colWidth)
                    this._isRowHeaderWidthOverrideSmaller = true;

                colWidth = widthOverride;
            }
        }
    }
    return colWidth;
}

// Also used for layer label block column widths 
AdfPivotTableUtils.prototype.setRowHeaderBlockColumnWidths = function(block, widthArray, totalWidth) {
    if (block) {
        var sizingCells = block.childNodes[0].rows[0].cells;
        var numCols = sizingCells.length;
        
        var isLayerLabel = (block==this._layerLabelBlock);
        var upperLimit = isLayerLabel ? numCols-2 : numCols;
        
        // handles all but last 2 columns for layer label block, and all columns otherwise
        for (var i=1; i<upperLimit; i++) {
            sizingCells[i].style.width = widthArray[i] + "px"; // 0px padding,border,margin for placeholder cells
        }
        
        // handles last 2 cols for layer label block, which contain the "intersection cells" and together correspond to a single RH column.
        if (isLayerLabel) {
            var totalIntersectionWidth = widthArray[upperLimit]; // width of last RH col.  This width must be shared by the 2 cells in the intersection.
            var rhLabelWidth = Math.ceil(this._innerRhLayerLabelPercentage * totalIntersectionWidth); // divvy up the space in proportion to original widths, so Geography gets more width than Year.  Arbitrarily round in favor of RH cell.
            var chLabelWidth = totalIntersectionWidth - rhLabelWidth;
            sizingCells[upperLimit].style.width = rhLabelWidth + "px"; // 0px padding,border,margin for placeholder cells
            sizingCells[upperLimit+1].style.width = chLabelWidth + "px"; // 0px padding,border,margin for placeholder cells
        }
        
        // don't use agent.setOuterWidth(), since it is slow in mozilla!
        block.style.width = totalWidth + "px"; // div width
        block['_width'] = totalWidth;
        block.childNodes[0].style.width = totalWidth + "px"; // table width
    }
}

/**
 * Precond: If the caller intends to use the widthBeforeViewport value "returned" via 
 * the sizeBeforeViewport arg, then max(viewportStartCol,0) must be in this column of blocks; otherwise 
 * the widthBeforeViewport logic won't work.  Note that if the viewport start column 
 * is >= the column extent, signaling "last block", the caller will not use the value, 
 * so the precond does not apply. 
 * 
 * @param sizeBeforeViewport optional output param: an object to which widthBeforeViewport will be assigned.
 * @return the average column width
 */
AdfPivotTableUtils.prototype.coordinateHTMLBlockColumnWidths = function(colHeaderBlock, databodyBlocks, sizeBeforeViewport)
{
  // routine strategy - fetch all widths from the tables and then size them last. 
  // TBD - Why isn't getComputedStyle() in setOuterWidth() a bottleneck, since it may force the computation of sizes/layout on each invokation? 
//  console.log('chbcw 100');
  var agent = AdfAgent.getAgent();
  var colHeaderTable = colHeaderBlock.childNodes[0];
  var length = databodyBlocks.length;
  
//  console.log('chbcw 200');
  // TBD - !!Figure out why we can't set the width on the placeholder row without causing it to increase in height in IE!!
  var colCells = colHeaderTable.rows[0].cells;
  var numCols = colCells.length-1;
  var widthArray = new Array(numCols);  
  var totalWidth = 0;

  var blockStartCol = this.getIntAttribute(colHeaderBlock, "_startSlice");
  var pane = (blockStartCol < this._splitSlice[0]) ? 0 : 1; // this works when splitSlice == -1, since we use pane 1 in that case
  var viewportStartCol = this._pivotTablePeer.getViewportStartSliceProperty(0, pane, false);
  var lastColBeforeViewport = viewportStartCol - blockStartCol - 1;
  var widthBeforeViewport = 0; // Let paneStartCol == either 0 or splitCol, depending on which pane this is.  Then if viewportStartCol<=paneStartCol (in which case we treat it as paneStartCol even if it's <paneStartCol), then blockStartCol==paneStartCol, so that lastColBeforeViewport<0, so that it never ==i in the loop below, so that widthBeforeViewport remains 0 (plus the startPixel if any) as desired.

//  console.log('chbcw 300');
  for(var i=0;i<numCols;i++)
  {
    var maxWidth;
    var _winW = agent.getIntAttribute(colCells[i+1],"_winW");
//    console.log('chbcw 310');
    if(_winW!=undefined) {
      maxWidth=_winW; // apply size override!!!!
//      console.log('chbcw 320');
    } else {
//      console.log('chbcw 330');
      maxWidth = colCells[i+1].offsetWidth; // width of col in header block
//      console.log('chbcw 340');
      for(var iBlock=0;iBlock<length;iBlock++)
      {
        var databodyTable = databodyBlocks[iBlock].childNodes[0];
//        console.log('chbcw 350');
        var databodyCells = databodyTable.rows[0].cells;
//        console.log('chbcw 360');
        var databodyColWidth = databodyCells[i+1].offsetWidth;
        maxWidth = Math.max(maxWidth,databodyColWidth);
//        console.log('chbcw 370');
      }
    }
    
//    console.log('chbcw 400');
    widthArray[i] = maxWidth;
    totalWidth = totalWidth + maxWidth;
    if (i == lastColBeforeViewport) {
      // set widthBeforeViewport to the total width of all the columns of the block 
      // that should precede the viewport:
      widthBeforeViewport = totalWidth;
    }
  }
  
  if (sizeBeforeViewport) {
    // increment widthBeforeViewport with the number of pixels of the viewportStartColumn 
    // that should precede the viewport:
    widthBeforeViewport += this._pivotTablePeer.getViewportStartSliceProperty(0, pane, true);
    sizeBeforeViewport.widthBeforeViewport[pane] = widthBeforeViewport;
  }
  
  for(i=0;i<numCols;i++)
  {
    var bOverride = agent.getAttribute(colCells[i+1],"_winW")!=undefined;
    if(!bOverride)  // don't set the width if it is an override, since overrides are set on the server
    {
      var widthVal = (widthArray[i]) + "px"; // 0px padding,border,margin for placeholder cells
      colCells[i+1].style.width = widthVal; 
      for(var iBlock=0;iBlock<length;iBlock++)
      {
        var databodyTable = databodyBlocks[iBlock].childNodes[0];
        var databodyCells = databodyTable.rows[0].cells;
        databodyCells[i+1].style.width = widthVal; 
      }
    }
  }
  // don't use agent.setOuterWidth(), since it is slow in mozilla!
  this.setCachedSize(colHeaderBlock, totalWidth, AdfPivotTableUtils.COLUMN_EDGE);  
  colHeaderBlock.childNodes[0].style.width = totalWidth + "px";
  for(var iBlock=0;iBlock<length;iBlock++)
  {
    this.setCachedSize(databodyBlocks[iBlock], totalWidth, AdfPivotTableUtils.COLUMN_EDGE);
    databodyBlocks[iBlock].childNodes[0].style.width = totalWidth + "px";
  }  
  return (numCols==0) ? 0 : totalWidth/numCols; // use to determine default col width
}

AdfPivotTableUtils.prototype.getSizeString = function(isWidth, sizingBlock)
{
  var isIE8Plus= (AdfAgent.AGENT.getPlatform()==AdfAgent.IE_PLATFORM) && (AdfAgent.AGENT.getVersion() >= 8);
  var sizes = "";
  var table = sizingBlock.childNodes[0];
  
  if(isWidth)
  {
    var newColCells = table.rows[0].cells;
    var length = newColCells.length;
    for(var i=1;i<length;i++) // always start at 1 to skip placeholder cells
        sizes += newColCells[i].offsetWidth + ",";
    
    // TBD - transition to using _width
    sizes += sizingBlock.offsetWidth; // put total width at the end of the size array
  } else
  {
    var length = table.rows.length;
    for(var i=1;i<length;i++) // always start at 1 to skip placeholder cells
      sizes += (isIE8Plus)  // in IE8, if the row contains any cells with rowspan>1, then row.offsetHeight returns the height of that cell, which includes the height of other rows, which is not what we want.  So we get the height of a rowspan=1 cell instead.  To avoid destabilizing other browsers we make the change in IE8 only.  
               ? table.rows[i].cells[0].offsetHeight + ","
               : table.rows[i].offsetHeight + ","; 
    // TBD - transition to using _height
    sizes += sizingBlock.offsetHeight; // put total height at the end of the size array
  }
  return sizes;
}

// find a block in the header from which we can extract sizes
AdfPivotTableUtils.prototype.getHeaderSizingBlock = function(edge) {
    for (var pane=this._startPane[edge]; pane<2; ++pane) {
        var headerBuffer = this._buffers2[edge][pane];
        var length = headerBuffer.length;
        for (i=0; i<length; i++) {
            var block = headerBuffer[i];
            if ((!block['_placeholder']) && (!block['_new']))
                return block;
        }
    }
    return null;
}


AdfPivotTableUtils.prototype.getRowHeaderWidths = function()
{
  var sizingBlock = this.getHeaderSizingBlock(1);
  return this.getSizeString(true, sizingBlock);
}

AdfPivotTableUtils.prototype.getColumnHeaderHeights = function()
{
  var sizingBlock = this.getHeaderSizingBlock(0);
  return this.getSizeString(false, sizingBlock);
}


AdfPivotTableUtils.prototype.coordinateColumnHeaderRowHeights = function(colHeaderBlocks, isInit)
{
  var agent = AdfAgent.getAgent();
  var platform = agent.getPlatform();
  var isIE     = platform==AdfAgent.IE_PLATFORM;
  var isIE8Plus= isIE && agent.getVersion() >= 8;
  var isWebkit = platform==AdfAgent.WEBKIT_PLATFORM;
  var colHeaderTable = colHeaderBlocks[0].childNodes[0];
  var numBlocks = colHeaderBlocks.length;
  var numRows = colHeaderTable.rows.length;
  var heightArray = new Array(numRows);      
  var totalHeight = 0;
  
  var component = this._pivotTablePeer.getComponent();
  var colHeaderStartLayer = component.getProperty(AdfRichPivotTable.__COLUMN_HEADER_START_LAYER_KEY);
  var lastLayerBeforeViewport = colHeaderStartLayer - 1;
  var heightBeforeViewport = 0; // If colHeaderStartLayer<=0 (in which case we treat it as 0 even if it's negative), then lastLayerBeforeViewport<0, so that it never ==i in the loop below, so that heightBeforeViewport remains 0 (plus the startPixel if any) as desired.
  
  var newOffsetHeight;
  if (isIE) // TODO: is this IE hack still needed in IE8??
  {
    // in IE, cell rowspan & empty row cause tr to have divided offset heights between the rows
    // this causes problem with the IE col header row height adjustment
    // the following block makes sure that the height of empty rows are set = 0
    // and add the height of empty rows to the last non-empty row height 
    // Note: The layer label block always has at least one non-empty TH per row, except in the degenerate cases where either (1) we're fed an empty layer label or (2) rowLayerCount==colLayerCount==0 thus only 1 datacell.  So this is unlikely to be an issue for that block.
    
    // block._processOffsetHeight has the following meaning:  undefined: block not processed yet.  1: being processed now.  2: finished processing block on previous call to this method.
    // TBD: there is no reason to have 3 values.  All we need are "undefined" (block not processed yet) and some value indicating "processed", to which all blocks are set at the end of this method.
    
    newOffsetHeight = new Array(numBlocks);
    
    // set the correct offset heights for empty rows in the new blocks (blocks that have never been processed before)
    for (var iBlock = 0; iBlock<numBlocks; iBlock++) 
    {
      var block = colHeaderBlocks[iBlock];
      if (block._processOffsetHeight == undefined)
      {
          block._processOffsetHeight = 1;
          newOffsetHeight[iBlock]= new Array(numRows);
          var rows = block.childNodes[0].rows;
          for (var iRow = numRows-1; iRow>0; iRow--) // note that there is no iteration for row 0, the row of sizing cells
          {
            var row = rows[iRow];
            if (newOffsetHeight[iBlock][iRow] == undefined)
                newOffsetHeight[iBlock][iRow] = (isIE8Plus) ? row.cells[0].offsetHeight : row.offsetHeight; // in IE8, if the row contains any cells with rowspan>1, then row.offsetHeight returns the height of that cell, which includes the height of other rows, which is not what we want.  So we get the height of a rowspan=1 cell instead.  To avoid destabilizing other browsers we make the change in IE8 only.  
            else // add the offset from lower layers:
                newOffsetHeight[iBlock][iRow] += ((isIE8Plus) ? row.cells[0].offsetHeight : row.offsetHeight); // in IE8, if the row contains any cells with rowspan>1, then row.offsetHeight returns the height of that cell, which includes the height of other rows, which is not what we want.  So we get the height of a rowspan=1 cell instead.  To avoid destabilizing other browsers we make the change in IE8 only.  

            if (row.cells.length == 1 && row.cells[0].className == AdfPivotTableUtils.COLUMN_HEADER_SIZING_CELL_CLASS) // 2nd condition is always true...
            {
                // transfer empty row's offset height to the row above
                newOffsetHeight[iBlock][iRow-1]=newOffsetHeight[iBlock][iRow];
                newOffsetHeight[iBlock][iRow] = 0;
            }                                
          }      
      }
    }
  }
  
  // TODO - reverse this loop for faster iteration
  for (var iRow=1; iRow<numRows; iRow++) // note that there is no iteration for row 0, the row of sizing cells
  {
    var rowHeight = AdfDhtmlPivotTablePeer.MIN_COL_HEADER_HEIGHT; 
    
    for (var iBlock=0; iBlock<numBlocks; iBlock++) {
        rowHeight = this.getColumnHeaderBlockRowHeight(colHeaderBlocks[iBlock], iBlock, iRow, numRows, rowHeight, newOffsetHeight, isIE, isIE8Plus);
    }
    
    // We debated whether to have the layer label block be (a) only a size taker or (b) also a size contributor.  The difference is seen when the layer label's row height or col width is larger than the corresponding header layer, in which case either (a) the layer label is truncated to avoid devoting screen real estate to the layer labels, or (b) the header layer gets bigger to accommodate the layer label.  We chose (b) size contributor.  To make it (a) a size taker only, delete this line of code in coordinateRowHeaderColumnWidths and/or coordinateColumnHeaderRowHeights:
    // Note: the preceding truncation comment is probably relevant only to column widths.  For row heights, since browser insists on making the row tall enough, we probably have to make it a size contributor.  
    // this._layerLabelBlock is null if persistent layer labels not enabled, in which case callee does nothing and rowHeight is not changed.
    rowHeight = this.getColumnHeaderBlockRowHeight(this._layerLabelBlock, -1, iRow, numRows, rowHeight, newOffsetHeight, isIE, isIE8Plus);
    
    heightArray[iRow] = rowHeight; // optimum row height/offset height
    totalHeight += rowHeight;
    
    var layer = iRow-1; // since first row is sizing row
    if (layer == lastLayerBeforeViewport) {
      // set heightBeforeViewport to the total height of all the layers of the block that should precede the viewport:
      heightBeforeViewport = totalHeight;
    }
  }
      
// var divAdjustment = true;
 
  for (var iBlock=0; iBlock<numBlocks; iBlock++) {
      this.setColumnHeaderBlockRowHeights(colHeaderBlocks[iBlock], heightArray, totalHeight, isIE, isIE8Plus, isWebkit);
  }
  
  // this._layerLabelBlock is null if persistent layer labels not enabled, in which case callee does nothing.
  this.setColumnHeaderBlockRowHeights(this._layerLabelBlock, heightArray, totalHeight, isIE, isIE8Plus, isWebkit);
  
  if (isInit) {
    // We set this here, and not in positionInitialBlocks with the databody scrollPos's, to hopefully minimize the visibility 
    // of the jump of the header scrollbars to the beginning and then back down to the correct value.  This doesn't help very much.  
    // TODO: Fix this per the todo in positionInitialBlocks().  At that time, move the set-header-scrollpos calls back to there if possible.  
    if (colHeaderStartLayer>=this._colLayerCount) { // the _colLayerCount==0 case goes to the scroll-to-end case, but has no impact except in the extreme case where the empty col header actually has a scrollbar, which could only happen in the case of a miniscule PT or possibly if persistent layer labels are enabled and skinned to be really tall.  Unlike the corresponding rowHeader.scrollLeft code, this case doesn't really matter, but for consistency with that case, we scroll-to-end, thus showing the bottom portion of the really tall layer label font.
      var scrollTop = 999999; // startLayer>=layerCount is sentinel for "scroll to the end"
    } else {
      heightBeforeViewport += component.getProperty(AdfRichPivotTable.__COLUMN_HEADER_START_PIXEL_KEY);
      scrollTop = heightBeforeViewport;
    }
    
    for (var pane=this._startPane[0]; pane<2; ++pane) {
      this._colHeaders[pane].scrollTop = scrollTop;
    }
    
    if (this._layerLabelBlock) { // if persistent layer labels enabled
      if (scrollTop==999999) // 999999 is the scrollToEnd value we set above
        scrollTop = this._colHeaders[1].scrollTop; // unlike the headers, the corner has contents other than the block, which means that its max scrollTop can be higher than the value that will scroll to the end of the block.  So unlike the colHeader, we can't handle the scroll-to-end case by setting the too-high scrollTop value and letting the browser bring it in bounds, so we must use the real scrollTop of the header.  In the non-scroll-to-end case, the computed value should always be in-bounds, so in that case we use the value we already have, to avoid asking the browser for this value in case it's expensive.  This issue doesn't seem to affect the max scroll width, so we don't bother with this in the corresponding code that sets _corner.scrollLeft.
      this._corner.scrollTop = scrollTop;
    }
  }
}

// Also used for layer label block row heights
AdfPivotTableUtils.prototype.getColumnHeaderBlockRowHeight = function(block, iBlock, iRow, numRows, rowHeight, newOffsetHeight, isIE, isIE8Plus) {
    if (block) {
        var row = block.childNodes[0].rows[iRow];        
        var _height;
        
        if (isIE && block._processOffsetHeight == 1) { // as desired, layer label blocks go to else clause, since 2nd condition not true.  If the earlier code starts processing this block like the others, 2nd condition will be true and will use if-clause for IE, as desired.
            _height = newOffsetHeight[iBlock][iRow]; // get from array
            
            // when we've processed the last row of the block, mark block as processed so that the next call to this method knows it's processed.
            if( iRow ==(numRows-1))
                block._processOffsetHeight = 2;
        } else {
            _height = (isIE8Plus) ? row.cells[0].offsetHeight : row.offsetHeight; // in IE8, if the row contains any cells with rowspan>1, then row.offsetHeight returns the height of that cell, which includes the height of other rows, which is not what we want.  So we get the height of a rowspan=1 cell instead.  To avoid destabilizing other browsers we make the change in IE8 only.  
        }
            
        if (rowHeight < _height)
            rowHeight = _height; // keep the biggest row height

        // keep the first row height from server around as a min row height
        if (row._minRH==undefined) {
            row._minRH = Math.max(_height, AdfDhtmlPivotTablePeer.MIN_COL_HEADER_HEIGHT);
          
            var isLayerLabel = (block==this._layerLabelBlock);
            if (!isLayerLabel) {
                // only apply the override height the first time the block is downloaded!!
                var heightOverride = AdfAgent.getAgent().getIntAttribute(row,"_winH"); 
                if (heightOverride!=undefined && heightOverride>rowHeight)
                    rowHeight = heightOverride;
            }
        }
    }
    return rowHeight;
}

// Also used for layer label block row heights
AdfPivotTableUtils.prototype.setColumnHeaderBlockRowHeights = function(block, heightArray, totalHeight, isIE, isIE8Plus, isWebkit) {
  if (block) {
      var colHeaderTable = block.childNodes[0];   
      var rows = colHeaderTable.rows;
      var numRows = rows.length;
      
      for (var i=1; i<numRows; i++) {
          var row = rows[i];
          var resizedElem = (isWebkit || isIE8Plus) ? row.cells[0] : row; // In FF and IE, we can successfully apply the size to the row or its sizing cell.  In webkit, only the cell works; resizing the row has no effect.  It's possible that we could use the cell for all browsers, but to avoid the risk of destabilizing FF and IE, we keep using the row for FF and IE and only use the cell for webkit.
          resizedElem.style.height = heightArray[i] + "px";
      }
      
      if (isIE) {
          // ie has a weird bug where offsetHeight is not always equal to the height that we are setting
          // most of the time it's due to the padding/border/margin (3px) in our case
          // and sometimes, due to the empty rows (row only containing sizing cell)
          // this is the fix to re-check the end result of offsetHeight and re-adjust the height
          for (var i=1; i<numRows; i++) {
              var endOffsetHeight = (isIE8Plus) ? rows[i].cells[0].offsetHeight : rows[i].offsetHeight; // in IE8, if the row contains any cells with rowspan>1, then row.offsetHeight returns the height of that cell, which includes the height of other rows, which is not what we want.  So we get the height of a rowspan=1 cell instead.  To avoid destabilizing other browsers we make the change in IE8 only.  
              if (endOffsetHeight != heightArray[i]) {
                  var newHeight = heightArray[i] + heightArray[i] - endOffsetHeight;
                  if (newHeight >= 0) {
                      var row = rows[i];
                      var resizedElem = (isWebkit || isIE8Plus) ? row.cells[0] : row; // In FF and IE, we can successfully apply the size to the row or its sizing cell.  In webkit, only the cell works; resizing the row has no effect.  It's possible that we could use the cell for all browsers, but to avoid the risk of destabilizing FF and IE, we keep using the row for FF and IE and only use the cell for webkit.
                      resizedElem.style.height = newHeight + "px";
                  }
              }
          }
      }
      
      block.style.height = totalHeight + "px"; // div height
      block['_height'] = totalHeight;
      colHeaderTable.style.height = totalHeight + "px"; // table height; previously for ie = (totalHeight+1)
  }
}

/**
 * Precond: If the caller intends to use the heightBeforeViewport value "returned" via 
 * the sizeBeforeViewport arg, then max(viewportStartRow,0) must be in this row of blocks; otherwise 
 * the heightBeforeViewport logic won't work.  Note that if the viewport start row 
 * is >= the row extent, signaling "last block", the caller will not use the value, 
 * so the precond does not apply.  
 * 
 * @param sizeBeforeViewport optional output variable: an object to which heightBeforeViewport will be assigned.
 */
AdfPivotTableUtils.prototype.coordinateHTMLBlockRowHeights = function(rowHeaderBlock, databodyBlocks, existingDatabodyBlocks, blockIndex, sizeBeforeViewport)
{
  // routine strategy - fetch all heights from the tables and then size them last. 
  // TBD - Why isn't getComputedStyle() in setOuterWidth() a bottleneck, since it may force the computation of sizes/layout on each invokation? 
  var agent = AdfAgent.getAgent();
  var platform = agent.getPlatform();
  var isIE     = platform==AdfAgent.IE_PLATFORM;
  var isIE8Plus= isIE && AdfAgent.AGENT.getVersion() >= 8;
  var isIE9Plus= isIE && AdfAgent.AGENT.getVersion() >= 9;
  var isWebkit = platform==AdfAgent.WEBKIT_PLATFORM;
  
  /*
   * Bug-32892714 : In a browsers other then Internet Explorer, Along with the last row not being displayed, some other rows were also overlapping at different zoom levels(200%).
  */
  
  //Bug-32892714 : Row header height needs to be incremented for all the browsers to avoid overlapping between rows.
  var rowHeaderHeightsNeedIncrement = rowHeaderBlock._new;
  var rowHeaderTable = rowHeaderBlock.childNodes[0];
  var numRows = rowHeaderTable.rows.length-1;
  var heightArray = new Array(numRows);  
  var sizeAllRowsArray = new Array(numRows);  
  var totalHeight = 0;
  var length;

  var blockStartRow = this.getIntAttribute(rowHeaderBlock, "_startSlice");
  var pane = (blockStartRow < this._splitSlice[1]) ? 0 : 1; // this works when splitSlice == -1, since we use pane 1 in that case
  var viewportStartRow = this._pivotTablePeer.getViewportStartSliceProperty(1, pane, false);
  var lastRowBeforeViewport = viewportStartRow - blockStartRow - 1;
  var heightBeforeViewport = 0; // Let paneStartRow == either 0 or splitRow, depending on which pane this is.  Then if viewportStartRow<=paneStartRow (in which case we treat it as paneStartRow even if it's <paneStartRow), then blockStartRow==paneStartRow, so that lastRowBeforeViewport<0, so that it never ==i in the loop below, so that heightBeforeViewport remains 0 (plus the startPixel if any) as desired.

  for(var i=0;i<numRows;i++)
  {
    // TBD - Determine whether we can use <tr> offsetHeight for sizing rows, instead of rowheaderTable.rows[i].cells[0], since we can get rid of placeholder cells in this case.  Answer:  no we can't--see below comment.
    var row = rowHeaderTable.rows[i+1];
    //Bug-32892714 : row.cells[0].offsetHeight contains the actual height of the table row.
    var rowHeight = row.cells[0].offsetHeight;
    
    // Bug 11779745: in IE9 the row header rows and databody rows were getting out of alignment.  Debugging tool revealed that whichever one (RH row or DB row) was driving the height (was taller) was showing in the Layout tab as having a fractional height, e.g. 13.28px + 4px border/padding = 17.28, but offsetHeight would return the rounded-down value of 17.  When we later set that rounded height on the rows, it was 0.28px too small and the browser would force it back up to the original fractional value, so that approx. every 4th row we'd accumulate an entire extra pixel, causing the row to be 1px too tall, thus being misaligned with the other block that lacked the periodic extra pixel.
    // Ideally, we would get, via some API, either the rounded-up value or the unrounded, potentially fractional value (which we could round up ourselves or even use as-is if that worked).  E.g.: 
    //     var computedStyle = agent.getComputedStyle(elem);
    //     return Math.ceil(parsefloat(computedStyle.height)) + parseInt(computedStyle.paddingTop) + parseInt(computedStyle.paddingBottom) + parseInt(computedStyle.borderTopWidth) + parseInt(computedStyle.borderBottomWidth);
    // Unfortunately, those values (with or without padding/border/etc.) don't seem to be available via any API.  getComputedStyle(elem).height and elem.currentStyle.height (the proprietary IE API) both return "auto" rather than a useful value in IE9 (and possibly other browsers), for both the cell and the row.  Using .getPropertyValue('height') rather than .height doesn't help.
    // Thus, we do the closest approximation:  in IE9 only, for new blocks only, we use (offsetHeight+1).  The difference btwn the ideal approach and this approach occurs when the returned offsetHeight is >= the true computed height (i.e. they're == because true computed height is integral, or it's > because true computed height was rounded up to generate the offsetHeight -- not sure if rounding up ever happens).  In this case, we're using offsetHeight+1 when offsetHeight would have been enough, so that the row height is bigger than necessary, which is unfortunate but apparently unavoidable given current API's.
    // Bug 13840794: In this approximate approach, it's important to do the increment for new blocks only.  Reason: when we size the rows the first time, we set integral values, so that subsequently the offsetHeights are exact, so that without the "new blocks only" check, every time a short-scroll causes this method to get the height of an existing block, we increment it, causing the row heights to grow by 1px apiece on each short-scroll.  Making matters worse, the existing databody blocks don't grow in this case, so that rows become misaligned across blocks.
    // The fractional row height / rounded offsetHeight issue is discussed here:  http://vadikom.com/dailies/offsetwidth-offsetheight-useless-in-ie9-firefox4/.  That site claims the problem can happen in FF4 too, but we're not going to destabilize code for that browser until we've actually seen a problem there.  Note that the "solution" on this site is the computedStyle solution, which as mentioned above does not work.
    if (rowHeaderHeightsNeedIncrement)
      rowHeight++;
      
//    AdfLogger.LOGGER.severe("RH: row.cells[0].offsetHeight:" + row.cells[0].offsetHeight + ", row.offsetHeight:" + row.offsetHeight);
    
    // keep the first row height from server around as a min row height
    if(row._minRH==undefined) 
    {
      if (rowHeight < AdfDhtmlPivotTablePeer.MIN_ROW_HEIGHT)
        rowHeight = AdfDhtmlPivotTablePeer.MIN_ROW_HEIGHT;
      
      row._minRH = rowHeight;
      
      // only apply the override height the first time the block is downloaded!!
      var heightOverride = agent.getIntAttribute(row,"_winH");
      if( (heightOverride!=undefined) && heightOverride>rowHeight)
        rowHeight = heightOverride;
    }
    length = databodyBlocks.length;
    var maxHeight = rowHeight;
    for(var iBlock=0;iBlock<length;iBlock++)
    {
      var databodyBlock = databodyBlocks[iBlock];
      var databodyTable = databodyBlock.childNodes[0];
      var _row = databodyTable.rows[i+1];

      // flag to indicate whether scroll height should be used in calculating scroll height
      var useScrollHeight = agent.getAttribute(_row,"_ss");
      var scrollHeight = useScrollHeight!=undefined ? this.getMaximumScrollHeight(_row.cells) : 0;
      //Bug-32892714 : _row.cells[0].offsetHeight contains the actual height of the data body row.
      var databodyHeight = _row.cells[0].offsetHeight;
      databodyHeight = Math.max(databodyHeight, scrollHeight);
      
      //Bug-32892714 : Data body height increment is required for all the browser to avoid the overlapping between data body rows.
      if (databodyBlock._new)
        databodyHeight++;
      
//      AdfLogger.LOGGER.severe("DB: _row.cells[0].offsetHeight:" + _row.cells[0].offsetHeight + ", _row.offsetHeight:" + _row.offsetHeight);
      
      // keep the first row height from server around as a min row height
      if(_row._minRH==undefined) 
      {
        if (databodyHeight < AdfDhtmlPivotTablePeer.MIN_ROW_HEIGHT)
          databodyHeight = AdfDhtmlPivotTablePeer.MIN_ROW_HEIGHT;
        
        _row._minRH = databodyHeight;
        
        // only apply the override height the first time the block is downloaded!!
        heightOverride = agent.getIntAttribute(_row,"_winH");
        if( (heightOverride!=undefined) && heightOverride>databodyHeight)
          databodyHeight = heightOverride;
      }      
      maxHeight = Math.max(maxHeight,databodyHeight);

      // if there are existing databody and row header blocks
      if(!rowHeaderBlock['_new'])
      {
        // we want to compare every row height to the previous row height ( on row header rows ) and reset
        // the row heights in all adjacent databody blocks as well.  Change this routine to get the
        // adjacent databody blocks sized as well. 
        
        // Set the size of the existing databody rows as well!
        sizeAllRowsArray[i]=rowHeight<maxHeight;
      }

    }

    heightArray[i] = maxHeight;
    totalHeight = totalHeight + maxHeight;
    if (i == lastRowBeforeViewport) {
      // set heightBeforeViewport to the total height of all the rows of the block 
      // that should precede the viewport:
      heightBeforeViewport = totalHeight;
    }
  }

  if (sizeBeforeViewport) {
    // increment heightBeforeViewport with the number of pixels of the viewportStartRow 
    // that should precede the viewport:
    heightBeforeViewport += this._pivotTablePeer.getViewportStartSliceProperty(1, pane, true);
    sizeBeforeViewport.heightBeforeViewport[pane] = heightBeforeViewport;
  }
  
  var ieDatabodyExtraHeight = 0; 
  var ieRowHeaderExtraHeight = 0; 
  if(isIE && !isIE8Plus) {
    ieDatabodyExtraHeight = AdfPivotTableUtils.CELL_EXTRA_HEIGHT; // 3px padding/border/margin for 1st cell in IE
    ieRowHeaderExtraHeight = ieDatabodyExtraHeight+1; // row header has 2 borders, not just one
  }
  
//  jimHeaderCell = [];
//  jimHeaderRow = [];
//  jimDatabodyCell = [];
//  jimDatabodyRow = [];
  
  for(i=0;i<numRows;i++)
  {
    var headerRow = rowHeaderTable.rows[i+1];
    var resizedElem = (isWebkit || isIE8Plus) ? headerRow.cells[0] : headerRow; // In FF and IE, we can successfully apply the size to the row or its sizing cell.  In webkit, only the cell works; resizing the row has no effect.  It's possible that we could use the cell for all browsers, but to avoid the risk of destabilizing FF and IE, we keep using the row for FF and IE and only use the cell for webkit.
    resizedElem.style.height = (heightArray[i]-ieRowHeaderExtraHeight) + "px"; // 0px padding/border/margin.
//    AdfLogger.LOGGER.severe("after setting headerRow to " + (heightArray[i]-ieRowHeaderExtraHeight) + ", we get " + 
//            "headerRow.cells[0].offsetHeight:" + headerRow.cells[0].offsetHeight + ", headerRow.offsetHeight:" + headerRow.offsetHeight);
//    jimHeaderCell[i] = headerRow.cells[0];
//    jimHeaderRow[i] = headerRow;
    length = databodyBlocks.length;
    for(var iBlock=0;iBlock<length;iBlock++)
    {
      var databodyTable = databodyBlocks[iBlock].childNodes[0];
      var databodyRow = databodyTable.rows[i+1];
      var resizedElem = (isWebkit || isIE8Plus) ? databodyRow.cells[0] : databodyRow; // In FF and IE, we can successfully apply the size to the row or its sizing cell.  In webkit, only the cell works; resizing the row has no effect.  It's possible that we could use the cell for all browsers, but to avoid the risk of destabilizing FF and IE, we keep using the row for FF and IE and only use the cell for webkit.
      resizedElem.style.height = (heightArray[i]-ieDatabodyExtraHeight) + "px"; 
//      AdfLogger.LOGGER.severe("after setting databodyRow to " + (heightArray[i]-ieDatabodyExtraHeight) + ", we get " + 
//            "databodyRow.cells[0].offsetHeight:" + databodyRow.cells[0].offsetHeight + ", databodyRow.offsetHeight:" + databodyRow.offsetHeight);
//      jimDatabodyCell[i] = databodyRow.cells[0];
//      jimDatabodyRow[i] = databodyRow;
      // update height of TD for text area stamping (EPM)
      // todo: Jim mentioned getCellOffset is called twice for each cell, once in getMaximumScrollHeight and once in
      //       updateTextAreaCellHeights.  This could probably be further optimized by storing the info from       
      //       getMaximumScrollHeight and reuse it in updateTextAreaCellHeights.
      //Bug-32892714 : Increase the data body row height and make it similar to the row header height.
      this.updateTextAreaCellHeights(databodyRow, heightArray[i]-ieDatabodyExtraHeight);
    }
    if(sizeAllRowsArray[i] && (existingDatabodyBlocks!=undefined) )
    {
      length = existingDatabodyBlocks.length;
      for(var iBlock=0;iBlock<length;iBlock++)
      {
        var databodyTable = existingDatabodyBlocks[iBlock].childNodes[0];
        var databodyRow = databodyTable.rows[i+1];
        var resizedElem = (isWebkit || isIE8Plus) ? databodyRow.cells[0] : databodyRow; // In FF and IE, we can successfully apply the size to the row or its sizing cell.  In webkit, only the cell works; resizing the row has no effect.  It's possible that we could use the cell for all browsers, but to avoid the risk of destabilizing FF and IE, we keep using the row for FF and IE and only use the cell for webkit.
        resizedElem.style.height = (heightArray[i]-ieDatabodyExtraHeight) + "px"; 
      }        
    }
  }
  
  // don't use agent.setOuterHeight(), since it is slow in mozilla!
  rowHeaderBlock.style.height = totalHeight + "px";
  if(!rowHeaderBlock['_new'])
  { 
    var heightDelta = rowHeaderBlock['_height'] - totalHeight;
    if(heightDelta!=0)
      this.moveExistingBlocksTop(pane, blockIndex+1, heightDelta);
  }
  rowHeaderBlock['_height'] = totalHeight;
  if(isIE && !isIE8Plus)
    rowHeaderBlock.childNodes[0].style.height = (totalHeight+1) + "px"; // add one for placeholder cells
  else
    rowHeaderBlock.childNodes[0].style.height = (totalHeight) + "px";

  length = databodyBlocks.length;
  for(var iBlock=0;iBlock<length;iBlock++)
  {
    if(isIE && !isIE8Plus)
      databodyBlocks[iBlock].style.height = (totalHeight+1) + "px"; // add one for placeholder cells
    else
      databodyBlocks[iBlock].style.height = totalHeight + "px";
    databodyBlocks[iBlock]['_height'] = totalHeight;
    databodyBlocks[iBlock].childNodes[0].style.height = totalHeight + "px";
  }  

  if(existingDatabodyBlocks!=undefined)
  {
    length = existingDatabodyBlocks.length;
    for(var iBlock=0;iBlock<length;iBlock++)
    {
      if(isIE && !isIE8Plus)
        existingDatabodyBlocks[iBlock].style.height = (totalHeight+1) + "px";
      else
        existingDatabodyBlocks[iBlock].style.height = totalHeight + "px";
      existingDatabodyBlocks[iBlock]['_height'] = totalHeight;
      existingDatabodyBlocks[iBlock].childNodes[0].style.height = totalHeight + "px";
    }  
  }
  
  return (numRows==0) ? 0 : totalHeight/numRows; // use to determine default row height
}

// TBD: Put shiftAllPlaceholderCells and shiftPlaceholderCells in an IE7.js file and use JS partioning to only send for IE7.  (Or should we just get rid of it once IE7 desupported?)
AdfPivotTableUtils.prototype.shiftAllPlaceholderCells = function(bNewBlocksOnly) {
    var buffer, block;
    
    for (var edge=0; edge<2; ++edge) {
        var edgeBuffers = this._buffers2[edge];
        for (var pane=this._startPane[edge]; pane<2; ++pane) {
            buffer = edgeBuffers[pane];
            var length = buffer.length;
            for (i=0; i<length; i++) {
                block = buffer[i]; 
                if (!block['_placeholder'] && (block['_new'] || !bNewBlocksOnly))
                    this.shiftPlaceholderCells(block);
            }
        }
    }

    for (var rowPane=this._startPane[1]; rowPane<2; ++rowPane) {
        var rowOfBuffers = this._databodyBuffers[rowPane];
        for (var colPane=this._startPane[0]; colPane<2; ++colPane) {
            buffer = rowOfBuffers[colPane];
            var numBlockRows = buffer.length;
            for (var i=0; i<numBlockRows; i++) {
                var rowOfBlocks = buffer[i];
                var numBlocks = rowOfBlocks.length;
                for (var j=0; j<numBlocks; j++) {
                    block = rowOfBlocks[j];
                    if (block && (block['_new'] || !bNewBlocksOnly))
                        this.shiftPlaceholderCells(block);
                }
            }
        }
    }
    
    if (this._layerLabelBlock && (this._layerLabelBlock['_new'] || !bNewBlocksOnly))
        this.shiftPlaceholderCells(this._layerLabelBlock);
}

AdfPivotTableUtils.prototype.shiftPlaceholderCells = function(block)
{
  // get the height of the HTML block's table
  var height = block.childNodes[0].offsetHeight; 

  // make the height of the block large enough to show all of the non-placeholder cells in the table
  block.style.height = (height-AdfPivotTableUtils.PLACEHOLDER_ROW_HEIGHT) + "px"; 
  
  // set the block's table's width, since all contents of the block are absolutely 
  // positioned and its width would be 0px otherwise!   
  // The reason why it is absolutely positioned is that when it was relatively positioned 
  // empty space would appear in the bottom of the pivotTable since the table is top:-1.  
  // However, it may be that it might be more performant to relatively position the block's 
  // table and then shrink the height down by 1 as is being done above.   
  this.setCachedSize(block, block.childNodes[0].offsetWidth, AdfPivotTableUtils.COLUMN_EDGE);
  block.style.overflow = "hidden";
}

/*
 * Used for Printable Page rendering.
 */
AdfPivotTableUtils.prototype.getDatabodySizes = function() ////////////////////// DONE
{
    var agent = AdfAgent.getAgent();
    var databodySizing = "";
    var prefix = "Rows:";
    var isWidth = false;
    
    for (var edge=1; edge>=0; --edge) {
        if (edge==0) {
            prefix = "Columns:";
            isWidth = true;
        }
        for (var pane=this._startPane[edge]; pane<2; ++pane) {
            databodySizing += prefix;
            var buffer = this._buffers2[edge][pane];
            var length = buffer.length;
            for (var i=0; i<length; i++) {
                var block = buffer[i];
                databodySizing += agent.getAttribute(block,"_startSlice") + ":" + this.getSizeString(isWidth, block) + ":";
            }
            databodySizing += ";";
        }
    } 
    
    return databodySizing;
}

/*
 * Used for Printable Page rendering.  Not called from anywhere in our JS code.  Called only by one-line Extended Renderer Script set from server in Printable Page mode.  
 */
// TBD re: performance: 
// - We may be able to move most or all of the code in this method to the server, since unlike sizePivotTable() (the non-printablePage case), all row/column sizes are fixed, rather than relying on the browser to autosize.  See also next bullet re: offsetW/H.
// - This method relies on offsetHeight/Width and clientHeight/Width more than the non-PrintablePage code does, e.g. sizePT.  Is probably faster to avoid those calls (which is why regular code avoids it), esp'ly if calling them violates our usual strategy of "do all gets, then all sets, rather than interleaving gets and sets which might cause repeated relayouts".  Also, would *need* to remove dependence on these browser-computed sizes if move logic to server.
// - If keep logic on client, then after removing the offsetW/H usage, reorder code so we do all gets, then all sets, instead of interleaving.
AdfPivotTableUtils.adjustPrintablePageRendering = function(clientId, isRtl) ////////////////////// DONE
{
  // avoid constructing same string gazillion times.
  var px = 'px';
  var zeroPx = '0px';
  
  var sizeAttrs =       ['width'      , 'height'];
  var clientSizeAttrs = ['clientWidth', 'clientHeight'];
  var offsetSizeAttrs = ['offsetWidth', 'offsetHeight'];

  var ptOuterContainer = document.getElementById(clientId);
  var innerContainer = ptOuterContainer.childNodes[0];
  
  var startPane = [parseInt(ptOuterContainer.getAttribute("_splitCol"))==-1 ? 1 : 0,
                   parseInt(ptOuterContainer.getAttribute("_splitRow"))==-1 ? 1 : 0];
  
  
  // -- get divs --
  
  var frameDivs = innerContainer.childNodes; // corner, headers, databody divs
  var i = 0; // index into frameDivs
  var corner = frameDivs[i++];
  
  var div;
  var block;
  
  var columnHeaders = [];
  var columnHeaderBlocks = [];
  var columnHeaderRows = [];

  for (var pane=startPane[0]; pane<2; ++pane) {
    div = frameDivs[i++];
    block = div.childNodes[0];
    
    columnHeaders[pane] = div;
    columnHeaderBlocks[pane] = block;
    columnHeaderRows[pane] = block.childNodes[0].childNodes[0].childNodes;
  }

  var rowHeaders = [];
  var rowHeaderBlocks = [];
  var rowHeaderRows = [];
  
  var headers      = [columnHeaders     , rowHeaders];
  var headerBlocks = [columnHeaderBlocks, rowHeaderBlocks];
  var headerRows   = [columnHeaderRows  , rowHeaderRows];
  
  // these 3 db arrays indexed by rowPane then colPane
  var databodies = [[],[]];
  var databodyBlocks = [[],[]];
  var databodyRows = [[],[]];
  
  for (var rowPane=startPane[1]; rowPane<2; ++rowPane) {
    div = frameDivs[i++];
    block = div.childNodes[0];
    
    rowHeaders[rowPane] = div;
    rowHeaderBlocks[rowPane] = block;
    rowHeaderRows[rowPane] = block.childNodes[0].childNodes[0].childNodes;
    
    for (var colPane=startPane[0]; colPane<2; ++colPane) {
      div = frameDivs[i++];
      block = div.childNodes[0];
    
      databodies[rowPane][colPane] = div;
      databodyBlocks[rowPane][colPane] = block; // in no-data case, databodyBlocks[1][1] is emptyText div (but the header blocks are normal, albeit mostly empty, blocks)
      databodyRows[rowPane][colPane] = block.childNodes[0].childNodes[0].childNodes; // in no-data case, databodyRows[1][1]is size-0 NodeList, which (barring a bug) will never be accessed because any for-loops over rows will never be entered. (But xxxHeaderRows vars are normal, albeit mostly empty, sets of rows)
    }
  }
  
  // TODO: PP mode punts on displaying status bar.  Renderer should render it and this method should populate it.  Rather than porting all the "how many cells selected" logic to server, either send up the label and text and send back down in expandos, or send down selection to use existing clientside logic, only if status bar enabled.  Until then, hardcode "no status bar":
  var statusBar = null; // TODO: get from frameDivs if present.  Rest of this method should already handle non-null status bar correctly, but untested.
  var hasStatusBar = (statusBar != null);
  
  
  // -- Update row heights --
  
  // Update columnHeader row heights
  for (var pane=startPane[0]; pane<2; ++pane) {
    var rows = columnHeaderRows[pane];
    var length = rows.length;
    for (var i = 1; i < length; i++)
    {
      var height = rows[i].getAttribute("_winH");
      if (height != null)
        rows[i]['style'].height = height + px;
    }
  }
  
  // Update rowHeader and databody row heights
  for (var rowPane=startPane[1]; rowPane<2; ++rowPane) {
    var rhRows = rowHeaderRows[rowPane];
    var dbRows = databodyRows[rowPane];
    var length = rhRows.length;
    for (i = 1; i < length; i++)
    {
      height = rhRows[i].getAttribute("_winH");
      if (height != null)
      {
        var heightPx = height + px;
        rhRows[i]['style'].height = heightPx;
        for (var colPane=startPane[0]; colPane<2; ++colPane) {
          dbRows[colPane][i]['style'].height = heightPx;
        }
      }
    }
  }
  
  
  // -- get extra sizes, and PT and status bar sizes --
  
  AdfPivotTableUtils.initSizeConstants();
  var containerExtraSize     = AdfPivotTableUtils.CONTAINER_EXTRA_SIZE;       // [containerExtraWidth, containerExtraHeight]
  var databodyExtraSize      = AdfPivotTableUtils.DATABODY_EXTRA_SIZE;        // [databodyExtraWidth,  databodyExtraHeight]
  var headerExtraPixelExtent = AdfPivotTableUtils.HEADER_EXTRA_PIXEL_EXTENT;  // [colHeaderExtraWidth, rowHeaderExtraHeight].  "Extent" means size slice-wise, not layer-wise.
  var headerExtraPixelDepth  = AdfPivotTableUtils.HEADER_EXTRA_PIXEL_DEPTH;   // [colHeaderExtraHeight, rowHeaderExtraWidth].  "Depth" means size layer-wise, not slice-wise.
  
  var ptInnerSize = [innerContainer['clientWidth'], innerContainer['clientHeight']]; // unlike sizePT(), accuracy based on assumption that padding is 0, since clientW/H includes padding.  (Also relies on absence of scrollbars, which is guaranteed barring a bug)
  var statusBarHeight = (hasStatusBar) ? statusBar.offsetHeight : 0;
  var ptAvailableSize = [ptInnerSize[0], ptInnerSize[1] - statusBarHeight];
  
  
  // -- Get header/databody block sizes, and allocate space between the headers and databody (but don't set sizes on DOM yet) --
  
  // TBD: does the following reasoning break down in the case where the opposing dim'n has a scrollbar, which is now absent, so that it was just over 50% before but now it's just under?  Is there an edge case bug here?
  // - test this thoroughly after fixing the splitPercentage bug below.  Bugs possible?
  // Note: this variable replaces canvasSize var from sizePT().  In this PrintablePage method, it's only used for allocateSpace().  In principle we should be using canvasSize, but for PrintablePage the allocateSpace() calcs work fine using blockSize.  Reason: this approximation could only cause problems if canvasSize and blockSize are different, which only happens when there was a scrollbar (in regular mode).  In this case the databody size from regular mode, which PP needs to match, is at least 50% of the space, and the PrintablePage block (not regular-mode block) is bigger than that databody size.  Two sub-cases: (1) header also wants >50% of space: allocateSpace() goes into the "both get 50%" case as desired.  (2) header wants <=50% of space: allocateSpace() goes into the "only db wants more than its 50%" case as desired.  Similar reasoning applies to the 2nd call to allocateSpace, which allocates space btwn db panes.
  var blockSize = [[],[]]; // indexed by edge/pane, not by row/col.  So is [[leftPaneWidth, rightPaneWidth], 
                           //                                               [topPaneHeight, bottomPaneHeight]]
  
  var desiredHeaderOuterPixelDepth = []; // [desiredColHeaderOuterHeight, desiredRowHeaderOuterWidth].  Note is in edge order (colHeader then rowHeader, NOT size order (height is before width, which is backwards).  "Depth" means size layer-wise, not slice-wise 
  var desiredDatabodyRegionSize = []; // [desiredDatabodyRegionWidth, desiredDatabodyRegionHeight]
  
  var isEmpty = (rowHeaderRows[1].length == 1) // then it's "no data" case.
  if (isEmpty)
  {
    var emptyTextDiv = databodyBlocks[1][1];
    var emptyTextInnerDiv = emptyTextDiv.childNodes[0];
    
    var totalHorizMargin = parseInt(emptyTextDiv.getAttribute("_totHorizMargin"));
    var totalVertMargin  = parseInt(emptyTextDiv.getAttribute("_totVertMargin"));
    
    desiredHeaderOuterPixelDepth = AdfPivotTableUtils.HEADER_OUTER_PIXEL_DEPTH_NO_DATA;
    desiredDatabodyRegionSize = [emptyTextInnerDiv.offsetWidth  + totalHorizMargin,
                                 emptyTextInnerDiv.offsetHeight + totalVertMargin];
  }
  else // normal case (not no-data)
  {
    for (var edge=0; edge<2; ++edge) // width then height
    {
      var offsetSizeAttr = offsetSizeAttrs[edge]; // offsetWidth then offsetHeight
      desiredDatabodyRegionSize[edge] = 0;
      
      for (var pane=startPane[edge]; pane<2; ++pane) {
        blockSize[edge][pane] = headerBlocks[edge][pane][offsetSizeAttr];
        desiredDatabodyRegionSize[edge] += blockSize[edge][pane];
      }
      
      var headerBlockPixelDepth = headerBlocks[1-edge][1][offsetSizeAttr]; // rowHeaderWidth then colHeaderHeight
      desiredHeaderOuterPixelDepth[1-edge] = headerBlockPixelDepth + headerExtraPixelDepth[1-edge]; // ditto
    }
  }
  
  var headerInnerPixelDepth  = []; // [colHeaderInnerHeight, rowHeaderInnerWidth].  Note is in edge order (colHeader then rowHeader, NOT size order (height is before width, which is backwards).  "Depth" means size layer-wise, not slice-wise 
  var headerOuterPixelDepth  = []; // [colHeaderOuterHeight, rowHeaderOuterWidth].  Note is in edge order (colHeader then rowHeader, NOT size order (height is before width, which is backwards).  "Depth" means size layer-wise, not slice-wise 
  
  // colHeaderHeight and rowHeaderWidth allocation.  Both default to 50% (i.e. 0.5 and '%') if app didn't set them
  // For 15.3%, number is 0.153, not 0.15 or 15.3 or 15.  For px, number is int.
  var headerSizeAllocation = [Number(ptOuterContainer.getAttribute("_sizeAlloc0")),  // casts won't bomb; server validates they are valid numbers
                              Number(ptOuterContainer.getAttribute("_sizeAlloc1"))];
  var headerSizeAllocUnit  = [ptOuterContainer.getAttribute("_sizeUnit0"),  // server ensures units are always '%' or 'px'
                              ptOuterContainer.getAttribute("_sizeUnit1")];
  
  for (var edge=0; edge<2; ++edge) {
    var ptAvailableContentSize = ptAvailableSize[edge] - headerExtraPixelDepth[1-edge];
    var desiredHeaderInnerPixelDepth = desiredHeaderOuterPixelDepth[1-edge] - headerExtraPixelDepth[1-edge];
    
    if (emptyTextDiv) { // if we're displaying the no-data message b/c there's no data 
        var headerAllocation = 0.15; // the amount of pivot table space (in % or px) to which the header is entitled if it needs it.  The headers are just empty space when we're displaying the "no data" message, so we give them much lower priority (than the default 0.5) in that case, but we don't want them to disappear altogether.
        var headerAllocUnit = '%';
    } else {
        headerAllocation = headerSizeAllocation[1-edge]; // 50% by default (if app didn't set it)
        headerAllocUnit  = headerSizeAllocUnit[1-edge]; // '%' or 'px'
    }
    
    var headerDatabodyAllocationInfo = AdfPivotTableUtils.allocateSpace(ptAvailableContentSize, desiredHeaderInnerPixelDepth, desiredDatabodyRegionSize[edge], headerAllocation, headerAllocUnit);
    
    headerInnerPixelDepth[1-edge] = headerDatabodyAllocationInfo[0]; // the index into headerDatabodyAllocationInfo does NOT represent an edge!
    headerOuterPixelDepth[1-edge] = headerInnerPixelDepth[1-edge] + headerExtraPixelDepth[1-edge];
  }
  
  
  // -- shrink to fit; calc databody region sizes (but don't set them on DOM yet) --
  
  var sizing = AdfAgent.getAgent().getAttribute(ptOuterContainer, "sizing");
  var bAutosize = AdfPivotTableUtils.isShrinkToFit(sizing, ptOuterContainer);
  
  if(bAutosize) {
      for (var edge=0; edge<2; ++edge) {
          var desiredTotalSize  = desiredHeaderOuterPixelDepth[1-edge]  + desiredDatabodyRegionSize[edge]; 
          ptAvailableSize[edge] = Math.min(ptAvailableSize[edge],  desiredTotalSize);
      }
      
      ptInnerSize[0] = ptAvailableSize[0];
      ptInnerSize[1] = ptAvailableSize[1] + statusBarHeight;
  }
  
  var databodyRegionSize     = [];      // [databodyRegionWidth, databodyRegionHeight]
  var databodyPaneRegionSize = [[],[]]; // indexed by edge (width/height), then by pane, i.e.:
                                        // [[leftPaneWidth, rightPaneWidth],
                                        //  [topPaneHeight, bottomPaneHeight]]

  for (var edge=0; edge<2; ++edge) {
    databodyRegionSize[edge] = ptAvailableSize[edge] - headerOuterPixelDepth[1-edge];

    if (startPane[edge]==0) {
      var splitPercentage = 0.5; //// TODO: use the actual value.  Unlike regular mode, requires sending it up to server and back down.
      var paneAllocationInfo = AdfPivotTableUtils.allocateSpace(databodyRegionSize[edge], blockSize[edge][0], blockSize[edge][1], splitPercentage, '%');
      
      databodyPaneRegionSize[edge][0] = paneAllocationInfo[0]; // the index into paneAllocationInfo does NOT represent an edge!
      databodyPaneRegionSize[edge][1] = databodyRegionSize[edge] - databodyPaneRegionSize[edge][0];
    } else { // else pane 1 takes up entire viewport.
      databodyPaneRegionSize[edge][1] = databodyRegionSize[edge];
    }
  }
  
  
  // -- position the PT divs (on DOM) --
  
  var top = headerOuterPixelDepth[0]; // rowHeader and databody top is where colHeader ends
  var topPx = top + px;
  for (var rowPane=startPane[1]; rowPane<2; ++rowPane) { 
    rowHeaders[rowPane]['style'].top = topPx;
    for (var colPane=startPane[0]; colPane<2; ++colPane) { 
      databodies[rowPane][colPane]['style'].top = topPx;
    }
/////////////// SAME TODO AS SIZEPT() : DO WE NEED TO INCREMENT BY AN "OUTER" HEIGHT INSTEAD, I.E. 1 PIXEL MORE?
    if (rowPane==0) {
      top += databodyPaneRegionSize[1][rowPane]; // this will be used iff there is a second pass of outer loop, in which case that second pass is for rowPane 1, for which we increment "top" by height of top pane.
      topPx = top + px;
    }
  }
  
  if (hasStatusBar) {
    statusBar.top = ptAvailableSize[1] + px;
    statusBar.left = zeroPx; // always set left even in RTL
  }
  
  if (isRtl)
  {
    var left = 0;
    var leftPx = zeroPx;
    for (var colPane=1; colPane>=startPane[0]; --colPane) { // iterate backwards, which is left to right
      columnHeaders[colPane]['style'].left = leftPx;
      for (var rowPane=startPane[1]; rowPane<2; ++rowPane) { 
        databodies[rowPane][colPane]['style'].left = leftPx;
      }
/////////////// TODO: ARE WE OFF BY 1PX DUE TO "INNER" VS. "OUTER" WIDTH ISSUES?
      if (colPane==1) {
        left += databodyPaneRegionSize[0][colPane]; // this will be used iff there is a second pass of outer loop, in which case that second pass is for colPane 0, for which all "lefts" are the size of pane 1.
        leftPx = left + px;
      }
    }
  }
  else
  {
    var left = headerOuterPixelDepth[1]; // colHeader and databody left is where rowHeader ends
    var leftPx = left + px;
    for (var colPane=startPane[0]; colPane<2; ++colPane) {
      columnHeaders[colPane]['style'].left = leftPx;
      for (var rowPane=startPane[1]; rowPane<2; ++rowPane) { 
        databodies[rowPane][colPane]['style'].left = leftPx;
      }
/////////////// SAME TODO AS SIZEPT() : DO WE NEED TO INCREMENT BY AN "OUTER" WIDTH INSTEAD, I.E. 1 PIXEL MORE?
      if (colPane==0) {
        left += databodyPaneRegionSize[0][colPane]; // this will be used iff there is a second pass of outer loop, in which case that second pass is for colPane 1, for which we increment "left" by width of left pane.
        leftPx = left + px;
      }
    }
  }
  
  
  // -- size the PT divs (on DOM) --
  
  corner['style'].width  = headerOuterPixelDepth[1] + px;  // rowHeaderOuterWidth  = cornerOuterWidth  = cornerInnerWidth,  since cornerExtraWidth  = 0.
  corner['style'].height = headerOuterPixelDepth[0] + px; // colHeaderOuterHeight = cornerOuterHeight = cornerInnerHeight, since cornerExtraHeight = 0.

  for (var edge=0; edge<2; ++edge) {
    var header = headers[edge];
    var depthAttr = sizeAttrs[1-edge]; // height then width
    var extentAttr = sizeAttrs[edge]; // width then height
    var headerInnerPixelDepthPx = headerInnerPixelDepth[edge] + px;
    for (var pane=startPane[edge]; pane<2; ++pane) {
      var headerInnerPixelExtent = databodyPaneRegionSize[edge][pane] - headerExtraPixelExtent[edge]; // databodyPaneRegionSize is headerOuterPixelExtent
      header[pane]['style'][depthAttr]  = headerInnerPixelDepthPx;
      header[pane]['style'][extentAttr] = headerInnerPixelExtent + px;
    }
  }
  
  for (var edge=0; edge<2; ++edge) {
    var sizeAttr = sizeAttrs[edge];
    for (var pane=startPane[edge]; pane<2; ++pane) {
      var databodyInnerSize  = databodyPaneRegionSize[edge][pane] - databodyExtraSize[edge]; // databodyOuterSize is databodyPaneRegionSize
      var databodyInnerSizePx = databodyInnerSize + px;
      for (var otherPane=startPane[1-edge]; otherPane<2; ++otherPane) {
        var databody = edge ? databodies[pane][otherPane] : databodies[otherPane][pane];
        databody['style'][sizeAttr] = databodyInnerSizePx;
      }
    }
  }
  
  var ptInnerWidthPx = ptInnerSize[0] + px;
  
  if (hasStatusBar) {
      statusBar['style']['width'] = ptInnerWidthPx; // don't change the height the browser calced for it
  }
  
  innerContainer['style']['width'] = ptInnerWidthPx;
  innerContainer['style']['height'] = ptInnerSize[1] + px;
  if(bAutosize)
  {
    var ptOuterContainerWidth = ptInnerSize[0] + containerExtraSize[0]; // the outer frame is 2 px bigger than the inner, due to borders on the inner
    ptOuterContainer['style']['width'] = ptOuterContainerWidth + px;
    var ptOuterContainerHeight = ptInnerSize[1] + containerExtraSize[1]; // the outer frame is 2 px bigger than the inner, due to borders on the inner
    ptOuterContainer['style']['height'] = ptOuterContainerHeight + px;
  }
  
  
  // -- Get start slices and layers --
  
  var startSlices = [];
  var startPixels = [];
  
  if (startPane[0] == 0) {
    startSlices[0] = [parseInt(ptOuterContainer.getAttribute('_startCol')), 
                      parseInt(ptOuterContainer.getAttribute('_secondStartCol'))];
    startPixels[0] = [parseInt(ptOuterContainer.getAttribute('_startColPixel')), 
                      parseInt(ptOuterContainer.getAttribute('_secondStartColPixel'))];
  } else {
    startSlices[0] = [null, 
                      parseInt(ptOuterContainer.getAttribute('_startCol'))];
    startPixels[0] = [null, 
                      parseInt(ptOuterContainer.getAttribute('_startColPixel'))];
  }
  
  if (startPane[1] == 0) {
    startSlices[1] = [parseInt(ptOuterContainer.getAttribute('_startRow')), 
                      parseInt(ptOuterContainer.getAttribute('_secondStartRow'))];
    startPixels[1] = [parseInt(ptOuterContainer.getAttribute('_startRowPixel')), 
                      parseInt(ptOuterContainer.getAttribute('_secondStartRowPixel'))];
  } else {
    startSlices[1] = [null, 
                      parseInt(ptOuterContainer.getAttribute('_startRow'))];
    startPixels[1] = [null, 
                      parseInt(ptOuterContainer.getAttribute('_startRowPixel'))];
  }
  
  var startLayers = [parseInt(ptOuterContainer.getAttribute('_colHeaderStartLayer')), 
                     parseInt(ptOuterContainer.getAttribute('_rowHeaderStartLayer'))];
  var layerPixels = [parseInt(ptOuterContainer.getAttribute('_colHeaderStartPixel')), 
                     parseInt(ptOuterContainer.getAttribute('_rowHeaderStartPixel'))];
  
  
  // -- Position blocks --
  
  for (var edge=0; edge<2; ++edge) // width, then height
  {
    if (edge) 
    {
      var start = 'top';
      var end = 'bottom';
    } 
    else 
    {
      if (isRtl) {
        start = 'right';
        end = 'left';
      } else {
        start = 'left';
        end = 'right';
      }
    }
    
    var offsetSizeAttr = offsetSizeAttrs[edge];
    var clientSizeAttr = clientSizeAttrs[edge];
    
    // Adjust positions of columnHeader and rowHeader blocks to match start slices
    for (var pane=startPane[edge]; pane<2; ++pane) // edge0: colHeader panes.  edge1: rowHeader panes
    {
      var header = headers[edge][pane];
      var headerBlock = headerBlocks[edge][pane];
      var slices = edge ? headerRows[edge][pane] : headerRows[edge][pane][0].childNodes;
      
      var sliceAdjust = startSlices[edge][pane] - parseInt(headerBlock.getAttribute('_startslice'));
      var pixelAdjust = -startPixels[edge][pane];
      
      for (i = 0; i < sliceAdjust; i++)
      {
        pixelAdjust -= slices[i + 1][offsetSizeAttr];
      }
      
      if (headerBlock[offsetSizeAttr] + pixelAdjust < header[clientSizeAttr]) // then must shift block toward end to fill whitespace.  Unlike non-PP code, accuracy based on assumption that padding is 0, since clientW/H includes padding.
      {
        // if block doesn't fill viewport, position at start so whitespace is at end; else position at end, so that we've shifted it just far enough to fill the whitespace.
        var side = (headerBlock[offsetSizeAttr] < header[clientSizeAttr]) ? start : end; // same clientWidth accuracy comment as above
        var positionPx = zeroPx;
      }
      else // normal case: no whitespace to fill, so honor the pixelAdjust that we calculated
      {
        side = start;
        positionPx = pixelAdjust + px;
      }
      
      headerBlock['style'][side] = positionPx;
      
      for (var otherPane=startPane[1-edge]; otherPane<2; ++otherPane) {
        var databodyBlock = (edge) ? databodyBlocks[pane][otherPane] : databodyBlocks[otherPane][pane];
        databodyBlock['style'][side] = positionPx;
      }
    }
    
    // Adjust positions of header blocks to match position of *header* scrollbars
    var layerAdjust = startLayers[1-edge];
    pixelAdjust = -layerPixels[1-edge];
      
    for (var pane=startPane[1-edge]; pane<2; ++pane) // edge=0: rowHeader panes.  edge=1: colHeader panes.  (Since using 1-edge)
    {
      var headerBlock = headerBlocks[1-edge][pane];
      
      if (pane==startPane[1-edge]) 
      {
        var header = headers[1-edge][pane];
        var layers = edge ? headerRows[1-edge][pane] : headerRows[1-edge][pane][0].childNodes;
        
        for (i = 0; i < layerAdjust; i++)
        {
          pixelAdjust -= layers[i + 1][offsetSizeAttr];
        }
        
        if (headerBlock[offsetSizeAttr] + pixelAdjust < header[clientSizeAttr]) // then must shift block toward end to fill whitespace.  Unlike non-PP code, accuracy based on assumption that padding is 0, since clientW/H includes padding.
        {
          // position at end, so that we've shifted it just far enough to fill the whitespace.
          side = end;
          positionPx = zeroPx;
        }
        else // normal case: no whitespace to fill, so honor the pixelAdjust that we calculated
        {
          side = start;
          positionPx = pixelAdjust + px;
        }
      }
      
      headerBlock['style'][side] = positionPx;
    }
  }
}

// This method should be called only in IE.
// In order for text area height to stretch the entire cell in IE, the td containing the text area
// must have a height specified.
AdfPivotTableUtils.prototype.updateTextAreaCellHeights = function(row, height) 
{
    var cells = row.cells;

    // skip the first cell element as it is the resized element
    for (var i=1; i<cells.length; i++) 
    {
        var cell = cells[i];
        var textarea = this.getFirstTextArea(cell);
        if (textarea != null)
        {
            var offset = this.getCellOffset(cell);

            // if height is not specified, find that out from the row
            if (height == undefined)
                height = row.offsetHeight;

            // the maximum height the stamp can occupy excludes any padding/margin/border of the cell 
            // note we do not exclude the padding/margin/border of the text area itself.  It is up to the
            // page author to make sure the height of the textarea is adjusted accordingly to account for
            // any padding/margin/border set on the text area.
            var cellHeight = Math.max(0, height - offset);
            cell.style.height = cellHeight + 'px';
        }
    }
}

// return the padding/margin/border in the cell
AdfPivotTableUtils.prototype.getCellOffset = function(cell) 
{
    // use the existing constant for now, this assumes the padding/margin/border of the TD is the same for all cells
    return AdfPivotTableUtils.CELL_EXTRA_HEIGHT;
}

// returns the first textarea within a particular node
AdfPivotTableUtils.prototype.getFirstTextArea = function(node) 
{
    var nodes = node.getElementsByTagName("TEXTAREA");
    var nodeCount = nodes.length;
    if (nodes.length > 0)
        return nodes[0];
    else
        return null;
}

// determine the maximum scroll height from text area within cells of a row
AdfPivotTableUtils.prototype.getMaximumScrollHeight = function(cells) 
{
    var maxSize = 0;
    // skip the first cell element as it is the resized element
    for (var i=1; i<cells.length; i++) 
    {
        var cell = cells[i];
        var offset = this.getCellOffset(cell);
        var nodes = cell.getElementsByTagName("*");
        var nodeCount = nodes.length;
        for( var j=0; j<nodeCount; j++) 
        {
             var elem = nodes[j];
             // the maximum size for the text area must include any padding/margin/border on the cell
             // note we do not account for the padding/margin/border of the elem.  Doing so would be
             // expensive (getComputedStyle for all elements within each cell).  Documented this and
             // suggested page author not to specify padding/margin/border on stamped element in order
             // to get an accurate result
             var size = elem['scrollHeight'] + offset;
             maxSize = Math.max(maxSize, size);
        }        
    }

    return maxSize;
}
