298 lines
9.2 KiB
JavaScript
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"];
|