summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/places/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/places/utils.js')
-rw-r--r--toolkit/jetpack/sdk/places/utils.js268
1 files changed, 268 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/places/utils.js b/toolkit/jetpack/sdk/places/utils.js
new file mode 100644
index 000000000..44366d2aa
--- /dev/null
+++ b/toolkit/jetpack/sdk/places/utils.js
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const { Class } = require('../core/heritage');
+const { method } = require('../lang/functional');
+const { defer, promised, all } = require('../core/promise');
+const { send } = require('../addon/events');
+const { EventTarget } = require('../event/target');
+const { merge } = require('../util/object');
+const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+
+Cu.importGlobalProperties(["URL"]);
+
+/*
+ * TreeNodes are used to construct dependency trees
+ * for BookmarkItems
+ */
+var TreeNode = Class({
+ initialize: function (value) {
+ this.value = value;
+ this.children = [];
+ },
+ add: function (values) {
+ [].concat(values).forEach(value => {
+ this.children.push(value instanceof TreeNode ? value : TreeNode(value));
+ });
+ },
+ get length () {
+ let count = 0;
+ this.walk(() => count++);
+ // Do not count the current node
+ return --count;
+ },
+ get: method(get),
+ walk: method(walk),
+ toString: () => '[object TreeNode]'
+});
+exports.TreeNode = TreeNode;
+
+/*
+ * Descends down from `node` applying `fn` to each in order.
+ * `fn` can return values or promises -- if promise returned,
+ * children are not processed until resolved. `fn` is passed
+ * one argument, the current node, `curr`.
+ */
+function walk (curr, fn) {
+ return promised(fn)(curr).then(val => {
+ return all(curr.children.map(child => walk(child, fn)));
+ });
+}
+
+/*
+ * Descends from the TreeNode `node`, returning
+ * the node with value `value` if found or `null`
+ * otherwise
+ */
+function get (node, value) {
+ if (node.value === value) return node;
+ for (let child of node.children) {
+ let found = get(child, value);
+ if (found) return found;
+ }
+ return null;
+}
+
+/*
+ * Constructs a tree of bookmark nodes
+ * returning the root (value: null);
+ */
+
+function constructTree (items) {
+ let root = TreeNode(null);
+ items.forEach(treeify.bind(null, root));
+
+ function treeify (root, item) {
+ // If node already exists, skip
+ let node = root.get(item);
+ if (node) return node;
+ node = TreeNode(item);
+
+ let parentNode = item.group ? treeify(root, item.group) : root;
+ parentNode.add(node);
+
+ return node;
+ }
+
+ return root;
+}
+exports.constructTree = constructTree;
+
+/*
+ * Shortcut for converting an id, or an object with an id, into
+ * an object with corresponding bookmark data
+ */
+function fetchItem (item) {
+ return send('sdk-places-bookmarks-get', { id: item.id || item });
+}
+exports.fetchItem = fetchItem;
+
+/*
+ * Takes an ID or an object with ID and checks it against
+ * the root bookmark folders
+ */
+function isRootGroup (id) {
+ id = id && id.id;
+ return ~[bmsrv.bookmarksMenuFolder, bmsrv.toolbarFolder,
+ bmsrv.unfiledBookmarksFolder
+ ].indexOf(id);
+}
+exports.isRootGroup = isRootGroup;
+
+/*
+ * Merges appropriate options into query based off of url
+ * 4 scenarios:
+ *
+ * 'moz.com' // domain: moz.com, domainIsHost: true
+ * --> 'http://moz.com', 'http://moz.com/thunderbird'
+ * '*.moz.com' // domain: moz.com, domainIsHost: false
+ * --> 'http://moz.com', 'http://moz.com/index', 'http://ff.moz.com/test'
+ * 'http://moz.com' // uri: http://moz.com/
+ * --> 'http://moz.com/'
+ * 'http://moz.com/*' // uri: http://moz.com/, domain: moz.com, domainIsHost: true
+ * --> 'http://moz.com/', 'http://moz.com/thunderbird'
+ */
+
+function urlQueryParser (query, url) {
+ if (!url) return;
+ if (/^https?:\/\//.test(url)) {
+ query.uri = url.charAt(url.length - 1) === '/' ? url : url + '/';
+ if (/\*$/.test(url)) {
+ // Wildcard searches on URIs are not supported, so try to extract a
+ // domain and filter the data later.
+ url = url.replace(/\*$/, '');
+ try {
+ query.domain = new URL(url).hostname;
+ query.domainIsHost = true;
+ // Unfortunately here we cannot use an expando to store the wildcard,
+ // cause the query is a wrapped native XPCOM object, so we reuse uri.
+ // We clearly don't want to query for both uri and domain, thus we'll
+ // have to handle this in host-query.js::execute()
+ query.uri = url;
+ } catch (ex) {
+ // Cannot extract an host cause it's not a valid uri, the query will
+ // just return nothing.
+ }
+ }
+ } else {
+ if (/^\*/.test(url)) {
+ query.domain = url.replace(/^\*\./, '');
+ query.domainIsHost = false;
+ } else {
+ query.domain = url;
+ query.domainIsHost = true;
+ }
+ }
+}
+exports.urlQueryParser = urlQueryParser;
+
+/*
+ * Takes an EventEmitter and returns a promise that
+ * aggregates results and handles a bulk resolve and reject
+ */
+
+function promisedEmitter (emitter) {
+ let { promise, resolve, reject } = defer();
+ let errors = [];
+ emitter.on('error', error => errors.push(error));
+ emitter.on('end', (items) => {
+ if (errors.length) reject(errors[0]);
+ else resolve(items);
+ });
+ return promise;
+}
+exports.promisedEmitter = promisedEmitter;
+
+
+// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
+function createQuery (type, query) {
+ query = query || {};
+ let qObj = {
+ searchTerms: query.query
+ };
+
+ urlQueryParser(qObj, query.url);
+
+ // 0 === history
+ if (type === 0) {
+ // PRTime used by query is in microseconds, not milliseconds
+ qObj.beginTime = (query.from || 0) * 1000;
+ qObj.endTime = (query.to || new Date()) * 1000;
+
+ // Set reference time to Epoch
+ qObj.beginTimeReference = 0;
+ qObj.endTimeReference = 0;
+ }
+ // 1 === bookmarks
+ else if (type === 1) {
+ qObj.tags = query.tags;
+ qObj.folder = query.group && query.group.id;
+ }
+ // 2 === unified (not implemented on platform)
+ else if (type === 2) {
+
+ }
+
+ return qObj;
+}
+exports.createQuery = createQuery;
+
+// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
+
+const SORT_MAP = {
+ title: 1,
+ date: 3, // sort by visit date
+ url: 5,
+ visitCount: 7,
+ // keywords currently unsupported
+ // keyword: 9,
+ dateAdded: 11, // bookmarks only
+ lastModified: 13 // bookmarks only
+};
+
+function createQueryOptions (type, options) {
+ options = options || {};
+ let oObj = {};
+ oObj.sortingMode = SORT_MAP[options.sort] || 0;
+ if (options.descending && options.sort)
+ oObj.sortingMode++;
+
+ // Resolve to default sort if ineligible based on query type
+ if (type === 0 && // history
+ (options.sort === 'dateAdded' || options.sort === 'lastModified'))
+ oObj.sortingMode = 0;
+
+ oObj.maxResults = typeof options.count === 'number' ? options.count : 0;
+
+ oObj.queryType = type;
+
+ return oObj;
+}
+exports.createQueryOptions = createQueryOptions;
+
+
+function mapBookmarkItemType (type) {
+ if (typeof type === 'number') {
+ if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark';
+ if (bmsrv.TYPE_FOLDER === type) return 'group';
+ if (bmsrv.TYPE_SEPARATOR === type) return 'separator';
+ } else {
+ if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK;
+ if ('group' === type) return bmsrv.TYPE_FOLDER;
+ if ('separator' === type) return bmsrv.TYPE_SEPARATOR;
+ }
+}
+exports.mapBookmarkItemType = mapBookmarkItemType;