hterm: fix mouse events to adjust for screen padding

Bug introduced when padding was added in crrev.com/c/2143311.

We clamp to the nearest value when click is in padding.

Bug: 267654
Change-Id: I82744a84a375bac165ff590ea35262e78f7a8ba6
Reviewed-on: https://chromium-review.googlesource.com/c/apps/libapps/+/2172936
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/hterm/js/hterm_scrollport.js b/hterm/js/hterm_scrollport.js
index 91c6ab7..1c07f2e 100644
--- a/hterm/js/hterm_scrollport.js
+++ b/hterm/js/hterm_scrollport.js
@@ -822,6 +822,16 @@
 };
 
 /**
+ * Get the horizontal position in px where the scrollbar starts.
+ *
+ * @return {number}
+ */
+hterm.ScrollPort.prototype.getScrollbarX = function() {
+  return hterm.getClientSize(lib.notNull(this.screen_)).width -
+         this.currentScrollbarWidthPx;
+};
+
+/**
  * Return the document that holds the visible rows of this hterm.ScrollPort.
  *
  * @return {!Document}
diff --git a/hterm/js/hterm_terminal.js b/hterm/js/hterm_terminal.js
index 13ca6b4..3a3a94a 100644
--- a/hterm/js/hterm_terminal.js
+++ b/hterm/js/hterm_terminal.js
@@ -597,7 +597,6 @@
         console.error(`Invalid screen padding size: ${v}`);
         return;
       }
-      terminal.setCssVar('screen-padding-size', `${v}px`);
       terminal.setScreenPaddingSize(v);
     },
 
@@ -1172,6 +1171,7 @@
  * @param {number} size
  */
 hterm.Terminal.prototype.setScreenPaddingSize = function(size) {
+  this.setCssVar('screen-padding-size', `${size}px`);
   this.scrollPort_.setScreenPaddingSize(size);
 };
 
@@ -3818,14 +3818,19 @@
   }
 
   // One based row/column stored on the mouse event.
+  const padding = this.scrollPort_.screenPaddingSize;
   e.terminalRow = Math.floor(
-      (e.clientY - this.scrollPort_.visibleRowTopMargin) /
+      (e.clientY - this.scrollPort_.visibleRowTopMargin - padding) /
       this.scrollPort_.characterSize.height) + 1;
   e.terminalColumn = Math.floor(
-      e.clientX / this.scrollPort_.characterSize.width) + 1;
+      (e.clientX - padding) / this.scrollPort_.characterSize.width) + 1;
 
-  if (e.type == 'mousedown' && e.terminalColumn > this.screenSize.width) {
-    // Mousedown in the scrollbar area.
+  // Clamp row and column.
+  e.terminalRow = lib.f.clamp(e.terminalRow, 1, this.screenSize.height);
+  e.terminalColumn = lib.f.clamp(e.terminalColumn, 1, this.screenSize.width);
+
+  // Ignore mousedown in the scrollbar area.
+  if (e.type == 'mousedown' && e.clientX >= this.scrollPort_.getScrollbarX()) {
     return;
   }
 
diff --git a/hterm/js/hterm_terminal_tests.js b/hterm/js/hterm_terminal_tests.js
index 85065dc..a1130ac 100644
--- a/hterm/js/hterm_terminal_tests.js
+++ b/hterm/js/hterm_terminal_tests.js
@@ -701,6 +701,74 @@
 });
 
 /**
+ * Check mouse row and column.
+ */
+it('mouse-row-column', function() {
+  const terminal = this.terminal;
+  let e;
+
+  // Turn on mouse click reporting.
+  terminal.vt.setDECMode('1000', true);
+
+  let eventReported = false;
+  terminal.onMouse = (e) => {
+    eventReported = true;
+  };
+  const send = (type, x, y) => {
+    eventReported = false;
+    const e = MockTerminalMouseEvent(type, {clientX: x, clientY: y});
+    terminal.onMouse_(e);
+    return e;
+  };
+
+  const padding = terminal.scrollPort_.screenPaddingSize;
+  const charWidth = terminal.scrollPort_.characterSize.width;
+  const charHeight = terminal.scrollPort_.characterSize.height;
+  const screenWidth = terminal.screenSize.width;
+  const screenHeight = terminal.screenSize.height;
+
+  // Cell 10, 10.
+  const x10 = padding + 9.5 * charWidth;
+  const y10 = padding + 9.5 * charHeight;
+  e = send('mousedown', x10, y10);
+  assert.isTrue(eventReported);
+  assert.equal(e.terminalRow, 10);
+  assert.equal(e.terminalColumn, 10);
+
+  // Top padding, clamp to row 1.
+  e = send('mousedown', x10, 0);
+  assert.isTrue(eventReported);
+  assert.equal(e.terminalRow, 1);
+  assert.equal(e.terminalColumn, 10);
+
+  // Right padding, clamp to width.
+  e = send('mousedown', padding + (screenWidth * charWidth) + 1, y10);
+  assert.isTrue(eventReported);
+  assert.equal(e.terminalRow, 10);
+  assert.equal(e.terminalColumn, screenWidth);
+
+  // Scrollbar area, ignore mousedown.
+  e = send('mousedown', (2 * padding) + (screenWidth * charWidth) + 1, y10);
+  assert.isFalse(eventReported);
+  e = send('mousemove', (2 * padding) + (screenWidth * charWidth) + 1, y10);
+  assert.isTrue(eventReported);
+  assert.equal(e.terminalRow, 10);
+  assert.equal(e.terminalColumn, screenWidth);
+
+  // Bottom padding, clamp to height.
+  e = send('mousedown', x10, padding + (screenHeight * charHeight) + 1);
+  assert.isTrue(eventReported);
+  assert.equal(e.terminalRow, screenHeight);
+  assert.equal(e.terminalColumn, 10);
+
+  // Left padding, clamp to column 1.
+  e = send('mousedown', 0, y10);
+  assert.isTrue(eventReported);
+  assert.equal(e.terminalRow, 10);
+  assert.equal(e.terminalColumn, 1);
+});
+
+/**
  * Check paste() is working correctly. Note that we do not test the legacy
  * pasting using document.execCommand() because it is hard to simulate the
  * behavior.