From 335cf0c77293ae06aafd75a2acfdea8ec761497e Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Thu, 14 Mar 2024 14:52:46 +0900 Subject: [PATCH] Re-do close watcher user activation tracking As noted in #10046, the close watcher anti-abuse measures were overly strict, and would prevent firing the cancel event even when the close watcher was created with user interaction. Additionally, the "is grouped with previous" infrastructure used to track close watcher groups was buggy, since close watchers could be destroyed and this would cause groups to spuriously collapse. To fix both of these problems, we re-do the close watcher anti-abuse protections. We now track the groups as a list-of-lists, and explicitly track how many groups should be allowed at a given time. We can then compare them when making decisions about whether to group a new close watcher, or whether to fire a cancel event. Note that the initial fix proposed in #10046 (and implemented in #10048) is incorrect, as it allows indefinite trapping of the user. Closes #10046. --- source | 281 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 173 insertions(+), 108 deletions(-) diff --git a/source b/source index 09230c2df53..402cad4fced 100644 --- a/source +++ b/source @@ -78586,9 +78586,17 @@ interface VisibilityStateEntry : PerformanceEntryorigin is same origin with document's origin.

-
  • For each window in windows, set - window's last activation timestamp to the current high resolution - time.

  • +
  • +

    For each window in windows:

    + +
      +
    1. Set window's last activation timestamp to the current + high resolution time.

    2. + +
    3. Notify the close watcher manager about user activation given + window.

    4. +
    +
  • An activation triggering input event is any event whose fullscreenchange to be eventually fired.

    +
  • +

    Optionally, skip to the step labeled alternative + processing.

    + +

    For example, if the user agent detects user frustration at repeated close + request interception by the current web page, it might take this path.

    +
  • +
  • Fire any relevant events, per UI Events or other relevant specifications. UIEVENTS

    @@ -81946,9 +81962,9 @@ body { display:none }
  • If closedSomething is true, then return.

  • -
  • Otherwise, there was nothing watching for a close request. The user agent - may instead interpret this interaction as some other action, instead of interpreting it as a - close request.

  • +
  • Alternative processing: Otherwise, there was nothing + watching for a close request. The user agent may instead interpret this interaction + as some other action, instead of interpreting it as a close request.

  • @@ -81963,25 +81979,83 @@ body { display:none }

    On platforms where a back button is a potential close request, no event is involved, so when the back button is pressed, the user agent proceeds directly to - process close watchers. If there is a close watcher on the close - watcher stack, then that will get triggered. If there is not, then the user agent can - interpret the back button press in another way, for example as a request to traverse the - history by a delta of −1.

    + process close watchers. If there is an active close watcher, then that will get + triggered. If there is not, then the user agent can interpret the back button press in another + way, for example as a request to traverse the history by a delta of −1.

    Close watcher infrastructure

    -

    Each Window has a close watcher stack, a list of close watchers, initially empty.

    +

    Each Window has a close watcher manager, which is a + struct with the following items:

    + + + +

    Most of the complexity of the close watcher manager comes from anti-abuse + protections designed to prevent developers from disabling users' history traversal abilities, for + platforms where a close request's fallback + action is the main mechanism of history traversal. In particular:

    + +

    The grouping of close watchers is designed so that if + multiple close watchers are created without history-action activation, they are + grouped together, so that a user-triggered close request will close all of the close + watchers in a group. This ensures that web developers can't intercept an unlimited number of + close requests by creating close watchers; instead they can create a number equal to at most 1 + + the number of times the user activates the page.

    + +

    The next user interaction allows a new group + boolean encourages web developers to create close watchers in + a way that is tied to individual user activations. Without + it, each user activation would increase the allowed number of groups, even if the web developer isn't "using" those user + activations to create close watchers. In short:

    + + + +

    This protection is not important for upholding our desired invariant of creating at + most (1 + the number of times the user activates the page) + groups. A determined abuser will just create one close watcher per user interaction, "banking" + them for future abuse. But this system causes more predictable behavior for the normal case, and + encourages non-abusive developers to create close watchers directly in response to user + interactions.

    + +

    To notify the close watcher manager about user activation given a + Window window:

    + +
      +
    1. Let manager be window's close watcher manager.

    2. + +
    3. If manager's next user interaction + allows a new group is true, then increment manager's allowed number of groups.

    4. + +
    5. Set manager's next user interaction + allows a new group to false.

    6. +
    + +

    A close watcher is a struct with the following items:

    @@ -81997,27 +82071,14 @@ body { display:none }
  • A close action, a list of steps. These steps can never throw an exception.

  • -
  • A was created with user activation - boolean.

  • - -
  • An is grouped with previous boolean.

  • -
  • An is running cancel action boolean.

  • -

    The is grouped with previous boolean - is set if a close watcher is created without history-action activation. - (Except the very first close watcher in the close watcher stack that is created - without transient activation, which gets a pass on this restriction.) It causes a user-triggered - close request to close all such grouped-together close watchers, thus ensuring that - web developers can't make close requests ineffective by creating an excessive number of close - watchers.

    -

    A close watcher closeWatcher is active if closeWatcher's window's - close watcher stack contains - closeWatcher.

    + close watcher manager contains any list which + contains closeWatcher.


    @@ -82026,49 +82087,9 @@ body { display:none } list of steps closeAction:

      -
    1. Assert: window's associated - Document is fully active.

    2. - -
    3. Let wasCreatedWithUserActivation and isGroupedWithPrevious be - null.

    4. - -
    5. -

      If window has history-action activation, then:

      - -
        -
      1. Consume history-action user activation given window.

      2. - -
      3. Set wasCreatedWithUserActivation to true.

      4. - -
      5. Set isGroupedWithPrevious to false.

      6. -
      -
    6. - -
    7. -

      Otherwise, if there is no close watcher in window's close - watcher stack whose was created with user - activation is false, then:

      - -
        -
      1. Set wasCreatedWithUserActivation to false.

      2. - -
      3. Set isGroupedWithPrevious to false.

      4. -
      - -

      This will be the one "free" close watcher created without user activation, which - is useful for cases like session inactivity timeout dialogs or notifications from the - server.

      -
    8. - -
    9. -

      Otherwise:

      - -
        -
      1. Set wasCreatedWithUserActivation to false.

      2. - -
      3. Set isGroupedWithPrevious to true.

      4. -
      -
    10. +
    11. Assert: window's associated Document is fully + active.

    12. Let closeWatcher be a new close watcher, with

      @@ -82083,19 +82104,36 @@ body { display:none }
      close action
      closeAction
      -
      was created with user activation
      -
      wasCreatedWithUserActivation
      - -
      is grouped with previous
      -
      isGroupedWithPrevious
      -
      is running cancel action
      false
    13. -
    14. Append closeWatcher to window's - close watcher stack.

    15. +
    16. Let manager be window's close watcher manager.

    17. + +
    18. If manager's groups's size is less than manager's allowed number of groups, then append « closeWatcher » to manager's groups.

    19. + +
    20. +

      Otherwise:

      + +
        +
      1. Assert: manager's groups's size is at least 1 in this branch, + since manager's allowed number of groups is always at least 1.

      2. + +
      3. Append closeWatcher to + manager's groups's last item.

      4. +
      +
    21. + +
    22. Set manager's next user interaction + allows a new group to true.

    23. Return closeWatcher.

    @@ -82105,19 +82143,23 @@ body { display:none }
    1. If closeWatcher is not active, then - return.

    2. + return true.

    3. If closeWatcher's is running - cancel action is true, then return.

    4. + cancel action is true, then return true.

    5. Let window be closeWatcher's window.

    6. If window's associated - Document is not fully active, then return.

    7. + Document is not fully active, then return true.

    8. -

      If window has history-action activation, then:

      +

      If window's close watcher manager's groups's size is less than + window's close watcher manager's allowed number of groups, and window has + history-action activation, then:

      1. Set closeWatcher's is running @@ -82135,21 +82177,20 @@ body { display:none }

        1. Consume history-action user activation given window.

        2. -
        3. Return.

        4. +
        5. Return false.

      -

      The guard on these substeps means that the cancel action only has a chance to prevent us from - continuing onward to the close action when - window has history-action activation. In particular, since these - substeps consume history-action user activation, requesting to close a close watcher - twice without any intervening user activation will skip these substeps.

      +

      Note that since these substeps consume history-action user + activation, requesting to close a + close watcher twice without any intervening user activation will skip + these substeps.

    9. Close closeWatcher.

    10. + +
    11. Return true.

    To close a close watcher @@ -82170,34 +82211,58 @@ body { display:none }

    To destroy a close watcher - closeWatcher, remove closeWatcher from - closeWatcher's window's close watcher - stack.

    + closeWatcher:

    + +
      +
    1. Let manager be closeWatcher's window's close watcher manager.

    2. + +
    3. For each group of manager's + groups: remove closeWatcher from group.

    4. + +
    5. Remove any item from manager's groups that is + empty.

    6. +

    -

    To process close watchers given a Window window:

    +

    To process close watchers given a Window window:

    1. Let processedACloseWatcher be false.

    2. -

      While window's close watcher stack is not empty:

      +

      If window's close watcher manager's groups is not empty:

        -
      1. Let closeWatcher be the last item in - window's close watcher stack.

      2. +
      3. Let group be the last item in + window's close watcher manager's groups.

      4. -
      5. Request to close - closeWatcher.

      6. +
      7. +

        For each closeWatcher of group, + in reverse order:

        -
      8. Set processedACloseWatcher to true.

      9. +
          +
        1. Set processedACloseWatcher to true.

        2. + +
        3. Let shouldProceed be the result of requesting to close + closeWatcher.

        4. -
        5. If closeWatcher's is grouped with - previous is false, then break.

        6. +
        7. If shouldProceed is false, then break.

        8. +
        +
    3. +
    4. If window's close watcher manager's allowed number of groups is greater than 1, decrement + it by 1.

    5. +
    6. Return processedACloseWatcher.