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

Means to observe availability of storage access #55

Open
jeremyroman opened this issue Aug 12, 2020 · 17 comments
Open

Means to observe availability of storage access #55

jeremyroman opened this issue Aug 12, 2020 · 17 comments
Assignees
Labels
future Will consider for a future revision

Comments

@jeremyroman
Copy link

The storage access API provides for querying the storage access state (hasStorageAccess) and requesting changes (requestStorageAccess), but not observing changes. Authors could poll hasStorageAccess, but this is inefficient and unergonomic.

A frame might have multiple libraries or widgets which need to be updated when the user approves storage access. Currently, the solutions available to the author are:

  1. manually identify all libraries/widgets which need to be notified and all widgets which can trigger a request, and route notifications between them
  2. replace requestStorageAccess with a wrapper
  3. poll hasStorageAccess (wasting CPU cycles)

The first of these gets even harder if the flag set changes due to a change outside the document. For instance, a storage access request originating from a same-origin frame elsewhere on the page causes a change with no notification in the current CG draft (since these share a partitioned storage key and thus flag set).

The simplest solution would seem to be:

partial interface Document {
    readonly attribute Promise<void> storageAccessAvailable;
};

which resolves whenever hasStorageAccess would start resolving to true (i.e., has storage access flag for the flag set corresponding to the document is set and the was expressly denied storage flag is not set).

Sample usage:

let loggedInWidget = document.getElementById('logged-in');
loggedInWidget.textContent = 'Unknown';
document.storageAccessAvailable.then(() => {
    // Runs immediately if storage access is already available.
    // Otherwise runs when it is granted, if ever.
    loggedInWidget.textContent = localStorage.getItem('user') ?? 'Logged out';
});
@jeremyroman
Copy link
Author

@johnwilander @annevk Would you mind taking a look at this idea, or advising me on how to properly ask for feedback? I'm not very clear on the etiquette for contributing here.

@johnwilander
Copy link
Collaborator

johnwilander commented Sep 10, 2020

Hi! Sorry for the delay. This was talked about in the context of per-frame vs per-page storage access. We eventually agreed to per-page storage access which introduces the problem you're trying to solve.

Looking at real world use cases though, the user has just interacted with iframeA from thirdParty.example, the frame called the Storage Access API, and the frame was granted access. What immediately needs to happen in sibling frames? The user is not interacting with them. Are you thinking of speculatively fetching resources in case the user interacts with iframeB from thirdParty.example?

@jeremyroman
Copy link
Author

A few cases:

  1. Like you describe: iframes iframeA and iframeB both from thirdparty.example, say a social embed which allows the user to like a piece of embedded content. The user interacts with iframeA, and approves a storage access prompt. When the user then scrolls and looks at iframeB, it still does not reflect whether the user has already liked the associated content (or any other personal state), even though the user has already approved access. But when the user does anything that causes iframeB to check, it finds that it already has access. thirdparty.example can fix this behavior by polling hasStorageAccess periodically, but this wakes up the CPU frequently and wastes resources.

  2. A single iframe from thirdparty.example uses multiple libraries with different authors. When the user interacts with any component in the iframe that requires storage access and it is granted, the other components have storage access but don't know it, so they appear to the user not to be responding to the prompt approval (which, from their perspective, applies to the page as a whole) correctly. Fixing this either requires that the author to tightly couple and coordinate this across these various libraries (i.e., building this exact API themselves, and trying to make sure every call site to requestStorageAccess hooks into it), or the library authors can each poll hasStorageAccess.

I will note that I'm particularly motivated by another use case that we're considering using Storage Access API for, which is uncredentialed prerendering (in which a prerendered page would be denied storage access in much the same way as a third-party frame might, and storage access would be granted only if the user chose to navigate to the prerendered page). In this case, the grant of storage access is also a result of an action outside the affected frame, and so it's necessary for the page to be able to observe it, preferably without polling.

@jakearchibald
Copy link

2. Fixing this either requires that the author to tightly couple and coordinate this across these various libraries (i.e., building this exact API themselves, and trying to make sure every call site to requestStorageAccess hooks into it), or the library authors can each poll hasStorageAccess.

+1. Say we had three components:

  • A login box.
  • Something that handles user visual preferences, eg font size or theme.
  • A button which lets the user opt in to storage access.

On page load, the login box knows it doesn't have storage access, so it puts itself into an indeterminate state. It wants to update this if storage becomes available, but it doesn't want to request storage access.

The visual preferences component may want to change page appearance once storage access is gained.

The button is the thing that calls requestStorageAccess when clicked. If storage access is gained by some other means, this button should go away, as it's no longer needed.

In this example, if we don't have something like storageAccessAvailable, this app will have to reinvent it using something like BroadcastChannel, and components which aren't built by the same team may end up using a different system which can get out of sync. The only reliable system is polling.

There's a similar issues with the storage event on localstorage. If storage changes, the event is fired on other window instances. This means that components on the same page won't hear about the change unless a custom pub/sub is created, or components resort to hacks like each creating an iframe to get another window object for the event. It'd be good to avoid the need for more hacks 😄.

@annevk
Copy link
Collaborator

annevk commented Sep 24, 2020

As I mentioned in #62 this seems reasonable to Mozilla. Did you also consider giving a signal before the switch happens to allow for any migration?

@jeremyroman
Copy link
Author

jeremyroman commented Sep 24, 2020

I admit to not being completely current on the state of pre-storage-access (i.e., document.hasStorageAccess() resolves to false) behavior of storage APIs (e.g. this doesn't seem to correspond with the current notion).

I think a migration signal might be interesting (though I think this issue makes sense independently from it). If I had to sketch one out, I would imagine it wouldn't affect the shape of this idea, because you could do something like:

document.addEventListener('beforestorageaccess', async e => {
  let readyToMigrate = (async function() {
    // open a database, fetch some rows, etc
    // exactly what must be pulled out depends on whether
    // database connections, transactions, etc. can persist
    // across the change
  })();
  e.waitUntil(readyToMigrate);
  let dataToMigrate = await readyToMigrate;
  await document.storageAccessAvailable;
  // write into the newly available database
});

The semantics of such a beforestorageaccess event would be entwined with the semantics of the storage partitioning (which I think are still evolving -- and raise questions like which frame is responsible for migrating data, if they share a partition before), but I think in all likely scenarios fit naturally with document.storageAccessAvailable.

@annevk
Copy link
Collaborator

annevk commented Sep 24, 2020

Yeah, no disagreement there. (FWIW, MDN matches what we currently ship on release, Firefox Nightly has where we want things to go. Which is approximately that third parties have partitioned storage by default and can get to non-partitioned storage for all same-origin documents within an agent.)

@johannhof johannhof added the agenda+F2F Request to add this issue or PR to the agenda for our upcoming F2F. label May 12, 2021
@johannhof johannhof removed the agenda+F2F Request to add this issue or PR to the agenda for our upcoming F2F. label Jun 3, 2021
@johannhof johannhof added the future Will consider for a future revision label Mar 22, 2022
@johannhof
Copy link
Member

This is generally a reasonable idea and it doesn't look problematic from a privacy perspective. We still consider this something to have in the next version of the spec, but not for immediate graduation. It would be interesting to see if there are real-world problems that we could solve with such a feature right now.

@lghall
Copy link

lghall commented Oct 24, 2022

Adding a +1 to this request - For Google Workspace, there are scenarios with multiple embeds in the same top-level page (for example, a third-party site that embeds multiple Google Docs, or a Google Doc + a Google Calendar appointment booking page). Having some way for embeds to automatically know when storage access has been granted is key to providing a smooth user experience.

@johannhof johannhof self-assigned this Jan 23, 2023
@johannhof johannhof removed the future Will consider for a future revision label Jan 23, 2023
@johannhof
Copy link
Member

Note that with #138 we have added formal integration with permissions, and I'll try to verify and test that storage access availability can be observed through the Permissions API. I'll close this issue once that's done.

@annevk
Copy link
Collaborator

annevk commented Jan 24, 2023

I'm not sure that addresses all cases from OP. You can observe that you can now call requestStorageAccess() in your document, but you can't observe that someone else has done so. So depending on the use case you might still want some kind of signal that requestStorageAccess() has been successfully invoked for your document.

@cfredric
Copy link
Contributor

I believe you ought to be able to use the permissions API to observe changes which might be caused by other documents:

navigator.permissions.query({ name: 'storage-access' }).then(async (status) => {
 if (status.state === 'granted') {
   await document.requestStorageAccess(); // should auto-resolve due to the existing grant
   doSomethingWithThirdPartyCookies();
 } else {
  status.addEventListenever('change', () => reactToPermissionChange(status));
 }
});

@annevk
Copy link
Collaborator

annevk commented Jan 24, 2023

Correct. I'm talking about observing a successful requestStorageAccess() invocation in your document, no other documents involved. That's one of the scenarios OP discusses, where you have multiple libraries interested in the outcome of that call. Of course all these libraries could call requestStorageAccess() themselves, but that might also have inadvertent side effects.

@johannhof
Copy link
Member

I don't see why @cfredric's code doesn't work for these libraries. Through the permissions API they can ensure that they won't prompt the user and will only call rSA when the main UI code has done so. What might be missing from the example is a hasStorageAccess call to avoid setting up the permissions part if it's not necessary, but otherwise it seems sufficient to me.

@annevk
Copy link
Collaborator

annevk commented Jan 25, 2023

That pattern encourages swapping Storage Access as soon as possible by all code that wants to do something once Storage Access is there. It doesn't allow for code reacting to Storage Access becoming available and leaving a single piece of code in charge of when to actually make the swap.

@johannhof
Copy link
Member

Yeah, that's fair, though I'd personally consider that a lower priority until we have evidence that this is an issue. It feels like allowing cross-frame observation of grants is more impactful but I could be wrong. Certainly don't want to block adding that kind of ability.

@annevk
Copy link
Collaborator

annevk commented Jan 25, 2023

I still think @jeremyroman's suggestion makes sense and as far as I can tell he specifically made it for the single-document case, only later elaborating on multiple documents.

I agree it's not quite a priority and #154 might also change things around, but I'd like to keep this open for now.

@annevk annevk added the future Will consider for a future revision label Jun 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
future Will consider for a future revision
Projects
None yet
Development

No branches or pull requests

7 participants