Skip to content
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

How are unloaded tabs represented? #626

Open
jakearchibald opened this issue Feb 16, 2015 · 25 comments
Open

How are unloaded tabs represented? #626

jakearchibald opened this issue Feb 16, 2015 · 25 comments
Milestone

Comments

@jakearchibald
Copy link
Contributor

Mobile browsers put tabs into an "asleep" state to save memory & battery as needed. If you focus the tab later, the page is reloaded, but may regain some state within form elements.

This behaviour is completely unspecified, but I think we should put some guidance in the SW spec for these pages.

Specifically:

  • Do asleep tabs prevent a SW moving from "waiting" to "active"?
  • Do asleep tabs show up in clients.getAll()?
@jakearchibald
Copy link
Contributor Author

My take:

I don't think asleep tabs should have an effect on the lifecycle within a registration. These tabs are going to reload anyway, having them reload with an updated version seems fine. Given that tab-management is pretty much a lost art in Android L, you'd very easily end up with old forgotten tabs preventing updates becoming active.

Having asleep tabs show up in clients.getAll() is somewhat desirable, as following a push message you can focus one of these existing tabs rather than opening a new one. However, given that tab management isn't really a thing anymore in Android L, maybe it doesn't matter if you open a new window vs focusing an asleep one. I don't think there's a performance benefit.

(I realise this is a bit Android-specific, so interested in hearing from other vendors)

@wanderview
Copy link
Member

I believe the goal on fxos is for tabs to get killed on memory pressure and relaunch with some kind of persisted state. I think treating waking asleep tabs as a reload would work for that case as well.

@johnmellor
Copy link

However, given that tab management isn't really a thing anymore in Android L, maybe it doesn't matter if you open a new window vs focusing an asleep one.

Even with Chrome on Android L, users still don't expect to see duplicate entries in their Recents (they can also disable Chrome Settings > "Merge tabs and apps" to go back to a traditional tab switcher, or use other browsers). So it's still better if webapps can reuse existing tabs, including asleep ones.

Having asleep tabs show up in clients.getAll() is somewhat desirable

How about adding an includeAsleep option to matchAll?

The expectation would be that if you includeAsleep, you shouldn't postMessage to all the clients (as that is likely to cause excessive RAM usage, as well as CPU churn from reloading them all); I'm not sure if/how that API should enforce that.

See also some earlier discussion on #414 (comment)

@jakearchibald
Copy link
Contributor Author

How about we add a note in the spec to allow the browser to resurrect an asleep tab with a matching url, if openWindow(url) is called?

That avoids the postMessage issue, and means asleep tabs don't need to be fully spec'd.

@johnmellor
Copy link

Two issues with that:

  1. The browser would presumably only overwrite tabs with exact URL matches, but webapps frequently change URL via history.pushState or location.hash. It's better to let the webapp decide whether to reuse tabs (e.g. maybe it's ok to reuse /sent to show /inbox).
  2. Browsers like to do nice things like restoring form data when resurrecting an asleep tab. If they just overwrite the asleep tab, there may be some data loss; though I guess they could avoid overwriting if the user modified form data.

@jakearchibald
Copy link
Contributor Author

Yeah, you're right. An option on clients.matchAll is the right way forward.

postMessage to asleep clients should fail I think, unless focus is called first.

@johnmellor
Copy link

Sounds good. Can we make Client.postMessage return a Promise instead of void, so we can communicate failure?

@jakearchibald
Copy link
Contributor Author

We should. Noted in #609 (comment)

@johnmellor
Copy link

Naming bikeshed: asleep? unloaded? evicted? frozen?

@jakearchibald
Copy link
Contributor Author

Unloaded & evicted correctly suggest it's not simply suspended

On Tue, 24 Feb 2015 17:46 John Mellor [email protected] wrote:

Naming bikeshed: asleep? unloaded? evicted? frozen?


Reply to this email directly or view it on GitHub
#626 (comment)
.

@jakearchibald
Copy link
Contributor Author

'unloaded' would clash with the back/forward cache definition. Can't think of anything better than 'evicted'.

@jakearchibald
Copy link
Contributor Author

If the 'evicted' tab had child clients (workers, iframes), would these still exist but in an evicted state? This is getting messy :(

@johnmellor
Copy link

To avoid having to standardize browsers' varying heuristics for restoring evicted tabs (e.g. restoring scroll/zoom and form data), I was thinking it would be simplest if calling focus() on an evicted tab just replaces that tab with an ordinary page load of its last known URL (making no attempt to restore state).

So no, only the top-level frame would have an evicted WindowClient.

@mfalken
Copy link
Member

mfalken commented Apr 21, 2015

Tracked in http://crbug.com/461413 but needs spec.

@jakearchibald
Copy link
Contributor Author

How about:

clients.matchAll({
  includeEvicted: true
})

includeEvicted will include EvictedWindowClients - clients that have been evicted from memory, but can be restored

[Exposed=ServiceWorker]
interface Client {
  readonly attribute USVString url;
  readonly attribute FrameType frameType;
  readonly attribute DOMString id;
};

[Exposed=ServiceWorker]
interface ActiveClient : Client {
  void postMessage(any message, optional sequence<Transferable> transfer);
};

[Exposed=ServiceWorker]
interface WindowClient : ActiveClient {
  readonly attribute VisibilityState visibilityState;
  readonly attribute boolean focused;
  Promise<WindowClient> focus();
};

[Exposed=ServiceWorker]
interface EvictedWindowClient : Client {
  Promise<WindowClient> focus();
};

enum FrameType {
  "auxiliary",
  "top-level",
  "nested",
  "none"
};

EvictedWindowClient have the url, frameType and id that they had prior to eviction. It does not have postMessage.

evictedWindowClient.focus() restores the tab (however the UA does that), then focuses the window, then resolves. Note that it resolves with a WindowClient, which will allow postmessaging.

EvictedWindowClients do not prevent a ServiceWorker from moving from waiting to active. On restoring a EvictedWindowClient, if it was evicted while under the control of a SW that's no longer active, or it was .claim()ed while evicted, it should do a full reload rather than any cleverer restoration.

Thoughts?

@jungkees
Copy link
Collaborator

One question is how much it will help to expose the evicted client concept to devs. FWIW, it seems it's the UA that actually uses the evicted state information. That is, it's the UA that uses that state information to restore the tabs, to exclude those evicted tabs in the decision on their service worker's Activate (waiting -> active) process, etc.

In the case we don't come up with compelling use cases/requirements, an option would be to define and use an internal slot, WindowClient's evicted flag, and make the UA use this in the related algorithms. For instance, we can put a step before step 5 in client.postMessage() that checks whether the context object's evicted flag is set, and if so reject the returned promise with an exception.

I think the decision hinges on whether devs really need it.

@nikhilm
Copy link
Contributor

nikhilm commented May 7, 2015

@jakearchibald 's proposal seems really complicated. Do we have a solid use-case for it?

@jakearchibald
Copy link
Contributor Author

@nikhilm the use-case is avoiding creating duplicate tabs when responding to notification clicks.

  1. Push message received indicating the user has a new chat message
  2. Notification shown
  3. User taps notification
  4. SW looks for an existing relevant tab to focus, only calling openWindow if it doesn't find one

Evicted tabs aren't real SW clients, as they don't have an environment settings object etc, they're essentially placeholders. But developers should be able to awaken one of these rather than creating a new window to the same url.

I put them into their own type since they're not a real client, and postMessage won't work. It should be a property on WindowClient rather than its own type, as long we're happy exposing a postMessage method that will never work.

@johnmellor
Copy link

We'd also like developers to be able to set the title of tabs (both evicted and non-evicted) from a push message, since this is a popular way of letting users know that a tab has updated (and remains visible after e.g. toast notifications may have expired).

For non-evicted tabs you can use postMessage for that, but for evicted tabs we'd need an API on EvictedWindowClient.

(it might also be helpful to set the URL of evicted tabs, e.g. by exposing history.pushState & replaceState, though the Service Worker can probably polyfill that using the fetch event).

@jakearchibald jakearchibald added this to the Version 2 milestone Oct 28, 2015
@jakearchibald
Copy link
Contributor Author

F2F: Making evicted tabs is a huge thing to add to the platform, and may lock browsers into a particular behaviour.

clients.openWindow(url, {
  reuseIfExistingClientMatches: /\/my-messaging-app\//
});

Something like the above could let us deal with this.

@jakearchibald
Copy link
Contributor Author

F2F: or we could treat these dead tabs as uncontrolled clients, feels hacky though

@jakearchibald
Copy link
Contributor Author

F2F: or could be vaguer…

clients.openWindow(url, {
  reuseExisting: true
});

@delapuente
Copy link

In the dev mindset, an open tab (i.e. a tab which is in the tab panel, in case of Firefox OS, an app present in the app switcher) is a client. It does not matter if it is evicted, sleep or whatever, if it is listed I don't mind (and I don't know) what special treatments the browser is giving to them so I would make getAll() to always include these windows and, for fine tuning, to opt-out:

clients.matchAll({
  state: 'evicted' // platform-dependent
})

@jakearchibald
Copy link
Contributor Author

F2F:

Current consensus is not to add clients for these (we could later), but allow clients.openWindow to reuse an unloaded tab if the URL is the same.

@jakearchibald
Copy link
Contributor Author

For TPAC:

  • The above still makes sense, but we may want to expose these now that page lifecycle is a thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants