host-bookmarks.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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 browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"].
  13. getService(Ci.nsIBrowserHistory);
  14. const asyncHistory = Cc["@mozilla.org/browser/history;1"].
  15. getService(Ci.mozIAsyncHistory);
  16. const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
  17. getService(Ci.nsINavBookmarksService);
  18. const taggingService = Cc["@mozilla.org/browser/tagging-service;1"].
  19. getService(Ci.nsITaggingService);
  20. const ios = Cc['@mozilla.org/network/io-service;1'].
  21. getService(Ci.nsIIOService);
  22. const { query } = require('./host-query');
  23. const {
  24. defer, all, resolve, promised, reject
  25. } = require('../../core/promise');
  26. const { request, response } = require('../../addon/host');
  27. const { send } = require('../../addon/events');
  28. const { on, emit } = require('../../event/core');
  29. const { filter } = require('../../event/utils');
  30. const { URL, isValidURI } = require('../../url');
  31. const { newURI } = require('../../url/utils');
  32. const DEFAULT_INDEX = bmsrv.DEFAULT_INDEX;
  33. const UNSORTED_ID = bmsrv.unfiledBookmarksFolder;
  34. const ROOT_FOLDERS = [
  35. bmsrv.unfiledBookmarksFolder, bmsrv.toolbarFolder,
  36. bmsrv.tagsFolder, bmsrv.bookmarksMenuFolder
  37. ];
  38. const EVENT_MAP = {
  39. 'sdk-places-bookmarks-create': createBookmarkItem,
  40. 'sdk-places-bookmarks-save': saveBookmarkItem,
  41. 'sdk-places-bookmarks-last-updated': getBookmarkLastUpdated,
  42. 'sdk-places-bookmarks-get': getBookmarkItem,
  43. 'sdk-places-bookmarks-remove': removeBookmarkItem,
  44. 'sdk-places-bookmarks-get-all': getAllBookmarks,
  45. 'sdk-places-bookmarks-get-children': getChildren
  46. };
  47. function typeMap (type) {
  48. if (typeof type === 'number') {
  49. if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark';
  50. if (bmsrv.TYPE_FOLDER === type) return 'group';
  51. if (bmsrv.TYPE_SEPARATOR === type) return 'separator';
  52. } else {
  53. if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK;
  54. if ('group' === type) return bmsrv.TYPE_FOLDER;
  55. if ('separator' === type) return bmsrv.TYPE_SEPARATOR;
  56. }
  57. }
  58. function getBookmarkLastUpdated ({id})
  59. resolve(bmsrv.getItemLastModified(id))
  60. exports.getBookmarkLastUpdated;
  61. function createBookmarkItem (data) {
  62. let error;
  63. if (data.group == null) data.group = UNSORTED_ID;
  64. if (data.index == null) data.index = DEFAULT_INDEX;
  65. if (data.type === 'group')
  66. data.id = bmsrv.createFolder(
  67. data.group, data.title, data.index
  68. );
  69. else if (data.type === 'separator')
  70. data.id = bmsrv.insertSeparator(
  71. data.group, data.index
  72. );
  73. else
  74. data.id = bmsrv.insertBookmark(
  75. data.group, newURI(data.url), data.index, data.title
  76. );
  77. // In the event where default or no index is provided (-1),
  78. // query the actual index for the response
  79. if (data.index === -1)
  80. data.index = bmsrv.getItemIndex(data.id);
  81. data.updated = bmsrv.getItemLastModified(data.id);
  82. return tag(data, true).then(() => data);
  83. }
  84. exports.createBookmarkItem = createBookmarkItem;
  85. function saveBookmarkItem (data) {
  86. let id = data.id;
  87. if (!id)
  88. reject('Item is missing id');
  89. let group = bmsrv.getFolderIdForItem(id);
  90. let index = bmsrv.getItemIndex(id);
  91. let type = bmsrv.getItemType(id);
  92. let title = typeMap(type) !== 'separator' ?
  93. bmsrv.getItemTitle(id) :
  94. undefined;
  95. let url = typeMap(type) === 'bookmark' ?
  96. bmsrv.getBookmarkURI(id).spec :
  97. undefined;
  98. if (url != data.url)
  99. bmsrv.changeBookmarkURI(id, newURI(data.url));
  100. else if (typeMap(type) === 'bookmark')
  101. data.url = url;
  102. if (title != data.title)
  103. bmsrv.setItemTitle(id, data.title);
  104. else if (typeMap(type) !== 'separator')
  105. data.title = title;
  106. if (data.group && data.group !== group)
  107. bmsrv.moveItem(id, data.group, data.index || -1);
  108. else if (data.index != null && data.index !== index) {
  109. // We use moveItem here instead of setItemIndex
  110. // so we don't have to manage the indicies of the siblings
  111. bmsrv.moveItem(id, group, data.index);
  112. } else if (data.index == null)
  113. data.index = index;
  114. data.updated = bmsrv.getItemLastModified(data.id);
  115. return tag(data).then(() => data);
  116. }
  117. exports.saveBookmarkItem = saveBookmarkItem;
  118. function removeBookmarkItem (data) {
  119. let id = data.id;
  120. if (!id)
  121. reject('Item is missing id');
  122. bmsrv.removeItem(id);
  123. return resolve(null);
  124. }
  125. exports.removeBookmarkItem = removeBookmarkItem;
  126. function getBookmarkItem (data) {
  127. let id = data.id;
  128. if (!id)
  129. reject('Item is missing id');
  130. let type = bmsrv.getItemType(id);
  131. data.type = typeMap(type);
  132. if (type === bmsrv.TYPE_BOOKMARK || type === bmsrv.TYPE_FOLDER)
  133. data.title = bmsrv.getItemTitle(id);
  134. if (type === bmsrv.TYPE_BOOKMARK) {
  135. data.url = bmsrv.getBookmarkURI(id).spec;
  136. // Should be moved into host-tags as a method
  137. data.tags = taggingService.getTagsForURI(newURI(data.url), {});
  138. }
  139. data.group = bmsrv.getFolderIdForItem(id);
  140. data.index = bmsrv.getItemIndex(id);
  141. data.updated = bmsrv.getItemLastModified(data.id);
  142. return resolve(data);
  143. }
  144. exports.getBookmarkItem = getBookmarkItem;
  145. function getAllBookmarks () {
  146. return query({}, { queryType: 1 }).then(bookmarks =>
  147. all(bookmarks.map(getBookmarkItem)));
  148. }
  149. exports.getAllBookmarks = getAllBookmarks;
  150. function getChildren ({ id }) {
  151. if (typeMap(bmsrv.getItemType(id)) !== 'group') return [];
  152. let ids = [];
  153. for (let i = 0; ids[ids.length - 1] !== -1; i++)
  154. ids.push(bmsrv.getIdForItemAt(id, i));
  155. ids.pop();
  156. return all(ids.map(id => getBookmarkItem({ id: id })));
  157. }
  158. exports.getChildren = getChildren;
  159. /*
  160. * Hook into host
  161. */
  162. let reqStream = filter(request, function (data) /sdk-places-bookmarks/.test(data.event));
  163. on(reqStream, 'data', function ({event, id, data}) {
  164. if (!EVENT_MAP[event]) return;
  165. let resData = {
  166. id: id,
  167. event: event
  168. };
  169. promised(EVENT_MAP[event])(data).then(res => {
  170. resData.data = res;
  171. respond(resData);
  172. }, reason => {
  173. resData.error = reason;
  174. respond(resData);
  175. });
  176. });
  177. function respond (data) {
  178. emit(response, 'data', data);
  179. }
  180. function tag (data, isNew) {
  181. // If a new item, we can skip checking what other tags
  182. // are on the item
  183. if (data.type !== 'bookmark') {
  184. return resolve();
  185. } else if (!isNew) {
  186. return send('sdk-places-tags-get-tags-by-url', { url: data.url })
  187. .then(tags => {
  188. return send('sdk-places-tags-untag', {
  189. tags: tags.filter(tag => !~data.tags.indexOf(tag)),
  190. url: data.url
  191. });
  192. }).then(() => send('sdk-places-tags-tag', {
  193. url: data.url, tags: data.tags
  194. }));
  195. }
  196. else if (data.tags && data.tags.length) {
  197. return send('sdk-places-tags-tag', { url: data.url, tags: data.tags });
  198. }
  199. else
  200. return resolve();
  201. }