-
Notifications
You must be signed in to change notification settings - Fork 332
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
XHR option to trigger busy indicators #19
Comments
Is there implementer interest to the extent they are willing to put UX resources on this? (Opera used to show all XMLHttpRequest activity and it was very bad (due to the background resources you mention).) |
I would ❤️ this so much and we'd totally use this on @github for pretty much all our page transitions instead of custom spinners. |
👍, but instead I'd suggest programmatic control over the feature, because loading may include JS, fonts, images or processing in a worker (e.g. if a data blob is cached, but requires processing before being fed to a view). |
And also because the actions may be canceled (e.g. a user clicks on a wrong link and then the correct one). |
More consistent UI behaviour between websites (and different browsers…) could make things much more clear for the users. Also, less code to implement for front-end developers, reducing need for additional building blocks. |
This should not be tied into Maybe we should consider something on the navigator.setBusy(true); I personally would like to see this happen not just because of the perception of slowness, but because of the duplication of user-agent UI that's happening with the "slim progress bar" trend (as seen on YouTube, Medium, etc) The performance angle is more nuanced. Many single page applications solve it by making the transition to the new page immediate, and displaying placeholders there (eg: Facebook Newsfeed) |
It's more than a binary state, you really want something associated with I kinda doubt UAs are going to give complete direct access to something Restricting to something request-like would cover most use cases.
|
It depends on how it's implemented. The iOS status bar binary loading state works pretty well, for example. |
Also consider that the "Stop" button is closely linked to "in progress".
|
Different parts of code may want to push/pop an async operation so this wouldn't work well as the primary user-facing API IMO. Instead I'd rather prefer something like this: window.attachToBusyIndicator(promise); Internally, we'd keep track of promises in progress and only show spinner when there is >= 1 such promise. When promises resolve, we remove them from the queue. var busyPromises = [];
function refresh() {
NativeImplementation.showBusyIndicator = busyPromises.length > 0;
}
window.attachToBusyIndicator = function (promise) {
function detach() {
busyPromises.splice(busyPromises.indexOf(promise), 1);
refresh();
}
promise.then(detach, detach);
busyPromises.push(promise);
refresh();
} This also works nice with cancellation because, whatever approach you choose for cancelling promises, if cancellation implies rejection (and it should), our counter will decrement on cancellation. Example of a nice cancellation API. |
I completely agree that the browser's busy indicator should not be tied to XHR or fetch(). What about JSONP, WebSockets, PeerConnections? they are perfectly valid ways of getting data which you have to wait on. Busy indicators, progress indicators, are visual indicators, and thus in principle should not be triggered by network operations, even as an opt-in. |
@gaearon I actually prefer browser APIs to be minimal. You can add the |
I was thinking about bad libraries messing with a boolean field that you have no control over. Imagine some SDK doing that. Maybe it's a library's problem though.. I'm not sure it's good to encourage bad patterns by exposing API that's too easy to misuse. And for a good API, I can't think of a better fit than promise. It represents an asynchronous operation in the language. You don't need to use promises in your code to represent async operations if you don't want to, but wrapping any async result (socket, XHR, whatever) in a promise is as straightforward as any other async API could be. If you want to track async operations in the UI, you need a way to represent an async value, and promises are baked into the language precisely to be the way to do that. If core browser APIs expose promise-based async operations such as Finally, I can't build promise-based layer on top of |
Your layer can be implemented by listening to |
Say I have two pending promises in my layer. Thus I set Then nasty module X comes along and does If it's a change event, it wouldn't, but then, when my pending promises have resolved, I'd have no way to know from my layer that somebody else wanted to keep the app busy. If we're sure we don't want a Promise API for that, I still suggest two methods over window.setBusyIndicator(): operationId
window.clearBusyIndicator(operationId) These can be called safely from different modules. |
It's the responsibility of "nasty module X" to retain its busy state for as long as it considers that it's busy, by watching for |
That said, to avoid contention issues, |
But then the order in which modules subscribed to |
I'd go for something like this, giving control in user-land : var busy = new navigator.BusyIndicator()
busy.start() // starts the busy indicator without progress
busy.end() // stops the busy indicator
busy.setProgress(.3) // sets the busy indicator at 30%, and starts it if it's not already
busy.setProgress(1) // calls (`busy.end`)
fetch("some/uri")
.then(busy.end) this way we'd be able to :
|
I'd leave progressing out of this. Otherwise it's not clear how progress value should be calculated from several (possibly partly unspecified) task progress values. |
yes that would be a difficulty, though could be interesting from a UX point of view |
Most browsers don't show progress bars for page transitions, so I'd be ok with just a binary busy state. |
All this talk is way premature given that we haven't gotten interest in this feature from even a single implementer yet :) |
@domenic do you mean no interest from browser vendors or client-side fill's? Client-side fill's for the functionality visibly to the user are easy, it's the browser support for showing on tab etc that will be sketchy |
I mean browser vendors, the ones implementing this spec :) |
I think this problem can be solved by add a new API: global fetch events. These events should be fired when the page is fetching any resources, no matter caused by XHR, fetch, The suggested API follows MutationObserver's style, because this may be more efficient for large amount of events: var fetchObserver = new FetchObserver(onFetch);
fetchObserver.connect(window, { // connect to current window object
contexts: ['fetch', 'xmlhttprequest', 'form'], // filter by [fetch's contexts](https://fetch.spec.whatwg.org/#concept-request-context)
types: ['start', 'progress', 'end', 'error'], // filter by types of fetch events
methods: ['GET', 'POST'], // filter by methods
nested: true, // also observe nested browsing contexts, e.g. iframes, workers
});
function onFetch(fetchRecords) {
for (var i = 0; i < fetchRecords.length; i++) {
var fetchRecord = fetchRecords[i];
if (fetchRecord.type === 'start' &&
/^http:\/\/somedomain\.net\/myapp\//.test(fetchRecord.url)) { // filter by URL
fetchRecords[i].affectBusyIndicator = true; // inform the UA that this fetch should affect the busy indicator
} else if (fetchRecord.type === 'progress') {
// compute and update the progress bar
}
}
} |
Yet another problem is: whether allow non-network IO operations (e.g. FileReader, IndexedDB, offline cache, etc.) affect busy indicator. For offline web apps, this seems a reasonable request. Maybe |
@duanyao "business" is not limited to IO either. Can be just processing in a worker. I wouldn't tie it to any API like fetch or even some generic IO API, it will fit more use cases as a standalone API and also be less complex. |
@jussi-kalliokoski
|
That's the webapp's problem. It's only going to affect that tab so we're fine. |
Sure, but this makes the API hard to use despite it looks very simple. |
I don't think this makes it harder to use. Not much harder anyway. There's much worse out there. |
I saw many codes using XHR just didn't handle error situations. So if those codes will use busy indicator in futrue, and a XHR fails, the busy indicator would on until next successful XHR. |
If they're not handling error situations there probably already are going to be inconsistencies in the app (or the app is one where they don't particularly care). Why would this one be special? |
No, not necessarily be inconsistent. Those app may just stops updating the content when the network is unavailable, quite acceptable. |
You're outlining a specific case where this would be okay; in general XHR is used for all sorts of things (eg displaying some new content on a click or mouseover), and in those cases the app would appear to not work anyway. Also, note that webapps already implement their own progress indicators, and if XHR isn't handled properly, those can spin indefinitely too. I don't see a change in the status quo brought by access to the tab progress indicator wrt this problem. |
This is why custom progress indicator is hard to get right. This proposal is supposed to address this issue, right? If you give developer a tool looks very simple, but actually difficult to use correctly, the result is not ideal. Managing the state of busy indicator explicitly is actually very hard.
So, I don't believe average developers can get explicit busy indicator management right easily, not to mention those who copy-paste code snippets from arbitrary sites. |
But the tool isn't the problem (the problem being "custom progress indicators get messed up if not done right") here, it's what the devs want to do -- and that's not changing.
Again, already a problem, if the user does something to trigger multiple XHRs at once, a site not designed to handle it will stumble. Most of the problems you list are problems with giving devs the XHR API; not with a busy indicator. |
It is not meaningful to blame XHR or developers here, because even the brand new Fetch API won't make busy indicator easier to handle. The problem is the explicit state management. UAs already do good jobs at managing the state of busy indicator, why not reuse them? So I suggested |
In Gecko one can use a dummy iframe for this. iframe.contentDocument.open(); starts the busy indicator, and iframe.contentDocument.close() would stop it. But anyhow, I don't object adding API for busy indicator. Fetch spec of course wouldn't be the right place, but probably HTML. |
It seems there is some interest from at least Mozilla in exposing this. I think the promise API proposed by @gaearon is the most promising. Maybe the constructor approach from @bloodyowl minus the progress bits (boolean seems fine given OS indicators to date). Though looking at https://w3c.github.io/wake-lock/ I wonder why @marcoscaceres ended up with just a property as API. whatwg/fetch is probably not the best place to hash this out. If some people here would like to turn this into something I can create whatwg/busy so there's a better place to evolve this. Any takers? (@duanyao you might be interested in #65. I don't think we want to tie a Busy Indicator API to fetching though. There's a number of other things such as WebSocket and WebRTC that are network-related and might cause a page to be busy. And I suppose we could even use it for non-network things.) |
First of all, great news. Promises are not a bad idea, but I can think of at least one use case only badly served with promises: We're using angular-ui/ui-router in a number of Angular apps. Now if the library itself adds support, promises are easy as it works internally with (Angular-)Promises. However as a library user, all I have are the state change events which map relativly poorly to promises. |
It's not a problem to adapt. |
Well it's tad bit more complicated, but you're right, it's not as bad as I though it would be: $rootScope.$on('$stateChangeStart', function() {
addBusyPromise(new Promise(resolve => {
$rootScope.on('$viewContentLoaded', resolve);
$rootScope.on('$stateChangeError', resolve);
}));
}); |
What happens if I have composite tasks with variable weighting? E.g. I have tasks to download and process and image, and I want to assign a higher weight to download stage because I know that (in absolute time) that will take much longer? How do I script that with above promise API? Note that Apple recently revamped their API for progress indicators: https://developer.apple.com/videos/wwdc/2015/?id=232 - lots of good examples to think through there. On a slightly different note..
Related, but on-topic rant: https://www.igvita.com/2015/06/25/browser-progress-bar-is-an-anti-pattern/ p.s. https://code.google.com/p/chromium/issues/detail?id=464377 |
This is a boolean progress indicator (the loading spinner), not anything with weighting or percentages. |
I think so far people have mainly talked about the ability to turn on the progress indicator when the browser normally have it turned off. Do we also need a way for the page to turn off the progress indicator once it has loaded enough stuff that the page "is ready"? See the paragraphs after the image in @igrigorik post. |
This is now whatwg/html#330. Thank you for the suggestion @stevesouders! |
# This is the 1st commit message: # This is a combination of 23 commits. # This is the 1st commit message: Integrate CORP and COEP This is part of the introduction of COEP (whatwg/html#5454). The CORP check now takes COEP into account. Also, responses coming from service workers are checked. # This is the commit message #2: Update fetch.bs Co-authored-by: Domenic Denicola <d@domenic.me> # This is the commit message #3: Update fetch.bs Co-authored-by: Domenic Denicola <d@domenic.me> # This is the commit message #4: fix # This is the commit message #5: fix # This is the commit message #6: fix # This is the commit message #7: fix # This is the commit message #8: fix # This is the commit message #9: fix # This is the commit message #10: fix # This is the commit message #11: fix # This is the commit message #12: fix # This is the commit message #13: fix # This is the commit message #14: fix # This is the commit message #15: fix # This is the commit message #16: fix # This is the commit message #17: fix # This is the commit message #18: Update fetch.bs Co-authored-by: Anne van Kesteren <annevk@annevk.nl> # This is the commit message #19: Update fetch.bs Co-authored-by: Anne van Kesteren <annevk@annevk.nl> # This is the commit message #20: fix # This is the commit message #21: fix # This is the commit message #22: fix # This is the commit message #23: fix # This is the commit message #2: fix
Browsers have default "busy indicators" that provide feedback to users that the page is loading, eg, the tab icon, status bar, and reload icon. These busy indicators are triggered in some situations (eg, clicking a link), but not others (eg, issuing an XHR). (See http://www.stevesouders.com/blog/2013/06/16/browser-busy-indicators/.)
In the absence of these busy indicators, users are uncertain and thus anxious about the status of pages loading. If the XHR response is slow to arrive and its content is critical to the user experience, the user has no idea that anything is happening. This problem grows as more pages adopt XHRs for core functionality.
A good example is this article from eBay about how their XHR-based single-page-web-app was perceived as slow until they added a progress bar (http://calendar.perfplanet.com/2014/the-power-of-perceived-performance/). That article also mentions Twitter's approach of adding a throbber to the page when XHRs are fetching tweets.
While requiring developers to add custom progress indicators is a workaround to this problem, it's not ideal because 1) it's more work for developers and 2) each progress indicator is a custom implementation and thus there is not a typical experience for the user that they are trained to look for and expect.
Since users are "trained" to look for the typical busy indicators, these indicators should be triggered when an XHR is issued for content the user is waiting on. However, XHRs are also used for background requests where the busy indicators should NOT be triggered. I suggest we add an option to XMLHttpRequest, eg, showBusy. Setting showBusy=true would explicitly tell the browser to trigger the busy indicators. This would allow for the current behavior (no busy indicators) to be preserved, but also allows an easier and more standard way for developers to use XHR while giving progress feedback to users.
The text was updated successfully, but these errors were encountered: