utils.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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. 'use strict';
  5. module.metadata = {
  6. "stability": "experimental",
  7. "engines": {
  8. "Firefox": "*"
  9. }
  10. };
  11. const { Cc, Ci } = require('chrome');
  12. const { Class } = require('../core/heritage');
  13. const { method } = require('../lang/functional');
  14. const { defer, promised, all } = require('../core/promise');
  15. const { send } = require('../addon/events');
  16. const { EventTarget } = require('../event/target');
  17. const { merge } = require('../util/object');
  18. const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
  19. getService(Ci.nsINavBookmarksService);
  20. /*
  21. * TreeNodes are used to construct dependency trees
  22. * for BookmarkItems
  23. */
  24. let TreeNode = Class({
  25. initialize: function (value) {
  26. this.value = value;
  27. this.children = [];
  28. },
  29. add: function (values) {
  30. [].concat(values).forEach(value => {
  31. this.children.push(value instanceof TreeNode ? value : TreeNode(value));
  32. });
  33. },
  34. get length () {
  35. let count = 0;
  36. this.walk(() => count++);
  37. // Do not count the current node
  38. return --count;
  39. },
  40. get: method(get),
  41. walk: method(walk),
  42. toString: function () '[object TreeNode]'
  43. });
  44. exports.TreeNode = TreeNode;
  45. /*
  46. * Descends down from `node` applying `fn` to each in order.
  47. * Can be asynchronous if `fn` returns a promise. `fn` is passed
  48. * one argument, the current node, `curr`
  49. */
  50. function walk (curr, fn) {
  51. return promised(fn)(curr).then(val => {
  52. return all(curr.children.map(child => walk(child, fn)));
  53. });
  54. }
  55. /*
  56. * Descends from the TreeNode `node`, returning
  57. * the node with value `value` if found or `null`
  58. * otherwise
  59. */
  60. function get (node, value) {
  61. if (node.value === value) return node;
  62. for (let child of node.children) {
  63. let found = get(child, value);
  64. if (found) return found;
  65. }
  66. return null;
  67. }
  68. /*
  69. * Constructs a tree of bookmark nodes
  70. * returning the root (value: null);
  71. */
  72. function constructTree (items) {
  73. let root = TreeNode(null);
  74. items.forEach(treeify.bind(null, root));
  75. function treeify (root, item) {
  76. // If node already exists, skip
  77. let node = root.get(item);
  78. if (node) return node;
  79. node = TreeNode(item);
  80. let parentNode = item.group ? treeify(root, item.group) : root;
  81. parentNode.add(node);
  82. return node;
  83. }
  84. return root;
  85. }
  86. exports.constructTree = constructTree;
  87. /*
  88. * Shortcut for converting an id, or an object with an id, into
  89. * an object with corresponding bookmark data
  90. */
  91. function fetchItem (item)
  92. send('sdk-places-bookmarks-get', { id: item.id || item })
  93. exports.fetchItem = fetchItem;
  94. /*
  95. * Takes an ID or an object with ID and checks it against
  96. * the root bookmark folders
  97. */
  98. function isRootGroup (id) {
  99. id = id && id.id;
  100. return ~[bmsrv.bookmarksMenuFolder, bmsrv.toolbarFolder,
  101. bmsrv.unfiledBookmarksFolder
  102. ].indexOf(id);
  103. }
  104. exports.isRootGroup = isRootGroup;
  105. /*
  106. * Merges appropriate options into query based off of url
  107. * 4 scenarios:
  108. *
  109. * 'moz.com' // domain: moz.com, domainIsHost: true
  110. * --> 'http://moz.com', 'http://moz.com/thunderbird'
  111. * '*.moz.com' // domain: moz.com, domainIsHost: false
  112. * --> 'http://moz.com', 'http://moz.com/index', 'http://ff.moz.com/test'
  113. * 'http://moz.com' // url: http://moz.com/, urlIsPrefix: false
  114. * --> 'http://moz.com/'
  115. * 'http://moz.com/*' // url: http://moz.com/, urlIsPrefix: true
  116. * --> 'http://moz.com/', 'http://moz.com/thunderbird'
  117. */
  118. function urlQueryParser (query, url) {
  119. if (!url) return;
  120. if (/^https?:\/\//.test(url)) {
  121. query.uri = url.charAt(url.length - 1) === '/' ? url : url + '/';
  122. if (/\*$/.test(url)) {
  123. query.uri = url.replace(/\*$/, '');
  124. query.uriIsPrefix = true;
  125. }
  126. } else {
  127. if (/^\*/.test(url)) {
  128. query.domain = url.replace(/^\*\./, '');
  129. query.domainIsHost = false;
  130. } else {
  131. query.domain = url;
  132. query.domainIsHost = true;
  133. }
  134. }
  135. }
  136. exports.urlQueryParser = urlQueryParser;
  137. /*
  138. * Takes an EventEmitter and returns a promise that
  139. * aggregates results and handles a bulk resolve and reject
  140. */
  141. function promisedEmitter (emitter) {
  142. let { promise, resolve, reject } = defer();
  143. let errors = [];
  144. emitter.on('error', error => errors.push(error));
  145. emitter.on('end', (items) => {
  146. if (errors.length) reject(errors[0]);
  147. else resolve(items);
  148. });
  149. return promise;
  150. }
  151. exports.promisedEmitter = promisedEmitter;
  152. // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
  153. function createQuery (type, query) {
  154. query = query || {};
  155. let qObj = {
  156. searchTerms: query.query
  157. };
  158. urlQueryParser(qObj, query.url);
  159. // 0 === history
  160. if (type === 0) {
  161. // PRTime used by query is in microseconds, not milliseconds
  162. qObj.beginTime = (query.from || 0) * 1000;
  163. qObj.endTime = (query.to || new Date()) * 1000;
  164. // Set reference time to Epoch
  165. qObj.beginTimeReference = 0;
  166. qObj.endTimeReference = 0;
  167. }
  168. // 1 === bookmarks
  169. else if (type === 1) {
  170. qObj.tags = query.tags;
  171. qObj.folder = query.group && query.group.id;
  172. }
  173. // 2 === unified (not implemented on platform)
  174. else if (type === 2) {
  175. }
  176. return qObj;
  177. }
  178. exports.createQuery = createQuery;
  179. // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
  180. const SORT_MAP = {
  181. title: 1,
  182. date: 3, // sort by visit date
  183. url: 5,
  184. visitCount: 7,
  185. // keywords currently unsupported
  186. // keyword: 9,
  187. dateAdded: 11, // bookmarks only
  188. lastModified: 13 // bookmarks only
  189. };
  190. function createQueryOptions (type, options) {
  191. options = options || {};
  192. let oObj = {};
  193. oObj.sortingMode = SORT_MAP[options.sort] || 0;
  194. if (options.descending && options.sort)
  195. oObj.sortingMode++;
  196. // Resolve to default sort if ineligible based on query type
  197. if (type === 0 && // history
  198. (options.sort === 'dateAdded' || options.sort === 'lastModified'))
  199. oObj.sortingMode = 0;
  200. oObj.maxResults = typeof options.count === 'number' ? options.count : 0;
  201. oObj.queryType = type;
  202. return oObj;
  203. }
  204. exports.createQueryOptions = createQueryOptions;
  205. function mapBookmarkItemType (type) {
  206. if (typeof type === 'number') {
  207. if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark';
  208. if (bmsrv.TYPE_FOLDER === type) return 'group';
  209. if (bmsrv.TYPE_SEPARATOR === type) return 'separator';
  210. } else {
  211. if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK;
  212. if ('group' === type) return bmsrv.TYPE_FOLDER;
  213. if ('separator' === type) return bmsrv.TYPE_SEPARATOR;
  214. }
  215. }
  216. exports.mapBookmarkItemType = mapBookmarkItemType;