var EXPORTED_SYMBOLS = ["ReftestFissionParent"];

class ReftestFissionParent extends JSWindowActorParent {

  tellChildrenToFlushRendering(browsingContext, ignoreThrottledAnimations, needsAnimationFrame) {
    let promises = [];
    this.tellChildrenToFlushRenderingRecursive(browsingContext, ignoreThrottledAnimations, needsAnimationFrame, promises);
    return Promise.allSettled(promises);
  }

  tellChildrenToFlushRenderingRecursive(browsingContext, ignoreThrottledAnimations, needsAnimationFrame, promises) {
    let cwg = browsingContext.currentWindowGlobal;
    if (cwg && cwg.isProcessRoot) {
      let a = cwg.getActor("ReftestFission");
      if (a) {
        let responsePromise = a.sendQuery("FlushRendering", {ignoreThrottledAnimations, needsAnimationFrame});
        promises.push(responsePromise);
      }
    }

    for (let context of browsingContext.children) {
      this.tellChildrenToFlushRenderingRecursive(context, ignoreThrottledAnimations, needsAnimationFrame, promises);
    }
  }

  // not including browsingContext
  getNearestProcessRootProperDescendants(browsingContext) {
    let result = [];
    for (let context of browsingContext.children) {
      this.getNearestProcessRootProperDescendantsRecursive(context, result);
    }
    return result;
  }

  getNearestProcessRootProperDescendantsRecursive(browsingContext, result) {
    let cwg = browsingContext.currentWindowGlobal;
    if (cwg && cwg.isProcessRoot) {
      result.push(browsingContext);
      return;
    }
    for (let context of browsingContext.children) {
      this.getNearestProcessRootProperDescendantsRecursive(context, result);
    }
  }

  // tell children and itself
  async tellChildrenToUpdateLayerTree(browsingContext) {
    let errorStrings = [];
    let infoStrings = [];

    let cwg = browsingContext.currentWindowGlobal;
    if (!cwg || !cwg.isProcessRoot) {
      if (cwg) {
        errorStrings.push("tellChildrenToUpdateLayerTree called on a non process root?");
      }
      return {errorStrings, infoStrings};
    }

    let actor = cwg.getActor("ReftestFission");
    if (!actor) {
      return {errorStrings, infoStrings};
    }

    // When we paint a document we also update the EffectsInfo visible rect in
    // nsSubDocumentFrame for any remote subdocuments. This visible rect is
    // used to limit painting for the subdocument in the subdocument's process.
    // So we want to ensure that the IPC message that updates the visible rect
    // to the subdocument's process arrives before we paint the subdocument
    // (otherwise our painting might not be up to date). We do this by sending,
    // and waiting for reply, an "EmptyMessage" to every direct descendant that
    // is in another process. Since we send the "EmptyMessage" after the
    // visible rect update message we know that the visible rect will be
    // updated by the time we hear back from the "EmptyMessage". Then we can
    // ask the subdocument process to paint.

    try {
      let result = await actor.sendQuery("UpdateLayerTree");
      errorStrings.push(...result.errorStrings);
    } catch (e) {
      infoStrings.push("tellChildrenToUpdateLayerTree UpdateLayerTree msg to child rejected: " + e);
    }

    let descendants = actor.getNearestProcessRootProperDescendants(browsingContext);
    for (let context of descendants) {
      let cwg2 = context.currentWindowGlobal;
      if (cwg2) {
        if (!cwg2.isProcessRoot) {
          errorStrings.push("getNearestProcessRootProperDescendants returned a non process root?");
        }
        let actor2 = cwg2.getActor("ReftestFission");
        if (actor2) {
          try {
            await actor2.sendQuery("EmptyMessage");
          } catch(e) {
            infoStrings.push("tellChildrenToUpdateLayerTree EmptyMessage msg to child rejected: " + e);
          }

          try {
            let result2 = await actor2.tellChildrenToUpdateLayerTree(context);
            errorStrings.push(...result2.errorStrings);
            infoStrings.push(...result2.infoStrings);
          } catch (e) {
            errorStrings.push("tellChildrenToUpdateLayerTree recursive tellChildrenToUpdateLayerTree call rejected: " + e);
          }

        }
      }
    }

    return {errorStrings, infoStrings};
  }

  tellChildrenToSetupDisplayport(browsingContext, promises) {
    let cwg = browsingContext.currentWindowGlobal;
    if (cwg && cwg.isProcessRoot) {
      let a = cwg.getActor("ReftestFission");
      if (a) {
        let responsePromise = a.sendQuery("SetupDisplayport");
        promises.push(responsePromise);
      }
    }

    for (let context of browsingContext.children) {
      this.tellChildrenToSetupDisplayport(context, promises);
    }
  }

  tellChildrenToSetupAsyncScrollOffsets(browsingContext, allowFailure, promises) {
    let cwg = browsingContext.currentWindowGlobal;
    if (cwg && cwg.isProcessRoot) {
      let a = cwg.getActor("ReftestFission");
      if (a) {
        let responsePromise = a.sendQuery("SetupAsyncScrollOffsets", {allowFailure});
        promises.push(responsePromise);
      }
    }

    for (let context of browsingContext.children) {
      this.tellChildrenToSetupAsyncScrollOffsets(context, allowFailure, promises);
    }
  }


  receiveMessage(msg) {
    switch (msg.name) {
      case "ForwardAfterPaintEvent":
      {
        let cwg = msg.data.toBrowsingContext.currentWindowGlobal;
        if (cwg) {
          let a = cwg.getActor("ReftestFission");
          if (a) {
            a.sendAsyncMessage("ForwardAfterPaintEventToSelfAndParent", msg.data);
          }
        }
        break;
      }
      case "FlushRendering":
      {
        let promise = this.tellChildrenToFlushRendering(msg.data.browsingContext, msg.data.ignoreThrottledAnimations, msg.data.needsAnimationFrame);
        return promise.then(function (results) {
          let errorStrings = [];
          let warningStrings = [];
          let infoStrings = [];
          for (let r of results) {
            if (r.status != "fulfilled") {
              if (r.status == "pending") {
                errorStrings.push("FlushRendering sendQuery to child promise still pending?");
              } else {
                // We expect actors to go away causing sendQuery's to fail, so
                // just note it.
                infoStrings.push("FlushRendering sendQuery to child promise rejected: " + r.reason);
              }
              continue;
            }

            errorStrings.push(...r.value.errorStrings);
            warningStrings.push(...r.value.warningStrings);
            infoStrings.push(...r.value.infoStrings);
          }
          return {errorStrings, warningStrings, infoStrings};
        });
      }
      case "UpdateLayerTree":
      {
        return this.tellChildrenToUpdateLayerTree(msg.data.browsingContext);
      }
      case "TellChildrenToSetupDisplayport":
      {
        let promises = [];
        this.tellChildrenToSetupDisplayport(msg.data.browsingContext, promises);
        return Promise.allSettled(promises).then(function (results) {
          let errorStrings = [];
          let infoStrings = [];
          for (let r of results) {
            if (r.status != "fulfilled") {
              // We expect actors to go away causing sendQuery's to fail, so
              // just note it.
              infoStrings.push("SetupDisplayport sendQuery to child promise rejected: " + r.reason);
              continue;
            }

            errorStrings.push(...r.value.errorStrings);
            infoStrings.push(...r.value.infoStrings);
          }
          return {errorStrings, infoStrings}
        });
      }

      case "SetupAsyncScrollOffsets":
      {
        let promises = [];
        this.tellChildrenToSetupAsyncScrollOffsets(this.manager.browsingContext, msg.data.allowFailure, promises);
        return Promise.allSettled(promises).then(function (results) {
          let errorStrings = [];
          let infoStrings = [];
          let updatedAny = false;
          for (let r of results) {
            if (r.status != "fulfilled") {
              // We expect actors to go away causing sendQuery's to fail, so
              // just note it.
              infoStrings.push("SetupAsyncScrollOffsets sendQuery to child promise rejected: " + r.reason);
              continue;
            }

            errorStrings.push(...r.value.errorStrings);
            infoStrings.push(...r.value.infoStrings);
            if (r.value.updatedAny) {
              updatedAny = true;
            }
          }
          return {errorStrings, infoStrings, updatedAny};
        });
      }

    }
  }

}
