910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_IMPL_H_
#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_IMPL_H_

#include <list>
#include <memory>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "components/metrics/metrics_provider.h"
#include "content/browser/accessibility/scoped_mode_collection.h"
#include "content/common/content_export.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/render_widget_host.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/platform/assistive_tech.h"
#include "ui/accessibility/platform/ax_platform.h"

namespace content {

struct FocusedNodeDetails;
class WebContentsImpl;

// The BrowserAccessibilityState class is used to determine if Chrome should be
// customized for users with assistive technology, such as screen readers. We
// modify the behavior of certain user interfaces to provide a better experience
// for screen reader users. The way we detect a screen reader program is
// different for each platform.
//
// Screen Reader Detection
// (1) On Windows, many screen reader detection mechanisms will give false
//     positives, such as relying on the SPI_GETSCREENREADER system parameter.
//     In Chrome, we attempt to dynamically detect a MSAA client screen reader
//     by calling NotifyWinEvent in NativeWidgetWin with a custom ID and wait
//     to see if the ID is requested by a subsequent call to WM_GETOBJECT.
// (2) On macOS, we dynamically detect if VoiceOver is running by Key-Value
//     Observing changes to the "voiceOverEnabled" property in NSWorkspace. We
//     also monitor the undocumented accessibility attribute
//     @"AXEnhancedUserInterface", which is set by other assistive
//     technologies.
class CONTENT_EXPORT BrowserAccessibilityStateImpl
    : public BrowserAccessibilityState,
      public ui::AXPlatform::Delegate,
      public content::RenderWidgetHost::InputEventObserver,
      public ScopedModeCollection::Delegate {
 public:
  BrowserAccessibilityStateImpl(const BrowserAccessibilityStateImpl&) = delete;
  BrowserAccessibilityStateImpl& operator=(
      const BrowserAccessibilityStateImpl&) = delete;

  ~BrowserAccessibilityStateImpl() override;

  // Returns the single process-wide instance.
  static BrowserAccessibilityStateImpl* GetInstance();

  // Returns a new instance. Only one instance may be live in the process at any
  // time.
  static std::unique_ptr<BrowserAccessibilityStateImpl> Create();

  // BrowserAccessibilityState implementation.
  ui::AXMode GetAccessibilityMode() override;
  ui::AXMode GetAccessibilityModeForBrowserContext(
      BrowserContext* browser_context) override;
  // Any currently running assistive tech that should prevent accessibility from
  // being auto-disabled.
  ui::AssistiveTech ActiveAssistiveTech() const override;
  void SetPerformanceFilteringAllowed(bool enabled) override;
  bool IsPerformanceFilteringAllowed() override;
  base::CallbackListSubscription RegisterFocusChangedCallback(
      FocusChangedCallback callback) override;
  std::unique_ptr<ScopedAccessibilityMode> CreateScopedModeForProcess(
      ui::AXMode mode) override;
  std::unique_ptr<ScopedAccessibilityMode> CreateScopedModeForBrowserContext(
      BrowserContext* browser_context,
      ui::AXMode mode) override;
  std::unique_ptr<ScopedAccessibilityMode> CreateScopedModeForWebContents(
      WebContents* web_contents,
      ui::AXMode mode) override;
  void SetAXModeChangeAllowed(bool allowed) override;
  bool IsAXModeChangeAllowed() const override;
  void SetActivationFromPlatformEnabled(bool enabled) override;
  bool IsActivationFromPlatformEnabled() override;
  bool IsAccessibilityPerformanceMeasurementExperimentActive() const override;
  void NotifyWebContentsPreferencesChanged() const override;

  // ui::AXPlatform::Delegate:
  void OnMinimalPropertiesUsed() override;
  void OnPropertiesUsedInBrowserUI() override;
  void OnPropertiesUsedInWebContent() override;
  void OnInlineTextBoxesUsedInWebContent() override;
  void OnExtendedPropertiesUsedInWebContent() override;
  void OnHTMLAttributesUsed() override;
  void OnActionFromAssistiveTech() override;

  // content::RenderWidgetHost::InputEventObserver:
  void OnInputEvent(const RenderWidgetHost& widget,
                    const blink::WebInputEvent& event,
                    InputEventSource source) override;

  // The global accessibility mode is automatically enabled based on
  // usage of accessibility APIs. When we detect a significant amount
  // of user inputs within a certain time period, but no accessibility
  // API usage, we automatically disable accessibility.
  void OnUserInputEvent();

  // Notifies listeners that the focused element changed inside a WebContents.
  void OnFocusChangedInPage(const FocusedNodeDetails& details);

  // Return true if auto-disable should be blocked.
  bool ShouldBlockAutoDisable();

  // Signal to BrowserAccessibilityState that a page navigation has occurred.
  void OnPageNavigationComplete();

  // Sets the initial accessibility mode for `web_contents` if it is not
  // hidden or if ProgressiveAccessibility is not enabled.
  void OnWebContentsInitialized(WebContentsImpl* web_contents);

  // Applies the effective accessibility mode for `web_contents` if
  // ProgressiveAccessibility is enabled.
  void OnWebContentsRevealed(WebContentsImpl* web_contents);

  // Tracks `web_contents` for the sake of disabling accessibility later if
  // ProgressiveAccessibility is enabled and disable_on_hide is selected. A
  // previously-hidden WebContents becomes eligible for disablement if it is
  // not among the last five to be hidden once it has been hidden for at least
  // five minutes.
  void OnWebContentsHidden(WebContentsImpl* web_contents);

  // Notifies the instance that `assistive_tech` is the most significant of any
  // assistive technologies discovered. AXPlatform observers are notified if
  // `assistive_tech` differs from the most recent discovery. Called by
  // subclasses or accessibility managers when they detect the presence of
  // assistive tech via platform-specific means.
  void OnAssistiveTechFound(ui::AssistiveTech assistive_tech);

  void SetDiscoverAssistiveTechnologyCallbackForTesting(
      base::RepeatingClosure callback) {
    discover_at_callback_for_testing_ = std::move(callback);
  }

  // A hidden WebContents is guaranteed to retain its accessibility state when
  // the ProgressiveAccessibility feature is in disable_on_hide mode for at
  // least five minutes, plus or minus twenty seconds.
  static base::TimeDelta GetRandomizedDisableDelay();
  static base::TimeDelta GetMaxDisableDelay();

  // The number of recently-hidden WebContents that will not have accessibility
  // disabled if the ProgressiveAccessibility feature is on in disable_on_hide
  // mode.
  static constexpr int kMaxPreservedWebContents = 5;

 protected:
  BrowserAccessibilityStateImpl();

  // Refreshes the assistive tech if an AXMode change indicates that the
  // presence of an active screen reader may have changed.
  // * Platforms that have a perfect signal for the presence of a screen reader
  // should not override this method: the default implementation treats the
  // screen reader flag as a deterministic indicator.
  // * Platforms such as Windows and Linux that require a slow computation
  // to determine the presence of a screen reader should begin the computation
  // when the presence of AXMode::kExtendedProperties is inconsistent with the
  // current known screen reader state.
  virtual void RefreshAssistiveTechIfNecessary(ui::AXMode new_mode);

  ui::AXPlatform& ax_platform() { return ax_platform_; }

 private:
  void UpdateAccessibilityActivityTask();

  // Stops tracking `web_contents` for disabling accessibility while it is
  // hidden.
  void OnDisablerDestroyedForWebContents(WebContentsImpl* web_contents);

  // Combines the effective accessibility mode for the process, for
  // `web_contents`'s BrowserContext, and for `web_contents` and applies it
  // to `web_contents` if ProgressiveAccessibility is disabled or if
  // `web_contents` is not hidden.
  void ApplyAccessibilityModeToWebContents(WebContentsImpl* web_contents,
                                           ui::AXMode process_mode,
                                           ui::AXMode browser_context_mode,
                                           ui::AXMode web_contents_mode);

  // ScopedModeCollection::Delegate:
  // Handles a change to the effective accessibility mode for the process.
  void OnModeChanged(ui::AXMode old_mode, ui::AXMode new_mode) override;

  // Filters out `kFromPlatform` from `mode` if activation from platform
  // integration is enabled; otherwise, filters all mode flags from `mode` if
  // `kFromPlatform` is present in it.
  ui::AXMode FilterModeFlags(ui::AXMode mode) override;

  // Handles a change to the effective accessibility mode for `browser_context`.
  void OnModeChangedForBrowserContext(BrowserContext* browser_context,
                                      ui::AXMode old_mode,
                                      ui::AXMode new_mode);

  // Handles a change to the effective accessibility mode for `web_contents`.
  void OnModeChangedForWebContents(WebContents* web_contents,
                                   ui::AXMode old_mode,
                                   ui::AXMode new_mode);

  // Add the AXModes + AXMode::kFromPlatform, when corresponding platform APIs
  // are used.
  void EnableAXModeFromPlatform(ui::AXMode modes_to_add);

  // Refreshes the instance's notion of active assistive technologies.
  // Implementations must call `OnAssistiveTechFound()` with the results of any
  // discovery.
  virtual void RefreshAssistiveTech();

  // Helper function to configure the accessibility performance experiment.
  std::unique_ptr<ScopedAccessibilityMode>
  ConfigureAccessibilityPerformanceExperiment();

  // Helper to disable and clean-up the accessibility performance experiment.
  void ExitPerformanceExperiment();

  // The process's single AXPlatform instance.
  ui::AXPlatform ax_platform_{*this};

  // Whether there is a pending task to run UpdateAccessibilityActivityTask.
  bool accessibility_update_task_pending_ = false;

  // Whether changes to the AXMode are allowed.
  // Changes are disallowed while running tests or when
  // --force-renderer-accessibility is used on the command line.
  bool allow_ax_mode_changes_ = true;

  // Keeps track of whether performance filtering is allowed for the device.
  // Default is true to defer to feature flag. Value may be set to false by
  // prefs.
  bool performance_filtering_allowed_ = true;

  // Tracks whether the accessibility engine has been used in any form during
  // the current session. Toggled to true when accessibility is first enabled,
  // and never toggled back to false.
  bool has_enabled_accessibility_in_session_ = false;

  // True if activation of accessibility from interactions with the platform's
  // accessibility integration is enabled.
  bool activation_from_platform_enabled_ = true;

  // Timer used to track the time between start-up and engine first-use.
  base::ElapsedTimer first_use_timer_;

  // Counter used to track the number of page navigations between start-up
  // and engine first-use.
  uint32_t num_page_navs_before_first_use_ = 0;

  // The time of the first user input event; if we receive multiple
  // user input events within a 30-second period and no
  base::TimeTicks first_user_input_event_time_;
  int user_input_event_count_ = 0;

  // The time accessibility became active, used to calculate active time.
  base::TimeTicks accessibility_active_start_time_;

  // The time accessibility became inactive, used to calculate inactive time.
  base::TimeTicks accessibility_inactive_start_time_;

  // The last time accessibility was active, used to calculate active time.
  base::TimeTicks accessibility_last_usage_time_;

  // The time accessibility was enabled, for statistics.
  base::TimeTicks accessibility_enabled_time_;

  // The time accessibility was auto-disabled, for statistics.
  base::TimeTicks accessibility_disabled_time_;

  base::RepeatingCallbackList<void(const FocusedNodeDetails&)>
      focus_changed_callbacks_;

  // The collection of active ScopedAccessibilityMode instances targeting all
  // WebContentses in the process.
  ScopedModeCollection scoped_modes_for_process_{*this};

  // A ScopedAccessibilityMode that holds the process-wide mode flags modified
  // via --force-renderer-accessibility on the command line.
  std::unique_ptr<ScopedAccessibilityMode> forced_accessibility_mode_;

  // A ScopedAccessibilityMode that holds process-wide mode flags required to
  // support the platform API calls being used.
  std::unique_ptr<ScopedAccessibilityMode> platform_ax_mode_;

  // Keeps track of whether the Accessibility Performance Measurement Experiment
  // is currently active. This is necessary because there are cases where we
  // don't want to make the experiment active, and checking the state of the
  // feature flag causes the study to be active. In this case, if the conditions
  // are met, this will contain the mode of the current experiment group,
  // nullptr otherwise.
  std::unique_ptr<ScopedAccessibilityMode> experiment_accessibility_mode_;

  // The most recently hidden WebContentses; used only when the disable-on-hide
  // feature of ProgressiveAccessibility is enabled. This container holds the
  // most-recently hidden WebContentses. Accessibility is disabled for each one
  // that is pushed out of this list when a sixth element is added.
  std::list<raw_ptr<WebContentsImpl>> last_hidden_;

  // Keeps track of whether screen reader presence was checked. Resets with
  // every new page load. A new check only occurs if kAXModeComplete is active
  // and a screen reader isn't running.
  bool has_recently_checked_for_screen_reader_ = false;

  base::RepeatingClosure discover_at_callback_for_testing_;
  friend class ui::AXPlatform;
};

}  // namespace content

#endif  // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_IMPL_H_