0

Problem:

My Plotly Dash app (python) has a clientside callback (javascript) which prompts the user to select a folder, then saves a file in a subfolder within that folder. Chrome asks for permission to read and write to the folder, which is fine, but I want the user to only have to give permission once. Unfortunately the permissions, which should persist until the tab closes, disappear often. Two "repeatable cases" are:

  • when the user clicks a simple button ~15 times very fast, previously accepted permissions will disappear (plotting a figure also does this in my real application)
  • downloading a file within a few seconds of reloading the page results in the permissions automatically going away within about 5 seconds

I can see the permissions (file and pen icon) disappear at the right of the chrome url banner.

What I've tried:

  • testing with Ublock Origin on/off (and removed from chrome) to see if the extension interfered (got idea from the only somewhat similar question I've come across: window.confirm disappears without interaction in Chrome)
  • turning debug mode off
  • using Edge instead of chrome (basically the same behavior was observed)
  • adding more computation to Test button to find repeatable case, but still needed to click it a lot to remove permissions (triggering callbacks / updating Dash components seems to be the issue, not server resources)
Example python script (dash app) to show permissions disappearing:
import dash
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
from dash import html

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
    dbc.Button(id="model-export-button", children="Export Model"),
    dbc.Label(id="test-label1", children="Click to download"),
    html.Br(),
    dbc.Button(id="test-button", children="Test button"),
    dbc.Label(id="test-label2", children="Button not clicked")
])

# Chrome web API used for downloading: https://web.dev/file-system-access/
app.clientside_callback(
    """
    async function(n_clicks) {
        // Select directory to download
        const directoryHandle = await window.showDirectoryPicker({id: 'save-dir', startIn: 'downloads'});
        
        // Create sub-folder in that directory
        const newDirectoryHandle = await directoryHandle.getDirectoryHandle("test-folder-name", {create: true});
        
        // Download files to sub-folder
        const fileHandle = await newDirectoryHandle.getFileHandle("test-file-name.txt", {create: true});
        const writable = await fileHandle.createWritable();
        await writable.write("Hello world.");
        await writable.close();
        
        // Create status message
        const event = new Date(Date.now());
        const msg = "File(s) saved successfully at " + event.toLocaleTimeString();
        return msg;
    }
    """,
    Output('test-label1', 'children'),
    Input('model-export-button', 'n_clicks'),
    prevent_initial_call=True
)

@app.callback(
    Output('test-label2', 'children'),
    Input('test-button', 'n_clicks'),
    prevent_initial_call=True
)
def test_button_function(n):
    return "Button has been clicked " + str(n) + " times"

if __name__ == "__main__":
    app.run_server(debug=False)
  • I’m having the same issue, did you manage to find a solution? Or did you report it to Chrome's bug tracker? – Guillaume Brunerie Jan 20 '23 at 15:27
  • No unfortunately I did not get a chance to test the [solution](https://stackoverflow.com/a/73962534/11689231) given below, and otherwise did not solve it at the time I was working on this project. – ColinChatfield Jan 26 '23 at 00:43

1 Answers1

0

This is now possible! In your code, replace the line…

await window.showDirectoryPicker({id: 'save-dir', startIn: 'downloads'});

…with…

await window.showDirectoryPicker({
  id: 'save-dir',
  startIn: 'downloads',
  mode: 'readwrite', // This is new!
});
DenverCoder9
  • 2,024
  • 11
  • 32