1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/places/utils.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,251 @@ 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 { Class } = require('../core/heritage'); 1.19 +const { method } = require('../lang/functional'); 1.20 +const { defer, promised, all } = require('../core/promise'); 1.21 +const { send } = require('../addon/events'); 1.22 +const { EventTarget } = require('../event/target'); 1.23 +const { merge } = require('../util/object'); 1.24 +const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. 1.25 + getService(Ci.nsINavBookmarksService); 1.26 + 1.27 +/* 1.28 + * TreeNodes are used to construct dependency trees 1.29 + * for BookmarkItems 1.30 + */ 1.31 +let TreeNode = Class({ 1.32 + initialize: function (value) { 1.33 + this.value = value; 1.34 + this.children = []; 1.35 + }, 1.36 + add: function (values) { 1.37 + [].concat(values).forEach(value => { 1.38 + this.children.push(value instanceof TreeNode ? value : TreeNode(value)); 1.39 + }); 1.40 + }, 1.41 + get length () { 1.42 + let count = 0; 1.43 + this.walk(() => count++); 1.44 + // Do not count the current node 1.45 + return --count; 1.46 + }, 1.47 + get: method(get), 1.48 + walk: method(walk), 1.49 + toString: function () '[object TreeNode]' 1.50 +}); 1.51 +exports.TreeNode = TreeNode; 1.52 + 1.53 +/* 1.54 + * Descends down from `node` applying `fn` to each in order. 1.55 + * `fn` can return values or promises -- if promise returned, 1.56 + * children are not processed until resolved. `fn` is passed 1.57 + * one argument, the current node, `curr`. 1.58 + */ 1.59 +function walk (curr, fn) { 1.60 + return promised(fn)(curr).then(val => { 1.61 + return all(curr.children.map(child => walk(child, fn))); 1.62 + }); 1.63 +} 1.64 + 1.65 +/* 1.66 + * Descends from the TreeNode `node`, returning 1.67 + * the node with value `value` if found or `null` 1.68 + * otherwise 1.69 + */ 1.70 +function get (node, value) { 1.71 + if (node.value === value) return node; 1.72 + for (let child of node.children) { 1.73 + let found = get(child, value); 1.74 + if (found) return found; 1.75 + } 1.76 + return null; 1.77 +} 1.78 + 1.79 +/* 1.80 + * Constructs a tree of bookmark nodes 1.81 + * returning the root (value: null); 1.82 + */ 1.83 + 1.84 +function constructTree (items) { 1.85 + let root = TreeNode(null); 1.86 + items.forEach(treeify.bind(null, root)); 1.87 + 1.88 + function treeify (root, item) { 1.89 + // If node already exists, skip 1.90 + let node = root.get(item); 1.91 + if (node) return node; 1.92 + node = TreeNode(item); 1.93 + 1.94 + let parentNode = item.group ? treeify(root, item.group) : root; 1.95 + parentNode.add(node); 1.96 + 1.97 + return node; 1.98 + } 1.99 + 1.100 + return root; 1.101 +} 1.102 +exports.constructTree = constructTree; 1.103 + 1.104 +/* 1.105 + * Shortcut for converting an id, or an object with an id, into 1.106 + * an object with corresponding bookmark data 1.107 + */ 1.108 +function fetchItem (item) 1.109 + send('sdk-places-bookmarks-get', { id: item.id || item }) 1.110 +exports.fetchItem = fetchItem; 1.111 + 1.112 +/* 1.113 + * Takes an ID or an object with ID and checks it against 1.114 + * the root bookmark folders 1.115 + */ 1.116 +function isRootGroup (id) { 1.117 + id = id && id.id; 1.118 + return ~[bmsrv.bookmarksMenuFolder, bmsrv.toolbarFolder, 1.119 + bmsrv.unfiledBookmarksFolder 1.120 + ].indexOf(id); 1.121 +} 1.122 +exports.isRootGroup = isRootGroup; 1.123 + 1.124 +/* 1.125 + * Merges appropriate options into query based off of url 1.126 + * 4 scenarios: 1.127 + * 1.128 + * 'moz.com' // domain: moz.com, domainIsHost: true 1.129 + * --> 'http://moz.com', 'http://moz.com/thunderbird' 1.130 + * '*.moz.com' // domain: moz.com, domainIsHost: false 1.131 + * --> 'http://moz.com', 'http://moz.com/index', 'http://ff.moz.com/test' 1.132 + * 'http://moz.com' // url: http://moz.com/, urlIsPrefix: false 1.133 + * --> 'http://moz.com/' 1.134 + * 'http://moz.com/*' // url: http://moz.com/, urlIsPrefix: true 1.135 + * --> 'http://moz.com/', 'http://moz.com/thunderbird' 1.136 + */ 1.137 + 1.138 +function urlQueryParser (query, url) { 1.139 + if (!url) return; 1.140 + if (/^https?:\/\//.test(url)) { 1.141 + query.uri = url.charAt(url.length - 1) === '/' ? url : url + '/'; 1.142 + if (/\*$/.test(url)) { 1.143 + query.uri = url.replace(/\*$/, ''); 1.144 + query.uriIsPrefix = true; 1.145 + } 1.146 + } else { 1.147 + if (/^\*/.test(url)) { 1.148 + query.domain = url.replace(/^\*\./, ''); 1.149 + query.domainIsHost = false; 1.150 + } else { 1.151 + query.domain = url; 1.152 + query.domainIsHost = true; 1.153 + } 1.154 + } 1.155 +} 1.156 +exports.urlQueryParser = urlQueryParser; 1.157 + 1.158 +/* 1.159 + * Takes an EventEmitter and returns a promise that 1.160 + * aggregates results and handles a bulk resolve and reject 1.161 + */ 1.162 + 1.163 +function promisedEmitter (emitter) { 1.164 + let { promise, resolve, reject } = defer(); 1.165 + let errors = []; 1.166 + emitter.on('error', error => errors.push(error)); 1.167 + emitter.on('end', (items) => { 1.168 + if (errors.length) reject(errors[0]); 1.169 + else resolve(items); 1.170 + }); 1.171 + return promise; 1.172 +} 1.173 +exports.promisedEmitter = promisedEmitter; 1.174 + 1.175 + 1.176 +// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions 1.177 +function createQuery (type, query) { 1.178 + query = query || {}; 1.179 + let qObj = { 1.180 + searchTerms: query.query 1.181 + }; 1.182 + 1.183 + urlQueryParser(qObj, query.url); 1.184 + 1.185 + // 0 === history 1.186 + if (type === 0) { 1.187 + // PRTime used by query is in microseconds, not milliseconds 1.188 + qObj.beginTime = (query.from || 0) * 1000; 1.189 + qObj.endTime = (query.to || new Date()) * 1000; 1.190 + 1.191 + // Set reference time to Epoch 1.192 + qObj.beginTimeReference = 0; 1.193 + qObj.endTimeReference = 0; 1.194 + } 1.195 + // 1 === bookmarks 1.196 + else if (type === 1) { 1.197 + qObj.tags = query.tags; 1.198 + qObj.folder = query.group && query.group.id; 1.199 + } 1.200 + // 2 === unified (not implemented on platform) 1.201 + else if (type === 2) { 1.202 + 1.203 + } 1.204 + 1.205 + return qObj; 1.206 +} 1.207 +exports.createQuery = createQuery; 1.208 + 1.209 +// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions 1.210 + 1.211 +const SORT_MAP = { 1.212 + title: 1, 1.213 + date: 3, // sort by visit date 1.214 + url: 5, 1.215 + visitCount: 7, 1.216 + // keywords currently unsupported 1.217 + // keyword: 9, 1.218 + dateAdded: 11, // bookmarks only 1.219 + lastModified: 13 // bookmarks only 1.220 +}; 1.221 + 1.222 +function createQueryOptions (type, options) { 1.223 + options = options || {}; 1.224 + let oObj = {}; 1.225 + oObj.sortingMode = SORT_MAP[options.sort] || 0; 1.226 + if (options.descending && options.sort) 1.227 + oObj.sortingMode++; 1.228 + 1.229 + // Resolve to default sort if ineligible based on query type 1.230 + if (type === 0 && // history 1.231 + (options.sort === 'dateAdded' || options.sort === 'lastModified')) 1.232 + oObj.sortingMode = 0; 1.233 + 1.234 + oObj.maxResults = typeof options.count === 'number' ? options.count : 0; 1.235 + 1.236 + oObj.queryType = type; 1.237 + 1.238 + return oObj; 1.239 +} 1.240 +exports.createQueryOptions = createQueryOptions; 1.241 + 1.242 + 1.243 +function mapBookmarkItemType (type) { 1.244 + if (typeof type === 'number') { 1.245 + if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; 1.246 + if (bmsrv.TYPE_FOLDER === type) return 'group'; 1.247 + if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; 1.248 + } else { 1.249 + if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; 1.250 + if ('group' === type) return bmsrv.TYPE_FOLDER; 1.251 + if ('separator' === type) return bmsrv.TYPE_SEPARATOR; 1.252 + } 1.253 +} 1.254 +exports.mapBookmarkItemType = mapBookmarkItemType;