-
Notifications
You must be signed in to change notification settings - Fork 135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dont block UI if .updateWith() not called (closes #589) #591
Conversation
index.html
Outdated
@@ -2417,6 +2422,9 @@ | |||
<li>If <var>event</var>'s <a>isTrusted</a> attribute is false, then | |||
then <a>throw</a> a "<a>InvalidStateError</a>" <a>DOMException</a>. | |||
</li> | |||
<li>If <var>event</var>.<a>[[\didUpdate]]</a> is true, then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this would happen if someone calls updateWith(p), p settles, and then someone calls updateWith(q), right?
Whereas the next line would happen if someone calls updateWith(p), and before p settles, someone calls updateWith(q)?
If I got that right, then I'd suggest adding quick examples or notes inside these steps re-stating the above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this would happen if someone calls updateWith(p), p settles, and then someone calls updateWith(q), right?
I was thinking of this case:
request.onaddresschange = ev => {
Promise.resolve({...details})
// next tick, so [[didUpdate]] is now true. Throws InvalidStateError.
.then(ev.updateWith.bind(ev));
}
Whereas the next line would happen if someone calls updateWith(p), and before p settles, someone calls updateWith(q)?
Correct. It also prevents someone holding a reference to event and calling .updateWith(p)
after p
has settled. The problem in the current spec is that [[\waitForUpdate]] and request.[[updating]] are both reset to false
in step 14 of updateWith()
- so theoretically, a developer could have just waited like this:
request.onaddresschange = async ev => {
const p = Promise.resolve({...details});
ev.updateWith(p);
await p; // [[waitForUpdate]] and request.[[updating]] are both reset to `false`!
// [[didUpdate]] is true, this now throws
const q = Promise.resolve({...details});
ev.updateWith(q);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed first case above...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, added example above.
index.html
Outdated
<li> | ||
<a>Fire an event</a> named <var>name</var> at <var>request</var> | ||
using <a>PaymentRequestUpdateEvent</a>. | ||
<li>Let <var>event</var> be the result of <a>fire an event</a> named |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't work. Fire an event returns a boolean, not an event.
You should instead:
- Let event be the result of creating an event using Event.
- Initialize event's type attribute to name
- Dispatch event at target.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't work. Fire an event returns a boolean, not an event.
Argh, I should have remembered that. I've used that boolean before elsewhere.
index.html
Outdated
<var>event</var>.<a>[[\waitForUpdate]]</a> is true, disable any part | ||
of the user interface that could cause another update event to be | ||
fired. Wait for <var>event</var>.<a>updateWith()</a> method's | ||
<var>detailsPromise</var> to settle. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
", then re-enable the relevant parts of user interface", I think.
Maybe this should be a series of nested steps under the "If ... is true".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.updateWith()
controls re-enabling the user interface, in step 14. It might be redundant to re-state it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, in that case, agreed.
</p> | ||
<aside class="note"> | ||
<p> | ||
If a developer wants to update the payment request, then they |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just noticing that this repeats text from slightly above. Let's delete the above text, as it feels pretty out of place anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deleted the one above.
index.html
Outdated
}; | ||
|
||
// ✅ Good - UI will wait. | ||
request.onshippingaddresschange = async ev => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No async
here
index.html
Outdated
|
||
// ✅ Good - UI will wait. | ||
request.onshippingaddresschange = async ev => { | ||
// This is fine, of course. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"of course" is a bit aggressive and implies the reader understands what's going on. You shouldn't really say "of course" while teaching someone something like this, IMO.
index.html
Outdated
// ❌ Bad - this won't work! | ||
request.onshippingaddresschange = async ev => { | ||
// `await` goes to next tick, so [[didUpdate]] is now true. | ||
const details = await Promise.resolve({ ...details, ...updatedProps }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do await getNewDetails(oldDetails)
to mirror the below.
index.html
Outdated
<a>[[\didUpdate]]</a> becomes true and <a>updateWith()</a> | ||
will throw. | ||
</p> | ||
<pre class="example" title="how to use `updateWith()` correctly."> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Backticks don't work in example titles. Respec bug? Or just remove it for now?
Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan to add support for this in ReSpec, so will leave it for now and we will get this for free soon.
index.html
Outdated
</p> | ||
<pre class="example" title="can only call `upadteWith()` once"> | ||
// ❌ Bad - calling updateWith() doesn't work! | ||
request.addEventListener("shippingaddresschange", async ev => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no async
index.html
Outdated
<pre class="example" title="how to use `updateWith()` correctly."> | ||
// ❌ Bad - this won't work! | ||
request.onshippingaddresschange = async ev => { | ||
// `await` goes to next tick, so [[didUpdate]] is now true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To distinguish this from the last example, we should say something like "await goes to next tick, and updateWith() was not called before that, so [[didUpdate]] gets set to true".
Although that calls into question whether didUpdate is the right name, since nothing actually updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know... "didUpdate" is not a great name... I wanted something that denoted "consumed" or "expired" or "this can't be used again". I'm open to suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could flip it and go with "canUpdate" or "updatePermitted" and turn it off after the opportunity to update is lost or has already been taken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although that calls into question whether didUpdate is the right name, since nothing actually updated.
Rethinking this: it actually did update... but instead of using the developer's details
, the sheet used the details that were provided by the payment sheet itself. So maybe updatedUI
might be better.
@dlongley, I'd prefer not to flip it. I don't want to rewrite it :(
index.html
Outdated
const p = Promise.resolve({ ...details }); | ||
ev.updateWith(p); | ||
await p; | ||
// 💥 [[didUpdate]] is now true, throws "InvalidStateError". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To distinguish this from the first example, we can say "the update has now completed, so [[didUpdate]] gets set to true; this throws "InvalidStateError".
index.html
Outdated
"!DOM#concept-event-create">creating an event</a> using | ||
<a>PaymentRequestUpdateEvent</a>. | ||
</li> | ||
<li>Initialize <var>event</var>'s type attribute to <var>name</var>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally link type, or at least <code>
ify it.
index.html
Outdated
<var>event</var>.<a>[[\waitForUpdate]]</a> is true, disable any part | ||
of the user interface that could cause another update event to be | ||
fired. Wait for <var>event</var>.<a>updateWith()</a> method's | ||
<var>detailsPromise</var> to settle. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
event.updateWith()'s detailsPromise is pretty terrible. Can we store it in an internal slot?
Actually, what is the point of this "wait" step? Nothing happens after it. I think it should just be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. I wasn't sure if it would be clear. But if one follows the updateWith() path, it provides all the steps there.
Is there a TL;DR for what we're trying to accomplish here? It's not obvious to me. :) |
@zkoch, tldr:
=== Note to self, add this test: var methods = [{ supportedMethods: ["basic-card"] }];
var details = {
total: { label: "", amount: { currency: "USD", value: "1" } },
};
var shippingOptions = [
{
id: "standard",
label: "🚛 Ground Shipping (2 days)",
amount: { currency: "USD", value: "5.00" },
selected: true,
},
{
id: "drone",
label: "🚀 Drone Express (2 hours)",
amount: { currency: "USD", value: "25.00" },
},
];
details.shippingOptions = shippingOptions;
var r = new PaymentRequest(methods, details, { requestShipping: true });
r.addEventListener(
"shippingaddresschange",
e => {
ev = e;
ev.updateWith(details);
var listener = ev2 => {
var newDetails = { ...details };
newDetails.total.label = "FAIL" + Math.random();
ev.updateWith(newDetails);
};
r.addEventListener("shippingoptionchange", listener);
r.addEventListener("shippingaddresschange", listener);
},
{ once: true }
);
r.show(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with one remaining nit; please don't wait on another round of review before merging :).
index.html
Outdated
<a>PaymentRequestUpdateEvent</a>. | ||
</li> | ||
<li>Initialize <var>event</var>'s <a data-cite="!DOM#dom-event-type"> | ||
type</a> attribute to <var>name</var>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be code-ified and linked? :)
I think the current spec already blocks this. The waitForUpdate slot is essentially equivalent to the new slot introduced. To avoid re-use of events, we should remove step 14, part 1, which sets waitForUpdate back to false. |
Nice! |
@adrianba, I removed the new slot as you suggested. Adding appropriate tests and see if this works. Will carry over domenic's R+ if it works ok. |
4ebbb7d
to
0e8e780
Compare
Rebased. |
Moved test requirement to WPT repo. I've got this mostly tested locally. Tests will hopefully land in the next few days. I feel confident merging this now having tested it. |
Tests:
web-platform-tests/wpt#7288
Preview | Diff