Only invoke translate callbacks once to avoid duplicate notifications.

This fixes a regression where the "page translated" translate info bar
is shown again and again as the user scrolls through a page which was
translated.

Reason:
iOS 12.2 will ship with native IntersectionObserver support. This means
that html content is no longer translated in full, but rather translated
incrementally as the user scrolls the elements into view. Each
incremental translation will fire off a "translate completed" callback,
but only the first one should be handled.

Bug: 925329
Change-Id: If780a7249329bf1458f2e5ea53290be8a5f9288a
Reviewed-on: https://chromium-review.googlesource.com/c/1437525
Commit-Queue: John Wu <jzw@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#627612}(cherry picked from commit 591b145bf81bf2d7763f4fbd0fc902ee96225064)
Reviewed-on: https://chromium-review.googlesource.com/c/1450393
Reviewed-by: John Wu <jzw@chromium.org>
Cr-Commit-Position: refs/branch-heads/3683@{#124}
Cr-Branched-From: e51029943e0a38dd794b73caaf6373d5496ae783-refs/heads/master@{#625896}
diff --git a/components/translate/core/browser/resources/translate.js b/components/translate/core/browser/resources/translate.js
index fb37e42..802a286 100644
--- a/components/translate/core/browser/resources/translate.js
+++ b/components/translate/core/browser/resources/translate.js
@@ -104,12 +104,18 @@
 
   /**
    * Callback invoked when Translate Element's ready state is known.
+   * Will only be invoked once to indicate successful or failed initialization.
+   * In the failure case, errorCode() and error() will indicate the reason.
+   * Only used on iOS.
    * @type {function}
    */
   var readyCallback;
 
   /**
    * Callback invoked when Translate Element's translation result is known.
+   * Will only be invoked once to indicate successful or failed translation.
+   * In the failure case, errorCode() and error() will indicate the reason.
+   * Only used on iOS.
    * @type {function}
    */
   var resultCallback;
@@ -150,6 +156,15 @@
       lib.restore();
       invokeResultCallback();
     }
+    // Translate works differently depending on the prescence of the native
+    // IntersectionObserver APIs.
+    // If it is available, translate will occur incrementally as the user
+    // scrolls elements into view, and this method will be called continuously
+    // with |opt_finished| always set as true.
+    // On the other hand, if it is unavailable, the entire page will be
+    // translated at once in a piece meal manner, and this method may still be
+    // called several times, though only the last call will have |opt_finished|
+    // set as true.
     if (finished) {
       endTime = performance.now();
       invokeResultCallback();
@@ -159,12 +174,14 @@
   function invokeReadyCallback() {
     if (readyCallback) {
       readyCallback();
+      readyCallback = null;
     }
   }
 
   function invokeResultCallback() {
     if (resultCallback) {
       resultCallback();
+      resultCallback = null;
     }
   }