Files
tubestation/browser/extensions/activity-stream/lib/PlacesFeed.jsm

298 lines
9.2 KiB
JavaScript

/* 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";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
"chrome://pocket/content/Pocket.jsm");
const LINK_BLOCKED_EVENT = "newtab-linkBlocked";
/**
* Observer - a wrapper around history/bookmark observers to add the QueryInterface.
*/
class Observer {
constructor(dispatch, observerInterface) {
this.dispatch = dispatch;
this.QueryInterface = XPCOMUtils.generateQI([observerInterface, Ci.nsISupportsWeakReference]);
}
}
/**
* HistoryObserver - observes events from PlacesUtils.history
*/
class HistoryObserver extends Observer {
constructor(dispatch) {
super(dispatch, Ci.nsINavHistoryObserver);
}
/**
* onDeleteURI - Called when an link is deleted from history.
*
* @param {obj} uri A URI object representing the link's url
* {str} uri.spec The URI as a string
*/
async onDeleteURI(uri) {
// Add to an existing array of links if we haven't dispatched yet
const {spec} = uri;
if (this._deletedLinks) {
this._deletedLinks.push(spec);
} else {
// Store an array of synchronously deleted links
this._deletedLinks = [spec];
// Only dispatch a single action when we've gotten all deleted urls
await Promise.resolve().then(() => {
this.dispatch({
type: at.PLACES_LINKS_DELETED,
data: this._deletedLinks
});
delete this._deletedLinks;
});
}
}
/**
* onClearHistory - Called when the user clears their entire history.
*/
onClearHistory() {
this.dispatch({type: at.PLACES_HISTORY_CLEARED});
}
// Empty functions to make xpconnect happy
onBeginUpdateBatch() {}
onEndUpdateBatch() {}
onVisit() {}
onTitleChanged() {}
onFrecencyChanged() {}
onManyFrecenciesChanged() {}
onPageChanged() {}
onDeleteVisits() {}
}
/**
* BookmarksObserver - observes events from PlacesUtils.bookmarks
*/
class BookmarksObserver extends Observer {
constructor(dispatch) {
super(dispatch, Ci.nsINavBookmarkObserver);
}
/**
* onItemAdded - Called when a bookmark is added
*
* @param {str} id
* @param {str} folderId
* @param {int} index
* @param {int} type Indicates if the bookmark is an actual bookmark,
* a folder, or a separator.
* @param {str} uri
* @param {str} title
* @param {int} dateAdded
* @param {str} guid The unique id of the bookmark
*/
onItemAdded(...args) {
const type = args[3];
if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK) {
return;
}
const uri = args[4];
const bookmarkTitle = args[5];
const dateAdded = args[6];
const bookmarkGuid = args[7];
this.dispatch({
type: at.PLACES_BOOKMARK_ADDED,
data: {
bookmarkGuid,
bookmarkTitle,
dateAdded,
url: uri.spec
}
});
}
/**
* onItemRemoved - Called when a bookmark is removed
*
* @param {str} id
* @param {str} folderId
* @param {int} index
* @param {int} type Indicates if the bookmark is an actual bookmark,
* a folder, or a separator.
* @param {str} uri
* @param {str} guid The unique id of the bookmark
*/
onItemRemoved(id, folderId, index, type, uri, guid) {
if (type === PlacesUtils.bookmarks.TYPE_BOOKMARK) {
this.dispatch({
type: at.PLACES_BOOKMARK_REMOVED,
data: {url: uri.spec, bookmarkGuid: guid}
});
}
}
/**
* onItemChanged - Called when a bookmark is modified
*
* @param {str} id description
* @param {str} property The property that was modified (e.g. uri, title)
* @param {bool} isAnnotation
* @param {any} value
* @param {int} lastModified
* @param {int} type Indicates if the bookmark is an actual bookmark,
* a folder, or a separator.
* @param {int} parent
* @param {str} guid The unique id of the bookmark
*/
async onItemChanged(...args) {
/*
// Disabled due to performance cost, see Issue 3203 /
// https://bugzilla.mozilla.org/show_bug.cgi?id=1392267.
//
// If this is used, please consider avoiding the call to
// NewTabUtils.activityStreamProvider.getBookmark which performs an additional
// fetch to the database.
// If you need more fields, please talk to the places team.
const property = args[1];
const type = args[5];
const guid = args[7];
// Only process this event if it is a TYPE_BOOKMARK, and uri or title was the property changed.
if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK || !["uri", "title"].includes(property)) {
return;
}
try {
// bookmark: {bookmarkGuid, bookmarkTitle, lastModified, url}
const bookmark = await NewTabUtils.activityStreamProvider.getBookmark(guid);
this.dispatch({type: at.PLACES_BOOKMARK_CHANGED, data: bookmark});
} catch (e) {
Cu.reportError(e);
}
*/
}
// Empty functions to make xpconnect happy
onBeginUpdateBatch() {}
onEndUpdateBatch() {}
onItemVisited() {}
onItemMoved() {}
}
class PlacesFeed {
constructor() {
this.historyObserver = new HistoryObserver(action => this.store.dispatch(ac.BroadcastToContent(action)));
this.bookmarksObserver = new BookmarksObserver(action => this.store.dispatch(ac.BroadcastToContent(action)));
}
addObservers() {
// NB: Directly get services without importing the *BIG* PlacesUtils module
Cc["@mozilla.org/browser/nav-history-service;1"]
.getService(Ci.nsINavHistoryService)
.addObserver(this.historyObserver, true);
Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
.getService(Ci.nsINavBookmarksService)
.addObserver(this.bookmarksObserver, true);
Services.obs.addObserver(this, LINK_BLOCKED_EVENT);
}
removeObservers() {
PlacesUtils.history.removeObserver(this.historyObserver);
PlacesUtils.bookmarks.removeObserver(this.bookmarksObserver);
Services.obs.removeObserver(this, LINK_BLOCKED_EVENT);
}
/**
* observe - An observer for the LINK_BLOCKED_EVENT.
* Called when a link is blocked.
*
* @param {null} subject
* @param {str} topic The name of the event
* @param {str} value The data associated with the event
*/
observe(subject, topic, value) {
if (topic === LINK_BLOCKED_EVENT) {
this.store.dispatch(ac.BroadcastToContent({
type: at.PLACES_LINK_BLOCKED,
data: {url: value}
}));
}
}
openNewWindow(action, isPrivate = false) {
const win = action._target.browser.ownerGlobal;
const privateParam = {private: isPrivate};
const params = (action.data.referrer) ?
Object.assign(privateParam, {referrerURI: Services.io.newURI(action.data.referrer)}) : privateParam;
win.openLinkIn(action.data.url, "window", params);
}
onAction(action) {
switch (action.type) {
case at.INIT:
// Briefly avoid loading services for observing for better startup timing
Services.tm.dispatchToMainThread(() => this.addObservers());
break;
case at.UNINIT:
this.removeObservers();
break;
case at.BLOCK_URL:
NewTabUtils.activityStreamLinks.blockURL({url: action.data});
break;
case at.BOOKMARK_URL:
NewTabUtils.activityStreamLinks.addBookmark(action.data, action._target.browser);
break;
case at.DELETE_BOOKMARK_BY_ID:
NewTabUtils.activityStreamLinks.deleteBookmark(action.data);
break;
case at.DELETE_HISTORY_URL: {
const {url, forceBlock} = action.data;
NewTabUtils.activityStreamLinks.deleteHistoryEntry(url);
if (forceBlock) {
NewTabUtils.activityStreamLinks.blockURL({url});
}
break;
}
case at.OPEN_NEW_WINDOW:
this.openNewWindow(action);
break;
case at.OPEN_PRIVATE_WINDOW:
this.openNewWindow(action, true);
break;
case at.SAVE_TO_POCKET:
Pocket.savePage(action._target.browser, action.data.site.url, action.data.site.title);
break;
case at.OPEN_LINK: {
const win = action._target.browser.ownerGlobal;
const where = win.whereToOpenLink(action.data.event);
if (action.data.referrer) {
win.openLinkIn(action.data.url, where, {referrerURI: Services.io.newURI(action.data.referrer)});
} else {
win.openLinkIn(action.data.url, where, {});
}
break;
}
}
}
}
this.PlacesFeed = PlacesFeed;
// Exported for testing only
PlacesFeed.HistoryObserver = HistoryObserver;
PlacesFeed.BookmarksObserver = BookmarksObserver;
this.EXPORTED_SYMBOLS = ["PlacesFeed"];