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

Chrome extention | re-use FileSystemFileHandle from indexedDB | popup requesting user permission not showing up #289

Closed
x-077 opened this issue Mar 28, 2021 · 9 comments

Comments

@x-077
Copy link

x-077 commented Mar 28, 2021

Hello,

Im trying to make a simple chrome extension that read a file from the FileSystem. To do that I have an "options" page set as default_popup in my extensions. This HTML page have one button.

When the user click on the button and select a file, I save the FileSystemFileHandle into indexedDB.

If the user click again on the button, I check in indexedDB if the FileSystemFileHandle exist. If so, I re-use it.

Problem :

As long as I keep the popup options open, I'm able to re-use the FileSystemFileHandle from indexedDB, meaning that I do get the window/popup requesting user permission once and I'm able to read the content of the file with the readFile method.

However, if I close the options and re-open it, once I click on the button, when it's re-using the FileSystemFileHandle from indexedDB, I dont get the window/popup requesting user permission and I can see that the permission is denied .

image

Im not sure where is the problem as Im calling the method requestPermission on the FileSystemFileHandle from indexedDB. (see method verifyPermission below)

scripts used :

the options.js file :

import { get, set } from "https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js";

const pre1 = document.querySelector("pre.file");
const button1 = document.querySelector("button.file");

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = 'readwrite';
  } else {
    options.mode = 'read';
  }
  // Check if permission was already granted. If so, return true.
  if ((await fileHandle.queryPermission(options)) === 'granted') {
    console.log('granted')
    return true;
  }
  // Request permission. If the user grants permission, return true.
  if ((await fileHandle.requestPermission(options)) === 'granted') {
    return true;
  }
  // The user didn't grant permission, so return false.
  return false;
}

async function readFile(fileHandler) {
  const file = await fileHandler.getFile();
  const contents = await file.text();
  console.log(contents)
}


button1.addEventListener("click", async () => {
  try {
    const fileHandleOrUndefined = await get("file");
    if (fileHandleOrUndefined) {
      console.log('Record exist in indexedDB')
      console.log(fileHandleOrUndefined)
      if(await verifyPermission(fileHandleOrUndefined, false)) {
        console.log(`permission Granted, reading file: `)
        readFile(fileHandleOrUndefined)
      } else {
        console.log(`permission refused`)
      }

      console.log(`Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`); 
      return;
    }

    
    console.log('Record does not exist in indexedDB, user have to select it')
    const [fileHandle] = await window.showOpenFilePicker();
    await set("file", fileHandle);
    
    if(await verifyPermission(fileHandle, false)) {
      console.log(`verification Granted, reading file: `)
      readFile(fileHandle)
    }
    console.log(`Stored file handle for "${fileHandle.name}" in IndexedDB.`)
  } catch (error) {
    console.log(error)
    alert(error.name, error.message);
  }
});

//window.addEventListener("load", () => button1.click())

The options.html file :

<!doctype html>
<html>
  <head>
    <title>FSTestExtension</title>
    <style>
      body {
        font-family: sans-serif;
	width: 124px;
        height: 20px;
      }
    </style>
  </head>
  <body>
    <button class="file" type="button">set config file path</button>
    <pre class="file"></pre>

    <script src="options.js" type="module"></script>
  </body>
</html>

The manifest.json file :

{
  "name": "FSTestExtension",
  "description": "Test chrome extensions FileSystem",
  "version": "1.0",

  "permissions": [
    "activeTab"
  ],

  "options_page": "options.html",

  "browser_action": {
    "default_icon": "icon.png",
    "default_title": "FSTestExtension",
    "default_popup": "options.html"
  },

  "manifest_version": 2,
  "content_security_policy": "script-src 'self' https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js; object-src 'self'"
}

Mind the permissions in the manifest.json file, it's for something else as it's not necessary in that example.

Additional Question :

Also I notice that if I trigger the click event of the button from window.addListener("load, () => button1.click()) within the options.js file , to prevent the user to click on the button, I get the following error :

image

I guess Im getting this error because it's not the user that click on the button, is there a workaround ? ( line commented in the snippet above as not really related to the main question)

Thanks

@Jaifroid
Copy link

I can't answer your first question as it may be an issue relating to extensions, or it may have to do with scope (possibly you need to extract the file handle from idxDB and store it in a global-scoped variable?).

On the second, I'm afraid there's no workaround to the requirement that the user give permission to access the file on each new session start. This is something discussed in a number of issues, but the security model at present doesn't persist permissions between sessions. It only persists the file handle (what you are storing in idxDB) which is convenient because the user doesn't have to pick the file again, but the user does have to grant permission to access the file.

An alternative is being discussed for installed apps, whereby the file permission could be persisted. In principle this should apply to installed extensions as well, though because extensions are proprietary, maybe it will be left up to each vendor to decide whether to implement. NB I'm not a developer of this spec or any implementation of it, just a user of the API.

@ozonep
Copy link

ozonep commented Apr 15, 2021

@Jaifroid I think he meant other issue. I'm having it as well.
I created a demo with web-worker, so in the main thread user clicks on a button to open directory picker, selects a directory, and then I save dirHandler in iDB.
Next, web-worker reads that iDB entry to get handler and use it to read/write data to local FS.
But the issue is - if I refresh the page, permissions are being invalidated, and even though from web worker I request a permission to 'readwrite' - I never get a prompt to actually approve it.
So handler just becomes useless.

@Jaifroid
Copy link

@ozonep AFAIK, Web Workers have no access to the DOM and therefore I presume have no access to launch permission prompts. It sounds like you will need to extract the file handle in the main thread, not a worker thread, or else serialize it and send it to the main thread to gain the required permissions. I don't know whether the permission would then make the file handle usable in the worker. It would be strange (and probably a bug) if not, because a picked file can certainly be dealt with by a worker.

@x-077
Copy link
Author

x-077 commented Apr 17, 2021

Hello @ozonep , @Jaifroid ,

Thanks for taking the time to respond. I did not look at it again since I posted the problem.

@Jaifroid , I do understand the security constrain, it's fine for me as for what I recall, I dont think it was possible to save the file location in indexDB in previous release and user had to pick it each time it was necessary ( I might be wrong .. :D )

My main problem here is that when I re-use the FileSystemFileHandle from indexedDB, I dont get the window/popup requesting user permission and I can see that the permission is denied ( cf first screenshot ).

As long as I can find a workaround for that, Im fine requesting the access to the user. And if it's not possible, I will just wait for new updates :).

In worst case I could save the content of the file that I need in indexDB , but I presume Its not the best on security level.

@ozonep
Copy link

ozonep commented Apr 17, 2021

@matth-c3 yeah, I spent hours trying to make it work :(
Even if I don't refresh the page, Handlers from IndexedDB aren't valid. I still get "prompt" when querying permissions, even though it doesn't make sense for me. It defeats the main purpose of storing handlers in iDB for me. To use in a worker....

@a-sully
Copy link
Collaborator

a-sully commented Jan 9, 2023

In worst case I could save the content of the file that I need in indexDB , but I presume Its not the best on security level.

Since this issue was filed, there's been a lot of work to support the Origin Private File System. If all you need is to persist the file contents across browsing sessions (in a file that only your extension can access), I'd recommend storing the file there

Stepping back, it seems to me there are two main issues here:

  1. that file permissions are not persisted across sessions, and
  2. that permission prompts cannot be shown from a worker. This behavior is actually not currently specified and there's an open Chromium bug to support this. From a recent comment on that bug:

Currently we don't support calling requestPermission() from a worker, though I agree that we should support that if the worker corresponds to a site with an open window. i.e. Service Workers running in the background without an open tab should NOT be able to show a prompt, while requesting permission from a Dedicated Worker tied to the site the user is interacting with should be supported.

Not sure when we'll get around to fixing it, but this is a known issue that we're hoping to address, especially as the SyncAccessHandle API drives more of this API's usage to workers

Closing this issue since the first point is covered by #297 and the second point is a Chromium implementation issue (please move further discussion to https://crbug.com/1359786)

@a-sully a-sully closed this as completed Jan 9, 2023
@Jaifroid
Copy link

@a-sully I believe you missed the main point of this issue: that the permissions prompt doesn't seem to show in a Chrome Extension. The issue is not about persisting access permissions (without prompt) across sessions, it's about the fact that there is (or was) no way to prompt the user to give permission to use the file or directory handle. Is this a problem with the spec (e.g., Chrome extensions don't use https://, but they do support Service Workers, so it is a privileged scheme), or is it a Chrome or Chromium-specific bug?

@a-sully
Copy link
Collaborator

a-sully commented Jan 12, 2023

Is this a problem with the spec (e.g., Chrome extensions don't use https://, but they do support Service Workers, so it is a privileged scheme), or is it a Chrome or Chromium-specific bug?

This is a Chromium implementation issue. I just discovered a recent bug report suggesting showDirectoryPicker() is broken for extensions and confirmed its validity.

@a-sully I believe you missed the main point of this issue

Right you are! As you can see from an earlier comment I made on that bug, I fully expected directory pickers to work for extensions. Somehow I hadn't seen #314? *sigh*

Since this is a Chromium implementation problem, I'll keep this issue closed and recommend that you subscribe to that bug for further updates. Sorry for the confusion!

@Jaifroid
Copy link

OK, thanks for the link to the Chromium bug -- that's very helpful.

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

4 participants