forked from mirrors/gecko-dev
		
	MozReview-Commit-ID: EEVSPkAgORY --HG-- extra : rebase_source : 8a3da61bdf0f65c518e6cea3c6f5cacf42c6d24d
		
			
				
	
	
		
			394 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			394 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* vim: set sts=2 sw=2 et tw=80: */
 | 
						|
"use strict";
 | 
						|
 | 
						|
// The ext-* files are imported into the same scopes.
 | 
						|
/* import-globals-from ext-browserAction.js */
 | 
						|
 | 
						|
Cu.import("resource://gre/modules/PlacesUtils.jsm");
 | 
						|
 | 
						|
const {
 | 
						|
  TYPE_BOOKMARK,
 | 
						|
  TYPE_FOLDER,
 | 
						|
  TYPE_SEPARATOR,
 | 
						|
} = PlacesUtils.bookmarks;
 | 
						|
 | 
						|
const BOOKMARKS_TYPES_TO_API_TYPES_MAP = new Map([
 | 
						|
  [TYPE_BOOKMARK, "bookmark"],
 | 
						|
  [TYPE_FOLDER, "folder"],
 | 
						|
  [TYPE_SEPARATOR, "separator"],
 | 
						|
]);
 | 
						|
 | 
						|
const BOOKMARK_SEPERATOR_URL = "data:";
 | 
						|
 | 
						|
XPCOMUtils.defineLazyGetter(this, "API_TYPES_TO_BOOKMARKS_TYPES_MAP", () => {
 | 
						|
  let theMap = new Map();
 | 
						|
 | 
						|
  for (let [code, name] of BOOKMARKS_TYPES_TO_API_TYPES_MAP) {
 | 
						|
    theMap.set(name, code);
 | 
						|
  }
 | 
						|
  return theMap;
 | 
						|
});
 | 
						|
 | 
						|
let listenerCount = 0;
 | 
						|
 | 
						|
function getUrl(type, url) {
 | 
						|
  switch (type) {
 | 
						|
    case TYPE_BOOKMARK:
 | 
						|
      return url;
 | 
						|
    case TYPE_SEPARATOR:
 | 
						|
      return BOOKMARK_SEPERATOR_URL;
 | 
						|
    default:
 | 
						|
      return undefined;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const getTree = (rootGuid, onlyChildren) => {
 | 
						|
  function convert(node, parent) {
 | 
						|
    let treenode = {
 | 
						|
      id: node.guid,
 | 
						|
      title: node.title || "",
 | 
						|
      index: node.index,
 | 
						|
      dateAdded: node.dateAdded / 1000,
 | 
						|
      type: BOOKMARKS_TYPES_TO_API_TYPES_MAP.get(node.typeCode),
 | 
						|
      url: getUrl(node.typeCode, node.uri),
 | 
						|
    };
 | 
						|
 | 
						|
    if (parent && node.guid != PlacesUtils.bookmarks.rootGuid) {
 | 
						|
      treenode.parentId = parent.guid;
 | 
						|
    }
 | 
						|
 | 
						|
    if (node.typeCode == TYPE_FOLDER) {
 | 
						|
      treenode.dateGroupModified = node.lastModified / 1000;
 | 
						|
 | 
						|
      if (!onlyChildren) {
 | 
						|
        treenode.children = node.children
 | 
						|
          ? node.children.map(child => convert(child, node))
 | 
						|
          : [];
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return treenode;
 | 
						|
  }
 | 
						|
 | 
						|
  return PlacesUtils.promiseBookmarksTree(rootGuid, {
 | 
						|
    excludeItemsCallback: item => {
 | 
						|
      return item.annos &&
 | 
						|
             item.annos.find(a => a.name == PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);
 | 
						|
    },
 | 
						|
  }).then(root => {
 | 
						|
    if (onlyChildren) {
 | 
						|
      let children = root.children || [];
 | 
						|
      return children.map(child => convert(child, root));
 | 
						|
    }
 | 
						|
    let treenode = convert(root, null);
 | 
						|
    treenode.parentId = root.parentGuid;
 | 
						|
    // It seems like the array always just contains the root node.
 | 
						|
    return [treenode];
 | 
						|
  }).catch(e => Promise.reject({message: e.message}));
 | 
						|
};
 | 
						|
 | 
						|
const convertBookmarks = result => {
 | 
						|
  let node = {
 | 
						|
    id: result.guid,
 | 
						|
    title: result.title || "",
 | 
						|
    index: result.index,
 | 
						|
    dateAdded: result.dateAdded.getTime(),
 | 
						|
    type: BOOKMARKS_TYPES_TO_API_TYPES_MAP.get(result.type),
 | 
						|
    url: getUrl(result.type, result.url && result.url.href),
 | 
						|
  };
 | 
						|
 | 
						|
  if (result.guid != PlacesUtils.bookmarks.rootGuid) {
 | 
						|
    node.parentId = result.parentGuid;
 | 
						|
  }
 | 
						|
 | 
						|
  if (result.type == TYPE_FOLDER) {
 | 
						|
    node.dateGroupModified = result.lastModified.getTime();
 | 
						|
  }
 | 
						|
 | 
						|
  return node;
 | 
						|
};
 | 
						|
 | 
						|
let observer = new class extends EventEmitter {
 | 
						|
  constructor() {
 | 
						|
    super();
 | 
						|
 | 
						|
    this.skipTags = true;
 | 
						|
    this.skipDescendantsOnItemRemoval = true;
 | 
						|
  }
 | 
						|
 | 
						|
  onBeginUpdateBatch() {}
 | 
						|
  onEndUpdateBatch() {}
 | 
						|
 | 
						|
  onItemAdded(id, parentId, index, itemType, uri, title, dateAdded, guid, parentGuid, source) {
 | 
						|
    let bookmark = {
 | 
						|
      id: guid,
 | 
						|
      parentId: parentGuid,
 | 
						|
      index,
 | 
						|
      title,
 | 
						|
      dateAdded: dateAdded / 1000,
 | 
						|
      type: BOOKMARKS_TYPES_TO_API_TYPES_MAP.get(itemType),
 | 
						|
      url: getUrl(itemType, uri && uri.spec),
 | 
						|
    };
 | 
						|
 | 
						|
    if (itemType == TYPE_FOLDER) {
 | 
						|
      bookmark.dateGroupModified = bookmark.dateAdded;
 | 
						|
    }
 | 
						|
 | 
						|
    this.emit("created", bookmark);
 | 
						|
  }
 | 
						|
 | 
						|
  onItemVisited() {}
 | 
						|
 | 
						|
  onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, itemType, guid, oldParentGuid, newParentGuid, source) {
 | 
						|
    let info = {
 | 
						|
      parentId: newParentGuid,
 | 
						|
      index: newIndex,
 | 
						|
      oldParentId: oldParentGuid,
 | 
						|
      oldIndex,
 | 
						|
    };
 | 
						|
    this.emit("moved", {guid, info});
 | 
						|
  }
 | 
						|
 | 
						|
  onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid, source) {
 | 
						|
    let node = {
 | 
						|
      id: guid,
 | 
						|
      parentId: parentGuid,
 | 
						|
      index,
 | 
						|
      type: BOOKMARKS_TYPES_TO_API_TYPES_MAP.get(itemType),
 | 
						|
      url: getUrl(itemType, uri && uri.spec),
 | 
						|
    };
 | 
						|
 | 
						|
    this.emit("removed", {guid, info: {parentId: parentGuid, index, node}});
 | 
						|
  }
 | 
						|
 | 
						|
  onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid, oldVal, source) {
 | 
						|
    let info = {};
 | 
						|
    if (prop == "title") {
 | 
						|
      info.title = val;
 | 
						|
    } else if (prop == "uri") {
 | 
						|
      info.url = val;
 | 
						|
    } else {
 | 
						|
      // Not defined yet.
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    this.emit("changed", {guid, info});
 | 
						|
  }
 | 
						|
}();
 | 
						|
 | 
						|
const decrementListeners = () => {
 | 
						|
  listenerCount -= 1;
 | 
						|
  if (!listenerCount) {
 | 
						|
    PlacesUtils.bookmarks.removeObserver(observer);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const incrementListeners = () => {
 | 
						|
  listenerCount++;
 | 
						|
  if (listenerCount == 1) {
 | 
						|
    PlacesUtils.bookmarks.addObserver(observer);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
this.bookmarks = class extends ExtensionAPI {
 | 
						|
  getAPI(context) {
 | 
						|
    return {
 | 
						|
      bookmarks: {
 | 
						|
        async get(idOrIdList) {
 | 
						|
          let list = Array.isArray(idOrIdList) ? idOrIdList : [idOrIdList];
 | 
						|
 | 
						|
          try {
 | 
						|
            let bookmarks = [];
 | 
						|
            for (let id of list) {
 | 
						|
              let bookmark = await PlacesUtils.bookmarks.fetch({guid: id});
 | 
						|
              if (!bookmark) {
 | 
						|
                throw new Error("Bookmark not found");
 | 
						|
              }
 | 
						|
              bookmarks.push(convertBookmarks(bookmark));
 | 
						|
            }
 | 
						|
            return bookmarks;
 | 
						|
          } catch (error) {
 | 
						|
            return Promise.reject({message: error.message});
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        getChildren: function(id) {
 | 
						|
          // TODO: We should optimize this.
 | 
						|
          return getTree(id, true);
 | 
						|
        },
 | 
						|
 | 
						|
        getTree: function() {
 | 
						|
          return getTree(PlacesUtils.bookmarks.rootGuid, false);
 | 
						|
        },
 | 
						|
 | 
						|
        getSubTree: function(id) {
 | 
						|
          return getTree(id, false);
 | 
						|
        },
 | 
						|
 | 
						|
        search: function(query) {
 | 
						|
          return PlacesUtils.bookmarks.search(query).then(result => result.map(convertBookmarks));
 | 
						|
        },
 | 
						|
 | 
						|
        getRecent: function(numberOfItems) {
 | 
						|
          return PlacesUtils.bookmarks.getRecent(numberOfItems).then(result => result.map(convertBookmarks));
 | 
						|
        },
 | 
						|
 | 
						|
        create: function(bookmark) {
 | 
						|
          let info = {
 | 
						|
            title: bookmark.title || "",
 | 
						|
          };
 | 
						|
 | 
						|
          info.type = API_TYPES_TO_BOOKMARKS_TYPES_MAP.get(bookmark.type);
 | 
						|
          if (!info.type) {
 | 
						|
            // If url is NULL or missing, it will be a folder.
 | 
						|
            if (bookmark.url !== null) {
 | 
						|
              info.type = TYPE_BOOKMARK;
 | 
						|
            } else {
 | 
						|
              info.type = TYPE_FOLDER;
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          if (info.type === TYPE_BOOKMARK) {
 | 
						|
            info.url = bookmark.url || "";
 | 
						|
          }
 | 
						|
 | 
						|
          if (bookmark.index !== null) {
 | 
						|
            info.index = bookmark.index;
 | 
						|
          }
 | 
						|
 | 
						|
          if (bookmark.parentId !== null) {
 | 
						|
            info.parentGuid = bookmark.parentId;
 | 
						|
          } else {
 | 
						|
            info.parentGuid = PlacesUtils.bookmarks.unfiledGuid;
 | 
						|
          }
 | 
						|
 | 
						|
          try {
 | 
						|
            return PlacesUtils.bookmarks.insert(info).then(convertBookmarks)
 | 
						|
              .catch(error => Promise.reject({message: error.message}));
 | 
						|
          } catch (e) {
 | 
						|
            return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        move: function(id, destination) {
 | 
						|
          let info = {
 | 
						|
            guid: id,
 | 
						|
          };
 | 
						|
 | 
						|
          if (destination.parentId !== null) {
 | 
						|
            info.parentGuid = destination.parentId;
 | 
						|
          }
 | 
						|
          info.index = (destination.index === null) ?
 | 
						|
            PlacesUtils.bookmarks.DEFAULT_INDEX : destination.index;
 | 
						|
 | 
						|
          try {
 | 
						|
            return PlacesUtils.bookmarks.update(info).then(convertBookmarks)
 | 
						|
              .catch(error => Promise.reject({message: error.message}));
 | 
						|
          } catch (e) {
 | 
						|
            return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        update: function(id, changes) {
 | 
						|
          let info = {
 | 
						|
            guid: id,
 | 
						|
          };
 | 
						|
 | 
						|
          if (changes.title !== null) {
 | 
						|
            info.title = changes.title;
 | 
						|
          }
 | 
						|
          if (changes.url !== null) {
 | 
						|
            info.url = changes.url;
 | 
						|
          }
 | 
						|
 | 
						|
          try {
 | 
						|
            return PlacesUtils.bookmarks.update(info).then(convertBookmarks)
 | 
						|
              .catch(error => Promise.reject({message: error.message}));
 | 
						|
          } catch (e) {
 | 
						|
            return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        remove: function(id) {
 | 
						|
          let info = {
 | 
						|
            guid: id,
 | 
						|
          };
 | 
						|
 | 
						|
          // The API doesn't give you the old bookmark at the moment
 | 
						|
          try {
 | 
						|
            return PlacesUtils.bookmarks.remove(info, {preventRemovalOfNonEmptyFolders: true})
 | 
						|
              .catch(error => Promise.reject({message: error.message}));
 | 
						|
          } catch (e) {
 | 
						|
            return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        removeTree: function(id) {
 | 
						|
          let info = {
 | 
						|
            guid: id,
 | 
						|
          };
 | 
						|
 | 
						|
          try {
 | 
						|
            return PlacesUtils.bookmarks.remove(info)
 | 
						|
              .catch(error => Promise.reject({message: error.message}));
 | 
						|
          } catch (e) {
 | 
						|
            return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        onCreated: new EventManager(context, "bookmarks.onCreated", fire => {
 | 
						|
          let listener = (event, bookmark) => {
 | 
						|
            fire.sync(bookmark.id, bookmark);
 | 
						|
          };
 | 
						|
 | 
						|
          observer.on("created", listener);
 | 
						|
          incrementListeners();
 | 
						|
          return () => {
 | 
						|
            observer.off("created", listener);
 | 
						|
            decrementListeners();
 | 
						|
          };
 | 
						|
        }).api(),
 | 
						|
 | 
						|
        onRemoved: new EventManager(context, "bookmarks.onRemoved", fire => {
 | 
						|
          let listener = (event, data) => {
 | 
						|
            fire.sync(data.guid, data.info);
 | 
						|
          };
 | 
						|
 | 
						|
          observer.on("removed", listener);
 | 
						|
          incrementListeners();
 | 
						|
          return () => {
 | 
						|
            observer.off("removed", listener);
 | 
						|
            decrementListeners();
 | 
						|
          };
 | 
						|
        }).api(),
 | 
						|
 | 
						|
        onChanged: new EventManager(context, "bookmarks.onChanged", fire => {
 | 
						|
          let listener = (event, data) => {
 | 
						|
            fire.sync(data.guid, data.info);
 | 
						|
          };
 | 
						|
 | 
						|
          observer.on("changed", listener);
 | 
						|
          incrementListeners();
 | 
						|
          return () => {
 | 
						|
            observer.off("changed", listener);
 | 
						|
            decrementListeners();
 | 
						|
          };
 | 
						|
        }).api(),
 | 
						|
 | 
						|
        onMoved: new EventManager(context, "bookmarks.onMoved", fire => {
 | 
						|
          let listener = (event, data) => {
 | 
						|
            fire.sync(data.guid, data.info);
 | 
						|
          };
 | 
						|
 | 
						|
          observer.on("moved", listener);
 | 
						|
          incrementListeners();
 | 
						|
          return () => {
 | 
						|
            observer.off("moved", listener);
 | 
						|
            decrementListeners();
 | 
						|
          };
 | 
						|
        }).api(),
 | 
						|
      },
 | 
						|
    };
 | 
						|
  }
 | 
						|
};
 |