diff options
Diffstat (limited to 'addon-sdk/source/lib/sdk/places')
-rw-r--r-- | addon-sdk/source/lib/sdk/places/bookmarks.js | 395 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/places/contract.js | 73 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/places/events.js | 128 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/places/favicon.js | 49 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/places/history.js | 65 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/places/host/host-bookmarks.js | 238 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/places/host/host-query.js | 179 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/places/host/host-tags.js | 92 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/places/utils.js | 268 |
9 files changed, 0 insertions, 1487 deletions
diff --git a/addon-sdk/source/lib/sdk/places/bookmarks.js b/addon-sdk/source/lib/sdk/places/bookmarks.js deleted file mode 100644 index c4f9528f1..000000000 --- a/addon-sdk/source/lib/sdk/places/bookmarks.js +++ /dev/null @@ -1,395 +0,0 @@ -/* 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": "unstable", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -/* - * Requiring hosts so they can subscribe to client messages - */ -require('./host/host-bookmarks'); -require('./host/host-tags'); -require('./host/host-query'); - -const { Cc, Ci } = require('chrome'); -const { Class } = require('../core/heritage'); -const { send } = require('../addon/events'); -const { defer, reject, all, resolve, promised } = require('../core/promise'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const { identity, defer:async } = require('../lang/functional'); -const { extend, merge } = require('../util/object'); -const { fromIterator } = require('../util/array'); -const { - constructTree, fetchItem, createQuery, - isRootGroup, createQueryOptions -} = require('./utils'); -const { - bookmarkContract, groupContract, separatorContract -} = require('./contract'); -const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); - -/* - * Mapping of uncreated bookmarks with their created - * counterparts - */ -const itemMap = new WeakMap(); - -/* - * Constant used by nsIHistoryQuery; 1 is a bookmark query - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions - */ -const BOOKMARK_QUERY = 1; - -/* - * Bookmark Item classes - */ - -const Bookmark = Class({ - extends: [ - bookmarkContract.properties(identity) - ], - initialize: function initialize (options) { - merge(this, bookmarkContract(extend(defaults, options))); - }, - type: 'bookmark', - toString: () => '[object Bookmark]' -}); -exports.Bookmark = Bookmark; - -const Group = Class({ - extends: [ - groupContract.properties(identity) - ], - initialize: function initialize (options) { - // Don't validate if root group - if (isRootGroup(options)) - merge(this, options); - else - merge(this, groupContract(extend(defaults, options))); - }, - type: 'group', - toString: () => '[object Group]' -}); -exports.Group = Group; - -const Separator = Class({ - extends: [ - separatorContract.properties(identity) - ], - initialize: function initialize (options) { - merge(this, separatorContract(extend(defaults, options))); - }, - type: 'separator', - toString: () => '[object Separator]' -}); -exports.Separator = Separator; - -/* - * Functions - */ - -function save (items, options) { - items = [].concat(items); - options = options || {}; - let emitter = EventTarget(); - let results = []; - let errors = []; - let root = constructTree(items); - let cache = new Map(); - - let isExplicitSave = item => !!~items.indexOf(item); - // `walk` returns an aggregate promise indicating the completion - // of the `commitItem` on each node, not whether or not that - // commit was successful - - // Force this to be async, as if a ducktype fails validation, - // the promise implementation will fire an error event, which will - // not trigger the handler as it's not yet bound - // - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => root.walk(preCommitItem).then(commitComplete))(); - - function preCommitItem ({value:item}) { - // Do nothing if tree root, default group (unsavable), - // or if it's a dependency and not explicitly saved (in the list - // of items to be saved), and not needed to be saved - if (item === null || // node is the tree root - isRootGroup(item) || - (getId(item) && !isExplicitSave(item))) - return; - - return promised(validate)(item) - .then(() => commitItem(item, options)) - .then(data => construct(data, cache)) - .then(savedItem => { - // If item was just created, make a map between - // the creation object and created object, - // so we can reference the item that doesn't have an id - if (!getId(item)) - saveId(item, savedItem.id); - - // Emit both the processed item, and original item - // so a mapping can be understood in handler - emit(emitter, 'data', savedItem, item); - - // Push to results iff item was explicitly saved - if (isExplicitSave(item)) - results[items.indexOf(item)] = savedItem; - }, reason => { - // Force reason to be a string for consistency - reason = reason + ''; - // Emit both the reason, and original item - // so a mapping can be understood in handler - emit(emitter, 'error', reason + '', item); - // Store unsaved item in results list - results[items.indexOf(item)] = item; - errors.push(reason); - }); - } - - // Called when traversal of the node tree is completed and all - // items have been committed - function commitComplete () { - emit(emitter, 'end', results); - } - - return emitter; -} -exports.save = save; - -function search (queries, options) { - queries = [].concat(queries); - let emitter = EventTarget(); - let cache = new Map(); - let queryObjs = queries.map(createQuery.bind(null, BOOKMARK_QUERY)); - let optionsObj = createQueryOptions(BOOKMARK_QUERY, options); - - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => { - send('sdk-places-query', { queries: queryObjs, options: optionsObj }) - .then(handleQueryResponse); - })(); - - function handleQueryResponse (data) { - let deferreds = data.map(item => { - return construct(item, cache).then(bookmark => { - emit(emitter, 'data', bookmark); - return bookmark; - }, reason => { - emit(emitter, 'error', reason); - errors.push(reason); - }); - }); - - all(deferreds).then(data => { - emit(emitter, 'end', data); - }, () => emit(emitter, 'end', [])); - } - - return emitter; -} -exports.search = search; - -function remove (items) { - return [].concat(items).map(item => { - item.remove = true; - return item; - }); -} - -exports.remove = remove; - -/* - * Internal Utilities - */ - -function commitItem (item, options) { - // Get the item's ID, or getId it's saved version if it exists - let id = getId(item); - let data = normalize(item); - let promise; - - data.id = id; - - if (!id) { - promise = send('sdk-places-bookmarks-create', data); - } else if (item.remove) { - promise = send('sdk-places-bookmarks-remove', { id: id }); - } else { - promise = send('sdk-places-bookmarks-last-updated', { - id: id - }).then(function (updated) { - // If attempting to save an item that is not the - // latest snapshot of a bookmark item, execute - // the resolution function - if (updated !== item.updated && options.resolve) - return fetchItem(id) - .then(options.resolve.bind(null, data)); - else - return data; - }).then(send.bind(null, 'sdk-places-bookmarks-save')); - } - - return promise; -} - -/* - * Turns a bookmark item into a plain object, - * converts `tags` from Set to Array, group instance to an id - */ -function normalize (item) { - let data = merge({}, item); - // Circumvent prototype property of `type` - delete data.type; - data.type = item.type; - data.tags = []; - if (item.tags) { - data.tags = fromIterator(item.tags); - } - data.group = getId(data.group) || exports.UNSORTED.id; - - return data; -} - -/* - * Takes a data object and constructs a BookmarkItem instance - * of it, recursively generating parent instances as well. - * - * Pass in a `cache` Map to reuse instances of - * bookmark items to reduce overhead; - * The cache object is a map of id to a deferred with a - * promise that resolves to the bookmark item. - */ -function construct (object, cache, forced) { - let item = instantiate(object); - let deferred = defer(); - - // Item could not be instantiated - if (!item) - return resolve(null); - - // Return promise for item if found in the cache, - // and not `forced`. `forced` indicates that this is the construct - // call that should not read from cache, but should actually perform - // the construction, as it was set before several async calls - if (cache.has(item.id) && !forced) - return cache.get(item.id).promise; - else if (cache.has(item.id)) - deferred = cache.get(item.id); - else - cache.set(item.id, deferred); - - // When parent group is found in cache, use - // the same deferred value - if (item.group && cache.has(item.group)) { - cache.get(item.group).promise.then(group => { - item.group = group; - deferred.resolve(item); - }); - - // If not in the cache, and a root group, return - // the premade instance - } else if (rootGroups.get(item.group)) { - item.group = rootGroups.get(item.group); - deferred.resolve(item); - - // If not in the cache or a root group, fetch the parent - } else { - cache.set(item.group, defer()); - fetchItem(item.group).then(group => { - return construct(group, cache, true); - }).then(group => { - item.group = group; - deferred.resolve(item); - }, deferred.reject); - } - - return deferred.promise; -} - -function instantiate (object) { - if (object.type === 'bookmark') - return Bookmark(object); - if (object.type === 'group') - return Group(object); - if (object.type === 'separator') - return Separator(object); - return null; -} - -/** - * Validates a bookmark item; will throw an error if ininvalid, - * to be used with `promised`. As bookmark items check on their class, - * this only checks ducktypes - */ -function validate (object) { - if (!isDuckType(object)) return true; - let contract = object.type === 'bookmark' ? bookmarkContract : - object.type === 'group' ? groupContract : - object.type === 'separator' ? separatorContract : - null; - if (!contract) { - throw Error('No type specified'); - } - - // If object has a property set, and undefined, - // manually override with default as it'll fail otherwise - let withDefaults = Object.keys(defaults).reduce((obj, prop) => { - if (obj[prop] == null) obj[prop] = defaults[prop]; - return obj; - }, extend(object)); - - contract(withDefaults); -} - -function isDuckType (item) { - return !(item instanceof Bookmark) && - !(item instanceof Group) && - !(item instanceof Separator); -} - -function saveId (unsaved, id) { - itemMap.set(unsaved, id); -} - -// Fetches an item's ID from itself, or from the mapped items -function getId (item) { - return typeof item === 'number' ? item : - item ? item.id || itemMap.get(item) : - null; -} - -/* - * Set up the default, root groups - */ - -var defaultGroupMap = { - MENU: bmsrv.bookmarksMenuFolder, - TOOLBAR: bmsrv.toolbarFolder, - UNSORTED: bmsrv.unfiledBookmarksFolder -}; - -var rootGroups = new Map(); - -for (let i in defaultGroupMap) { - let group = Object.freeze(Group({ title: i, id: defaultGroupMap[i] })); - rootGroups.set(defaultGroupMap[i], group); - exports[i] = group; -} - -var defaults = { - group: exports.UNSORTED, - index: -1 -}; diff --git a/addon-sdk/source/lib/sdk/places/contract.js b/addon-sdk/source/lib/sdk/places/contract.js deleted file mode 100644 index a3541c34d..000000000 --- a/addon-sdk/source/lib/sdk/places/contract.js +++ /dev/null @@ -1,73 +0,0 @@ -/* 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": "unstable" -}; - -const { Cc, Ci } = require('chrome'); -const { isValidURI, URL } = require('../url'); -const { contract } = require('../util/contract'); -const { extend } = require('../util/object'); - -// map of property validations -const validItem = { - id: { - is: ['number', 'undefined', 'null'], - }, - group: { - is: ['object', 'number', 'undefined', 'null'], - ok: function (value) { - return value && - (value.toString && value.toString() === '[object Group]') || - typeof value === 'number' || - value.type === 'group'; - }, - msg: 'The `group` property must be a valid Group object' - }, - index: { - is: ['undefined', 'null', 'number'], - map: value => value == null ? -1 : value, - msg: 'The `index` property must be a number.' - }, - updated: { - is: ['number', 'undefined'] - } -}; - -const validTitle = { - title: { - is: ['string'], - msg: 'The `title` property must be defined.' - } -}; - -const validURL = { - url: { - is: ['string'], - ok: isValidURI, - msg: 'The `url` property must be a valid URL.' - } -}; - -const validTags = { - tags: { - is: ['object'], - ok: tags => tags instanceof Set, - map: function (tags) { - if (Array.isArray(tags)) - return new Set(tags); - if (tags == null) - return new Set(); - return tags; - }, - msg: 'The `tags` property must be a Set, or an array' - } -}; - -exports.bookmarkContract = contract( - extend(validItem, validTitle, validURL, validTags)); -exports.separatorContract = contract(validItem); -exports.groupContract = contract(extend(validItem, validTitle)); diff --git a/addon-sdk/source/lib/sdk/places/events.js b/addon-sdk/source/lib/sdk/places/events.js deleted file mode 100644 index a3f95ee03..000000000 --- a/addon-sdk/source/lib/sdk/places/events.js +++ /dev/null @@ -1,128 +0,0 @@ -/* 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 } = require('chrome'); -const { Unknown } = require('../platform/xpcom'); -const { Class } = require('../core/heritage'); -const { merge } = require('../util/object'); -const bookmarkService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] - .getService(Ci.nsINavBookmarksService); -const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] - .getService(Ci.nsINavHistoryService); -const { mapBookmarkItemType } = require('./utils'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const { when } = require('../system/unload'); - -const emitter = EventTarget(); - -var HISTORY_ARGS = { - onBeginUpdateBatch: [], - onEndUpdateBatch: [], - onClearHistory: [], - onDeleteURI: ['url'], - onDeleteVisits: ['url', 'visitTime'], - onPageChanged: ['url', 'property', 'value'], - onTitleChanged: ['url', 'title'], - onVisit: [ - 'url', 'visitId', 'time', 'sessionId', 'referringId', 'transitionType' - ] -}; - -var HISTORY_EVENTS = { - onBeginUpdateBatch: 'history-start-batch', - onEndUpdateBatch: 'history-end-batch', - onClearHistory: 'history-start-clear', - onDeleteURI: 'history-delete-url', - onDeleteVisits: 'history-delete-visits', - onPageChanged: 'history-page-changed', - onTitleChanged: 'history-title-changed', - onVisit: 'history-visit' -}; - -var BOOKMARK_ARGS = { - onItemAdded: [ - 'id', 'parentId', 'index', 'type', 'url', 'title', 'dateAdded' - ], - onItemChanged: [ - 'id', 'property', null, 'value', 'lastModified', 'type', 'parentId' - ], - onItemMoved: [ - 'id', 'previousParentId', 'previousIndex', 'currentParentId', - 'currentIndex', 'type' - ], - onItemRemoved: ['id', 'parentId', 'index', 'type', 'url'], - onItemVisited: ['id', 'visitId', 'time', 'transitionType', 'url', 'parentId'] -}; - -var BOOKMARK_EVENTS = { - onItemAdded: 'bookmark-item-added', - onItemChanged: 'bookmark-item-changed', - onItemMoved: 'bookmark-item-moved', - onItemRemoved: 'bookmark-item-removed', - onItemVisited: 'bookmark-item-visited', -}; - -function createHandler (type, propNames) { - propNames = propNames || []; - return function (...args) { - let data = propNames.reduce((acc, prop, i) => { - if (prop) - acc[prop] = formatValue(prop, args[i]); - return acc; - }, {}); - - emit(emitter, 'data', { - type: type, - data: data - }); - }; -} - -/* - * Creates an observer, creating handlers based off of - * the `events` names, and ordering arguments from `propNames` hash - */ -function createObserverInstance (events, propNames) { - let definition = Object.keys(events).reduce((prototype, eventName) => { - prototype[eventName] = createHandler(events[eventName], propNames[eventName]); - return prototype; - }, {}); - - return Class(merge(definition, { extends: Unknown }))(); -} - -/* - * Formats `data` based off of the value of `type` - */ -function formatValue (type, data) { - if (type === 'type') - return mapBookmarkItemType(data); - if (type === 'url' && data) - return data.spec; - return data; -} - -var historyObserver = createObserverInstance(HISTORY_EVENTS, HISTORY_ARGS); -historyService.addObserver(historyObserver, false); - -var bookmarkObserver = createObserverInstance(BOOKMARK_EVENTS, BOOKMARK_ARGS); -bookmarkService.addObserver(bookmarkObserver, false); - -when(() => { - historyService.removeObserver(historyObserver); - bookmarkService.removeObserver(bookmarkObserver); -}); - -exports.events = emitter; diff --git a/addon-sdk/source/lib/sdk/places/favicon.js b/addon-sdk/source/lib/sdk/places/favicon.js deleted file mode 100644 index 05b057db1..000000000 --- a/addon-sdk/source/lib/sdk/places/favicon.js +++ /dev/null @@ -1,49 +0,0 @@ -/* 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": "unstable", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci, Cu } = require("chrome"); -const { defer, reject } = require("../core/promise"); -const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"]. - getService(Ci.nsIFaviconService); -const AsyncFavicons = FaviconService.QueryInterface(Ci.mozIAsyncFavicons); -const { isValidURI } = require("../url"); -const { newURI, getURL } = require("../url/utils"); - -/** - * Takes an object of several possible types and - * returns a promise that resolves to the page's favicon URI. - * @param {String|Tab} object - * @param {Function} (callback) - * @returns {Promise} - */ - -function getFavicon (object, callback) { - let url = getURL(object); - let deferred = defer(); - - if (url && isValidURI(url)) { - AsyncFavicons.getFaviconURLForPage(newURI(url), function (aURI) { - if (aURI && aURI.spec) - deferred.resolve(aURI.spec.toString()); - else - deferred.reject(null); - }); - } else { - deferred.reject(null); - } - - if (callback) deferred.promise.then(callback, callback); - return deferred.promise; -} -exports.getFavicon = getFavicon; diff --git a/addon-sdk/source/lib/sdk/places/history.js b/addon-sdk/source/lib/sdk/places/history.js deleted file mode 100644 index b243b024c..000000000 --- a/addon-sdk/source/lib/sdk/places/history.js +++ /dev/null @@ -1,65 +0,0 @@ -/* 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": "unstable", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -/* - * Requiring hosts so they can subscribe to client messages - */ -require('./host/host-bookmarks'); -require('./host/host-tags'); -require('./host/host-query'); - -const { Cc, Ci } = require('chrome'); -const { Class } = require('../core/heritage'); -const { events, send } = require('../addon/events'); -const { defer, reject, all } = require('../core/promise'); -const { uuid } = require('../util/uuid'); -const { flatten } = require('../util/array'); -const { has, extend, merge, pick } = require('../util/object'); -const { emit } = require('../event/core'); -const { defer: async } = require('../lang/functional'); -const { EventTarget } = require('../event/target'); -const { - urlQueryParser, createQuery, createQueryOptions -} = require('./utils'); - -/* - * Constant used by nsIHistoryQuery; 0 is a history query - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions - */ -const HISTORY_QUERY = 0; - -var search = function query (queries, options) { - queries = [].concat(queries); - let emitter = EventTarget(); - let queryObjs = queries.map(createQuery.bind(null, HISTORY_QUERY)); - let optionsObj = createQueryOptions(HISTORY_QUERY, options); - - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => { - send('sdk-places-query', { - query: queryObjs, - options: optionsObj - }).then(results => { - results.map(item => emit(emitter, 'data', item)); - emit(emitter, 'end', results); - }, reason => { - emit(emitter, 'error', reason); - emit(emitter, 'end', []); - }); - })(); - - return emitter; -}; -exports.search = search; diff --git a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js b/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js deleted file mode 100644 index 3245c4070..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js +++ /dev/null @@ -1,238 +0,0 @@ -/* 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 } = require('chrome'); -const browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"]. - getService(Ci.nsIBrowserHistory); -const asyncHistory = Cc["@mozilla.org/browser/history;1"]. - getService(Ci.mozIAsyncHistory); -const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); -const taggingService = Cc["@mozilla.org/browser/tagging-service;1"]. - getService(Ci.nsITaggingService); -const ios = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); -const { query } = require('./host-query'); -const { - defer, all, resolve, promised, reject -} = require('../../core/promise'); -const { request, response } = require('../../addon/host'); -const { send } = require('../../addon/events'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); -const { URL, isValidURI } = require('../../url'); -const { newURI } = require('../../url/utils'); - -const DEFAULT_INDEX = bmsrv.DEFAULT_INDEX; -const UNSORTED_ID = bmsrv.unfiledBookmarksFolder; -const ROOT_FOLDERS = [ - bmsrv.unfiledBookmarksFolder, bmsrv.toolbarFolder, - bmsrv.tagsFolder, bmsrv.bookmarksMenuFolder -]; - -const EVENT_MAP = { - 'sdk-places-bookmarks-create': createBookmarkItem, - 'sdk-places-bookmarks-save': saveBookmarkItem, - 'sdk-places-bookmarks-last-updated': getBookmarkLastUpdated, - 'sdk-places-bookmarks-get': getBookmarkItem, - 'sdk-places-bookmarks-remove': removeBookmarkItem, - 'sdk-places-bookmarks-get-all': getAllBookmarks, - 'sdk-places-bookmarks-get-children': getChildren -}; - -function typeMap (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; - } -} - -function getBookmarkLastUpdated ({id}) { - return resolve(bmsrv.getItemLastModified(id)); -} -exports.getBookmarkLastUpdated; - -function createBookmarkItem (data) { - let error; - - if (data.group == null) data.group = UNSORTED_ID; - if (data.index == null) data.index = DEFAULT_INDEX; - - if (data.type === 'group') - data.id = bmsrv.createFolder( - data.group, data.title, data.index - ); - else if (data.type === 'separator') - data.id = bmsrv.insertSeparator( - data.group, data.index - ); - else - data.id = bmsrv.insertBookmark( - data.group, newURI(data.url), data.index, data.title - ); - - // In the event where default or no index is provided (-1), - // query the actual index for the response - if (data.index === -1) - data.index = bmsrv.getItemIndex(data.id); - - try { - data.updated = bmsrv.getItemLastModified(data.id); - } - catch (e) { - console.exception(e); - } - - return tag(data, true).then(() => data); -} -exports.createBookmarkItem = createBookmarkItem; - -function saveBookmarkItem (data) { - let id = data.id; - if (!id) - reject('Item is missing id'); - - let group = bmsrv.getFolderIdForItem(id); - let index = bmsrv.getItemIndex(id); - let type = bmsrv.getItemType(id); - let title = typeMap(type) !== 'separator' ? - bmsrv.getItemTitle(id) : - undefined; - let url = typeMap(type) === 'bookmark' ? - bmsrv.getBookmarkURI(id).spec : - undefined; - - if (url != data.url) - bmsrv.changeBookmarkURI(id, newURI(data.url)); - else if (typeMap(type) === 'bookmark') - data.url = url; - - if (title != data.title) - bmsrv.setItemTitle(id, data.title); - else if (typeMap(type) !== 'separator') - data.title = title; - - if (data.group && data.group !== group) - bmsrv.moveItem(id, data.group, data.index || -1); - else if (data.index != null && data.index !== index) { - // We use moveItem here instead of setItemIndex - // so we don't have to manage the indicies of the siblings - bmsrv.moveItem(id, group, data.index); - } else if (data.index == null) - data.index = index; - - data.updated = bmsrv.getItemLastModified(data.id); - - return tag(data).then(() => data); -} -exports.saveBookmarkItem = saveBookmarkItem; - -function removeBookmarkItem (data) { - let id = data.id; - - if (!id) - reject('Item is missing id'); - - bmsrv.removeItem(id); - return resolve(null); -} -exports.removeBookmarkItem = removeBookmarkItem; - -function getBookmarkItem (data) { - let id = data.id; - - if (!id) - reject('Item is missing id'); - - let type = bmsrv.getItemType(id); - - data.type = typeMap(type); - - if (type === bmsrv.TYPE_BOOKMARK || type === bmsrv.TYPE_FOLDER) - data.title = bmsrv.getItemTitle(id); - - if (type === bmsrv.TYPE_BOOKMARK) { - data.url = bmsrv.getBookmarkURI(id).spec; - // Should be moved into host-tags as a method - data.tags = taggingService.getTagsForURI(newURI(data.url), {}); - } - - data.group = bmsrv.getFolderIdForItem(id); - data.index = bmsrv.getItemIndex(id); - data.updated = bmsrv.getItemLastModified(data.id); - - return resolve(data); -} -exports.getBookmarkItem = getBookmarkItem; - -function getAllBookmarks () { - return query({}, { queryType: 1 }).then(bookmarks => - all(bookmarks.map(getBookmarkItem))); -} -exports.getAllBookmarks = getAllBookmarks; - -function getChildren ({ id }) { - if (typeMap(bmsrv.getItemType(id)) !== 'group') return []; - let ids = []; - for (let i = 0; ids[ids.length - 1] !== -1; i++) - ids.push(bmsrv.getIdForItemAt(id, i)); - ids.pop(); - return all(ids.map(id => getBookmarkItem({ id: id }))); -} -exports.getChildren = getChildren; - -/* - * Hook into host - */ - -var reqStream = filter(request, (data) => /sdk-places-bookmarks/.test(data.event)); -on(reqStream, 'data', ({ event, id, data }) => { - if (!EVENT_MAP[event]) return; - - let resData = { id: id, event: event }; - - promised(EVENT_MAP[event])(data). - then(res => resData.data = res, e => resData.error = e). - then(() => emit(response, 'data', resData)); -}); - -function tag (data, isNew) { - // If a new item, we can skip checking what other tags - // are on the item - if (data.type !== 'bookmark') { - return resolve(); - } - else if (!isNew) { - return send('sdk-places-tags-get-tags-by-url', { url: data.url }) - .then(tags => { - return send('sdk-places-tags-untag', { - tags: tags.filter(tag => !~data.tags.indexOf(tag)), - url: data.url - }); - }).then(() => send('sdk-places-tags-tag', { - url: data.url, tags: data.tags - })); - } - else if (data.tags && data.tags.length) { - return send('sdk-places-tags-tag', { url: data.url, tags: data.tags }); - } - else - return resolve(); -} - diff --git a/addon-sdk/source/lib/sdk/places/host/host-query.js b/addon-sdk/source/lib/sdk/places/host/host-query.js deleted file mode 100644 index f2dbd6550..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-query.js +++ /dev/null @@ -1,179 +0,0 @@ -/* 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 } = require('chrome'); -const { all } = require('../../core/promise'); -const { safeMerge, omit } = require('../../util/object'); -const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] - .getService(Ci.nsINavHistoryService); -const bookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] - .getService(Ci.nsINavBookmarksService); -const { request, response } = require('../../addon/host'); -const { newURI } = require('../../url/utils'); -const { send } = require('../../addon/events'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); - -const ROOT_FOLDERS = [ - bookmarksService.unfiledBookmarksFolder, bookmarksService.toolbarFolder, - bookmarksService.bookmarksMenuFolder -]; - -const EVENT_MAP = { - 'sdk-places-query': queryReceiver -}; - -// Properties that need to be manually -// copied into a nsINavHistoryQuery object -const MANUAL_QUERY_PROPERTIES = [ - 'uri', 'folder', 'tags', 'url', 'folder' -]; - -const PLACES_PROPERTIES = [ - 'uri', 'title', 'accessCount', 'time' -]; - -function execute (queries, options) { - return new Promise(resolve => { - let root = historyService - .executeQueries(queries, queries.length, options).root; - // Let's extract an eventual uri wildcard, if both domain and uri are set. - // See utils.js::urlQueryParser() for more details. - // In case of multiple queries, we only retain the first found wildcard. - let uriWildcard = queries.reduce((prev, query) => { - if (query.uri && query.domain) { - if (!prev) - prev = query.uri.spec; - query.uri = null; - } - return prev; - }, ""); - resolve(collect([], root, uriWildcard)); - }); -} - -function collect (acc, node, uriWildcard) { - node.containerOpen = true; - for (let i = 0; i < node.childCount; i++) { - let child = node.getChild(i); - - if (!uriWildcard || child.uri.startsWith(uriWildcard)) { - acc.push(child); - } - if (child.type === child.RESULT_TYPE_FOLDER) { - let container = child.QueryInterface(Ci.nsINavHistoryContainerResultNode); - collect(acc, container, uriWildcard); - } - } - node.containerOpen = false; - return acc; -} - -function query (queries, options) { - return new Promise((resolve, reject) => { - queries = queries || []; - options = options || {}; - let optionsObj, queryObjs; - - optionsObj = historyService.getNewQueryOptions(); - queryObjs = [].concat(queries).map(createQuery); - if (!queryObjs.length) { - queryObjs = [historyService.getNewQuery()]; - } - safeMerge(optionsObj, options); - - /* - * Currently `places:` queries are not supported - */ - optionsObj.excludeQueries = true; - - execute(queryObjs, optionsObj).then((results) => { - if (optionsObj.queryType === 0) { - return results.map(normalize); - } - else if (optionsObj.queryType === 1) { - // Formats query results into more standard - // data structures for returning - return all(results.map(({itemId}) => - send('sdk-places-bookmarks-get', { id: itemId }))); - } - }).then(resolve, reject); - }); -} -exports.query = query; - -function createQuery (query) { - query = query || {}; - let queryObj = historyService.getNewQuery(); - - safeMerge(queryObj, omit(query, MANUAL_QUERY_PROPERTIES)); - - if (query.tags && Array.isArray(query.tags)) - queryObj.tags = query.tags; - if (query.uri || query.url) - queryObj.uri = newURI(query.uri || query.url); - if (query.folder) - queryObj.setFolders([query.folder], 1); - return queryObj; -} - -function queryReceiver (message) { - let queries = message.data.queries || message.data.query; - let options = message.data.options; - let resData = { - id: message.id, - event: message.event - }; - - query(queries, options).then(results => { - resData.data = results; - respond(resData); - }, reason => { - resData.error = reason; - respond(resData); - }); -} - -/* - * Converts a nsINavHistoryResultNode into a plain object - * - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode - */ -function normalize (historyObj) { - return PLACES_PROPERTIES.reduce((obj, prop) => { - if (prop === 'uri') - obj.url = historyObj.uri; - else if (prop === 'time') { - // Cast from microseconds to milliseconds - obj.time = Math.floor(historyObj.time / 1000) - } - else if (prop === 'accessCount') - obj.visitCount = historyObj[prop]; - else - obj[prop] = historyObj[prop]; - return obj; - }, {}); -} - -/* - * Hook into host - */ - -var reqStream = filter(request, data => /sdk-places-query/.test(data.event)); -on(reqStream, 'data', function (e) { - if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e); -}); - -function respond (data) { - emit(response, 'data', data); -} diff --git a/addon-sdk/source/lib/sdk/places/host/host-tags.js b/addon-sdk/source/lib/sdk/places/host/host-tags.js deleted file mode 100644 index 929a5d5af..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-tags.js +++ /dev/null @@ -1,92 +0,0 @@ -/* 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 } = require('chrome'); -const taggingService = Cc["@mozilla.org/browser/tagging-service;1"]. - getService(Ci.nsITaggingService); -const ios = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); -const { URL } = require('../../url'); -const { newURI } = require('../../url/utils'); -const { request, response } = require('../../addon/host'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); - -const EVENT_MAP = { - 'sdk-places-tags-tag': tag, - 'sdk-places-tags-untag': untag, - 'sdk-places-tags-get-tags-by-url': getTagsByURL, - 'sdk-places-tags-get-urls-by-tag': getURLsByTag -}; - -function tag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.tagURI(newURI(data.url), data.tags); - respond(resData); -} - -function untag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.untagURI(newURI(data.url), data.tags); - respond(resData); -} - -function getURLsByTag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService - .getURIsForTag(data.tag).map(uri => uri.spec); - respond(resData); -} - -function getTagsByURL (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.getTagsForURI(newURI(data.url), {}); - respond(resData); -} - -/* - * Hook into host - */ - -var reqStream = filter(request, function (data) { - return /sdk-places-tags/.test(data.event); -}); - -on(reqStream, 'data', function (e) { - if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e); -}); - -function respond (data) { - emit(response, 'data', data); -} diff --git a/addon-sdk/source/lib/sdk/places/utils.js b/addon-sdk/source/lib/sdk/places/utils.js deleted file mode 100644 index 44366d2aa..000000000 --- a/addon-sdk/source/lib/sdk/places/utils.js +++ /dev/null @@ -1,268 +0,0 @@ -/* 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; |