Status

()

The Mozilla Toolkit is a set of APIs, built on top of Gecko, which provide advanced services to XUL applications. These services include Profile Management, Chrome Registration, Browsing History, Extension and Theme Management, Application Update Service, and Safe Mode. (More info)

Security

(public)

User Story

I've heard from several people that they're looking for an API that enables the usage of tabbrowser.showTab, tabbrowser.hideTab and/or tabbrowser.showOnlyTheseTabs. The primary use case I can point to right now is TabGroups (in fact I think this is blocking TabGroups from being ported to a WebExtension).
My initial suggestion was to fix up browser.tabs.update to include a "hidden" field, but bwinton mentioned that there are plans to refactor the tabbrowser APIs to only do batch operations, and that individual WE operations on tabs probably won't have a good chance of approval right now.
A suggested alternative would be browser.tabs.hide([tab1, tab2, ...]) and browser.tabs.show([tab1, tab2, ...]). It sounds to me like all stakeholders would find this acceptable.
Firefox Engineering will not work on faster tabstrip APIs in the coming months because everyone is _quite_ busy with Photon, but if the API that I suggested above is compatible with the planned improvements, maybe we can land this feature using the old tabbrowser.showTab/hideTab methods in time for 57, and switch to the new thing once it's implemented.
Dietrich mentioned he wanted to do a WE experiment for this.
Thoughts?

Would gladly appreciate that and immediately spend some time porting my TabGroups extension to it, just to avoid having too much fallout. `browser.tabs.hide` and `.show` would perfectly suit all needs, since everything else can be worked around.
> Dietrich mentioned he wanted to do a WE experiment for this.
++ as well. If I can help somehow, ping me.

I'm +1 on the suggested alternative API, and will gladly help out in any way I can.
(Indeed, everything from that point on is a good summarization of my conversations with Dolske, Andy, and Dietrich at SF! :)
If someone wants to start a repo on GitHub for the experiment, that might be a decent way to move forward…

Experiment here: https://github.com/autonome/webext-experiment-showOnlyTheseTabs
Thanks to rpl for helping me get it working.
On the add-on side, this is enough for any interested party to be able start prototyping/migrating a TabGroups or similar type of add-on to it. Then they'd just have to migrate the API usage when it is complete.
On the API side, this just mostly mirrors the existing API. The next step would be to update it based on the show/hide API semantics y'all agreed on here.

(In reply to Johann Hofmann [:johannh] from comment #0)
> A suggested alternative would be browser.tabs.hide([tab1, tab2, ...]) and
> browser.tabs.show([tab1, tab2, ...]). It sounds to me like all stakeholders
> would find this acceptable.
Should these be hide/show exclusive?
Eg, does show([1,2]) hide tabs 3 and 4 if they're visible? And does hide([1,2]) show tabs 3 and 4 if they're hidden?
Or non-exclusive:
Eg, show([1,2]) shows those tabs in addition to tabs 3 and 4 (if they're visible)?
Should we add a magic value for show all?
Eg: show([])
I'm going to assume "hide all" is not a supported scenario :)

What if the tab ids span multiple windows? Should we restrict per window and add window as a constraining parameter, or just leave it simple and more powerful?
Johann and I talked also about the idea of show/hide vs show/hide+suspend. One of the limitations of tab groups as a feature and add-on was that loaded tabs in inactive groups still consume resources, even though the user has shown clear intent that they're done with those tabs, in a way that is far more strong than switching tabs.
I'd love to be able to do something like: hideAndLazify([some tabs]).

(In reply to Dietrich Ayala (:dietrich) from comment #5)
> Johann and I talked also about the idea of show/hide vs show/hide+suspend.
> One of the limitations of tab groups as a feature and add-on was that loaded
> tabs in inactive groups still consume resources, even though the user has
> shown clear intent that they're done with those tabs, in a way that is far
> more strong than switching tabs.
>
> I'd love to be able to do something like: hideAndLazify([some tabs]).Bug 1378647 and bug 1322485

Thanks Tim! Technically we'd only need to depend on bug 1284886 for the internal support. However, we should have a cohesive story for add-on authors for how all these APIs coexist and make sure they play nice together, so is good to be aware of these.
If we didn't add a combined hide+lazify functionality, and used .discard() it'd look something like:
<pseudocode>
var tabIds = getTabIdsYouWantToHide();
browser.tabs.hide(tabIds);
tabIds.map(getTabForId).forEach(tabs.discard)
</pseudocode>
Which seems fine.
Downside is that there's no action on that bug :(

(In reply to Dietrich Ayala (:dietrich) from comment #4)
> (In reply to Johann Hofmann [:johannh] from comment #0)
> > A suggested alternative would be browser.tabs.hide([tab1, tab2, ...]) and
> > browser.tabs.show([tab1, tab2, ...]). It sounds to me like all stakeholders
> > would find this acceptable.
>
> Should these be hide/show exclusive?
>
> Eg, does show([1,2]) hide tabs 3 and 4 if they're visible? And does
> hide([1,2]) show tabs 3 and 4 if they're hidden?
>
> Or non-exclusive:
>
> Eg, show([1,2]) shows those tabs in addition to tabs 3 and 4 (if they're
> visible)?
My vote is on non-exclusive. It's a more obvious API and it allows for more granular control.
> Should we add a magic value for show all?
>
> Eg: show([])
It sounds handy to have a show-all, but this one could easily surprise extension authors when doing something like
browser.tabs.show(tabs.filter((tab) => !tab.title.includes("secret")))
>
> I'm going to assume "hide all" is not a supported scenario :)> What if the tab ids span multiple windows? Should we restrict per window and add window as a constraining parameter, or just leave it simple and more powerful?
I think this API is better when it's kept simple. It's really not that hard to manually do the things you mention with basic non-exclusive tabs.show and tabs.hide.
I'm not sure about discard. I wonder if we should just discard hidden tabs by default, to restrict the way that extension authors can abuse this to waste resources.

If a tab is hidden like this, would that also mean that it is disabled? Meaning that it will never get activated automatically and that if the last visible tab is closed, but there is a hidden one, a new tab will be opened instead of activating the hidden one.

(In reply to Johann Hofmann [:johannh] from comment #0)
> A suggested alternative would be browser.tabs.hide([tab1, tab2, ...]) and
> browser.tabs.show([tab1, tab2, ...]). It sounds to me like all stakeholders
> would find this acceptable.
I'm not sure about having 2 methods on browser.tabs. I feel like this should just be an update property.
If we really want to support batch operations, then a new updateMultiple/updateBatch method would be the way to go IMO.
But having it be an update property is much better because you can then update the `discarded` property at the same time as the `hidden` one.
You'd have stuff like this:
browser.tabs.update(tabId, { hidden: true, discarded: false });
browser.tabs.updateBatch([tab1, tab2, tab3], { hidden: false, discarded: true });
and so on...
I feel like this much more consistent with the current APIs, and also more flexible.

(In reply to Tim Nguyen :ntim from comment #14)
> I'm not sure about having 2 methods on browser.tabs. I feel like this should
> just be an update property.
There are already a bunch of methods on browser.tabs. `highlight`, in particular, is very similar to this in that it handle multiple tabs by default.
> If we really want to support batch operations, then a new
> updateMultiple/updateBatch method would be the way to go IMO.
It's more than that. We _only_ want to support batch operations. Operations on a single tab were explicitly and strongly discouraged by the Firefox Front-End Engineering team.
> But having it be an update property is much better because you can then
> update the `discarded` property at the same time as the `hidden` one.
But it's much worse because it forces us into an operation-per-tab, which we want to avoid.
> I feel like this much more consistent with the current APIs, and also more
> flexible.
`setZoom`, `setZoomSettings`, `highlight`, `move`, and `print` are all methods on the `browser.tabs` object. Adding `show`/`hide` seems pretty consistent to me, and I'm not convinced that the flexibility is useful here.

(In reply to Blake Winton (:bwinton) (:☕️) from comment #15)
> > If we really want to support batch operations, then a new
> > updateMultiple/updateBatch method would be the way to go IMO.
>
> It's more than that. We _only_ want to support batch operations.
> Operations on a single tab were explicitly and strongly discouraged by the
> Firefox Front-End Engineering team.
Then the solution is to start supporting batch operations on browser.tabs.update(). Perhaps the first argument of update() could support passing an array instead of an extra updateBatch() method. Then updating the `hidden` property would only work if you specify an array.
browser.tabs.update([tab1, tab2, tab3], { hidden: true, discard: true });
But tbh, you could also just write an array with 1 item (even with your show/hide proposal) to workaround the batch limitation
> > I feel like this much more consistent with the current APIs, and also more
> > flexible.
>
> `setZoom`, `setZoomSettings`, `highlight`, `move`, and `print` are all
> methods on the `browser.tabs` object.
Only `highlight` uses batch operations.
print, setZoom, setZoomSettings don't update a property on a tab, they just execute an operation on the page content related to it.
`move` needs some special data associated to it, so it makes sense as a separate method.
> and I'm not convinced that the flexibility is useful here.
It is useful in this case, :jkt mentioned hiding should suspend/discard the tab by default. How would you hide a tab without suspending/discarding it ? In the case of tab groups, we do *not* want to discard/suspend tabs.

(In reply to Tim Nguyen :ntim from comment #16)
> > > If we really want to support batch operations, then a new
> > > updateMultiple/updateBatch method would be the way to go IMO.
> > It's more than that. We _only_ want to support batch operations.
> > Operations on a single tab were explicitly and strongly discouraged by the
> > Firefox Front-End Engineering team.
> Then the solution is to start supporting batch operations on
> browser.tabs.update(). Perhaps the first argument of update() could support
> passing an array instead of an extra updateBatch() method. Then updating the
> `hidden` property would only work if you specify an array.
You _could_, but most people won't, for compatibility with other browsers, and having some properties work some times but not others depends on what kind of arguments you pass in seems like a terrible idea to me. If nothing else, imagine how the documentation or autocompletions for that would look!
> But tbh, you could also just write an array with 1 item (even with your
> show/hide proposal) to workaround the batch limitation
Sure, but there it's very obvious that you're doing something weird and probably shouldn't.
> > and I'm not convinced that the flexibility is useful here.
> It is useful in this case, :jkt mentioned hiding should suspend/discard the
> tab by default. How would you hide a tab without suspending/discarding it ?
> In the case of tab groups, we do *not* want to discard/suspend tabs.
Don't we? Also, `tabs.hide([tabids]);tabs.suspend([tabids])` isn't that hard to write if that's what you want.

My issue with non suspended tabs is they could continue to consume resources and make network requests without the user really knowing where the issue was coming from. This comes at a priv/sec and performance issue.
> Only `highlight` uses batch operations.
Also move(), I don't see any issue in exposing it as an array access only.
There is also the option of something like:
browser.tabs.visibility([tabIds], {keepActive: true, hidden: true});
This exposes one method only to reduce collisions of other extension apis whilst being similar to other APIs like move. Mostly the browser tries to only keep active pinned tabs right on session restore right? It would be nice to keep that similarity with this API.

(In reply to Jonathan Kingston [:jkt] from comment #18)
> My issue with non suspended tabs is they could continue to consume resources
> and make network requests without the user really knowing where the issue
> was coming from. This comes at a priv/sec and performance issue.
>
> > Only `highlight` uses batch operations.
>
> Also move(), I don't see any issue in exposing it as an array access only.
>
> There is also the option of something like:
>
> browser.tabs.visibility([tabIds], {keepActive: true, hidden: true});
>
> This exposes one method only to reduce collisions of other extension apis
> whilst being similar to other APIs like move. Mostly the browser tries to
> only keep active pinned tabs right on session restore right? It would be
> nice to keep that similarity with this API.
I support the option of being able to keep a tab active while hidden - this is something I loved about panorama / tab groups and that pretty much no chrome addon could offer (Being able to interact with a client sided js web app, switch groups, then come back and have the same instance of the same tab - not an attempt at restoring the state when the tab was hidden).
A simple example of this is filling out a form, then hiding and showing tabs / switching tab groups, and being able to come back with the partially entered data. This also applies to more complex examples like javascript web app state.

(In reply to Blake Winton (:bwinton) (:☕️) from comment #17)
> (In reply to Tim Nguyen :ntim from comment #16)
> > > > If we really want to support batch operations, then a new
> > > > updateMultiple/updateBatch method would be the way to go IMO.
> > > It's more than that. We _only_ want to support batch operations.
> > > Operations on a single tab were explicitly and strongly discouraged by the
> > > Firefox Front-End Engineering team.
> > Then the solution is to start supporting batch operations on
> > browser.tabs.update(). Perhaps the first argument of update() could support
> > passing an array instead of an extra updateBatch() method. Then updating the
> > `hidden` property would only work if you specify an array.
>
> You _could_, but most people won't, for compatibility with other browsers,
> and having some properties work some times but not others depends on what
> kind of arguments you pass in seems like a terrible idea to me. If nothing
> else, imagine how the documentation or autocompletions for that would look!
> Don't we?
If you move tabs between groups, or switch groups, you don't want your tabs to unload and lose your data there.
> > > and I'm not convinced that the flexibility is useful here.
> > It is useful in this case, :jkt mentioned hiding should suspend/discard the
> > tab by default. How would you hide a tab without suspending/discarding it ?
> > In the case of tab groups, we do *not* want to discard/suspend tabs.
>
> Also, `tabs.hide([tabids]);tabs.suspend([tabids])` isn't that
> hard to write if that's what you want.
:jkt suggests that should be the default (tabs.hide + tabs.suspend) for performance, but what about hiding without suspending the tab ? You would have to do: tabs.hide + tabs.unsuspend, which would reload the page, since tabs.hide would already unload/suspend the page. Passing a second argument with { suspended: false } would essentially be reimplementing some type of browser.tabs.update() that hides tabs.
Which is why I'm tempted to create a new browser.tabs.updateBatch() method supporting only the properties relevant to batch actions (hiding, suspending, highlighting, ...), this also solves your concern about browser.tabs.update becoming too complicated.

(In reply to Jonathan Kingston [:jkt] from comment #18)
> My issue with non suspended tabs is they could continue to consume resources
> and make network requests without the user really knowing where the issue
> was coming from. This comes at a priv/sec and performance issue.
>
> > Only `highlight` uses batch operations.
>
> Also move(), I don't see any issue in exposing it as an array access only.
>
> There is also the option of something like:
>
> browser.tabs.visibility([tabIds], {keepActive: true, hidden: true});
>
> This exposes one method only to reduce collisions of other extension apis
> whilst being similar to other APIs like move. Mostly the browser tries to
> only keep active pinned tabs right on session restore right? It would be
> nice to keep that similarity with this API.
Could be nice to have a more generic method for batch updates (updateBatch?) and only support hidden/discarded/highlighted/... or anything else relevant to batch updating. I think it would be ok to have some properties supported for update and different properties supported for updateBatch, because some of `update` properties don't really make sense with `updateBatch` and vice-versa (like `active` which only makes sense for `update`)

Whatever semantics you choose, the API needs to be able to support both hidden inactive and hidden active tabs.
For users with lots of tabs, active hidden tabs were the worst part of TabGroups. For users with fewer tabs and who switched groups more often, it was the best part.
I recommend to not make too many assumptions about some eventual UI usage.
The balance of cross-browser compat and API flexibility seems like far better criteria for choosing the feature set and deciding the semantics.

I was asked to chime in here. So, some considerations now that, apparently, the decision from bug 1357214 is being reversed:
- what happens if all the tabs are hidden? The tabstrip doesn't currently deal well with this (and arguably shouldn't have to). This ties into the point comment 4 makes - if the API isn't exclusive and you first hide 2 tabs and then 2 other tabs, it's not necessarily obvious to the second call why it would (presumably) fail in some way.
- what happens with beforeunload? See also discussion in bug 1284886. This ties into the suspend discussion. This could be vaguely sane if we force suspending hidden tabs. If it's an option to not suspend them, it's not clear how you would deal with navigation in those hidden tabs - do we just force-unhide them? This would quickly break any illusions of coherent grouping. As a result, I think making suspending/discarding optional when hiding tabs is problematic.
- the APIs are (presumably?) async (or can happen from more than 1 add-on), so what happens if a partially-overlapping hide and show call (ie hide [1,5,10], show [2,3,5]) race, especially if we decide these calls are exclusive?
- if you're hiding but not suspending tabs, what happens when they pop alerts that would normally focus/select the tab? What about popups?
- if you're hiding *and* suspending tabs, it would trigger unload handlers in one tab, which can, in reaction, close other tabs that they control (on the same site), which makes the first point (ensuring there's always at least 1 non-hidden tab in a given window) more complicated. If you don't also suspend tabs, the hidden tabs could still close other tabs at will later, which should force one or more of the previous tabs to become visible again. In fact, it's also possible for a single remaining visible tab in one window to be closed by one in another window, so you'll have this problem regardless.
- I don't think the current show/hide implementations on tabbrowser do any visibilitychange notifications. Given that we've just improved those on OS X ( bug 1236512 ) I guess if we support hiding tabs without suspending them, we should make sure we send the correct notifications to pages.
Given all the above, it seems in addition to whatever update/show/hide API you go with, you also need an event API so (other) extensions can know when tabs are being hidden/shown, especially if the browser is forced to intervene without explicit show/hide calls in order to deal with tabs closing / beforeunload / whatever.

If hiding would always mean suspending, what would the point of hiding be? You could just close the tab and recover it when needed?
For generating thumbnails (see https://bugzilla.mozilla.org/show_bug.cgi?id=1246693) I would need hiding without suspending...
Regarding pop alerts / popups(assuming they are not user initiated which would be hard if a tab is hidden) / tabs that control other tabs - those should just always be forbidden anyway. Just saying...

(In reply to sblask from comment #25)
> Regarding pop alerts / popups(assuming they are not user initiated which
> would be hard if a tab is hidden)
Users can and do allow them for specific pages, so we can't just assume this doesn't happen.
> / tabs that control other tabs
This is a web feature that you probably rely on without realizing it, that is:
var w = window.open("compose_my_email.html", "_blank")
is pseudo-equivalent to what gmail does when writing an email and opening that in a new 'window'. And yes, if you close the original gmail tab, gmail then closes the dependent window it opened for you (which opened in a separate tab). See e.g. bug 1377039 .
Making it impossible for script-opened windows to close themselves (or for same-origin pages to do the same) would contravene the web spec and break real webpages, so we can't just break that usecase.

(In reply to :Gijs from comment #24)
> - what happens if all the tabs are hidden? The tabstrip doesn't currently
> deal well with this (and arguably shouldn't have to). This ties into the
> point comment 4 makes - if the API isn't exclusive and you first hide 2 tabs
> and then 2 other tabs, it's not necessarily obvious to the second call why
> it would (presumably) fail in some way.
Perhaps it should have a minimum of one tab.
> - the APIs are (presumably?) async (or can happen from more than 1 add-on),
> so what happens if a partially-overlapping hide and show call (ie hide
> [1,5,10], show [2,3,5]) race, especially if we decide these calls are
> exclusive?
async. These are all the exact same race conditions that can occur right now with tabs.move, tabs.update etc. its not great, but I don't think we need to anything special here.

(In reply to Dietrich Ayala (:dietrich) from comment #23)
> For users with lots of tabs, active hidden tabs were the worst part of
> TabGroups. For users with fewer tabs and who switched groups more often, it
> was the best part.
As a user with lots of tabs I feel misrepresented here. I do *not* want my tabs discarded, even if they are in a different group. I switch groups frequently and have interactive websites (some which also use desktop notifications) open in different groups. Discarding them just because I switch groups would be unwelcome due to loss of background updates.
I also use the tabbar switch-to behavior (% tabname) which also automatically switches tab groups. In essence I just want a set of related tabs on the tab bar but still be able to have other tabs open.
Having the ability to discard invisible tabs is certainly desirable for some use-cases, but it would be quite annoying for others.
Those features should be considered orthogonal.

(In reply to :Gijs from comment #24)
> - if you're hiding but not suspending tabs, what happens when they pop
> alerts that would normally focus/select the tab? What about popups?
If necessary the browser should be allowed to unhide a tab even though an addon set it to hidden. This can be necessary for popups, alerts, when using switch-to behavior or when using desktop notifications.
Addons should be notified of this change. For example if the browser decides to switch to a hidden tab where the hidden tab belongs to a tab group, some tab-subtree or similar thing (addon-internal concepts) then they can treat this as an external trigger to switch to the previously hidden group/subtree/whatever.
The tabgroups addon already does exactly this to deal with the above-mentioned cases.
Change notifications also allow different tab-related addons to interact with each other. Some use-cases may be mutually exclusive, but others may not. For example an addon that simply counts the number of visible tabs and updates its toolbar icon to show the number can incorporate those change notifications to deal with the changes made by a tab groups addon.

(In reply to The 8472 from comment #28)
> (In reply to Dietrich Ayala (:dietrich) from comment #23)
> > For users with lots of tabs, active hidden tabs were the worst part of
> > TabGroups. For users with fewer tabs and who switched groups more often, it
> > was the best part.
>
> As a user with lots of tabs I feel misrepresented here. I do *not* want my
> tabs discarded, even if they are in a different group.
More concisely, I think the point is that if user opens a tab, then leave it there.. It is his own business to ultimately keep "track" of it.
If he feels like the session's getting heavier he can always restart the browser (whenever bug 1366009 lands).
Which at the very worst is simply _current situation_.

Yes, as a tabgroup user i have no interest that tabs will be suspended or discarded automatically in background on group switching, it is nice if the user can decide if tabs should be unloaded, suspended or whatever but this should be a tab based explicit user action. If youtube shall play in background or i get a message in background then it should also work. At the moment i have also no automatically tab/group switching on such events as notifications or popups. And this is ok! Things should made not be more complicate as needed.

(In reply to The 8472 from comment #28)
> (In reply to Dietrich Ayala (:dietrich) from comment #23)
> > For users with lots of tabs, active hidden tabs were the worst part of
> > TabGroups. For users with fewer tabs and who switched groups more often, it
> > was the best part.
>
> As a user with lots of tabs I feel misrepresented here. I do *not* want my
> tabs discarded, even if they are in a different group.
Yep, as I said in that comment, we need *both*, so that new add-ons can be created that work in all permutations of suspend or not.
Remember - we're designing an *API*, not a feature.
Also, to all the TabGroups users - this API isn't solely to rebuild TabGroups. Actually, I'm not sure the original add-on can get working again as-is, even with this API...

(In reply to Dietrich Ayala (:dietrich) from comment #32)
> Remember - we're designing an *API*, not a feature.
>
> Also, to all the TabGroups users - this API isn't solely to rebuild
> TabGroups. Actually, I'm not sure the original add-on can get working again
> as-is, even with this API...
Nevertheless, it is a step in the right direction, IMHO. This API would allow us to begin to rebuild that functionality. Speaking as yet another TabGroups user, with several hundred tabs, thank you all for working on this.

I think it'll be possible to build something pretty similar to the current TabGroups plugin once we can hide tabs (without suspending them). UI-wise I think it'll not be possible to display the current TabGroup name next to the browser-action item, but apart from that I don't see what would be missing.

(In reply to github from comment #34)
> UI-wise I think it'll not be possible to display the current TabGroup name next to the
> browser-action item, but apart from that I don't see what would be missing.
You can use a badge to display text.

True .... only up to 4 chars though.
I am currently playing around with marrying contextual identities with tabgroups. As the identity is displayed in the location bar, the issue of not deplaying the tab name is actually not an issue anymore :)

are there any news on this? I have kind of a tabgroups successor in an okaish state but still need to be able to hide tabs somehow. Now that with nightly TabGroups is becoming less and less functional, I would really like to have a replacement on WebExtensions technology (and I think I am not alone).
I already played around with a work-around that moves tabs to a different, minimized window -- but that is not a workable solution.
Cheers