tor-browser

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

BookmarksPolicies.sys.mjs (8710B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /*
      6 * A Bookmark object received through the policy engine will be an
      7 * object with the following properties:
      8 *
      9 * - URL (URL)
     10 *   (required) The URL for this bookmark
     11 *
     12 * - Title (string)
     13 *   (required) The title for this bookmark
     14 *
     15 * - Placement (string)
     16 *   (optional) Either "toolbar" or "menu". If missing or invalid,
     17 *   "toolbar" will be used
     18 *
     19 * - Folder (string)
     20 *   (optional) The name of the folder to put this bookmark into.
     21 *   If present, a folder with this name will be created in the
     22 *   chosen placement above, and the bookmark will be created there.
     23 *   If missing, the bookmark will be created directly into the
     24 *   chosen placement.
     25 *
     26 * - Favicon (URL)
     27 *   (optional) An http:, https: or data: URL with the favicon.
     28 *   If possible, we recommend against using this property, in order
     29 *   to keep the json file small.
     30 *   If a favicon is not provided through the policy, it will be loaded
     31 *   naturally after the user first visits the bookmark.
     32 *
     33 *
     34 * Note: The Policy Engine automatically converts the strings given to
     35 * the URL and favicon properties into a URL object.
     36 *
     37 * The schema for this object is defined in policies-schema.json.
     38 */
     39 
     40 const lazy = {};
     41 
     42 ChromeUtils.defineESModuleGetters(lazy, {
     43  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
     44 });
     45 
     46 const PREF_LOGLEVEL = "browser.policies.loglevel";
     47 
     48 ChromeUtils.defineLazyGetter(lazy, "log", () => {
     49  let { ConsoleAPI } = ChromeUtils.importESModule(
     50    "resource://gre/modules/Console.sys.mjs"
     51  );
     52  return new ConsoleAPI({
     53    prefix: "BookmarksPolicies",
     54    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
     55    // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
     56    maxLogLevel: "error",
     57    maxLogLevelPref: PREF_LOGLEVEL,
     58  });
     59 });
     60 
     61 export const BookmarksPolicies = {
     62  // These prefixes must only contain characters
     63  // allowed by PlacesUtils.isValidGuid
     64  BOOKMARK_GUID_PREFIX: "PolB-",
     65  FOLDER_GUID_PREFIX: "PolF-",
     66 
     67  /**
     68   * Process the bookmarks specified by the policy engine.
     69   *
     70   * @param {object[]} param
     71   *        This will be an array of bookmarks objects, as
     72   *        described on the top of this file.
     73   */
     74  processBookmarks(param) {
     75    calculateLists(param).then(async function addRemoveBookmarks(results) {
     76      for (let bookmark of results.add.values()) {
     77        await insertBookmark(bookmark).catch(lazy.log.error);
     78      }
     79      for (let bookmark of results.remove.values()) {
     80        await lazy.PlacesUtils.bookmarks.remove(bookmark).catch(lazy.log.error);
     81      }
     82      for (let bookmark of results.emptyFolders.values()) {
     83        await lazy.PlacesUtils.bookmarks.remove(bookmark).catch(lazy.log.error);
     84      }
     85 
     86      lazy.gFoldersMapPromise.then(map => map.clear());
     87    });
     88  },
     89 };
     90 
     91 /**
     92 * This function calculates the differences between the existing bookmarks
     93 * that are managed by the policy engine (which are known through a guid
     94 * prefix) and the specified bookmarks in the policy file.
     95 * They can differ if the policy file has changed.
     96 *
     97 * @param {object[]} specifiedBookmarks
     98 *        This will be an array of bookmarks objects, as
     99 *        described on the top of this file.
    100 */
    101 async function calculateLists(specifiedBookmarks) {
    102  // --------- STEP 1 ---------
    103  // Build two Maps (one with the existing bookmarks, another with
    104  // the specified bookmarks), to make iteration quicker.
    105 
    106  // LIST A
    107  // MAP of url (string) -> bookmarks objects from the Policy Engine
    108  let specifiedBookmarksMap = new Map();
    109  for (let bookmark of specifiedBookmarks) {
    110    specifiedBookmarksMap.set(bookmark.URL.href, bookmark);
    111  }
    112 
    113  // LIST B
    114  // MAP of url (string) -> bookmarks objects from Places
    115  let existingBookmarksMap = new Map();
    116  await lazy.PlacesUtils.bookmarks.fetch(
    117    { guidPrefix: BookmarksPolicies.BOOKMARK_GUID_PREFIX },
    118    bookmark => existingBookmarksMap.set(bookmark.url.href, bookmark)
    119  );
    120 
    121  // --------- STEP 2 ---------
    122  //
    123  //     /=====/====\=====\
    124  //    /     /      \     \
    125  //    |     |      |     |
    126  //    |  A  |  {}  |  B  |
    127  //    |     |      |     |
    128  //    \     \      /     /
    129  //     \=====\====/=====/
    130  //
    131  // Find the intersection of the two lists. Items in the intersection
    132  // are removed from the original lists.
    133  //
    134  // The items remaining in list A are new bookmarks to be added.
    135  // The items remaining in list B are old bookmarks to be removed.
    136  //
    137  // There's nothing to do with items in the intersection, so there's no
    138  // need to keep track of them.
    139  //
    140  // BONUS: It's necessary to keep track of the folder names that were
    141  // seen, to make sure we remove the ones that were left empty.
    142 
    143  let foldersSeen = new Set();
    144 
    145  for (let [url, item] of specifiedBookmarksMap) {
    146    foldersSeen.add(item.Folder);
    147 
    148    if (existingBookmarksMap.has(url)) {
    149      lazy.log.debug(`Bookmark intersection: ${url}`);
    150      // If this specified bookmark exists in the existing bookmarks list,
    151      // we can remove it from both lists as it's in the intersection.
    152      specifiedBookmarksMap.delete(url);
    153      existingBookmarksMap.delete(url);
    154    }
    155  }
    156 
    157  for (let url of specifiedBookmarksMap.keys()) {
    158    lazy.log.debug(`Bookmark to add: ${url}`);
    159  }
    160 
    161  for (let url of existingBookmarksMap.keys()) {
    162    lazy.log.debug(`Bookmark to remove: ${url}`);
    163  }
    164 
    165  // SET of folders to be deleted (bookmarks object from Places)
    166  let foldersToRemove = new Set();
    167 
    168  // If no bookmarks will be deleted, then no folder will
    169  // need to be deleted either, so this next section can be skipped.
    170  if (existingBookmarksMap.size > 0) {
    171    await lazy.PlacesUtils.bookmarks.fetch(
    172      { guidPrefix: BookmarksPolicies.FOLDER_GUID_PREFIX },
    173      folder => {
    174        if (!foldersSeen.has(folder.title)) {
    175          lazy.log.debug(`Folder to remove: ${folder.title}`);
    176          foldersToRemove.add(folder);
    177        }
    178      }
    179    );
    180  }
    181 
    182  return {
    183    add: specifiedBookmarksMap,
    184    remove: existingBookmarksMap,
    185    emptyFolders: foldersToRemove,
    186  };
    187 }
    188 
    189 async function insertBookmark(bookmark) {
    190  let parentGuid = await getParentGuid(bookmark.Placement, bookmark.Folder);
    191 
    192  await lazy.PlacesUtils.bookmarks.insert({
    193    url: bookmark.URL.URI,
    194    title: bookmark.Title,
    195    guid: lazy.PlacesUtils.generateGuidWithPrefix(
    196      BookmarksPolicies.BOOKMARK_GUID_PREFIX
    197    ),
    198    parentGuid,
    199  });
    200 
    201  if (bookmark.Favicon) {
    202    setFaviconForBookmark(bookmark);
    203  }
    204 }
    205 
    206 function setFaviconForBookmark(bookmark) {
    207  if (bookmark.Favicon.protocol != "data:") {
    208    lazy.log.error(
    209      `Pass a valid data: URI for favicon on bookmark "${bookmark.Title}", instead of "${bookmark.Favicon.URI.spec}"`
    210    );
    211    return;
    212  }
    213 
    214  lazy.PlacesUtils.favicons
    215    .setFaviconForPage(
    216      bookmark.URL.URI,
    217      Services.io.newURI("fake-favicon-uri:" + bookmark.URL.href),
    218      bookmark.Favicon.URI
    219    )
    220    .catch(lazy.log.error);
    221 }
    222 
    223 // Cache of folder names to guids to be used by the getParentGuid
    224 // function. The name consists in the parentGuid (which should always
    225 // be the menuGuid or the toolbarGuid) + the folder title. This is to
    226 // support having the same folder name in both the toolbar and menu.
    227 ChromeUtils.defineLazyGetter(lazy, "gFoldersMapPromise", () => {
    228  return new Promise(resolve => {
    229    let foldersMap = new Map();
    230    return lazy.PlacesUtils.bookmarks
    231      .fetch(
    232        {
    233          guidPrefix: BookmarksPolicies.FOLDER_GUID_PREFIX,
    234        },
    235        result => {
    236          foldersMap.set(`${result.parentGuid}|${result.title}`, result.guid);
    237        }
    238      )
    239      .then(() => resolve(foldersMap));
    240  });
    241 });
    242 
    243 async function getParentGuid(placement, folderTitle) {
    244  // Defaults to toolbar if no placement was given.
    245  let parentGuid =
    246    placement == "menu"
    247      ? lazy.PlacesUtils.bookmarks.menuGuid
    248      : lazy.PlacesUtils.bookmarks.toolbarGuid;
    249 
    250  if (!folderTitle) {
    251    // If no folderTitle is given, this bookmark is to be placed directly
    252    // into the toolbar or menu.
    253    return parentGuid;
    254  }
    255 
    256  let foldersMap = await lazy.gFoldersMapPromise;
    257  let folderName = `${parentGuid}|${folderTitle}`;
    258 
    259  if (foldersMap.has(folderName)) {
    260    return foldersMap.get(folderName);
    261  }
    262 
    263  let guid = lazy.PlacesUtils.generateGuidWithPrefix(
    264    BookmarksPolicies.FOLDER_GUID_PREFIX
    265  );
    266  await lazy.PlacesUtils.bookmarks.insert({
    267    type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
    268    title: folderTitle,
    269    guid,
    270    parentGuid,
    271  });
    272 
    273  foldersMap.set(folderName, guid);
    274  return guid;
    275 }