/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_layers_FocusState_h
#define mozilla_layers_FocusState_h

#include <unordered_map>  // for std::unordered_map
#include <unordered_set>  // for std::unordered_set

#include "mozilla/layers/FocusTarget.h"          // for FocusTarget
#include "mozilla/layers/ScrollableLayerGuid.h"  // for ViewID
#include "mozilla/Mutex.h"                       // for Mutex

namespace mozilla {
namespace layers {

/**
 * This class is used for tracking chrome and content focus targets and
 * calculating global focus information from them for use by APZCTreeManager
 * for async keyboard scrolling.
 *
 * # Calculating the element to scroll
 *
 * Chrome and content processes have independently focused elements. This makes
 * it difficult to calculate the global focused element and its scrollable
 * frame from the chrome or content side. So instead we send the local focus
 * information from each process to here and then calculate the global focus
 * information. This local information resides in a `focus target`.
 *
 * A focus target indicates that either:
 *    1. The focused element is a remote browser along with its layer tree ID
 *    2. The focused element is not scrollable
 *    3. The focused element is scrollable along with the ViewID's of its
         scrollable layers
 *
 * Using this information we can determine the global focus information by
 * starting at the focus target of the root layer tree ID and following remote
 * browsers until we reach a scrollable or non-scrollable focus target.
 *
 * # Determinism and sequence numbers
 *
 * The focused element in content can be changed within any javascript code. And
 * javascript can run in response to an event or at any moment from `setTimeout`
 * and others. This makes it impossible to always have the current focus
 * information in APZ as it can be changed asynchronously at any moment. If we
 * don't have the latest focus information, we may incorrectly scroll a target
 * when we shouldn't.
 *
 * A tradeoff is designed here whereby we will maintain deterministic focus
 * changes for user input, but not for other javascript code. The reasoning
 * here is that `setTimeout` and others are already non-deterministic and so it
 * might not be as breaking to web content.
 *
 * To maintain deterministic focus changes for a given stream of user inputs,
 * we invalidate our focus state whenever we receive a user input that may
 * trigger event listeners. We then attach a new sequence number to these
 * events and dispatch them to content. Content will then include the latest
 * sequence number it has processed to every focus update. Using this we can
 * determine whether any potentially focus changing events have yet to be
 * handled by content.
 *
 * Once we have received the latest focus sequence number from content, we know
 * that all event listeners triggered by user inputs, and their resulting focus
 * changes, have been processed and so we have a current target that we can use
 * again.
 */
class FocusState final {
 public:
  FocusState();

  /**
   * The sequence number of the last potentially focus changing event processed
   * by APZ. This number starts at one and increases monotonically. This number
   * will never be zero as that is used to catch uninitialized focus sequence
   * numbers on input events.
   */
  uint64_t LastAPZProcessedEvent() const;

  /**
   * Notify focus state of a potentially focus changing event. This will
   * increment the current focus sequence number. The new value can be gotten
   * from LastAPZProcessedEvent().
   */
  void ReceiveFocusChangingEvent();

  /**
   * Update the internal focus tree and recalculate the global focus target for
   * a focus target update received from chrome or content.
   *
   * @param aRootLayerTreeId the layer tree ID of the root layer for the
                             parent APZCTreeManager
   * @param aOriginatingLayersId the layer tree ID that this focus target
                                 belongs to
   */
  void Update(LayersId aRootLayerTreeId, LayersId aOriginatingLayersId,
              const FocusTarget& aTarget);

  /**
   * Removes a focus target by its layer tree ID.
   */
  void RemoveFocusTarget(LayersId aLayersId);

  /**
   * Gets the scrollable layer that should be horizontally scrolled for a key
   * event, if any. The returned ScrollableLayerGuid doesn't contain a
   * presShellId, and so it should not be used in comparisons.
   *
   * No scrollable layer is returned if any of the following are true:
   *   1. We don't have a current focus target
   *   2. There are event listeners that could change the focus
   *   3. The target has not been layerized
   */
  Maybe<ScrollableLayerGuid> GetHorizontalTarget() const;
  /**
   * The same as GetHorizontalTarget() but for vertical scrolling.
   */
  Maybe<ScrollableLayerGuid> GetVerticalTarget() const;

  /**
   * Gets whether it is safe to not increment the focus sequence number for an
   * unmatched keyboard event.
   */
  bool CanIgnoreKeyboardShortcutMisses() const;

  /**
   * Reset to initial state.
   */
  void Reset();

 private:
  /**
   * Whether the current focus state is known to be current or else if an event
   * has been processed that could change the focus but we have not received an
   * update with a new confirmed target.
   * This can only be called by methods that have already acquired mMutex; they
   * have to pass their lock as compile-time proof.
   */
  bool IsCurrent(const MutexAutoLock& aLock) const;

 private:
  // All methods should hold this lock, since this class is accessed via both
  // the updater and controller threads.
  mutable Mutex mMutex MOZ_UNANNOTATED;

  // The set of focus targets received indexed by their layer tree ID
  std::unordered_map<LayersId, FocusTarget, LayersId::HashFn> mFocusTree;

  // The focus sequence number of the last potentially focus changing event
  // processed by APZ. This number starts at one and increases monotonically.
  // We don't worry about wrap around here because at a pace of 100
  // increments/sec, it would take 5.85*10^9 years before we would wrap around.
  // This number will never be zero as that is used to catch uninitialized focus
  // sequence numbers on input events.
  uint64_t mLastAPZProcessedEvent;
  // The focus sequence number last received in a focus update.
  uint64_t mLastContentProcessedEvent;

  // A flag whether there is a key listener on the event target chain for the
  // focused element
  bool mFocusHasKeyEventListeners;
  // A flag that is false until the first call to Update().
  bool mReceivedUpdate;

  // The layer tree ID which contains the scrollable frame of the focused
  // element
  LayersId mFocusLayersId;
  // The scrollable layer corresponding to the scrollable frame that is used to
  // scroll the focused element. This depends on the direction the user is
  // scrolling.
  ScrollableLayerGuid::ViewID mFocusHorizontalTarget;
  ScrollableLayerGuid::ViewID mFocusVerticalTarget;
};

}  // namespace layers
}  // namespace mozilla

#endif  // mozilla_layers_FocusState_h
