1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/places/host/host-query.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,171 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +module.metadata = { 1.11 + "stability": "experimental", 1.12 + "engines": { 1.13 + "Firefox": "*" 1.14 + } 1.15 +}; 1.16 + 1.17 +const { Cc, Ci } = require('chrome'); 1.18 +const { defer, all, resolve } = require('../../core/promise'); 1.19 +const { safeMerge, omit } = require('../../util/object'); 1.20 +const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] 1.21 + .getService(Ci.nsINavHistoryService); 1.22 +const bookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] 1.23 + .getService(Ci.nsINavBookmarksService); 1.24 +const { request, response } = require('../../addon/host'); 1.25 +const { newURI } = require('../../url/utils'); 1.26 +const { send } = require('../../addon/events'); 1.27 +const { on, emit } = require('../../event/core'); 1.28 +const { filter } = require('../../event/utils'); 1.29 + 1.30 +const ROOT_FOLDERS = [ 1.31 + bookmarksService.unfiledBookmarksFolder, bookmarksService.toolbarFolder, 1.32 + bookmarksService.bookmarksMenuFolder 1.33 +]; 1.34 + 1.35 +const EVENT_MAP = { 1.36 + 'sdk-places-query': queryReceiver 1.37 +}; 1.38 + 1.39 +// Properties that need to be manually 1.40 +// copied into a nsINavHistoryQuery object 1.41 +const MANUAL_QUERY_PROPERTIES = [ 1.42 + 'uri', 'folder', 'tags', 'url', 'folder' 1.43 +]; 1.44 + 1.45 +const PLACES_PROPERTIES = [ 1.46 + 'uri', 'title', 'accessCount', 'time' 1.47 +]; 1.48 + 1.49 +function execute (queries, options) { 1.50 + let deferred = defer(); 1.51 + let root = historyService 1.52 + .executeQueries(queries, queries.length, options).root; 1.53 + 1.54 + let items = collect([], root); 1.55 + deferred.resolve(items); 1.56 + return deferred.promise; 1.57 +} 1.58 + 1.59 +function collect (acc, node) { 1.60 + node.containerOpen = true; 1.61 + for (let i = 0; i < node.childCount; i++) { 1.62 + let child = node.getChild(i); 1.63 + acc.push(child); 1.64 + if (child.type === child.RESULT_TYPE_FOLDER) { 1.65 + let container = child.QueryInterface(Ci.nsINavHistoryContainerResultNode); 1.66 + collect(acc, container); 1.67 + } 1.68 + } 1.69 + node.containerOpen = false; 1.70 + return acc; 1.71 +} 1.72 + 1.73 +function query (queries, options) { 1.74 + queries = queries || []; 1.75 + options = options || {}; 1.76 + let deferred = defer(); 1.77 + let optionsObj, queryObjs; 1.78 + 1.79 + try { 1.80 + optionsObj = historyService.getNewQueryOptions(); 1.81 + queryObjs = [].concat(queries).map(createQuery); 1.82 + if (!queryObjs.length) { 1.83 + queryObjs = [historyService.getNewQuery()]; 1.84 + } 1.85 + safeMerge(optionsObj, options); 1.86 + } catch (e) { 1.87 + deferred.reject(e); 1.88 + return deferred.promise; 1.89 + } 1.90 + 1.91 + /* 1.92 + * Currently `places:` queries are not supported 1.93 + */ 1.94 + optionsObj.excludeQueries = true; 1.95 + 1.96 + execute(queryObjs, optionsObj).then(function (results) { 1.97 + if (optionsObj.queryType === 0) { 1.98 + return results.map(normalize); 1.99 + } else if (optionsObj.queryType === 1) { 1.100 + // Formats query results into more standard 1.101 + // data structures for returning 1.102 + return all(results.map(({itemId}) => 1.103 + send('sdk-places-bookmarks-get', { id: itemId }))); 1.104 + } 1.105 + }).then(deferred.resolve, deferred.reject); 1.106 + 1.107 + return deferred.promise; 1.108 +} 1.109 +exports.query = query; 1.110 + 1.111 +function createQuery (query) { 1.112 + query = query || {}; 1.113 + let queryObj = historyService.getNewQuery(); 1.114 + 1.115 + safeMerge(queryObj, omit(query, MANUAL_QUERY_PROPERTIES)); 1.116 + 1.117 + if (query.tags && Array.isArray(query.tags)) 1.118 + queryObj.tags = query.tags; 1.119 + if (query.uri || query.url) 1.120 + queryObj.uri = newURI(query.uri || query.url); 1.121 + if (query.folder) 1.122 + queryObj.setFolders([query.folder], 1); 1.123 + return queryObj; 1.124 +} 1.125 + 1.126 +function queryReceiver (message) { 1.127 + let queries = message.data.queries || message.data.query; 1.128 + let options = message.data.options; 1.129 + let resData = { 1.130 + id: message.id, 1.131 + event: message.event 1.132 + }; 1.133 + 1.134 + query(queries, options).then(results => { 1.135 + resData.data = results; 1.136 + respond(resData); 1.137 + }, reason => { 1.138 + resData.error = reason; 1.139 + respond(resData); 1.140 + }); 1.141 +} 1.142 + 1.143 +/* 1.144 + * Converts a nsINavHistoryResultNode into a plain object 1.145 + * 1.146 + * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode 1.147 + */ 1.148 +function normalize (historyObj) { 1.149 + return PLACES_PROPERTIES.reduce((obj, prop) => { 1.150 + if (prop === 'uri') 1.151 + obj.url = historyObj.uri; 1.152 + else if (prop === 'time') { 1.153 + // Cast from microseconds to milliseconds 1.154 + obj.time = Math.floor(historyObj.time / 1000) 1.155 + } else if (prop === 'accessCount') 1.156 + obj.visitCount = historyObj[prop]; 1.157 + else 1.158 + obj[prop] = historyObj[prop]; 1.159 + return obj; 1.160 + }, {}); 1.161 +} 1.162 + 1.163 +/* 1.164 + * Hook into host 1.165 + */ 1.166 + 1.167 +let reqStream = filter(request, function (data) /sdk-places-query/.test(data.event)); 1.168 +on(reqStream, 'data', function (e) { 1.169 + if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e); 1.170 +}); 1.171 + 1.172 +function respond (data) { 1.173 + emit(response, 'data', data); 1.174 +}