From 2b432f7be5916e840428f1d42dc03b8073df3a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20C=C3=A1ceres?= Date: Thu, 2 Jun 2022 12:30:19 +1000 Subject: [PATCH 1/5] Rewrite .subscribe() to fix various bugs * Fix `PushSubscriptionOptionsInit` usage. * Do less work in parallel. * Handle `userVisibleOnly` member. * Resolve promises using networking tasks source. --- index.html | 108 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 43 deletions(-) diff --git a/index.html b/index.html index 275be05..b7a856c 100644 --- a/index.html +++ b/index.html @@ -195,13 +195,16 @@

subscription having the new keys as |newSubscription|.

- To create a push subscription, given an PushSubscriptionOptions object - of |options|, the user agent must run the following steps: + To create a push subscription, given an {{PushSubscriptionOptionsInit}} + |optionsDictionary:PushSubscriptionOptionsInit|:

-
    +
    1. Let |subscription| be a new push subscription.
    2. -
    3. Set the `options` attribute of |subscription| to be a copy of |options|. +
    4. Let |options:PushSubscriptionOptions| be a newly created {{PushSubscriptionOptions}} + object, initializing its attributes with the corresponding values of |optionsDictionary|. +
    5. +
    6. Set |subscription|'s {{PushSubscription/options}} attribute to |options|.
    7. Generate a new P-256 ECDH key pair [[ANSI-X9-62]]. Store the private key in an internal slot on |subscription|; this value MUST NOT be made available to applications. @@ -548,73 +551,92 @@

      MAY support content codings defined in previous versions of the draft for compatibility reasons.

      +

      + `subscribe()` method +

      - The subscribe method when invoked MUST run the following steps: + The subscribe() method when invoked MUST run the following steps:

      -
        -
      1. Let |promise| be a new promise. +
          +
        1. If the user agent requires |options|'s {{PushSubscriptionOptionsInit/userVisibleOnly}} + member to be `true`, return [=a promise rejected with=] an {{"NotAllowedError"}} + {{DOMException}}.
        2. -
        3. Return |promise| and continue the following steps asynchronously. +
        4. If the |options| argument does not include a non-null value for the + {{PushSubscriptionOptionsInit/applicationServerKey}} member, and the push service + requires one to be given, return [=a promise rejected with=] a {{"NotSupportedError"}} + {{DOMException}}.
        5. If the |options| argument includes a non-null value for the {{PushSubscriptionOptions/applicationServerKey}} attribute, run the following sub-steps:
            -
          1. If the |applicationServerKey| is provided as a {{DOMString}}, set its value to an - {{ArrayBuffer}} containing the sequence of octets that result from decoding - |applicationServerKey| using the base64url encoding [[RFC7515]]. If decoding fails, - reject promise with a {{DOMException}} whose name is {{"InvalidCharacterError"}} and - terminate these steps. +
          2. If |options|'s {{PushSubscriptionOptionsInit/applicationServerKey}} is a + {{DOMString}}, set its value to an {{ArrayBuffer}} containing the sequence of octets + that result from decoding |options|'s + {{PushSubscriptionOptionsInit/applicationServerKey}} using the base64url encoding + [[RFC7515]]. +
          3. +
          4. If decoding fails, return [=a promise rejected with=] a {{"InvalidCharacterError"}} + {{DOMException}}.
          5. -
          6. Ensure that |applicationServerKey| describes a valid point on the P-256 curve. If - the |applicationServerKey| value is invalid, reject |promise| with a {{DOMException}} - whose name is {{"InvalidAccessError"}} and terminate these steps. +
          7. Ensure that |options|'s {{PushSubscriptionOptionsInit/applicationServerKey}} + describes a valid point on the P-256 curve. If its value is invalid, return [=a promise + rejected with=] an {{"InvalidAccessError"}} {{DOMException}}.
        6. -
        7. If the |options| argument does not include a non-null value for the - {{PushSubscriptionOptions/applicationServerKey}} attribute, and the push service - requires one to be given, reject |promise| with a {{DOMException}} whose name is - {{"NotSupportedError"}} and terminate these steps. +
        8. Let |registration:ServiceWorkerRegistration| be the {{PushManager}}'s associated + service worker registration. +
        9. +
        10. If |registration|'s [=service worker registration/active worker=] is null, return [=a + promise rejected with=] an {{"InvalidStateError"}} {{DOMException}}. +
        11. +
        12. Let |sw| be |registration|'s [=service worker registration/active worker=]. +
        13. +
        14. Let |promise| be [=a new promise=].
        15. -
        16. Let |registration| be the {{PushManager}}'s associated service worker - registration. +
        17. Let |global:Window| be [=this=]'s [=relevant global object=].
        18. -
        19. If |registration|'s [=service worker registration/active worker=] is null, reject - |promise| with a {{DOMException}} whose name is {{"InvalidStateError"}} and terminate these - steps. +
        20. Return |promise| and continue [=in parallel=].
        21. Let |permission| be [=request permission to use=] "push".
        22. -
        23. If |permission| is "denied", reject |promise| with a {{DOMException}} whose name is - {{"NotAllowedError"}} and terminate these steps. +
        24. If |permission| is {{PermissionState/"denied"}}, [=queue a global task=] on the [=user + interaction task source=] using |global| to reject |promise| with a {{"NotAllowedError"}} + {{DOMException}} and terminate these steps.
        25. -
        26. If the Service Worker is already subscribed, run the following substeps: +
        27. If |sw| is already subscribed, run the following substeps:
            -
          1. Retrieve the push subscription associated with the Service Worker. +
          2. Let |subscription| be the push subscription associated with |sw|.
          3. -
          4. If there is an error, reject |promise| with a {{DOMException}} whose name is - {{"AbortError"}} and terminate these steps. +
          5. If |subscription| is an error, [=queue a global task=] on the [=networking task + source=] using |global| to [=reject=] |promise| with an {{"AbortError"}} + {{DOMException}} and terminate these steps.
          6. -
          7. Let |subscription| be the retrieved subscription. +
          8. Compare the |options| argument with the `options` attribute of |subscription|. The + contents of {{BufferSource}} values are compared for equality rather than + [=ECMAScript/reference record|reference=].
          9. -
          10. Compare the |options| argument with the `options` attribute of |subscription|. If - any attribute on |options| contains a different value to that stored for - |subscription|, then reject |promise| with an {{InvalidStateError}} and terminate these - steps. The contents of {{BufferSource}} values are compared for equality rather than - references. +
          11. If any attribute on |options| contains a different value to that stored for + |subscription|, then [=queue a global task=] on the [=networking task source=] using + |global| to [=reject=] |promise| with an {{"InvalidStateError"}} {{DOMException}} and + terminate these steps.
          12. -
          13. When the request has been completed, resolve |promise| with |subscription|. +
          14. When the request has been completed, [=queue a global task=] on the [=networking + task source=] using |global| to [=resolve=] |promise| with |subscription| and terminate + this algorithm.
        28. -
        29. Let |subscription| be the result of running the create a push subscription steps +
        30. Let |subscription| be the result of running the [=create a push subscription=] steps given |options|.
        31. -
        32. If there is an error, reject |promise| with a {{DOMException}} whose name is - {{"AbortError"}} and terminate these steps. +
        33. If |subscription| is an error, [=queue a global task=] on the [=networking task + source=] using |global| to [=reject=] |promise| with a {{"AbortError"}} {{DOMException}} + and terminate these steps.
        34. -
        35. Resolve |promise| with a {{PushSubscription}} providing the details of the new +
        36. Otherwise, [=queue a global task=] on the [=networking task source=] using |global| to + [=resolve=] |promise| with a {{PushSubscription}} providing the details of the new |subscription|.
        From 449bc2bbc93a18834a48faf612e5de1f174eb768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20C=C3=A1ceres?= Date: Tue, 21 Jun 2022 14:57:29 +1000 Subject: [PATCH 2/5] Update index.html Co-authored-by: Martin Thomson --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index b7a856c..c93ff59 100644 --- a/index.html +++ b/index.html @@ -558,8 +558,8 @@

        The subscribe() method when invoked MUST run the following steps:

          -
        1. If the user agent requires |options|'s {{PushSubscriptionOptionsInit/userVisibleOnly}} - member to be `true`, return [=a promise rejected with=] an {{"NotAllowedError"}} +
        2. If the |options| argument has a {{PushSubscriptionOptionsInit/userVisibleOnly}} value set to `false` and + the user agent requires it to be `true`, return [=a promise rejected with=] an {{"NotAllowedError"}} {{DOMException}}.
        3. If the |options| argument does not include a non-null value for the From a8018439d848985e0064d56a673781df2d564239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20C=C3=A1ceres?= Date: Tue, 21 Jun 2022 17:34:30 +1000 Subject: [PATCH 3/5] Review feedback --- index.html | 87 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/index.html b/index.html index c93ff59..895da10 100644 --- a/index.html +++ b/index.html @@ -199,10 +199,11 @@

          |optionsDictionary:PushSubscriptionOptionsInit|:

            -
          1. Let |subscription| be a new push subscription. +
          2. Let |subscription:PushSubscription| be a new {{PushSubscription}}.
          3. Let |options:PushSubscriptionOptions| be a newly created {{PushSubscriptionOptions}} - object, initializing its attributes with the corresponding values of |optionsDictionary|. + object, initializing its attributes with the corresponding members and values of + |optionsDictionary|.
          4. Set |subscription|'s {{PushSubscription/options}} attribute to |options|.
          5. @@ -217,11 +218,21 @@

            key can be retrieved by calling the {{PushSubscription/getKey()}} method of the {{PushSubscription}} with an argument of {{PushEncryptionKeyName/"auth"}}. -
          6. Make a request to the push service to create a new push subscription. - Include the {{PushSubscriptionOptions/applicationServerKey}} attribute of |options| when - it has been set. +
          7. Request a new push subscription. Include the + {{PushSubscriptionOptions/applicationServerKey}} attribute of |options| when it has been + set. Rethrow any [=exceptions=].
          8. -
          9. When the request has completed, return |subscription|. +
          10. When the push subscription request has completed successfully: +
              +
            1. Set |subscription|'s {{PushSubscription/endpoint}} attribute to the [=URL=] + provided by the push subscription. +
            2. +
            3. If provided by the push subscription, set |subscription|'s + {{PushSubscription/expirationTime}}. +
            4. +
            +
          11. +
          12. Return |subscription|.
          @@ -558,15 +569,21 @@

          The subscribe() method when invoked MUST run the following steps:

            -
          1. If the |options| argument has a {{PushSubscriptionOptionsInit/userVisibleOnly}} value set to `false` and - the user agent requires it to be `true`, return [=a promise rejected with=] an {{"NotAllowedError"}} - {{DOMException}}. +
          2. If the |options| argument has a {{PushSubscriptionOptionsInit/userVisibleOnly}} value + set to `false` and the user agent requires it to be `true`, return [=a promise rejected + with=] an {{"NotAllowedError"}} {{DOMException}}.
          3. If the |options| argument does not include a non-null value for the {{PushSubscriptionOptionsInit/applicationServerKey}} member, and the push service requires one to be given, return [=a promise rejected with=] a {{"NotSupportedError"}} {{DOMException}}.
          4. +
          5. Let |promise| be [=a new promise=]. +
          6. +
          7. Let |global:Window| be [=this=]' [=relevant global object=]. +
          8. +
          9. Return |promise| and continue [=in parallel=]. +
          10. If the |options| argument includes a non-null value for the {{PushSubscriptionOptions/applicationServerKey}} attribute, run the following sub-steps:
              @@ -576,42 +593,40 @@

              {{PushSubscriptionOptionsInit/applicationServerKey}} using the base64url encoding [[RFC7515]]. -
            1. If decoding fails, return [=a promise rejected with=] a {{"InvalidCharacterError"}} - {{DOMException}}. +
            2. If decoding fails, [=queue a global task=] on the [=user interaction task source=] + using |global| to [=reject=] |promise| with an {{"InvalidCharacterError"}} + {{DOMException}} and terminate these steps.
            3. Ensure that |options|'s {{PushSubscriptionOptionsInit/applicationServerKey}} - describes a valid point on the P-256 curve. If its value is invalid, return [=a promise - rejected with=] an {{"InvalidAccessError"}} {{DOMException}}. + describes a valid point on the P-256 curve. If its value is invalid, [=queue a global + task=] on the [=user interaction task source=] using |global| to [=reject=] |promise| + with an {{"InvalidAccessError"}} {{DOMException}} and terminate these steps.
          11. -
          12. Let |registration:ServiceWorkerRegistration| be the {{PushManager}}'s associated - service worker registration. +
          13. Let |registration:ServiceWorkerRegistration| be [=this=]'s associated service worker + registration.
          14. -
          15. If |registration|'s [=service worker registration/active worker=] is null, return [=a - promise rejected with=] an {{"InvalidStateError"}} {{DOMException}}. +
          16. If |registration|'s [=service worker registration/active worker=] is null, [=queue a + global task=] on the [=user interaction task source=] using |global| to [=reject=] + |promise| with an {{"InvalidStateError"}} {{DOMException}} and terminate these steps.
          17. Let |sw| be |registration|'s [=service worker registration/active worker=].
          18. -
          19. Let |promise| be [=a new promise=]. -
          20. -
          21. Let |global:Window| be [=this=]'s [=relevant global object=]. -
          22. -
          23. Return |promise| and continue [=in parallel=]. -
          24. Let |permission| be [=request permission to use=] "push".
          25. If |permission| is {{PermissionState/"denied"}}, [=queue a global task=] on the [=user - interaction task source=] using |global| to reject |promise| with a {{"NotAllowedError"}} - {{DOMException}} and terminate these steps. + interaction task source=] using |global| to [=reject=] |promise| with a + {{"NotAllowedError"}} {{DOMException}} and terminate these steps.
          26. -
          27. If |sw| is already subscribed, run the following substeps: +
          28. If |sw| is already subscribed, run the following sub-steps:
              -
            1. Let |subscription| be the push subscription associated with |sw|. +
            2. Try to retrieve the push subscription associated with the |sw|. If there is + an error, [=queue a global task=] on the [=networking task source=] using |global| to + [=reject=] |promise| with an {{"AbortError"}} {{DOMException}} and terminate these + steps.
            3. -
            4. If |subscription| is an error, [=queue a global task=] on the [=networking task - source=] using |global| to [=reject=] |promise| with an {{"AbortError"}} - {{DOMException}} and terminate these steps. +
            5. Let |subscription| be the push subscription associated with |sw|.
            6. Compare the |options| argument with the `options` attribute of |subscription|. The contents of {{BufferSource}} values are compared for equality rather than @@ -624,16 +639,14 @@

            7. When the request has been completed, [=queue a global task=] on the [=networking task source=] using |global| to [=resolve=] |promise| with |subscription| and terminate - this algorithm. + these steps.
          29. -
          30. Let |subscription| be the result of running the [=create a push subscription=] steps - given |options|. -
          31. -
          32. If |subscription| is an error, [=queue a global task=] on the [=networking task - source=] using |global| to [=reject=] |promise| with a {{"AbortError"}} {{DOMException}} - and terminate these steps. +
          33. Let |subscription| be the result of trying to [=create a push subscription=] with + |options|. If creating the subscription [=exception/throws=] an [=exception=], [=queue a + global task=] on the [=networking task source=] using |global| to [=reject=] |promise| with + a that [=exception=] and terminate these these steps.
          34. Otherwise, [=queue a global task=] on the [=networking task source=] using |global| to [=resolve=] |promise| with a {{PushSubscription}} providing the details of the new From 1e439f497d11767dcfd4d0c03544c6a0380b6e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20C=C3=A1ceres?= Date: Tue, 28 Jun 2022 17:20:12 +1000 Subject: [PATCH 4/5] remove type --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 895da10..6fb97b7 100644 --- a/index.html +++ b/index.html @@ -580,7 +580,7 @@

          35. Let |promise| be [=a new promise=].
          36. -
          37. Let |global:Window| be [=this=]' [=relevant global object=]. +
          38. Let |global| be [=this=]' [=relevant global object=].
          39. Return |promise| and continue [=in parallel=].
          40. From 10e0d68784d56b8c5d55a137db548469a2900e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20C=C3=A1ceres?= Date: Thu, 30 Jun 2022 15:52:40 +1000 Subject: [PATCH 5/5] Add note, use networking task source --- index.html | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/index.html b/index.html index 6fb97b7..9fbeca9 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,12 @@ companyURL: "https://www.mozilla.org/", w3cid: "68503" }, + { + name: "Marcos Caceres", + company: "Apple Inc.", + companyURL: "https://www.apple.com/", + w3cid: "39125" + }, { name: "Bryan Sullivan", company: "AT&T", @@ -569,20 +575,30 @@

            The subscribe() method when invoked MUST run the following steps:

              -
            1. If the |options| argument has a {{PushSubscriptionOptionsInit/userVisibleOnly}} value - set to `false` and the user agent requires it to be `true`, return [=a promise rejected - with=] an {{"NotAllowedError"}} {{DOMException}}. -
            2. -
            3. If the |options| argument does not include a non-null value for the - {{PushSubscriptionOptionsInit/applicationServerKey}} member, and the push service - requires one to be given, return [=a promise rejected with=] a {{"NotSupportedError"}} - {{DOMException}}. -
            4. Let |promise| be [=a new promise=].
            5. Let |global| be [=this=]' [=relevant global object=].
            6. Return |promise| and continue [=in parallel=]. + +
            7. +
            8. If the |options| argument has a {{PushSubscriptionOptionsInit/userVisibleOnly}} value + set to `false` and the user agent requires it to be `true`, [=queue a global task=] on the + [=networking task source=] using |global| to [=reject=] |promise| {{"NotAllowedError"}} + {{DOMException}} +
            9. +
            10. If the |options| argument does not include a non-null value for the + {{PushSubscriptionOptionsInit/applicationServerKey}} member, and the push service + requires one to be given, [=queue a global task=] on the [=networking task source=] using + |global| to [=reject=] |promise| with a {{"NotSupportedError"}} {{DOMException}}.
            11. If the |options| argument includes a non-null value for the {{PushSubscriptionOptions/applicationServerKey}} attribute, run the following sub-steps: @@ -593,14 +609,14 @@

              {{PushSubscriptionOptionsInit/applicationServerKey}} using the base64url encoding [[RFC7515]].

            12. -
            13. If decoding fails, [=queue a global task=] on the [=user interaction task source=] - using |global| to [=reject=] |promise| with an {{"InvalidCharacterError"}} - {{DOMException}} and terminate these steps. +
            14. If decoding fails, [=queue a global task=] on the [=networking task source=] using + |global| to [=reject=] |promise| with an {{"InvalidCharacterError"}} {{DOMException}} + and terminate these steps.
            15. Ensure that |options|'s {{PushSubscriptionOptionsInit/applicationServerKey}} describes a valid point on the P-256 curve. If its value is invalid, [=queue a global - task=] on the [=user interaction task source=] using |global| to [=reject=] |promise| - with an {{"InvalidAccessError"}} {{DOMException}} and terminate these steps. + task=] on the [=networking task source=] using |global| to [=reject=] |promise| with an + {{"InvalidAccessError"}} {{DOMException}} and terminate these steps.
            @@ -608,8 +624,8 @@

            registration.
          41. If |registration|'s [=service worker registration/active worker=] is null, [=queue a - global task=] on the [=user interaction task source=] using |global| to [=reject=] - |promise| with an {{"InvalidStateError"}} {{DOMException}} and terminate these steps. + global task=] on the [=networking task source=] using |global| to [=reject=] |promise| with + an {{"InvalidStateError"}} {{DOMException}} and terminate these steps.
          42. Let |sw| be |registration|'s [=service worker registration/active worker=].