infiniteScroll: fix incompatibility w/ message links

When opening a link to a specific message inside a thread in both TW
basic and the Community Console, the "load more" button might be visible
for an instant before/while scrolling to and focusing the specific
message being linked. This causes the "load more" button to be pressed
automatically when the infinite scroll feature is enabled.

This CL fixes this issue by adding a delay of 3.5s before enabling the
infinite scroll feature when the URL specifies a message to focus.

Fixed: twpowertools:141
Change-Id: Ie9126141ee9f763707bd61b272ec53b4e1d8079f
diff --git a/src/contentScripts/communityConsole/infiniteScroll.js b/src/contentScripts/communityConsole/infiniteScroll.js
index 0d2f3c1..447974e 100644
--- a/src/contentScripts/communityConsole/infiniteScroll.js
+++ b/src/contentScripts/communityConsole/infiniteScroll.js
@@ -9,6 +9,7 @@
   'scTailwindThreadMessagegapload-all': 'threadall',
   'scTailwindThreadMessagegapload-more': 'thread',
 };
+const kArtificialScrollingDelay = 3500;
 
 export default class InfiniteScroll {
   constructor() {
@@ -41,7 +42,11 @@
     });
   }
 
-  observeLoadMoreBar(bar) {
+  isPotentiallyArtificialScroll() {
+    return window.location.href.includes('/message/');
+  }
+
+  observeWithPotentialDelay(node) {
     if (this.intersectionObserver === null) {
       console.warn(
           '[infinitescroll] ' +
@@ -49,24 +54,25 @@
       return;
     }
 
+    if (this.isPotentiallyArtificialScroll()) {
+      window.setTimeout(
+          () => {this.intersectionObserver.observe(node)},
+          kArtificialScrollingDelay);
+    } else {
+      this.intersectionObserver.observe(node);
+    }
+  }
+
+  observeLoadMoreBar(bar) {
     getOptions(['thread', 'threadall']).then(threadOptions => {
       if (threadOptions.thread)
-        this.intersectionObserver.observe(
-            bar.querySelector('.load-more-button'));
+        this.observeWithPotentialDelay(bar.querySelector('.load-more-button'));
       if (threadOptions.threadall)
-        this.intersectionObserver.observe(
-            bar.querySelector('.load-all-button'));
+        this.observeWithPotentialDelay(bar.querySelector('.load-all-button'));
     });
   }
 
   observeLoadMoreInteropBtn(btn) {
-    if (this.intersectionObserver === null) {
-      console.warn(
-          '[infinitescroll] ' +
-          'The intersectionObserver is not ready yet.');
-      return;
-    }
-
     let parentClasses = btn.parentNode?.classList;
     let feature = null;
     for (const [c, f] of Object.entries(kInteropLoadMoreClasses)) {
@@ -77,7 +83,7 @@
     }
     if (feature === null) return;
     isOptionEnabled(feature).then(isEnabled => {
-      if (isEnabled) this.intersectionObserver.observe(btn);
+      if (isEnabled) this.observeWithPotentialDelay(btn);
     });
   }
 };
diff --git a/src/contentScripts/publicThread.js b/src/contentScripts/publicThread.js
index 5ad2a81..dd2088a 100644
--- a/src/contentScripts/publicThread.js
+++ b/src/contentScripts/publicThread.js
@@ -21,6 +21,7 @@
     ],
   }
 ];
+const kMsgidDelay = 3500;
 
 var intersectionObserver;
 
@@ -36,6 +37,34 @@
   threshold: 1.0,
 };
 
+function setUpInfiniteScroll(options) {
+  for (const entry of kLoadMoreButtons) {
+    if (options[entry.feature]) {
+      for (const selector of entry.buttonSelectors) {
+        let buttons = document.querySelectorAll(selector);
+        buttons.forEach(button => {
+          intersectionObserver = new IntersectionObserver(
+              intersectionCallback, intersectionOptions);
+          intersectionObserver.observe(button);
+        });
+      }
+    }
+  }
+}
+
+function setUpInfiniteScrollWithPotentialDelay(options) {
+  // If the msgid query parameter is set, the page will scroll to that message,
+  // which will show the "load more" button.
+  const params = new URLSearchParams(window.location.search);
+  if (params.has('msgid')) {
+    window.setTimeout(() => {
+      setUpInfiniteScroll(options);
+    }, kMsgidDelay);
+  } else {
+    setUpInfiniteScroll(options);
+  }
+}
+
 getOptions(null).then(options => {
   var redirectLink = document.querySelector('.community-console');
   if (options.redirect && redirectLink !== null) {
@@ -49,16 +78,7 @@
 
     window.location = redirectUrl;
   } else {
-    for (const entry of kLoadMoreButtons)
-      if (options[entry.feature])
-        for (const selector of entry.buttonSelectors) {
-          let button = document.querySelector(selector);
-          if (button !== null) {
-            intersectionObserver = new IntersectionObserver(
-                intersectionCallback, intersectionOptions);
-            intersectionObserver.observe(button);
-          }
-        }
+    setUpInfiniteScrollWithPotentialDelay(options);
 
     if (options.imagemaxheight)
       injectStylesheet(chrome.runtime.getURL('css/image_max_height.css'));