summaryrefslogtreecommitdiffstats
path: root/browser/components/backup/resources/MiscDataBackupResource.sys.mjs
blob: 3d661145993c32b26f43c4495c1cae85f1dae497 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ActivityStreamStorage:
    "resource://activity-stream/lib/ActivityStreamStorage.sys.mjs",
  ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs",
});

const SNIPPETS_TABLE_NAME = "snippets";
const FILES_FOR_BACKUP = [
  "enumerate_devices.txt",
  "protections.sqlite",
  "SiteSecurityServiceState.bin",
];

/**
 * Class representing miscellaneous files for telemetry, site storage,
 * media device origin mapping, chrome privileged IndexedDB databases,
 * and Mozilla Accounts within a user profile.
 */
export class MiscDataBackupResource extends BackupResource {
  static get key() {
    return "miscellaneous";
  }

  static get requiresEncryption() {
    return false;
  }

  async backup(stagingPath, profilePath = PathUtils.profileDir) {
    const files = ["enumerate_devices.txt", "SiteSecurityServiceState.bin"];
    await BackupResource.copyFiles(profilePath, stagingPath, files);

    const sqliteDatabases = ["protections.sqlite"];
    await BackupResource.copySqliteDatabases(
      profilePath,
      stagingPath,
      sqliteDatabases
    );

    // Bug 1890585 - we don't currently have the ability to copy the
    // chrome-privileged IndexedDB databases under storage/permanent/chrome.
    // Instead, we'll manually export any IndexedDB data we need to backup
    // to a separate JSON file.

    // The first IndexedDB database we want to back up is the ActivityStream
    // one - specifically, the "snippets" table, as this contains information
    // on ASRouter impressions, blocked messages, message group impressions,
    // etc.
    let storage = new lazy.ActivityStreamStorage({
      storeNames: [SNIPPETS_TABLE_NAME],
    });
    let snippetsTable = await storage.getDbTable(SNIPPETS_TABLE_NAME);
    let snippetsObj = {};
    for (let key of await snippetsTable.getAllKeys()) {
      snippetsObj[key] = await snippetsTable.get(key);
    }
    let snippetsBackupFile = PathUtils.join(
      stagingPath,
      "activity-stream-snippets.json"
    );
    await IOUtils.writeJSON(snippetsBackupFile, snippetsObj);

    return null;
  }

  async recover(_manifestEntry, recoveryPath, destProfilePath) {
    await BackupResource.copyFiles(
      recoveryPath,
      destProfilePath,
      FILES_FOR_BACKUP
    );

    // The times.json file, the one that powers ProfileAge, works hand in hand
    // with the Telemetry client ID. We don't want to accidentally _overwrite_
    // a pre-existing times.json with data from a different profile, because
    // then the client ID wouldn't match the times.json data anymore.
    //
    // The rule that we're following for backups and recoveries is that the
    // recovered profile always inherits the client ID (and therefore the
    // times.json) from the profile that _initiated recovery_.
    //
    // This means we want to copy the times.json file from the profile that's
    // currently in use to the destProfilePath.
    await BackupResource.copyFiles(PathUtils.profileDir, destProfilePath, [
      "times.json",
    ]);

    // We also want to write the recoveredFromBackup timestamp now.
    let profileAge = await lazy.ProfileAge(destProfilePath);
    await profileAge.recordRecoveredFromBackup();

    // The activity-stream-snippets data will need to be written during the
    // postRecovery phase, so we'll stash the path to the JSON file in the
    // post recovery entry.
    let snippetsBackupFile = PathUtils.join(
      recoveryPath,
      "activity-stream-snippets.json"
    );
    return { snippetsBackupFile };
  }

  async postRecovery(postRecoveryEntry) {
    let { snippetsBackupFile } = postRecoveryEntry;

    // If for some reason, the activity-stream-snippets data file has been
    // removed already, there's nothing to do.
    if (!IOUtils.exists(snippetsBackupFile)) {
      return;
    }

    let snippetsData = await IOUtils.readJSON(snippetsBackupFile);
    let storage = new lazy.ActivityStreamStorage({
      storeNames: [SNIPPETS_TABLE_NAME],
    });
    let snippetsTable = await storage.getDbTable(SNIPPETS_TABLE_NAME);
    for (let key in snippetsData) {
      let value = snippetsData[key];
      await snippetsTable.set(key, value);
    }
  }

  async measure(profilePath = PathUtils.profileDir) {
    let fullSize = 0;

    for (let filePath of FILES_FOR_BACKUP) {
      let resourcePath = PathUtils.join(profilePath, filePath);
      let resourceSize = await BackupResource.getFileSize(resourcePath);
      if (Number.isInteger(resourceSize)) {
        fullSize += resourceSize;
      }
    }

    let chromeIndexedDBDirPath = PathUtils.join(
      profilePath,
      "storage",
      "permanent",
      "chrome"
    );
    let chromeIndexedDBDirSize = await BackupResource.getDirectorySize(
      chromeIndexedDBDirPath
    );
    if (Number.isInteger(chromeIndexedDBDirSize)) {
      fullSize += chromeIndexedDBDirSize;
    }

    Glean.browserBackup.miscDataSize.set(fullSize);
  }
}