Firefox Quantum for Android - backup profile without root? On Firefox Quantum for Android (on a non-rooted phone), how do I back up my profile to another place? This would be easy if my phone was rooted (just go access the folder directly and copy it elsewhere), but it isn't rooted.

I've tried numerous different things, some of which have gotten very frustratingly close, but nothing has worked:


Using an app like Helium Backup that uses adb to backup app data. This works for some apps, but for Firefox it gives me a "Backup Disallowed" dialog box with the message "Firefox has disallowed backup of application data. Please leave a comment with the developer of Firefox and ask them to allow application data backup."
Using the browser to navigate to the profile directory and attempt to download items from there. I can clearly see my profile by going to file:///data/data/org.mozilla.firefox/files/mozilla/r3dact3d.default/, and theoretically individual files could be downloaded over enough time. But attempting to open anything just navigates to it. This 'works' for text files like .ini since I can manually copy+paste out the contents, but not for binary files (like the all-important browser.db) which always open a blank page.
Doing the above, but using USB debugging and opening up WebIDE on my laptop's Firefox to poke around. I can run arbitrary Javascript on the page that shows me my profile directory, but nothing here lets me download the files it's showing me. The closest I can get is if I modify the anchor tags to add a download attribute (so they'll download instead of navigating), but then all the links pop up an "Unable to download file" error message in the footer (versus "Download started..." which I would expect). I have no clue what this particular error message means, and Googling doesn't help.
Doing the above, but holding down on each link and choosing Open With an App (vs. Open Link in New Tab, etc.). If I can get a file explorer app to come up, it always gives me a similar error message "This item cannot be saved."
Attempting to tell Firefox (through about:config, perhaps) to save or move my profile to a different place that wouldn't require root to access. Apparently Firefox does not allow such a thing.
Trying to find some feature of Firefox Sync that backs up a profile on mobile. This feature doesn't seem to exist.
Finding an add-on that will copy the profile to another place. Before Firefox Quantum, there was an add-on named Copy Profile that did this exact thing. The add-on isn't compatible with Firefox Quantum anymore and I can't find any replacements.
Using adb to manually downgrade Firefox so Copy Profile will work again. At least one user has tried something similar already, and the result is that the app data between Firefox Quantum and Firefox non-Quantum is set up differently enough that the newly downgraded app immediately crashes on open.


I find it hard to believe that a task as important and straightforward as backing up my Firefox profile is impossible on unrooted Android. Has anyone else run into this problem or a similar one? Any solutions or suggestions?

Thanks!
I had the same problem and got inspired by your idea to use WebIDE. It turns out it is possible to access the profile files throughout JavaScript at least in my Firefox for Android version 59.0.2, but this only helps partially to back up all profile data.

Principle

The file contents can be accessed using Ajax (see here), also if they are binary (reference). It however seems like you can only access files that are in your current directory or that are in a subdirectory.

For example, the contents of browser.db can be accessed by navigating to file:///data/data/org.mozilla.firefox/files/mozilla/XXXXXXXX.default/ and then executing in the console via WebIDE:

var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "browser.db");
xmlhttp.responseType = "arraybuffer";
xmlhttp.send();


The result will then be stored in xmlhttp.response as an arraybuffer. If the data is non-binary, "text" can be used instead of "arraybuffer".

For small, non-binary files it can by sufficient to copy them to the clipboard with copy(xmlhttp.responseText), but for larger files this can throw an out of memory error.

The same technique can be used to get a listing of files and directories. The problem is that this (for me at least) worked only for the directory the browser was navigated to, but not for subdirectories. The returned data is a list of files as plain text:

300: file:///data/data/org.mozilla.firefox/files/mozilla/XXXXXXXX.default/
200: filename content-length last-modified file-type
201: AlternateServices.txt 0 Thu,%2010%20May%202018%2015:33:20%20GMT FILE 
201: OfflineCache 0 Thu,%2009%20Nov%202017%2018:28:54%20GMT DIRECTORY
...


Backing up

For completeness, here are two scripts I used to backup the files. Both are only quick&amp;dirty and minimal scripts. The data is transferred to a Python server running on the computer, which then stores them.

Important: They only backup the files in the current directory, but not the directories (See the limitation above). However since most of the profile is in the root directory this might be sufficient. If you want to store the files in the subdirectories as well you need to paste and run the JavaScript again for each directory. The neccessary directories will be created by the server.

Python server (save this as server.py and then run e.g. with python3 server.py 2222 depending on the port you want to use)

import os
from http.server import BaseHTTPRequestHandler, HTTPServer

class FileReceiveHandler(BaseHTTPRequestHandler):
    def _set_headers(self):
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain')
        self.end_headers()

    def do_POST(self):
        content_length = int(self.headers['Content-Length']) # &lt;--- Gets the size of data
        post_data = self.rfile.read(content_length) # &lt;--- Gets the data itself
        print(self.path)

        # Store file
        relative_file_path = os.path.join('received_files', self.path.lstrip('/'))
        directories = os.path.dirname(relative_file_path)
        os.makedirs(directories, exist_ok=True)
        with open(relative_file_path, 'wb') as f:
            f.write(post_data)

        self._set_headers()
        self.wfile.write(b'received')


if __name__ == '__main__':
    from sys import argv
    port = int(argv[1]) if len(argv) == 2 else 80
    server_address = ('', port)
    httpd = HTTPServer(server_address, FileReceiveHandler)
    print('Starting server')
    httpd.serve_forever()


This is the source I used as a reference for the server.

JavaScript source for the browser

Adapt the first two lines to your own profile path and local IP address and port. Then paste this script directly in the console of WebIDE with the browser navigated to the profile directory.

var profile_basepath = "file:///data/data/org.mozilla.firefox/files/mozilla/XXXXXXXX.default";
var server = "http://&lt;your_local_ip&gt;:2222";

function send() {
    var list_xmlhttp = new XMLHttpRequest();

    // Get files
    list_xmlhttp.onreadystatechange = function() {
        if(list_xmlhttp.status == 200 &amp;&amp; list_xmlhttp.readyState == 4){
            itemslist = list_xmlhttp.response.split('\n');

            for (var i = 0; i &lt; itemslist.length; i++) {
                if (itemslist[i].startsWith('201:')) {
                    parts = itemslist[i].split(' ');
                    if (parts[4] == 'FILE') {
                        handleFile(parts);
                    }
                }
            }
            console.log(itemslist);
        }
    };
    list_xmlhttp.open("GET", window.location.href, true);
    list_xmlhttp.responseType = "text";
    list_xmlhttp.send();
}

function handleFile(parts) {
    var filename = JSON.parse(JSON.stringify(parts[1]));

    // Get file content
    var file_xmlhttp = new XMLHttpRequest();
    file_xmlhttp.onreadystatechange = function() {
        if(file_xmlhttp.status == 200 &amp;&amp; file_xmlhttp.readyState == 4){
            // Send file
            var filepath = window.location.href.substring(profile_basepath.length) + filename;
            var send_xmlhttp = new XMLHttpRequest();
            send_xmlhttp.open("POST", server + filepath, true);
            send_xmlhttp.send(file_xmlhttp.response);

            console.log(filepath + " sent");
        }
    }
    file_xmlhttp.open("GET", window.location.href + parts[1], true);
    file_xmlhttp.responseType = "arraybuffer";
    file_xmlhttp.send();
}

send();


It might take some time until the script has finished.