tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PlacesBrowserStartup.sys.mjs (13934B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      6 
      7 const lazy = {};
      8 ChromeUtils.defineESModuleGetters(lazy, {
      9  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
     10  BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.sys.mjs",
     11  BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.sys.mjs",
     12  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
     13  DistributionManagement: "resource:///modules/distribution.sys.mjs",
     14  PlacesBackups: "resource://gre/modules/PlacesBackups.sys.mjs",
     15  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
     16  PlacesUIUtils: "moz-src:///browser/components/places/PlacesUIUtils.sys.mjs",
     17 });
     18 
     19 XPCOMUtils.defineLazyServiceGetters(lazy, {
     20  BrowserHandler: ["@mozilla.org/browser/clh;1", Ci.nsIBrowserHandler],
     21  UserIdleService: [
     22    "@mozilla.org/widget/useridleservice;1",
     23    Ci.nsIUserIdleService,
     24  ],
     25 });
     26 
     27 // Seconds of idle before trying to create a bookmarks backup.
     28 const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
     29 // Minimum interval between backups.  We try to not create more than one backup
     30 // per interval.
     31 const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
     32 
     33 export let PlacesBrowserStartup = {
     34  _migrationImportsDefaultBookmarks: false,
     35  _placesInitialized: false,
     36  _placesBrowserInitComplete: false,
     37  _isObservingIdle: false,
     38  _bookmarksBackupIdleTime: null,
     39  _firstWindowReady: Promise.withResolvers(),
     40 
     41  onFirstWindowReady(window) {
     42    this._firstWindowReady.resolve();
     43    // Set the default favicon size for UI views that use the page-icon protocol.
     44    lazy.PlacesUtils.favicons.setDefaultIconURIPreferredSize(
     45      16 * window.devicePixelRatio
     46    );
     47  },
     48 
     49  backendInitComplete() {
     50    if (!this._migrationImportsDefaultBookmarks) {
     51      this.initPlaces();
     52    }
     53  },
     54 
     55  willImportDefaultBookmarks() {
     56    this._migrationImportsDefaultBookmarks = true;
     57  },
     58 
     59  didImportDefaultBookmarks() {
     60    this.initPlaces({ initialMigrationPerformed: true });
     61  },
     62 
     63  /**
     64   * Initialize Places
     65   * - imports the bookmarks html file if bookmarks database is empty, try to
     66   *   restore bookmarks from a JSON backup if the backend indicates that the
     67   *   database was corrupt.
     68   *
     69   * These prefs can be set up by the frontend:
     70   *
     71   * WARNING: setting these preferences to true will overwite existing bookmarks
     72   *
     73   * - browser.places.importBookmarksHTML
     74   *   Set to true will import the bookmarks.html file from the profile folder.
     75   * - browser.bookmarks.restore_default_bookmarks
     76   *   Set to true by safe-mode dialog to indicate we must restore default
     77   *   bookmarks.
     78   *
     79   * @param {object} [options={}]
     80   * @param {boolean} [options.initialMigrationPerformed=false]
     81   *   Whether we performed an initial migration from another browser or via
     82   *   Firefox Refresh.
     83   */
     84  initPlaces({ initialMigrationPerformed = false } = {}) {
     85    if (this._placesInitialized) {
     86      throw new Error("Cannot initialize Places more than once");
     87    }
     88    this._placesInitialized = true;
     89 
     90    // We must instantiate the history service since it will tell us if we
     91    // need to import or restore bookmarks due to first-run, corruption or
     92    // forced migration (due to a major schema change).
     93    // If the database is corrupt or has been newly created we should
     94    // import bookmarks.
     95    let dbStatus = lazy.PlacesUtils.history.databaseStatus;
     96 
     97    // Show a notification with a "more info" link for a locked places.sqlite.
     98    if (dbStatus == lazy.PlacesUtils.history.DATABASE_STATUS_LOCKED) {
     99      // Note: initPlaces should always happen when the first window is ready,
    100      // in any case, better safe than sorry.
    101      this._firstWindowReady.promise.then(() => {
    102        this._showPlacesLockedNotificationBox();
    103        this._placesBrowserInitComplete = true;
    104        Services.obs.notifyObservers(null, "places-browser-init-complete");
    105      });
    106      return;
    107    }
    108 
    109    let importBookmarks =
    110      !initialMigrationPerformed &&
    111      (dbStatus == lazy.PlacesUtils.history.DATABASE_STATUS_CREATE ||
    112        dbStatus == lazy.PlacesUtils.history.DATABASE_STATUS_CORRUPT);
    113 
    114    // Check if user or an extension has required to import bookmarks.html
    115    let importBookmarksHTML = false;
    116    try {
    117      importBookmarksHTML = Services.prefs.getBoolPref(
    118        "browser.places.importBookmarksHTML"
    119      );
    120      if (importBookmarksHTML) {
    121        importBookmarks = true;
    122      }
    123    } catch (ex) {}
    124 
    125    // Support legacy bookmarks.html format for apps that depend on that format.
    126    let autoExportHTML = Services.prefs.getBoolPref(
    127      "browser.bookmarks.autoExportHTML",
    128      false
    129    ); // Do not export.
    130    if (autoExportHTML) {
    131      // Sqlite.sys.mjs and Places shutdown happen at profile-before-change, thus,
    132      // to be on the safe side, this should run earlier.
    133      lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
    134        "Places: export bookmarks.html",
    135        () =>
    136          lazy.BookmarkHTMLUtils.exportToFile(
    137            lazy.BookmarkHTMLUtils.defaultPath
    138          )
    139      );
    140    }
    141 
    142    (async () => {
    143      // Check if Safe Mode or the user has required to restore bookmarks from
    144      // default profile's bookmarks.html
    145      let restoreDefaultBookmarks = false;
    146      try {
    147        restoreDefaultBookmarks = Services.prefs.getBoolPref(
    148          "browser.bookmarks.restore_default_bookmarks"
    149        );
    150        if (restoreDefaultBookmarks) {
    151          // Ensure that we already have a bookmarks backup for today.
    152          await this._backupBookmarks();
    153          importBookmarks = true;
    154        }
    155      } catch (ex) {}
    156 
    157      // If the user did not require to restore default bookmarks, or import
    158      // from bookmarks.html, we will try to restore from JSON
    159      if (importBookmarks && !restoreDefaultBookmarks && !importBookmarksHTML) {
    160        // get latest JSON backup
    161        let lastBackupFile = await lazy.PlacesBackups.getMostRecentBackup();
    162        if (lastBackupFile) {
    163          // restore from JSON backup
    164          await lazy.BookmarkJSONUtils.importFromFile(lastBackupFile, {
    165            replace: true,
    166            source: lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
    167          });
    168          importBookmarks = false;
    169        } else {
    170          // We have created a new database but we don't have any backup available
    171          importBookmarks = true;
    172          if (await IOUtils.exists(lazy.BookmarkHTMLUtils.defaultPath)) {
    173            // If bookmarks.html is available in current profile import it...
    174            importBookmarksHTML = true;
    175          } else {
    176            // ...otherwise we will restore defaults
    177            restoreDefaultBookmarks = true;
    178          }
    179        }
    180      }
    181 
    182      // Import default bookmarks when necessary.
    183      // Otherwise, if any kind of import runs, default bookmarks creation should be
    184      // delayed till the import operations has finished.  Not doing so would
    185      // cause them to be overwritten by the newly imported bookmarks.
    186      if (!importBookmarks) {
    187        // Now apply distribution customized bookmarks.
    188        // This should always run after Places initialization.
    189        try {
    190          await lazy.DistributionManagement.applyBookmarks();
    191        } catch (e) {
    192          console.error(e);
    193        }
    194      } else {
    195        // An import operation is about to run.
    196        let bookmarksUrl = null;
    197        if (restoreDefaultBookmarks) {
    198          // User wants to restore the default set of bookmarks shipped with the
    199          // browser, those that new profiles start with.
    200          bookmarksUrl = "chrome://browser/content/default-bookmarks.html";
    201        } else if (await IOUtils.exists(lazy.BookmarkHTMLUtils.defaultPath)) {
    202          bookmarksUrl = PathUtils.toFileURI(
    203            lazy.BookmarkHTMLUtils.defaultPath
    204          );
    205        }
    206 
    207        if (bookmarksUrl) {
    208          // Import from bookmarks.html file.
    209          try {
    210            if (
    211              Services.policies.isAllowed("defaultBookmarks") &&
    212              // Default bookmarks are imported after startup, and they may
    213              // influence the outcome of tests, thus it's possible to use
    214              // this test-only pref to skip the import.
    215              !(
    216                Cu.isInAutomation &&
    217                Services.prefs.getBoolPref(
    218                  "browser.bookmarks.testing.skipDefaultBookmarksImport",
    219                  false
    220                )
    221              )
    222            ) {
    223              await lazy.BookmarkHTMLUtils.importFromURL(bookmarksUrl, {
    224                replace: true,
    225                source: lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
    226              });
    227            }
    228          } catch (e) {
    229            console.error("Bookmarks.html file could be corrupt. ", e);
    230          }
    231          try {
    232            // Now apply distribution customized bookmarks.
    233            // This should always run after Places initialization.
    234            await lazy.DistributionManagement.applyBookmarks();
    235          } catch (e) {
    236            console.error(e);
    237          }
    238        } else {
    239          console.error(new Error("Unable to find bookmarks.html file."));
    240        }
    241 
    242        // Reset preferences, so we won't try to import again at next run
    243        if (importBookmarksHTML) {
    244          Services.prefs.setBoolPref(
    245            "browser.places.importBookmarksHTML",
    246            false
    247          );
    248        }
    249        if (restoreDefaultBookmarks) {
    250          Services.prefs.setBoolPref(
    251            "browser.bookmarks.restore_default_bookmarks",
    252            false
    253          );
    254        }
    255      }
    256 
    257      // Initialize bookmark archiving on idle.
    258      // If the last backup has been created before the last browser session,
    259      // and is days old, be more aggressive with the idle timer.
    260      let idleTime = BOOKMARKS_BACKUP_IDLE_TIME_SEC;
    261      if (!(await lazy.PlacesBackups.hasRecentBackup())) {
    262        idleTime /= 2;
    263      }
    264 
    265      if (!this._isObservingIdle) {
    266        lazy.UserIdleService.addIdleObserver(this._backupBookmarks, idleTime);
    267        Services.obs.addObserver(this, "profile-before-change");
    268        this._isObservingIdle = true;
    269      }
    270 
    271      this._bookmarksBackupIdleTime = idleTime;
    272 
    273      if (this._isNewProfile) {
    274        // New profiles may have existing bookmarks (imported from another browser or
    275        // copied into the profile) and we want to show the bookmark toolbar for them
    276        // in some cases.
    277        await lazy.PlacesUIUtils.maybeToggleBookmarkToolbarVisibility();
    278      }
    279    })()
    280      .catch(ex => {
    281        console.error(ex);
    282      })
    283      .then(() => {
    284        // NB: deliberately after the catch so that we always do this, even if
    285        // we threw halfway through initializing in the Task above.
    286        this._placesBrowserInitComplete = true;
    287        Services.obs.notifyObservers(null, "places-browser-init-complete");
    288      });
    289  },
    290 
    291  /**
    292   * If a backup for today doesn't exist, this creates one.
    293   */
    294  async _backupBookmarks() {
    295    let lastBackupFile = await lazy.PlacesBackups.getMostRecentBackup();
    296    // Should backup bookmarks if there are no backups or the maximum
    297    // interval between backups elapsed.
    298    if (
    299      !lastBackupFile ||
    300      Date.now() - lazy.PlacesBackups.getDateForFile(lastBackupFile).getTime() >
    301        BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS * 86400000
    302    ) {
    303      let maxBackups = Services.prefs.getIntPref(
    304        "browser.bookmarks.max_backups"
    305      );
    306      await lazy.PlacesBackups.create(maxBackups);
    307    }
    308  },
    309 
    310  /**
    311   * Show the notificationBox for a locked places database.
    312   */
    313  async _showPlacesLockedNotificationBox() {
    314    var win = lazy.BrowserWindowTracker.getTopWindow({
    315      allowFromInactiveWorkspace: true,
    316    });
    317    var buttons = [{ supportPage: "places-locked" }];
    318 
    319    var notifyBox = win.gBrowser.getNotificationBox();
    320    var notification = await notifyBox.appendNotification(
    321      "places-locked",
    322      {
    323        label: { "l10n-id": "places-locked-prompt" },
    324        priority: win.gNotificationBox.PRIORITY_CRITICAL_MEDIUM,
    325      },
    326      buttons
    327    );
    328    notification.persistence = -1; // Until user closes it
    329  },
    330 
    331  notifyIfInitializationComplete() {
    332    if (this._placesBrowserInitComplete) {
    333      Services.obs.notifyObservers(null, "places-browser-init-complete");
    334    }
    335  },
    336 
    337  async maybeAddImportButton() {
    338    // First check if we've already added the import button, in which
    339    // case we should check for events indicating we can remove it.
    340    if (
    341      Services.prefs.getBoolPref("browser.bookmarks.addedImportButton", false)
    342    ) {
    343      lazy.PlacesUIUtils.removeImportButtonWhenImportSucceeds();
    344      return;
    345    }
    346 
    347    // Otherwise, check if this is a new profile where we need to add it.
    348    // `maybeAddImportButton` will call
    349    // `removeImportButtonWhenImportSucceeds`itself if/when it adds the
    350    // button. Doing things in this order avoids listening for removal
    351    // more than once.
    352    if (
    353      lazy.BrowserHandler.firstRunProfile &&
    354      // Not in automation: the button changes CUI state, breaking tests
    355      !Cu.isInAutomation
    356    ) {
    357      await lazy.PlacesUIUtils.maybeAddImportButton();
    358    }
    359  },
    360 
    361  handleShutdown() {
    362    if (this._bookmarksBackupIdleTime) {
    363      lazy.UserIdleService.removeIdleObserver(
    364        this._backupBookmarks,
    365        this._bookmarksBackupIdleTime
    366      );
    367      this._bookmarksBackupIdleTime = null;
    368    }
    369  },
    370 
    371  observe(subject, topic, _data) {
    372    switch (topic) {
    373      case "profile-before-change":
    374        this.handleShutdown();
    375        break;
    376    }
    377  },
    378 };
    379 
    380 PlacesBrowserStartup._backupBookmarks =
    381  PlacesBrowserStartup._backupBookmarks.bind(PlacesBrowserStartup);
    382 
    383 PlacesBrowserStartup.QueryInterface = ChromeUtils.generateQI([Ci.nsIObserver]);