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

Comments

@jakearchibald
Copy link
Contributor

commented Feb 16, 2015

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

This comment has been minimized.

Copy link
Contributor Author

commented Feb 16, 2015

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

This comment has been minimized.

Copy link
Member

commented Feb 16, 2015

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

This comment has been minimized.

Copy link

commented Feb 16, 2015

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

This comment has been minimized.

Copy link
Contributor Author

commented Feb 16, 2015

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

This comment has been minimized.

Copy link

commented Feb 16, 2015

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

This comment has been minimized.

Copy link
Contributor Author

commented Feb 18, 2015

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

This comment has been minimized.

Copy link

commented Feb 18, 2015

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

@jakearchibald

This comment has been minimized.

Copy link
Contributor Author

commented Feb 18, 2015

We should. Noted in #609 (comment)

@johnmellor

This comment has been minimized.

Copy link

commented Feb 24, 2015

Naming bikeshed: asleep? unloaded? evicted? frozen?

@jakearchibald

This comment has been minimized.

Copy link
Contributor Author

commented Feb 24, 2015

Unloaded & evicted correctly suggest it's not simply suspended

On Tue, 24 Feb 2015 17:46 John Mellor notifications@github.com wrote:

Naming bikeshed: asleep? unloaded? evicted? frozen?


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

@jakearchibald

This comment has been minimized.

Copy link
Contributor Author

commented Feb 26, 2015

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

@jakearchibald

This comment has been minimized.

Copy link
Contributor Author

commented Feb 26, 2015

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

@johnmellor

This comment has been minimized.

Copy link

commented Feb 26, 2015

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.

@mattto

This comment has been minimized.

Copy link
Member

commented Apr 21, 2015

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

@jakearchibald

This comment has been minimized.

Copy link
Contributor Author

commented Apr 22, 2015

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

This comment has been minimized.

Copy link
Collaborator

commented Apr 23, 2015

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

This comment has been minimized.

Copy link
Contributor

commented May 7, 2015

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

@jakearchibald

This comment has been minimized.

Copy link
Contributor Author

commented May 19, 2015

@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

This comment has been minimized.

Copy link

commented Sep 5, 2015

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

This comment has been minimized.

Copy link
Contributor Author

commented Apr 12, 2016

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

This comment has been minimized.

Copy link
Contributor Author

commented Apr 12, 2016

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

@jakearchibald

This comment has been minimized.

Copy link
Contributor Author

commented Apr 12, 2016

F2F: or could be vaguer…

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

This comment has been minimized.

Copy link

commented Jun 7, 2016

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

This comment has been minimized.

Copy link
Contributor Author

commented Aug 3, 2016

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

This comment has been minimized.

Copy link
Contributor Author

commented Sep 15, 2019

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
Projects
None yet
7 participants
You can’t perform that action at this time.