updateViewportScrolling = function(evt, scroller, scrollTimestamp) {
    var $this = $(this),
        data = $this.data(pluginName),
        containerDimensions = data.containerDimensions,
        threshold = data.options.scrollingThreshold,
        scrollSpeedMultiplier = data.options.scrollSpeedMultiplier;

    if (data.scrollingTimeout) {
      window.clearTimeout(data.scrollingTimeout);
      delete data.scrollingTimeout;
    }

    // Compute a multiplier based on the actual amount of time that
    // passed since the last scrolling update, to keep scrolling speed
    // constant as if scrolling occurred at exactly 60fps.
    var scrollLagMultiplier = scrollTimestamp ?
        (new Date().getTime() - scrollTimestamp) / 16 : 1;
    var tickTimestamp = scrollTimestamp;

    var scrolled = false;
    var scrollableDistances = scroller.getScrollableDistances();
    var scrollBorders = scroller.getScrollBorders ?
        scroller.getScrollBorders() : getDefaultScrollBorders.call(this);
    var scrollableElement = scroller.getScrollableElement ?
        scroller.getScrollableElement() :
        getDefaultScrollableElement.call(this);

    var scrollMetrics = [
      { // top
        distance: Math.max(evt.pageY - scrollBorders[0], 0),
        direction: -1,
        scrollAxis: 'vertical',
        positionProperty: 'pageY'
      },
      { // right
        distance: Math.max(scrollBorders[1] - evt.pageX, 0),
        direction: 1,
        scrollAxis: 'horizontal',
        positionProperty: 'pageX'
      },
      { // bottom
        distance: Math.max(scrollBorders[2] - evt.pageY, 0),
        direction: 1,
        scrollAxis: 'vertical',
        positionProperty: 'pageY'
      },
      { // left
        distance: Math.max(evt.pageX - scrollBorders[3], 0),
        direction: -1,
        scrollAxis: 'horizontal',
        positionProperty: 'pageX'
      }
    ];

    for (var i = scrollMetrics.length - 1; i >= 0; i--) {
      var metric = scrollMetrics[i];
      var available = scrollableDistances[i];

      if (
          // We are within a minimum threshold distance from the viewport
          // border, and
          metric.distance < threshold &&

          // We still have room for scrolling, and
          available > 0 &&

            // We are moving toward the viewport border
            sign(
                data.curPosition[metric.positionProperty] -
                data.lastPosition[metric.positionProperty]) ==
                    metric.direction
        ) {

        // Compute the scrolling shift: the closer we push the mouse toward the
        // viewport border, the bigger the shift.
        var shift = metric.direction * Math.round(Math.min(
            available,
            Math.ceil((threshold - metric.distance) / 10) *
                scrollLagMultiplier * scrollSpeedMultiplier));

        // Scroll in the desired direction
        scroller.scroll(metric.scrollAxis, shift);

        if (scrollableElement == this) {
          // If we scrolled the content of the selection container, move the
          // selection box starting position in the opposite direction by the
          // same amount, to keep its origin fixed (with respect to the
          // selection container top-left corner).
          data.startPosition[metric.positionProperty] -= shift;
          data.curPosition[metric.positionProperty] -= shift;
        } else {
          // Otherwise, if a wrapping element was scrolled (assuming it wraps
          // the selection container), advance the mouse position by the same
          // amount.
          evt[metric.positionProperty] += shift;
        }

        scrolled = true;
      }
    }

    if (scrolled) {
      // If scrolling started, continue scrolling until another mouse movement
      // is detected (to handle the case when the mouse is moved toward a
      // viewport border and left stationary for the scrolling to continue at a
      // constant speed).
      data.scrollingTimeout = window.setTimeout($.proxy(
          function() { tick.call(this, evt, tickTimestamp); }, this),
          16);  // try to keep scrolling at 60fps.
    } else if (scroller.next) {
      // Delegate to a chained scroller, if present.
      updateViewportScrolling.call(this, evt, scroller.next, scrollTimestamp);
    }
  }
Triggers the selection container viewport scrolling, if the selection
box is being dragged too close to the viewport borders.

@param {!Event} evt The last mousemove event received.
@param {!Object} scroller A scroller implementation, for example
    'documentScroller' or 'contentScroller'.
@param {number?} scrollTimestamp The timestamp at which the last scrolling
    operation was performed. Undefined if a mouse movement occurred in
    between.