addon-sdk/source/lib/sdk/places/host/host-query.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:d57147ae3f85
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 "use strict";
6
7 module.metadata = {
8 "stability": "experimental",
9 "engines": {
10 "Firefox": "*"
11 }
12 };
13
14 const { Cc, Ci } = require('chrome');
15 const { defer, all, resolve } = require('../../core/promise');
16 const { safeMerge, omit } = require('../../util/object');
17 const historyService = Cc['@mozilla.org/browser/nav-history-service;1']
18 .getService(Ci.nsINavHistoryService);
19 const bookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1']
20 .getService(Ci.nsINavBookmarksService);
21 const { request, response } = require('../../addon/host');
22 const { newURI } = require('../../url/utils');
23 const { send } = require('../../addon/events');
24 const { on, emit } = require('../../event/core');
25 const { filter } = require('../../event/utils');
26
27 const ROOT_FOLDERS = [
28 bookmarksService.unfiledBookmarksFolder, bookmarksService.toolbarFolder,
29 bookmarksService.bookmarksMenuFolder
30 ];
31
32 const EVENT_MAP = {
33 'sdk-places-query': queryReceiver
34 };
35
36 // Properties that need to be manually
37 // copied into a nsINavHistoryQuery object
38 const MANUAL_QUERY_PROPERTIES = [
39 'uri', 'folder', 'tags', 'url', 'folder'
40 ];
41
42 const PLACES_PROPERTIES = [
43 'uri', 'title', 'accessCount', 'time'
44 ];
45
46 function execute (queries, options) {
47 let deferred = defer();
48 let root = historyService
49 .executeQueries(queries, queries.length, options).root;
50
51 let items = collect([], root);
52 deferred.resolve(items);
53 return deferred.promise;
54 }
55
56 function collect (acc, node) {
57 node.containerOpen = true;
58 for (let i = 0; i < node.childCount; i++) {
59 let child = node.getChild(i);
60 acc.push(child);
61 if (child.type === child.RESULT_TYPE_FOLDER) {
62 let container = child.QueryInterface(Ci.nsINavHistoryContainerResultNode);
63 collect(acc, container);
64 }
65 }
66 node.containerOpen = false;
67 return acc;
68 }
69
70 function query (queries, options) {
71 queries = queries || [];
72 options = options || {};
73 let deferred = defer();
74 let optionsObj, queryObjs;
75
76 try {
77 optionsObj = historyService.getNewQueryOptions();
78 queryObjs = [].concat(queries).map(createQuery);
79 if (!queryObjs.length) {
80 queryObjs = [historyService.getNewQuery()];
81 }
82 safeMerge(optionsObj, options);
83 } catch (e) {
84 deferred.reject(e);
85 return deferred.promise;
86 }
87
88 /*
89 * Currently `places:` queries are not supported
90 */
91 optionsObj.excludeQueries = true;
92
93 execute(queryObjs, optionsObj).then(function (results) {
94 if (optionsObj.queryType === 0) {
95 return results.map(normalize);
96 } else if (optionsObj.queryType === 1) {
97 // Formats query results into more standard
98 // data structures for returning
99 return all(results.map(({itemId}) =>
100 send('sdk-places-bookmarks-get', { id: itemId })));
101 }
102 }).then(deferred.resolve, deferred.reject);
103
104 return deferred.promise;
105 }
106 exports.query = query;
107
108 function createQuery (query) {
109 query = query || {};
110 let queryObj = historyService.getNewQuery();
111
112 safeMerge(queryObj, omit(query, MANUAL_QUERY_PROPERTIES));
113
114 if (query.tags && Array.isArray(query.tags))
115 queryObj.tags = query.tags;
116 if (query.uri || query.url)
117 queryObj.uri = newURI(query.uri || query.url);
118 if (query.folder)
119 queryObj.setFolders([query.folder], 1);
120 return queryObj;
121 }
122
123 function queryReceiver (message) {
124 let queries = message.data.queries || message.data.query;
125 let options = message.data.options;
126 let resData = {
127 id: message.id,
128 event: message.event
129 };
130
131 query(queries, options).then(results => {
132 resData.data = results;
133 respond(resData);
134 }, reason => {
135 resData.error = reason;
136 respond(resData);
137 });
138 }
139
140 /*
141 * Converts a nsINavHistoryResultNode into a plain object
142 *
143 * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode
144 */
145 function normalize (historyObj) {
146 return PLACES_PROPERTIES.reduce((obj, prop) => {
147 if (prop === 'uri')
148 obj.url = historyObj.uri;
149 else if (prop === 'time') {
150 // Cast from microseconds to milliseconds
151 obj.time = Math.floor(historyObj.time / 1000)
152 } else if (prop === 'accessCount')
153 obj.visitCount = historyObj[prop];
154 else
155 obj[prop] = historyObj[prop];
156 return obj;
157 }, {});
158 }
159
160 /*
161 * Hook into host
162 */
163
164 let reqStream = filter(request, function (data) /sdk-places-query/.test(data.event));
165 on(reqStream, 'data', function (e) {
166 if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e);
167 });
168
169 function respond (data) {
170 emit(response, 'data', data);
171 }

mercurial