docs: function(sortedInputEvents) {
        var protoIRs = [];
        var currentPIR = undefined;
        var mouseDownEvent = undefined;
        forEventTypesIn(
            sortedInputEvents, MOUSE_DRAG_TYPE_NAMES, function(event) {
          switch (event.typeName) {
            case INPUT_TYPE.MOUSE_DOWN:
              if (causedFrame(event)) {
                var pir = new ProtoIR(ProtoIR.RESPONSE_TYPE, MOUSE_IR_NAME);
                pir.pushEvent(event);
                protoIRs.push(pir);
              } else {
                // Responses typically don't start until the mouse up event.
                // Add this MouseDown to the Response that starts at the MouseUp.
                mouseDownEvent = event;
              }
              break;
              // There may be more than 100ms between the start of the mouse down
              // and the start of the mouse up. Chrome and the web don't start to
              // respond until the mouse up. ResponseIRs start deducting comfort
              // at 100ms duration. If more than that 100ms duration is burned
              // through while waiting for the user to release the
              // mouse button, then ResponseIR will unfairly start deducting
              // comfort before Chrome even has a mouse up to respond to.
              // It is technically possible for a site to afford one response on
              // mouse down and another on mouse up, but that is an edge case. The
              // vast majority of mouse downs are not responses.
  
            case INPUT_TYPE.MOUSE_MOVE:
              if (!causedFrame(event)) {
                // Ignore MouseMoves that do not affect the screen. They are not
                // part of an interaction record by definition.
                var pir = new ProtoIR(ProtoIR.IGNORED_TYPE);
                pir.pushEvent(event);
                protoIRs.push(pir);
              } else if (!currentPIR ||
                         !currentPIR.isNear(event, MOUSE_MOVE_THRESHOLD_MS)) {
                // The first MouseMove after a MouseDown or after a while is a
                // Response.
                currentPIR = new ProtoIR(ProtoIR.RESPONSE_TYPE, MOUSE_IR_NAME);
                currentPIR.pushEvent(event);
                if (mouseDownEvent) {
                  currentPIR.associatedEvents.push(mouseDownEvent);
                  mouseDownEvent = undefined;
                }
                protoIRs.push(currentPIR);
              } else {
                // Merge this event into an Animation.
                if (currentPIR.irType === ProtoIR.ANIMATION_TYPE) {
                  currentPIR.pushEvent(event);
                } else {
                  currentPIR = new ProtoIR(ProtoIR.ANIMATION_TYPE, MOUSE_IR_NAME);
                  currentPIR.pushEvent(event);
                  protoIRs.push(currentPIR);
                }
              }
              break;
  
            case INPUT_TYPE.MOUSE_UP:
              if (!mouseDownEvent) {
                var pir = new ProtoIR(causedFrame(event) ? ProtoIR.RESPONSE_TYPE :
                    ProtoIR.IGNORED_TYPE, MOUSE_IR_NAME);
                pir.pushEvent(event);
                protoIRs.push(pir);
                break;
              }
  
              if (currentPIR) {
                currentPIR.pushEvent(event);
              } else {
                currentPIR = new ProtoIR(ProtoIR.RESPONSE_TYPE, MOUSE_IR_NAME);
                if (mouseDownEvent)
                  currentPIR.associatedEvents.push(mouseDownEvent);
                currentPIR.pushEvent(event);
                protoIRs.push(currentPIR);
              }
              mouseDownEvent = undefined;
              currentPIR = undefined;
              break;
          }
        });
        if (mouseDownEvent) {
          currentPIR = new ProtoIR(ProtoIR.IGNORED_TYPE);
          currentPIR.pushEvent(mouseDownEvent);
          protoIRs.push(currentPIR);
        }
        return protoIRs;
      }