mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-12 21:05:36 +00:00
Bug 1379522 - Add pinned sites, context menu ordering, cleared history, other fixes to Activity Stream. r=ursula
MozReview-Commit-ID: ESlcuSuzoDH --HG-- extra : rebase_source : 83739c36ee85a074fc672a8c446e0bea095e2284
This commit is contained in:
parent
dce63d52a4
commit
3c6952efcf
@ -39,6 +39,7 @@ for (const type of [
|
||||
"NEW_TAB_VISIBLE",
|
||||
"OPEN_NEW_WINDOW",
|
||||
"OPEN_PRIVATE_WINDOW",
|
||||
"PINNED_SITES_UPDATED",
|
||||
"PLACES_BOOKMARK_ADDED",
|
||||
"PLACES_BOOKMARK_CHANGED",
|
||||
"PLACES_BOOKMARK_REMOVED",
|
||||
@ -47,11 +48,14 @@ for (const type of [
|
||||
"PLACES_LINK_DELETED",
|
||||
"PREFS_INITIAL_VALUES",
|
||||
"PREF_CHANGED",
|
||||
"SAVE_TO_POCKET",
|
||||
"SCREENSHOT_UPDATED",
|
||||
"SET_PREF",
|
||||
"TELEMETRY_PERFORMANCE_EVENT",
|
||||
"TELEMETRY_UNDESIRED_EVENT",
|
||||
"TELEMETRY_USER_EVENT",
|
||||
"TOP_SITES_PIN",
|
||||
"TOP_SITES_UNPIN",
|
||||
"TOP_SITES_UPDATED",
|
||||
"UNINIT"
|
||||
]) {
|
||||
|
@ -51,9 +51,44 @@ function App(prevState = INITIAL_STATE.App, action) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* insertPinned - Inserts pinned links in their specified slots
|
||||
*
|
||||
* @param {array} a list of links
|
||||
* @param {array} a list of pinned links
|
||||
* @return {array} resulting list of links with pinned links inserted
|
||||
*/
|
||||
function insertPinned(links, pinned) {
|
||||
// Remove any pinned links
|
||||
const pinnedUrls = pinned.map(link => link && link.url);
|
||||
let newLinks = links.filter(link => (link ? !pinnedUrls.includes(link.url) : false));
|
||||
newLinks = newLinks.map(link => {
|
||||
if (link && link.isPinned) {
|
||||
delete link.isPinned;
|
||||
delete link.pinTitle;
|
||||
delete link.pinIndex;
|
||||
}
|
||||
return link;
|
||||
});
|
||||
|
||||
// Then insert them in their specified location
|
||||
pinned.forEach((val, index) => {
|
||||
if (!val) { return; }
|
||||
let link = Object.assign({}, val, {isPinned: true, pinIndex: index, pinTitle: val.title});
|
||||
if (index > newLinks.length) {
|
||||
newLinks[index] = link;
|
||||
} else {
|
||||
newLinks.splice(index, 0, link);
|
||||
}
|
||||
});
|
||||
|
||||
return newLinks;
|
||||
}
|
||||
|
||||
function TopSites(prevState = INITIAL_STATE.TopSites, action) {
|
||||
let hasMatch;
|
||||
let newRows;
|
||||
let pinned;
|
||||
switch (action.type) {
|
||||
case at.TOP_SITES_UPDATED:
|
||||
if (!action.data) {
|
||||
@ -62,7 +97,7 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
|
||||
return Object.assign({}, prevState, {initialized: true, rows: action.data});
|
||||
case at.SCREENSHOT_UPDATED:
|
||||
newRows = prevState.rows.map(row => {
|
||||
if (row.url === action.data.url) {
|
||||
if (row && row.url === action.data.url) {
|
||||
hasMatch = true;
|
||||
return Object.assign({}, row, {screenshot: action.data.screenshot});
|
||||
}
|
||||
@ -71,7 +106,7 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
|
||||
return hasMatch ? Object.assign({}, prevState, {rows: newRows}) : prevState;
|
||||
case at.PLACES_BOOKMARK_ADDED:
|
||||
newRows = prevState.rows.map(site => {
|
||||
if (site.url === action.data.url) {
|
||||
if (site && site.url === action.data.url) {
|
||||
const {bookmarkGuid, bookmarkTitle, lastModified} = action.data;
|
||||
return Object.assign({}, site, {bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified});
|
||||
}
|
||||
@ -80,7 +115,7 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
|
||||
return Object.assign({}, prevState, {rows: newRows});
|
||||
case at.PLACES_BOOKMARK_REMOVED:
|
||||
newRows = prevState.rows.map(site => {
|
||||
if (site.url === action.data.url) {
|
||||
if (site && site.url === action.data.url) {
|
||||
const newSite = Object.assign({}, site);
|
||||
delete newSite.bookmarkGuid;
|
||||
delete newSite.bookmarkTitle;
|
||||
@ -92,7 +127,11 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
|
||||
return Object.assign({}, prevState, {rows: newRows});
|
||||
case at.PLACES_LINK_DELETED:
|
||||
case at.PLACES_LINK_BLOCKED:
|
||||
newRows = prevState.rows.filter(val => val.url !== action.data.url);
|
||||
newRows = prevState.rows.filter(val => val && val.url !== action.data.url);
|
||||
return Object.assign({}, prevState, {rows: newRows});
|
||||
case at.PINNED_SITES_UPDATED:
|
||||
pinned = action.data;
|
||||
newRows = insertPinned(prevState.rows, pinned);
|
||||
return Object.assign({}, prevState, {rows: newRows});
|
||||
default:
|
||||
return prevState;
|
||||
@ -128,5 +167,6 @@ function Prefs(prevState = INITIAL_STATE.Prefs, action) {
|
||||
|
||||
this.INITIAL_STATE = INITIAL_STATE;
|
||||
this.reducers = {TopSites, App, Prefs, Dialog};
|
||||
this.insertPinned = insertPinned;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE"];
|
||||
this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE", "insertPinned"];
|
||||
|
@ -63,17 +63,11 @@
|
||||
/******/ __webpack_require__.p = "";
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 18);
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 19);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([
|
||||
/* 0 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = React;
|
||||
|
||||
/***/ }),
|
||||
/* 1 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
@ -103,7 +97,7 @@ const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS :
|
||||
// UNINIT: "UNINIT"
|
||||
// }
|
||||
const actionTypes = {};
|
||||
for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "INIT", "LOCALE_UPDATED", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "NEW_TAB_VISIBLE", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SCREENSHOT_UPDATED", "SET_PREF", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_UPDATED", "UNINIT"]) {
|
||||
for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "INIT", "LOCALE_UPDATED", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "NEW_TAB_VISIBLE", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PINNED_SITES_UPDATED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SET_PREF", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
|
||||
actionTypes[type] = type;
|
||||
}
|
||||
|
||||
@ -281,6 +275,12 @@ module.exports = {
|
||||
CONTENT_MESSAGE_TYPE
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 1 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = React;
|
||||
|
||||
/***/ }),
|
||||
/* 2 */
|
||||
/***/ (function(module, exports) {
|
||||
@ -300,7 +300,42 @@ module.exports = ReactIntl;
|
||||
"use strict";
|
||||
|
||||
|
||||
const React = __webpack_require__(0);
|
||||
/**
|
||||
* shortURL - Creates a short version of a link's url, used for display purposes
|
||||
* e.g. {url: http://www.foosite.com, eTLD: "com"} => "foosite"
|
||||
*
|
||||
* @param {obj} link A link object
|
||||
* {str} link.url (required)- The url of the link
|
||||
* {str} link.eTLD (required) - The tld of the link
|
||||
* e.g. for https://foo.org, the tld would be "org"
|
||||
* Note that this property is added in various queries for ActivityStream
|
||||
* via Services.eTLD.getPublicSuffix
|
||||
* {str} link.hostname (optional) - The hostname of the url
|
||||
* e.g. for http://www.hello.com/foo/bar, the hostname would be "www.hello.com"
|
||||
* @return {str} A short url
|
||||
*/
|
||||
module.exports = function shortURL(link) {
|
||||
if (!link.url && !link.hostname) {
|
||||
return "";
|
||||
}
|
||||
const eTLD = link.eTLD;
|
||||
|
||||
const hostname = (link.hostname || new URL(link.url).hostname).replace(/^www\./i, "");
|
||||
|
||||
// Remove the eTLD (e.g., com, net) and the preceding period from the hostname
|
||||
const eTLDLength = (eTLD || "").length || hostname.match(/\.com$/) && 3;
|
||||
const eTLDExtra = eTLDLength > 0 ? -(eTLDLength + 1) : Infinity;
|
||||
return hostname.slice(0, eTLDExtra).toLowerCase();
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 5 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
const React = __webpack_require__(1);
|
||||
|
||||
var _require = __webpack_require__(2);
|
||||
|
||||
@ -311,10 +346,10 @@ var _require2 = __webpack_require__(3);
|
||||
const addLocaleData = _require2.addLocaleData,
|
||||
IntlProvider = _require2.IntlProvider;
|
||||
|
||||
const TopSites = __webpack_require__(14);
|
||||
const Search = __webpack_require__(13);
|
||||
const ConfirmDialog = __webpack_require__(9);
|
||||
const PreferencesPane = __webpack_require__(12);
|
||||
const TopSites = __webpack_require__(15);
|
||||
const Search = __webpack_require__(14);
|
||||
const ConfirmDialog = __webpack_require__(10);
|
||||
const PreferencesPane = __webpack_require__(13);
|
||||
|
||||
// Locales that should be displayed RTL
|
||||
const RTL_LIST = ["ar", "he", "fa", "ur"];
|
||||
@ -385,17 +420,17 @@ class Base extends React.Component {
|
||||
module.exports = connect(state => ({ App: state.App, Prefs: state.Prefs }))(Base);
|
||||
|
||||
/***/ }),
|
||||
/* 5 */
|
||||
/* 6 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
var _require = __webpack_require__(1);
|
||||
var _require = __webpack_require__(0);
|
||||
|
||||
const at = _require.actionTypes;
|
||||
|
||||
var _require2 = __webpack_require__(16);
|
||||
var _require2 = __webpack_require__(17);
|
||||
|
||||
const perfSvc = _require2.perfService;
|
||||
|
||||
@ -460,7 +495,7 @@ module.exports = class DetectUserSessionStart {
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 6 */
|
||||
/* 7 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
@ -468,13 +503,13 @@ module.exports = class DetectUserSessionStart {
|
||||
|
||||
/* eslint-env mozilla/frame-script */
|
||||
|
||||
var _require = __webpack_require__(17);
|
||||
var _require = __webpack_require__(18);
|
||||
|
||||
const createStore = _require.createStore,
|
||||
combineReducers = _require.combineReducers,
|
||||
applyMiddleware = _require.applyMiddleware;
|
||||
|
||||
var _require2 = __webpack_require__(1);
|
||||
var _require2 = __webpack_require__(0);
|
||||
|
||||
const au = _require2.actionUtils;
|
||||
|
||||
@ -540,7 +575,7 @@ module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME;
|
||||
module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
|
||||
|
||||
/***/ }),
|
||||
/* 7 */
|
||||
/* 8 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
@ -549,7 +584,7 @@ module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
var _require = __webpack_require__(1);
|
||||
var _require = __webpack_require__(0);
|
||||
|
||||
const at = _require.actionTypes;
|
||||
|
||||
@ -607,12 +642,49 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* insertPinned - Inserts pinned links in their specified slots
|
||||
*
|
||||
* @param {array} a list of links
|
||||
* @param {array} a list of pinned links
|
||||
* @return {array} resulting list of links with pinned links inserted
|
||||
*/
|
||||
function insertPinned(links, pinned) {
|
||||
// Remove any pinned links
|
||||
const pinnedUrls = pinned.map(link => link && link.url);
|
||||
let newLinks = links.filter(link => link ? !pinnedUrls.includes(link.url) : false);
|
||||
newLinks = newLinks.map(link => {
|
||||
if (link && link.isPinned) {
|
||||
delete link.isPinned;
|
||||
delete link.pinTitle;
|
||||
delete link.pinIndex;
|
||||
}
|
||||
return link;
|
||||
});
|
||||
|
||||
// Then insert them in their specified location
|
||||
pinned.forEach((val, index) => {
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
let link = Object.assign({}, val, { isPinned: true, pinIndex: index, pinTitle: val.title });
|
||||
if (index > newLinks.length) {
|
||||
newLinks[index] = link;
|
||||
} else {
|
||||
newLinks.splice(index, 0, link);
|
||||
}
|
||||
});
|
||||
|
||||
return newLinks;
|
||||
}
|
||||
|
||||
function TopSites() {
|
||||
let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.TopSites;
|
||||
let action = arguments[1];
|
||||
|
||||
let hasMatch;
|
||||
let newRows;
|
||||
let pinned;
|
||||
switch (action.type) {
|
||||
case at.TOP_SITES_UPDATED:
|
||||
if (!action.data) {
|
||||
@ -621,7 +693,7 @@ function TopSites() {
|
||||
return Object.assign({}, prevState, { initialized: true, rows: action.data });
|
||||
case at.SCREENSHOT_UPDATED:
|
||||
newRows = prevState.rows.map(row => {
|
||||
if (row.url === action.data.url) {
|
||||
if (row && row.url === action.data.url) {
|
||||
hasMatch = true;
|
||||
return Object.assign({}, row, { screenshot: action.data.screenshot });
|
||||
}
|
||||
@ -630,7 +702,7 @@ function TopSites() {
|
||||
return hasMatch ? Object.assign({}, prevState, { rows: newRows }) : prevState;
|
||||
case at.PLACES_BOOKMARK_ADDED:
|
||||
newRows = prevState.rows.map(site => {
|
||||
if (site.url === action.data.url) {
|
||||
if (site && site.url === action.data.url) {
|
||||
var _action$data2 = action.data;
|
||||
const bookmarkGuid = _action$data2.bookmarkGuid,
|
||||
bookmarkTitle = _action$data2.bookmarkTitle,
|
||||
@ -643,7 +715,7 @@ function TopSites() {
|
||||
return Object.assign({}, prevState, { rows: newRows });
|
||||
case at.PLACES_BOOKMARK_REMOVED:
|
||||
newRows = prevState.rows.map(site => {
|
||||
if (site.url === action.data.url) {
|
||||
if (site && site.url === action.data.url) {
|
||||
const newSite = Object.assign({}, site);
|
||||
delete newSite.bookmarkGuid;
|
||||
delete newSite.bookmarkTitle;
|
||||
@ -655,7 +727,11 @@ function TopSites() {
|
||||
return Object.assign({}, prevState, { rows: newRows });
|
||||
case at.PLACES_LINK_DELETED:
|
||||
case at.PLACES_LINK_BLOCKED:
|
||||
newRows = prevState.rows.filter(val => val.url !== action.data.url);
|
||||
newRows = prevState.rows.filter(val => val && val.url !== action.data.url);
|
||||
return Object.assign({}, prevState, { rows: newRows });
|
||||
case at.PINNED_SITES_UPDATED:
|
||||
pinned = action.data;
|
||||
newRows = insertPinned(prevState.rows, pinned);
|
||||
return Object.assign({}, prevState, { rows: newRows });
|
||||
default:
|
||||
return prevState;
|
||||
@ -698,23 +774,24 @@ function Prefs() {
|
||||
var reducers = { TopSites, App, Prefs, Dialog };
|
||||
module.exports = {
|
||||
reducers,
|
||||
INITIAL_STATE
|
||||
INITIAL_STATE,
|
||||
insertPinned
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 8 */
|
||||
/* 9 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = ReactDOM;
|
||||
|
||||
/***/ }),
|
||||
/* 9 */
|
||||
/* 10 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
const React = __webpack_require__(0);
|
||||
const React = __webpack_require__(1);
|
||||
|
||||
var _require = __webpack_require__(2);
|
||||
|
||||
@ -724,7 +801,7 @@ var _require2 = __webpack_require__(3);
|
||||
|
||||
const FormattedMessage = _require2.FormattedMessage;
|
||||
|
||||
var _require3 = __webpack_require__(1);
|
||||
var _require3 = __webpack_require__(0);
|
||||
|
||||
const actionTypes = _require3.actionTypes,
|
||||
ac = _require3.actionCreators;
|
||||
@ -827,13 +904,13 @@ module.exports._unconnected = ConfirmDialog;
|
||||
module.exports.Dialog = ConfirmDialog;
|
||||
|
||||
/***/ }),
|
||||
/* 10 */
|
||||
/* 11 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
const React = __webpack_require__(0);
|
||||
const React = __webpack_require__(1);
|
||||
|
||||
class ContextMenu extends React.Component {
|
||||
constructor(props) {
|
||||
@ -910,108 +987,39 @@ class ContextMenu extends React.Component {
|
||||
module.exports = ContextMenu;
|
||||
|
||||
/***/ }),
|
||||
/* 11 */
|
||||
/* 12 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
const React = __webpack_require__(0);
|
||||
const React = __webpack_require__(1);
|
||||
|
||||
var _require = __webpack_require__(3);
|
||||
|
||||
const injectIntl = _require.injectIntl;
|
||||
|
||||
const ContextMenu = __webpack_require__(10);
|
||||
const ContextMenu = __webpack_require__(11);
|
||||
|
||||
var _require2 = __webpack_require__(1);
|
||||
var _require2 = __webpack_require__(0);
|
||||
|
||||
const at = _require2.actionTypes,
|
||||
ac = _require2.actionCreators;
|
||||
const ac = _require2.actionCreators;
|
||||
|
||||
|
||||
const RemoveBookmark = site => ({
|
||||
id: "menu_action_remove_bookmark",
|
||||
icon: "bookmark-remove",
|
||||
action: ac.SendToMain({
|
||||
type: at.DELETE_BOOKMARK_BY_ID,
|
||||
data: site.bookmarkGuid
|
||||
}),
|
||||
userEvent: "BOOKMARK_DELETE"
|
||||
});
|
||||
|
||||
const AddBookmark = site => ({
|
||||
id: "menu_action_bookmark",
|
||||
icon: "bookmark",
|
||||
action: ac.SendToMain({
|
||||
type: at.BOOKMARK_URL,
|
||||
data: site.url
|
||||
}),
|
||||
userEvent: "BOOKMARK_ADD"
|
||||
});
|
||||
|
||||
const OpenInNewWindow = site => ({
|
||||
id: "menu_action_open_new_window",
|
||||
icon: "new-window",
|
||||
action: ac.SendToMain({
|
||||
type: at.OPEN_NEW_WINDOW,
|
||||
data: { url: site.url }
|
||||
}),
|
||||
userEvent: "OPEN_NEW_WINDOW"
|
||||
});
|
||||
|
||||
const OpenInPrivateWindow = site => ({
|
||||
id: "menu_action_open_private_window",
|
||||
icon: "new-window-private",
|
||||
action: ac.SendToMain({
|
||||
type: at.OPEN_PRIVATE_WINDOW,
|
||||
data: { url: site.url }
|
||||
}),
|
||||
userEvent: "OPEN_PRIVATE_WINDOW"
|
||||
});
|
||||
|
||||
const BlockUrl = site => ({
|
||||
id: "menu_action_dismiss",
|
||||
icon: "dismiss",
|
||||
action: ac.SendToMain({
|
||||
type: at.BLOCK_URL,
|
||||
data: site.url
|
||||
}),
|
||||
userEvent: "BLOCK"
|
||||
});
|
||||
|
||||
const DeleteUrl = site => ({
|
||||
id: "menu_action_delete",
|
||||
icon: "delete",
|
||||
action: {
|
||||
type: at.DIALOG_OPEN,
|
||||
data: {
|
||||
onConfirm: [ac.SendToMain({ type: at.DELETE_HISTORY_URL, data: site.url }), ac.UserEvent({ event: "DELETE" })],
|
||||
body_string_id: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
|
||||
confirm_button_string_id: "menu_action_delete"
|
||||
}
|
||||
},
|
||||
userEvent: "DIALOG_OPEN"
|
||||
});
|
||||
const linkMenuOptions = __webpack_require__(16);
|
||||
const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow"];
|
||||
|
||||
class LinkMenu extends React.Component {
|
||||
getOptions() {
|
||||
const props = this.props;
|
||||
const site = props.site;
|
||||
const site = props.site,
|
||||
index = props.index,
|
||||
source = props.source;
|
||||
|
||||
const isBookmark = site.bookmarkGuid;
|
||||
const isDefault = site.isDefault;
|
||||
// Handle special case of default site
|
||||
|
||||
const options = [
|
||||
const propOptions = !site.isDefault ? props.options : DEFAULT_SITE_MENU_OPTIONS;
|
||||
|
||||
// Bookmarks
|
||||
!isDefault && (isBookmark ? RemoveBookmark(site) : AddBookmark(site)), !isDefault && { type: "separator" },
|
||||
|
||||
// Menu items for all sites
|
||||
OpenInNewWindow(site), OpenInPrivateWindow(site),
|
||||
|
||||
// Blocking and deleting
|
||||
!isDefault && { type: "separator" }, !isDefault && BlockUrl(site), !isDefault && DeleteUrl(site)].filter(o => o).map(option => {
|
||||
const options = propOptions.map(o => linkMenuOptions[o](site, index)).map(option => {
|
||||
const action = option.action,
|
||||
id = option.id,
|
||||
type = option.type,
|
||||
@ -1024,8 +1032,8 @@ class LinkMenu extends React.Component {
|
||||
if (userEvent) {
|
||||
props.dispatch(ac.UserEvent({
|
||||
event: userEvent,
|
||||
source: props.source,
|
||||
action_position: props.index
|
||||
source,
|
||||
action_position: index
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -1052,13 +1060,13 @@ module.exports = injectIntl(LinkMenu);
|
||||
module.exports._unconnected = LinkMenu;
|
||||
|
||||
/***/ }),
|
||||
/* 12 */
|
||||
/* 13 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
const React = __webpack_require__(0);
|
||||
const React = __webpack_require__(1);
|
||||
|
||||
var _require = __webpack_require__(2);
|
||||
|
||||
@ -1069,7 +1077,7 @@ var _require2 = __webpack_require__(3);
|
||||
const injectIntl = _require2.injectIntl,
|
||||
FormattedMessage = _require2.FormattedMessage;
|
||||
|
||||
var _require3 = __webpack_require__(1);
|
||||
var _require3 = __webpack_require__(0);
|
||||
|
||||
const ac = _require3.actionCreators;
|
||||
|
||||
@ -1178,14 +1186,14 @@ module.exports.PreferencesPane = PreferencesPane;
|
||||
module.exports.PreferencesInput = PreferencesInput;
|
||||
|
||||
/***/ }),
|
||||
/* 13 */
|
||||
/* 14 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
/* globals ContentSearchUIController */
|
||||
|
||||
|
||||
const React = __webpack_require__(0);
|
||||
const React = __webpack_require__(1);
|
||||
|
||||
var _require = __webpack_require__(2);
|
||||
|
||||
@ -1196,7 +1204,7 @@ var _require2 = __webpack_require__(3);
|
||||
const FormattedMessage = _require2.FormattedMessage,
|
||||
injectIntl = _require2.injectIntl;
|
||||
|
||||
var _require3 = __webpack_require__(1);
|
||||
var _require3 = __webpack_require__(0);
|
||||
|
||||
const ac = _require3.actionCreators;
|
||||
|
||||
@ -1277,13 +1285,13 @@ module.exports = connect()(injectIntl(Search));
|
||||
module.exports._unconnected = Search;
|
||||
|
||||
/***/ }),
|
||||
/* 14 */
|
||||
/* 15 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
const React = __webpack_require__(0);
|
||||
const React = __webpack_require__(1);
|
||||
|
||||
var _require = __webpack_require__(2);
|
||||
|
||||
@ -1293,14 +1301,15 @@ var _require2 = __webpack_require__(3);
|
||||
|
||||
const FormattedMessage = _require2.FormattedMessage;
|
||||
|
||||
const shortURL = __webpack_require__(15);
|
||||
const LinkMenu = __webpack_require__(11);
|
||||
const shortURL = __webpack_require__(4);
|
||||
const LinkMenu = __webpack_require__(12);
|
||||
|
||||
var _require3 = __webpack_require__(1);
|
||||
var _require3 = __webpack_require__(0);
|
||||
|
||||
const ac = _require3.actionCreators;
|
||||
|
||||
const TOP_SITES_SOURCE = "TOP_SITES";
|
||||
const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"];
|
||||
|
||||
class TopSite extends React.Component {
|
||||
constructor(props) {
|
||||
@ -1374,7 +1383,8 @@ class TopSite extends React.Component {
|
||||
onUpdate: val => this.setState({ showContextMenu: val }),
|
||||
site: link,
|
||||
index: index,
|
||||
source: TOP_SITES_SOURCE })
|
||||
source: TOP_SITES_SOURCE,
|
||||
options: TOP_SITES_CONTEXT_MENU_OPTIONS })
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1390,7 +1400,7 @@ const TopSites = props => React.createElement(
|
||||
React.createElement(
|
||||
"ul",
|
||||
{ className: "top-sites-list" },
|
||||
props.TopSites.rows.map((link, index) => React.createElement(TopSite, {
|
||||
props.TopSites.rows.map((link, index) => link && React.createElement(TopSite, {
|
||||
key: link.url,
|
||||
dispatch: props.dispatch,
|
||||
link: link,
|
||||
@ -1403,42 +1413,118 @@ module.exports._unconnected = TopSites;
|
||||
module.exports.TopSite = TopSite;
|
||||
|
||||
/***/ }),
|
||||
/* 15 */
|
||||
/* 16 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
var _require = __webpack_require__(0);
|
||||
|
||||
const at = _require.actionTypes,
|
||||
ac = _require.actionCreators;
|
||||
|
||||
const shortURL = __webpack_require__(4);
|
||||
|
||||
/**
|
||||
* shortURL - Creates a short version of a link's url, used for display purposes
|
||||
* e.g. {url: http://www.foosite.com, eTLD: "com"} => "foosite"
|
||||
*
|
||||
* @param {obj} link A link object
|
||||
* {str} link.url (required)- The url of the link
|
||||
* {str} link.eTLD (required) - The tld of the link
|
||||
* e.g. for https://foo.org, the tld would be "org"
|
||||
* Note that this property is added in various queries for ActivityStream
|
||||
* via Services.eTLD.getPublicSuffix
|
||||
* {str} link.hostname (optional) - The hostname of the url
|
||||
* e.g. for http://www.hello.com/foo/bar, the hostname would be "www.hello.com"
|
||||
* @return {str} A short url
|
||||
* List of functions that return items that can be included as menu options in a
|
||||
* LinkMenu. All functions take the site as the first parameter, and optionally
|
||||
* the index of the site.
|
||||
*/
|
||||
module.exports = function shortURL(link) {
|
||||
if (!link.url && !link.hostname) {
|
||||
return "";
|
||||
}
|
||||
const eTLD = link.eTLD;
|
||||
|
||||
const hostname = (link.hostname || new URL(link.url).hostname).replace(/^www\./i, "");
|
||||
|
||||
// Remove the eTLD (e.g., com, net) and the preceding period from the hostname
|
||||
const eTLDLength = (eTLD || "").length || hostname.match(/\.com$/) && 3;
|
||||
const eTLDExtra = eTLDLength > 0 ? -(eTLDLength + 1) : Infinity;
|
||||
return hostname.slice(0, eTLDExtra).toLowerCase();
|
||||
module.exports = {
|
||||
Separator: () => ({ type: "separator" }),
|
||||
RemoveBookmark: site => ({
|
||||
id: "menu_action_remove_bookmark",
|
||||
icon: "bookmark-remove",
|
||||
action: ac.SendToMain({
|
||||
type: at.DELETE_BOOKMARK_BY_ID,
|
||||
data: site.bookmarkGuid
|
||||
}),
|
||||
userEvent: "BOOKMARK_DELETE"
|
||||
}),
|
||||
AddBookmark: site => ({
|
||||
id: "menu_action_bookmark",
|
||||
icon: "bookmark",
|
||||
action: ac.SendToMain({
|
||||
type: at.BOOKMARK_URL,
|
||||
data: site.url
|
||||
}),
|
||||
userEvent: "BOOKMARK_ADD"
|
||||
}),
|
||||
OpenInNewWindow: site => ({
|
||||
id: "menu_action_open_new_window",
|
||||
icon: "new-window",
|
||||
action: ac.SendToMain({
|
||||
type: at.OPEN_NEW_WINDOW,
|
||||
data: { url: site.url }
|
||||
}),
|
||||
userEvent: "OPEN_NEW_WINDOW"
|
||||
}),
|
||||
OpenInPrivateWindow: site => ({
|
||||
id: "menu_action_open_private_window",
|
||||
icon: "new-window-private",
|
||||
action: ac.SendToMain({
|
||||
type: at.OPEN_PRIVATE_WINDOW,
|
||||
data: { url: site.url }
|
||||
}),
|
||||
userEvent: "OPEN_PRIVATE_WINDOW"
|
||||
}),
|
||||
BlockUrl: site => ({
|
||||
id: "menu_action_dismiss",
|
||||
icon: "dismiss",
|
||||
action: ac.SendToMain({
|
||||
type: at.BLOCK_URL,
|
||||
data: site.url
|
||||
}),
|
||||
userEvent: "BLOCK"
|
||||
}),
|
||||
DeleteUrl: site => ({
|
||||
id: "menu_action_delete",
|
||||
icon: "delete",
|
||||
action: {
|
||||
type: at.DIALOG_OPEN,
|
||||
data: {
|
||||
onConfirm: [ac.SendToMain({ type: at.DELETE_HISTORY_URL, data: site.url }), ac.UserEvent({ event: "DELETE" })],
|
||||
body_string_id: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
|
||||
confirm_button_string_id: "menu_action_delete"
|
||||
}
|
||||
},
|
||||
userEvent: "DIALOG_OPEN"
|
||||
}),
|
||||
PinTopSite: (site, index) => ({
|
||||
id: "menu_action_pin",
|
||||
icon: "pin",
|
||||
action: ac.SendToMain({
|
||||
type: at.TOP_SITES_PIN,
|
||||
data: { site: { url: site.url, title: shortURL(site) }, index }
|
||||
}),
|
||||
userEvent: "PIN"
|
||||
}),
|
||||
UnpinTopSite: site => ({
|
||||
id: "menu_action_unpin",
|
||||
icon: "unpin",
|
||||
action: ac.SendToMain({
|
||||
type: at.TOP_SITES_UNPIN,
|
||||
data: { site: { url: site.url } }
|
||||
}),
|
||||
userEvent: "UNPIN"
|
||||
}),
|
||||
SaveToPocket: site => ({
|
||||
id: "menu_action_save_to_pocket",
|
||||
icon: "pocket",
|
||||
action: ac.SendToMain({
|
||||
type: at.SAVE_TO_POCKET,
|
||||
data: { site: { url: site.url, title: site.title } }
|
||||
}),
|
||||
userEvent: "SAVE_TO_POCKET"
|
||||
})
|
||||
};
|
||||
|
||||
module.exports.CheckBookmark = site => site.bookmarkGuid ? module.exports.RemoveBookmark(site) : module.exports.AddBookmark(site);
|
||||
module.exports.CheckPinTopSite = (site, index) => site.isPinned ? module.exports.UnpinTopSite(site) : module.exports.PinTopSite(site, index);
|
||||
|
||||
/***/ }),
|
||||
/* 16 */
|
||||
/* 17 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
@ -1543,33 +1629,33 @@ module.exports = {
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 17 */
|
||||
/* 18 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = Redux;
|
||||
|
||||
/***/ }),
|
||||
/* 18 */
|
||||
/* 19 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
const React = __webpack_require__(0);
|
||||
const ReactDOM = __webpack_require__(8);
|
||||
const Base = __webpack_require__(4);
|
||||
const React = __webpack_require__(1);
|
||||
const ReactDOM = __webpack_require__(9);
|
||||
const Base = __webpack_require__(5);
|
||||
|
||||
var _require = __webpack_require__(2);
|
||||
|
||||
const Provider = _require.Provider;
|
||||
|
||||
const initStore = __webpack_require__(6);
|
||||
const initStore = __webpack_require__(7);
|
||||
|
||||
var _require2 = __webpack_require__(7);
|
||||
var _require2 = __webpack_require__(8);
|
||||
|
||||
const reducers = _require2.reducers;
|
||||
|
||||
const DetectUserSessionStart = __webpack_require__(5);
|
||||
const DetectUserSessionStart = __webpack_require__(6);
|
||||
|
||||
new DetectUserSessionStart().sendEventOrAddListener();
|
||||
|
||||
|
@ -44,6 +44,12 @@ input {
|
||||
background-image: url("assets/glyph-newWindow-private-16.svg"); }
|
||||
.icon.icon-settings {
|
||||
background-image: url("assets/glyph-settings-16.svg"); }
|
||||
.icon.icon-pin {
|
||||
background-image: url("assets/glyph-pin-16.svg"); }
|
||||
.icon.icon-unpin {
|
||||
background-image: url("assets/glyph-unpin-16.svg"); }
|
||||
.icon.icon-pocket {
|
||||
background-image: url("assets/glyph-pocket-16.svg"); }
|
||||
.icon.icon-pin-small {
|
||||
background-image: url("assets/glyph-pin-12.svg");
|
||||
background-size: 12px;
|
||||
|
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill: #4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<path d="M14.707,13.293,11.414,10l2.293-2.293a1,1,0,0,0,0-1.414A4.384,4.384,0,0,0,10.586,5h-.172A2.415,2.415,0,0,1,8,2.586V2a1,1,0,0,0-1.707-.707l-5,5A1,1,0,0,0,2,8h.586A2.415,2.415,0,0,1,5,10.414v.169a4.036,4.036,0,0,0,1.337,3.166,1,1,0,0,0,1.37-.042L10,11.414l3.293,3.293a1,1,0,0,0,1.414-1.414ZM7.129,11.456A2.684,2.684,0,0,1,7,10.583v-.169A4.386,4.386,0,0,0,5.708,7.293,4.414,4.414,0,0,0,4.136,6.278L6.279,4.136A4.4,4.4,0,0,0,7.292,5.707,4.384,4.384,0,0,0,10.414,7h.172a2.4,2.4,0,0,1,.848.152Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 652 B |
@ -0,0 +1,6 @@
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M8 15a8 8 0 0 1-8-8V3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4a8 8 0 0 1-8 8zm3.985-10.032a.99.99 0 0 0-.725.319L7.978 8.57 4.755 5.336A.984.984 0 0 0 4 4.968a1 1 0 0 0-.714 1.7l-.016.011 3.293 3.306.707.707a1 1 0 0 0 1.414 0l.707-.707L12.7 6.679a1 1 0 0 0-.715-1.711z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 597 B |
@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill: #4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<path d="M11.414,10l2.293-2.293a1,1,0,0,0,0-1.414,4.418,4.418,0,0,0-.8-.622L11.425,7.15l.008,0-4.3,4.3,0-.017-1.48,1.476a3.865,3.865,0,0,0,.692.834,1,1,0,0,0,1.37-.042L10,11.414l3.293,3.293a1,1,0,0,0,1.414-1.414Z"/>
|
||||
<path d="M14.707,1.293a1,1,0,0,0-1.414,0L9.7,4.882A2.382,2.382,0,0,1,8,2.586V2a1,1,0,0,0-1.707-.707l-5,5A1,1,0,0,0,2,8h.586a2.382,2.382,0,0,1,2.3,1.7L1.293,13.293a1,1,0,1,0,1.414,1.414l12-12A1,1,0,0,0,14.707,1.293Zm-9,6A4.414,4.414,0,0,0,4.136,6.278L6.279,4.136A4.4,4.4,0,0,0,7.292,5.707a4.191,4.191,0,0,0,.9.684l-1.8,1.8A4.2,4.2,0,0,0,5.708,7.293Z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 739 B |
File diff suppressed because it is too large
Load Diff
@ -55,8 +55,9 @@ this.TelemetryFeed = class TelemetryFeed {
|
||||
* addSession - Start tracking a new session
|
||||
*
|
||||
* @param {string} id the portID of the open session
|
||||
* @param {number} absVisChangeTime absolute timestamp of
|
||||
* @param {number} absVisChangeTime Optional. Absolute timestamp of
|
||||
* document.visibilityState becoming visible
|
||||
* @return {obj} Session object
|
||||
*/
|
||||
addSession(id, absVisChangeTime) {
|
||||
// XXX note that there is a race condition here; we're assuming that no
|
||||
@ -78,25 +79,30 @@ this.TelemetryFeed = class TelemetryFeed {
|
||||
// introduce, rather than doing the correct by complicated thing. It may
|
||||
// well be worth reexamining this hypothesis after we have more experience
|
||||
// with the data.
|
||||
let absBrowserOpenTabStart =
|
||||
perfService.getMostRecentAbsMarkStartByName("browser-open-newtab-start");
|
||||
let absBrowserOpenTabStart;
|
||||
try {
|
||||
absBrowserOpenTabStart = perfService.getMostRecentAbsMarkStartByName("browser-open-newtab-start");
|
||||
} catch (e) {
|
||||
// Just use undefined so it doesn't get sent to the server
|
||||
}
|
||||
|
||||
this.sessions.set(id, {
|
||||
// If we're missing either starting timestamps, treat it as an unexpected
|
||||
// session; otherwise, assume it's the usual behavior.
|
||||
const triggerType = absBrowserOpenTabStart === undefined ||
|
||||
absVisChangeTime === undefined ? "unexpected" : "menu_plus_or_keyboard";
|
||||
|
||||
const session = {
|
||||
start_time: Components.utils.now(),
|
||||
session_id: String(gUUIDGenerator.generateUUID()),
|
||||
page: "about:newtab", // TODO: Handle about:home here and in perf below
|
||||
perf: {
|
||||
load_trigger_ts: absBrowserOpenTabStart,
|
||||
load_trigger_type: "menu_plus_or_keyboard",
|
||||
load_trigger_type: triggerType,
|
||||
visibility_event_rcvd_ts: absVisChangeTime
|
||||
}
|
||||
});
|
||||
|
||||
let duration = absVisChangeTime - absBrowserOpenTabStart;
|
||||
this.store.dispatch({
|
||||
type: at.TELEMETRY_PERFORMANCE_EVENT,
|
||||
data: {visability_duration: duration}
|
||||
});
|
||||
};
|
||||
this.sessions.set(id, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,7 +139,7 @@ this.TelemetryFeed = class TelemetryFeed {
|
||||
|
||||
// If the ping is part of a user session, add session-related info
|
||||
if (portID) {
|
||||
const session = this.sessions.get(portID);
|
||||
const session = this.sessions.get(portID) || this.addSession(portID);
|
||||
Object.assign(ping, {
|
||||
session_id: session.session_id,
|
||||
page: session.page
|
||||
|
@ -8,6 +8,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
const {Prefs} = Cu.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
|
||||
const {insertPinned} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
|
||||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
@ -39,23 +40,6 @@ this.TopSitesFeed = class TopSitesFeed {
|
||||
const action = {type: at.SCREENSHOT_UPDATED, data: {url, screenshot}};
|
||||
this.store.dispatch(ac.BroadcastToContent(action));
|
||||
}
|
||||
sortLinks(frecent, pinned) {
|
||||
let sortedLinks = [...frecent, ...DEFAULT_TOP_SITES];
|
||||
sortedLinks = sortedLinks.filter(link => !NewTabUtils.pinnedLinks.isPinned(link));
|
||||
|
||||
// Insert the pinned links in their specified location
|
||||
pinned.forEach((val, index) => {
|
||||
if (!val) { return; }
|
||||
let link = Object.assign({}, val, {isPinned: true, pinIndex: index, pinTitle: val.title});
|
||||
if (index > sortedLinks.length) {
|
||||
sortedLinks[index] = link;
|
||||
} else {
|
||||
sortedLinks.splice(index, 0, link);
|
||||
}
|
||||
});
|
||||
|
||||
return sortedLinks.slice(0, TOP_SITES_SHOWMORE_LENGTH);
|
||||
}
|
||||
async getLinksWithDefaults(action) {
|
||||
let pinned = NewTabUtils.pinnedLinks.links;
|
||||
let frecent = await NewTabUtils.activityStreamLinks.getTopSites();
|
||||
@ -66,38 +50,65 @@ this.TopSitesFeed = class TopSitesFeed {
|
||||
frecent = frecent.filter(link => link && link.type !== "affiliate");
|
||||
}
|
||||
|
||||
return this.sortLinks(frecent, pinned);
|
||||
return insertPinned([...frecent, ...DEFAULT_TOP_SITES], pinned).slice(0, TOP_SITES_SHOWMORE_LENGTH);
|
||||
}
|
||||
async refresh(action) {
|
||||
async refresh(target = null) {
|
||||
const links = await this.getLinksWithDefaults();
|
||||
|
||||
// First, cache existing screenshots in case we need to reuse them
|
||||
const currentScreenshots = {};
|
||||
for (const link of this.store.getState().TopSites.rows) {
|
||||
if (link.screenshot) {
|
||||
if (link && link.screenshot) {
|
||||
currentScreenshots[link.url] = link.screenshot;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, get a screenshot for every item
|
||||
for (let link of links) {
|
||||
if (!link) { continue; }
|
||||
if (currentScreenshots[link.url]) {
|
||||
link.screenshot = currentScreenshots[link.url];
|
||||
} else {
|
||||
this.getScreenshot(link.url);
|
||||
}
|
||||
}
|
||||
|
||||
const newAction = {type: at.TOP_SITES_UPDATED, data: links};
|
||||
|
||||
// Send an update to content so the preloaded tab can get the updated content
|
||||
this.store.dispatch(ac.SendToContent(newAction, action.meta.fromTarget));
|
||||
if (target) {
|
||||
// Send an update to content so the preloaded tab can get the updated content
|
||||
this.store.dispatch(ac.SendToContent(newAction, target));
|
||||
} else {
|
||||
// Broadcast an update to all open content pages
|
||||
this.store.dispatch(ac.BroadcastToContent(newAction));
|
||||
}
|
||||
this.lastUpdated = Date.now();
|
||||
}
|
||||
openNewWindow(action, isPrivate = false) {
|
||||
const win = action._target.browser.ownerGlobal;
|
||||
win.openLinkIn(action.data.url, "window", {private: isPrivate});
|
||||
}
|
||||
_getPinnedWithData() {
|
||||
// Augment the pinned links with any other extra data we have for them already in the store
|
||||
const links = this.store.getState().TopSites.rows;
|
||||
const pinned = NewTabUtils.pinnedLinks.links;
|
||||
return pinned.map(pinnedLink => (pinnedLink ? Object.assign(links.find(link => link && link.url === pinnedLink.url) || {}, pinnedLink, {isDefault: false}) : pinnedLink));
|
||||
}
|
||||
pin(action) {
|
||||
const {site, index} = action.data;
|
||||
NewTabUtils.pinnedLinks.pin(site, index);
|
||||
this.store.dispatch(ac.BroadcastToContent({
|
||||
type: at.PINNED_SITES_UPDATED,
|
||||
data: this._getPinnedWithData()
|
||||
}));
|
||||
}
|
||||
unpin(action) {
|
||||
const {site} = action.data;
|
||||
NewTabUtils.pinnedLinks.unpin(site);
|
||||
this.store.dispatch(ac.BroadcastToContent({
|
||||
type: at.PINNED_SITES_UPDATED,
|
||||
data: this._getPinnedWithData()
|
||||
}));
|
||||
}
|
||||
onAction(action) {
|
||||
let realRows;
|
||||
switch (action.type) {
|
||||
@ -115,16 +126,24 @@ this.TopSitesFeed = class TopSitesFeed {
|
||||
// is greater than 15 minutes, refresh the data.
|
||||
(Date.now() - this.lastUpdated >= UPDATE_TIME)
|
||||
) {
|
||||
this.refresh(action);
|
||||
this.refresh(action.meta.fromTarget);
|
||||
}
|
||||
break;
|
||||
case at.OPEN_NEW_WINDOW:
|
||||
this.openNewWindow(action);
|
||||
break;
|
||||
case at.OPEN_PRIVATE_WINDOW: {
|
||||
case at.OPEN_PRIVATE_WINDOW:
|
||||
this.openNewWindow(action, true);
|
||||
break;
|
||||
}
|
||||
case at.PLACES_HISTORY_CLEARED:
|
||||
this.refresh();
|
||||
break;
|
||||
case at.TOP_SITES_PIN:
|
||||
this.pin(action);
|
||||
break;
|
||||
case at.TOP_SITES_UNPIN:
|
||||
this.unpin(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -39,7 +39,10 @@ const UserEventAction = Joi.object().keys({
|
||||
"OPEN_NEWTAB_PREFS",
|
||||
"CLOSE_NEWTAB_PREFS",
|
||||
"BOOKMARK_DELETE",
|
||||
"BOOKMARK_ADD"
|
||||
"BOOKMARK_ADD",
|
||||
"PIN",
|
||||
"UNPIN",
|
||||
"SAVE_TO_POCKET"
|
||||
]).required(),
|
||||
source: Joi.valid(["TOP_SITES"]),
|
||||
action_position: Joi.number().integer()
|
||||
@ -82,8 +85,8 @@ const SessionPing = Joi.object().keys(Object.assign({}, baseKeys, {
|
||||
//
|
||||
// Not required at least for the error cases where the observer event
|
||||
// doesn't fire
|
||||
load_trigger_type: Joi.valid(["menu_plus_or_keyboard"])
|
||||
.notes(["server counter", "server counter alert"]),
|
||||
load_trigger_type: Joi.valid(["menu_plus_or_keyboard", "unexpected"])
|
||||
.notes(["server counter", "server counter alert"]).required(),
|
||||
|
||||
// When the page itself receives an event that document.visibilityState
|
||||
// == visible.
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {reducers, INITIAL_STATE} = require("common/Reducers.jsm");
|
||||
const {reducers, INITIAL_STATE, insertPinned} = require("common/Reducers.jsm");
|
||||
const {TopSites, App, Prefs, Dialog} = reducers;
|
||||
const {actionTypes: at} = require("common/Actions.jsm");
|
||||
|
||||
@ -107,6 +107,13 @@ describe("Reducers", () => {
|
||||
assert.deepEqual(nextState.rows, [{url: "foo.com"}]);
|
||||
});
|
||||
});
|
||||
it("should insert pinned links on PINNED_SITES_UPDATED", () => {
|
||||
const oldState = {rows: [{url: "foo.com"}, {url: "bar.com"}]};
|
||||
const action = {type: at.PINNED_SITES_UPDATED, data: [{url: "baz.com", title: "baz"}]};
|
||||
const nextState = TopSites(oldState, action);
|
||||
console.log(nextState.rows);
|
||||
assert.deepEqual(nextState.rows, [{url: "baz.com", title: "baz", isPinned: true, pinIndex: 0, pinTitle: "baz"}, {url: "foo.com"}, {url: "bar.com"}]);
|
||||
});
|
||||
});
|
||||
describe("Prefs", () => {
|
||||
function prevState(custom = {}) {
|
||||
@ -173,4 +180,69 @@ describe("Reducers", () => {
|
||||
assert.deepEqual(INITIAL_STATE.Dialog, nextState);
|
||||
});
|
||||
});
|
||||
describe("#insertPinned", () => {
|
||||
let links;
|
||||
|
||||
beforeEach(() => {
|
||||
links = new Array(12).fill(null).map((v, i) => ({url: `site${i}.com`}));
|
||||
});
|
||||
|
||||
it("should place pinned links where they belong", () => {
|
||||
const pinned = [
|
||||
{"url": "http://github.com/mozilla/activity-stream", "title": "moz/a-s"},
|
||||
{"url": "http://example.com", "title": "example"}
|
||||
];
|
||||
const result = insertPinned(links, pinned);
|
||||
for (let index of [0, 1]) {
|
||||
assert.equal(result[index].url, pinned[index].url);
|
||||
assert.ok(result[index].isPinned);
|
||||
assert.equal(result[index].pinTitle, pinned[index].title);
|
||||
assert.equal(result[index].pinIndex, index);
|
||||
}
|
||||
assert.deepEqual(result.slice(2), links);
|
||||
});
|
||||
it("should handle empty slots in the pinned list", () => {
|
||||
const pinned = [
|
||||
null,
|
||||
{"url": "http://github.com/mozilla/activity-stream", "title": "moz/a-s"},
|
||||
null,
|
||||
null,
|
||||
{"url": "http://example.com", "title": "example"}
|
||||
];
|
||||
const result = insertPinned(links, pinned);
|
||||
for (let index of [1, 4]) {
|
||||
assert.equal(result[index].url, pinned[index].url);
|
||||
assert.ok(result[index].isPinned);
|
||||
assert.equal(result[index].pinTitle, pinned[index].title);
|
||||
assert.equal(result[index].pinIndex, index);
|
||||
}
|
||||
result.splice(4, 1);
|
||||
result.splice(1, 1);
|
||||
assert.deepEqual(result, links);
|
||||
});
|
||||
it("should handle a pinned site past the end of the list of links", () => {
|
||||
const pinned = [];
|
||||
pinned[11] = {"url": "http://github.com/mozilla/activity-stream", "title": "moz/a-s"};
|
||||
const result = insertPinned([], pinned);
|
||||
assert.equal(result[11].url, pinned[11].url);
|
||||
assert.isTrue(result[11].isPinned);
|
||||
assert.equal(result[11].pinTitle, pinned[11].title);
|
||||
assert.equal(result[11].pinIndex, 11);
|
||||
});
|
||||
it("should unpin previously pinned links no longer in the pinned list", () => {
|
||||
const pinned = [];
|
||||
links[2].isPinned = true;
|
||||
links[2].pinTitle = "pinned site";
|
||||
links[2].pinIndex = 2;
|
||||
const result = insertPinned(links, pinned);
|
||||
assert.notProperty(result[2], "isPinned");
|
||||
assert.notProperty(result[2], "pinTitle");
|
||||
assert.notProperty(result[2], "pinIndex");
|
||||
});
|
||||
it("should handle a link present in both the links and pinned list", () => {
|
||||
const pinned = [links[7]];
|
||||
const result = insertPinned(links, pinned);
|
||||
assert.equal(links.length, result.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -30,11 +30,6 @@ describe("TelemetryFeed", () => {
|
||||
"common/PerfService.jsm": {perfService}
|
||||
});
|
||||
|
||||
function addSession(id) {
|
||||
instance.addSession(id);
|
||||
return instance.sessions.get(id);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
sandbox = globals.sandbox;
|
||||
@ -64,26 +59,52 @@ describe("TelemetryFeed", () => {
|
||||
});
|
||||
});
|
||||
describe("#addSession", () => {
|
||||
it("should add a session", () => {
|
||||
addSession("foo");
|
||||
assert.isTrue(instance.sessions.has("foo"));
|
||||
it("should add a session and return it", () => {
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
assert.equal(instance.sessions.get("foo"), session);
|
||||
});
|
||||
it("should set the start_time", () => {
|
||||
sandbox.spy(Components.utils, "now");
|
||||
const session = addSession("foo");
|
||||
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
assert.calledOnce(Components.utils.now);
|
||||
assert.equal(session.start_time, Components.utils.now.firstCall.returnValue);
|
||||
});
|
||||
it("should set the session_id", () => {
|
||||
sandbox.spy(global.gUUIDGenerator, "generateUUID");
|
||||
const session = addSession("foo");
|
||||
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
assert.calledOnce(global.gUUIDGenerator.generateUUID);
|
||||
assert.equal(session.session_id, global.gUUIDGenerator.generateUUID.firstCall.returnValue);
|
||||
});
|
||||
it("should set the page", () => {
|
||||
const session = addSession("foo");
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
assert.equal(session.page, "about:newtab"); // This is hardcoded for now.
|
||||
});
|
||||
it("should set the perf type when lacking timestamp", () => {
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
assert.propertyVal(session.perf, "load_trigger_type", "unexpected");
|
||||
});
|
||||
it("should set the perf type with timestamp", () => {
|
||||
const session = instance.addSession("foo", 123);
|
||||
|
||||
assert.propertyVal(session.perf, "load_trigger_type", "menu_plus_or_keyboard"); // This is hardcoded for now.
|
||||
});
|
||||
it("should save visibility time", () => {
|
||||
const session = instance.addSession("foo", 123);
|
||||
|
||||
assert.propertyVal(session.perf, "visibility_event_rcvd_ts", 123);
|
||||
});
|
||||
it("should not save visibility time when lacking timestamp", () => {
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
assert.propertyVal(session.perf, "visibility_event_rcvd_ts", undefined);
|
||||
});
|
||||
});
|
||||
describe("#browserOpenNewtabStart", () => {
|
||||
it("should call perfService.mark with browser-open-newtab-start", () => {
|
||||
@ -102,20 +123,25 @@ describe("TelemetryFeed", () => {
|
||||
});
|
||||
it("should add a session_duration", () => {
|
||||
sandbox.stub(instance, "sendEvent");
|
||||
const session = addSession("foo");
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
instance.endSession("foo");
|
||||
|
||||
assert.property(session, "session_duration");
|
||||
});
|
||||
it("should remove the session from .sessions", () => {
|
||||
sandbox.stub(instance, "sendEvent");
|
||||
addSession("foo");
|
||||
instance.addSession("foo");
|
||||
|
||||
instance.endSession("foo");
|
||||
|
||||
assert.isFalse(instance.sessions.has("foo"));
|
||||
});
|
||||
it("should call createSessionSendEvent and sendEvent with the sesssion", () => {
|
||||
sandbox.stub(instance, "sendEvent");
|
||||
sandbox.stub(instance, "createSessionEndEvent");
|
||||
const session = addSession("foo");
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
instance.endSession("foo");
|
||||
|
||||
// Did we call sendEvent with the result of createSessionEndEvent?
|
||||
@ -124,7 +150,7 @@ describe("TelemetryFeed", () => {
|
||||
});
|
||||
});
|
||||
describe("ping creators", () => {
|
||||
beforeEach(async () => await instance.init());
|
||||
beforeEach(() => instance.init());
|
||||
describe("#createPing", () => {
|
||||
it("should create a valid base ping without a session if no portID is supplied", async () => {
|
||||
const ping = await instance.createPing();
|
||||
@ -145,13 +171,21 @@ describe("TelemetryFeed", () => {
|
||||
assert.propertyVal(ping, "session_id", sessionID);
|
||||
assert.propertyVal(ping, "page", "about:newtab");
|
||||
});
|
||||
it("should create an unexpected base ping if no session yet portID is supplied", async () => {
|
||||
const ping = await instance.createPing("foo");
|
||||
|
||||
assert.validate(ping, BasePing);
|
||||
assert.propertyVal(ping, "page", "about:newtab");
|
||||
assert.propertyVal(instance.sessions.get("foo").perf, "load_trigger_type", "unexpected");
|
||||
});
|
||||
});
|
||||
describe("#createUserEvent", () => {
|
||||
it("should create a valid event", async () => {
|
||||
const portID = "foo";
|
||||
const data = {source: "TOP_SITES", event: "CLICK"};
|
||||
const action = ac.SendToMain(ac.UserEvent(data), portID);
|
||||
const session = addSession(portID);
|
||||
const session = instance.addSession(portID);
|
||||
|
||||
const ping = await instance.createUserEvent(action);
|
||||
|
||||
// Is it valid?
|
||||
@ -163,6 +197,7 @@ describe("TelemetryFeed", () => {
|
||||
describe("#createUndesiredEvent", () => {
|
||||
it("should create a valid event without a session", async () => {
|
||||
const action = ac.UndesiredEvent({source: "TOP_SITES", event: "MISSING_IMAGE", value: 10});
|
||||
|
||||
const ping = await instance.createUndesiredEvent(action);
|
||||
|
||||
// Is it valid?
|
||||
@ -174,7 +209,8 @@ describe("TelemetryFeed", () => {
|
||||
const portID = "foo";
|
||||
const data = {source: "TOP_SITES", event: "MISSING_IMAGE", value: 10};
|
||||
const action = ac.SendToMain(ac.UndesiredEvent(data), portID);
|
||||
const session = addSession(portID);
|
||||
const session = instance.addSession(portID);
|
||||
|
||||
const ping = await instance.createUndesiredEvent(action);
|
||||
|
||||
// Is it valid?
|
||||
@ -199,7 +235,8 @@ describe("TelemetryFeed", () => {
|
||||
const portID = "foo";
|
||||
const data = {event: "PAGE_LOADED", value: 100};
|
||||
const action = ac.SendToMain(ac.PerfEvent(data), portID);
|
||||
const session = addSession(portID);
|
||||
const session = instance.addSession(portID);
|
||||
|
||||
const ping = await instance.createPerformanceEvent(action);
|
||||
|
||||
// Is it valid?
|
||||
@ -228,6 +265,21 @@ describe("TelemetryFeed", () => {
|
||||
assert.propertyVal(ping, "page", "about:newtab");
|
||||
assert.propertyVal(ping, "session_duration", 12345);
|
||||
});
|
||||
it("should create a valid unexpected session event", async () => {
|
||||
const ping = await instance.createSessionEndEvent({
|
||||
session_id: FAKE_UUID,
|
||||
page: "about:newtab",
|
||||
session_duration: 12345,
|
||||
perf: {load_trigger_type: "unexpected"}
|
||||
});
|
||||
|
||||
// Is it valid?
|
||||
assert.validate(ping, SessionPing);
|
||||
assert.propertyVal(ping, "session_id", FAKE_UUID);
|
||||
assert.propertyVal(ping, "page", "about:newtab");
|
||||
assert.propertyVal(ping, "session_duration", 12345);
|
||||
assert.propertyVal(ping.perf, "load_trigger_type", "unexpected");
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("#sendEvent", () => {
|
||||
|
@ -4,6 +4,7 @@ const {UPDATE_TIME, TOP_SITES_SHOWMORE_LENGTH} = require("lib/TopSitesFeed.jsm")
|
||||
const {FakePrefs, GlobalOverrider} = require("test/unit/utils");
|
||||
const action = {meta: {fromTarget: {}}};
|
||||
const {actionTypes: at} = require("common/Actions.jsm");
|
||||
const {insertPinned} = require("common/Reducers.jsm");
|
||||
const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({url: `site${i}.com`}));
|
||||
const FAKE_SCREENSHOT = "data123";
|
||||
|
||||
@ -24,13 +25,18 @@ describe("Top Sites Feed", () => {
|
||||
activityStreamLinks: {getTopSites: sandbox.spy(() => Promise.resolve(links))},
|
||||
pinnedLinks: {
|
||||
links: [],
|
||||
isPinned: () => false
|
||||
isPinned: () => false,
|
||||
pin: sandbox.spy(),
|
||||
unpin: sandbox.spy()
|
||||
}
|
||||
};
|
||||
globals.set("NewTabUtils", fakeNewTabUtils);
|
||||
globals.set("PreviewProvider", {getThumbnail: sandbox.spy(() => Promise.resolve(FAKE_SCREENSHOT))});
|
||||
FakePrefs.prototype.prefs["default.sites"] = "https://foo.com/";
|
||||
({TopSitesFeed, DEFAULT_TOP_SITES} = injector({"lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs}}));
|
||||
({TopSitesFeed, DEFAULT_TOP_SITES} = injector({
|
||||
"lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs},
|
||||
"common/Reducers.jsm": {insertPinned}
|
||||
}));
|
||||
feed = new TopSitesFeed();
|
||||
feed.store = {dispatch: sinon.spy(), getState() { return {TopSites: {rows: Array(12).fill("site")}}; }};
|
||||
links = FAKE_LINKS;
|
||||
@ -56,54 +62,6 @@ describe("Top Sites Feed", () => {
|
||||
assert.equal(DEFAULT_TOP_SITES.length, 0);
|
||||
});
|
||||
});
|
||||
describe("#sortLinks", () => {
|
||||
beforeEach(() => {
|
||||
feed.init();
|
||||
});
|
||||
|
||||
it("should place pinned links where they belong", () => {
|
||||
const pinned = [
|
||||
{"url": "http://github.com/mozilla/activity-stream", "title": "moz/a-s"},
|
||||
{"url": "http://example.com", "title": "example"}
|
||||
];
|
||||
const result = feed.sortLinks(links, pinned);
|
||||
for (let index of [0, 1]) {
|
||||
assert.equal(result[index].url, pinned[index].url);
|
||||
assert.ok(result[index].isPinned);
|
||||
assert.equal(result[index].pinTitle, pinned[index].title);
|
||||
assert.equal(result[index].pinIndex, index);
|
||||
}
|
||||
assert.deepEqual(result.slice(2), links.slice(0, -2));
|
||||
});
|
||||
it("should handle empty slots in the pinned list", () => {
|
||||
const pinned = [
|
||||
null,
|
||||
{"url": "http://github.com/mozilla/activity-stream", "title": "moz/a-s"},
|
||||
null,
|
||||
null,
|
||||
{"url": "http://example.com", "title": "example"}
|
||||
];
|
||||
const result = feed.sortLinks(links, pinned);
|
||||
for (let index of [1, 4]) {
|
||||
assert.equal(result[index].url, pinned[index].url);
|
||||
assert.ok(result[index].isPinned);
|
||||
assert.equal(result[index].pinTitle, pinned[index].title);
|
||||
assert.equal(result[index].pinIndex, index);
|
||||
}
|
||||
result.splice(4, 1);
|
||||
result.splice(1, 1);
|
||||
assert.deepEqual(result, links.slice(0, -2));
|
||||
});
|
||||
it("should handle a pinned site past the end of the list of frecent+default", () => {
|
||||
const pinned = [];
|
||||
pinned[11] = {"url": "http://github.com/mozilla/activity-stream", "title": "moz/a-s"};
|
||||
const result = feed.sortLinks([], pinned);
|
||||
assert.equal(result[11].url, pinned[11].url);
|
||||
assert.isTrue(result[11].isPinned);
|
||||
assert.equal(result[11].pinTitle, pinned[11].title);
|
||||
assert.equal(result[11].pinIndex, 11);
|
||||
});
|
||||
});
|
||||
describe("#getLinksWithDefaults", () => {
|
||||
beforeEach(() => {
|
||||
feed.init();
|
||||
@ -156,6 +114,13 @@ describe("Top Sites Feed", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
it("should handle empty slots in the resulting top sites array", async () => {
|
||||
links = [FAKE_LINKS[0]];
|
||||
fakeNewTabUtils.pinnedLinks.links = [null, null, FAKE_LINKS[1], null, null, null, null, null, FAKE_LINKS[2]];
|
||||
sandbox.stub(feed, "getScreenshot");
|
||||
await feed.refresh(action);
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
});
|
||||
});
|
||||
describe("getScreenshot", () => {
|
||||
it("should call PreviewProvider.getThumbnail with the right url", async () => {
|
||||
@ -165,36 +130,37 @@ describe("Top Sites Feed", () => {
|
||||
});
|
||||
});
|
||||
describe("#onAction", () => {
|
||||
const newTabAction = {type: at.NEW_TAB_LOAD, meta: {fromTarget: "target"}};
|
||||
it("should call refresh if there are not enough sites on NEW_TAB_LOAD", () => {
|
||||
feed.store.getState = function() { return {TopSites: {rows: []}}; };
|
||||
sinon.stub(feed, "refresh");
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
assert.calledOnce(feed.refresh);
|
||||
feed.onAction(newTabAction);
|
||||
assert.calledWith(feed.refresh, newTabAction.meta.fromTarget);
|
||||
});
|
||||
it("should call refresh if there are not sites on NEW_TAB_LOAD, not counting defaults", () => {
|
||||
feed.store.getState = function() { return {TopSites: {rows: [{url: "foo.com"}, ...DEFAULT_TOP_SITES]}}; };
|
||||
sinon.stub(feed, "refresh");
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
assert.calledOnce(feed.refresh);
|
||||
feed.onAction(newTabAction);
|
||||
assert.calledWith(feed.refresh, newTabAction.meta.fromTarget);
|
||||
});
|
||||
it("should not call refresh if there are enough sites on NEW_TAB_LOAD", () => {
|
||||
feed.lastUpdated = Date.now();
|
||||
sinon.stub(feed, "refresh");
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
feed.onAction(newTabAction);
|
||||
assert.notCalled(feed.refresh);
|
||||
});
|
||||
it("should call refresh if .lastUpdated is too old on NEW_TAB_LOAD", () => {
|
||||
feed.lastUpdated = 0;
|
||||
clock.tick(UPDATE_TIME);
|
||||
sinon.stub(feed, "refresh");
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
assert.calledOnce(feed.refresh);
|
||||
feed.onAction(newTabAction);
|
||||
assert.calledWith(feed.refresh, newTabAction.meta.fromTarget);
|
||||
});
|
||||
it("should not call refresh if .lastUpdated is less than update time on NEW_TAB_LOAD", () => {
|
||||
feed.lastUpdated = 0;
|
||||
clock.tick(UPDATE_TIME - 1);
|
||||
sinon.stub(feed, "refresh");
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
feed.onAction(newTabAction);
|
||||
assert.notCalled(feed.refresh);
|
||||
});
|
||||
it("should call openNewWindow with the correct url on OPEN_NEW_WINDOW", () => {
|
||||
@ -219,5 +185,36 @@ describe("Top Sites Feed", () => {
|
||||
feed.onAction(openWindowAction);
|
||||
assert.calledOnce(openWindowAction._target.browser.ownerGlobal.openLinkIn);
|
||||
});
|
||||
it("should call with correct parameters on TOP_SITES_PIN", () => {
|
||||
const pinAction = {
|
||||
type: at.TOP_SITES_PIN,
|
||||
data: {site: {url: "foo.com"}, index: 7}
|
||||
};
|
||||
feed.onAction(pinAction);
|
||||
assert.calledOnce(fakeNewTabUtils.pinnedLinks.pin);
|
||||
assert.calledWith(fakeNewTabUtils.pinnedLinks.pin, pinAction.data.site, pinAction.data.index);
|
||||
});
|
||||
it("should call unpin with correct parameters on TOP_SITES_UNPIN", () => {
|
||||
fakeNewTabUtils.pinnedLinks.links = [null, null, {url: "foo.com"}, null, null, null, null, null, FAKE_LINKS[0]];
|
||||
const unpinAction = {
|
||||
type: at.TOP_SITES_UNPIN,
|
||||
data: {site: {url: "foo.com"}}
|
||||
};
|
||||
feed.onAction(unpinAction);
|
||||
assert.calledOnce(fakeNewTabUtils.pinnedLinks.unpin);
|
||||
assert.calledWith(fakeNewTabUtils.pinnedLinks.unpin, unpinAction.data.site);
|
||||
});
|
||||
it("should call refresh without a target if we clear history with PLACES_HISTORY_CLEARED", () => {
|
||||
sandbox.stub(feed, "refresh");
|
||||
feed.onAction({type: at.PLACES_HISTORY_CLEARED});
|
||||
assert.calledOnce(feed.refresh);
|
||||
assert.equal(feed.refresh.firstCall.args[0], null);
|
||||
});
|
||||
it("should still dispatch an action even if there's no target provided", async () => {
|
||||
sandbox.stub(feed, "getScreenshot");
|
||||
await feed.refresh();
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.propertyVal(feed.store.dispatch.firstCall.args[0], "type", at.TOP_SITES_UPDATED);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -292,6 +292,9 @@ user_pref("browser.translation.engine", "bing");
|
||||
// Make sure we don't try to load snippets from the network.
|
||||
user_pref("browser.aboutHomeSnippets.updateUrl", "nonexistent://test");
|
||||
|
||||
// Use an empty list of sites to avoid fetching
|
||||
user_pref("browser.newtabpage.activity-stream.default.sites", "");
|
||||
|
||||
// Don't fetch directory tiles data from real servers
|
||||
user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
|
||||
|
||||
|
@ -96,6 +96,7 @@ DEFAULTS = dict(
|
||||
'dom.send_after_paint_to_content': True,
|
||||
'security.turn_off_all_security_so_that_viruses_can_'
|
||||
'take_over_this_computer': True,
|
||||
'browser.newtabpage.activity-stream.default.sites': '',
|
||||
'browser.newtabpage.directory.source':
|
||||
'${webserver}/directoryLinks.json',
|
||||
'browser.newtabpage.introShown': True,
|
||||
|
Loading…
Reference in New Issue
Block a user