Bug 1399226 - Add high-res icons, image placeholders and bug fixes to Activity Stream. r=dmose
MozReview-Commit-ID: HlYR6LZsM5G --HG-- extra : rebase_source : 4551dbbbd4572070c68964e33447d2c3282a0ec4
|
@ -39,6 +39,7 @@ for (const type of [
|
|||
"NEW_TAB_INIT",
|
||||
"NEW_TAB_INITIAL_STATE",
|
||||
"NEW_TAB_LOAD",
|
||||
"NEW_TAB_REHYDRATED",
|
||||
"NEW_TAB_STATE_REQUEST",
|
||||
"NEW_TAB_UNLOAD",
|
||||
"OPEN_LINK",
|
||||
|
@ -61,6 +62,7 @@ for (const type of [
|
|||
"SECTION_ENABLE",
|
||||
"SECTION_REGISTER",
|
||||
"SECTION_UPDATE",
|
||||
"SECTION_UPDATE_CARD",
|
||||
"SET_PREF",
|
||||
"SHOW_FIREFOX_ACCOUNTS",
|
||||
"SNIPPETS_DATA",
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
"use strict";
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (typeof Components !== "undefined" && Components.utils) {
|
||||
// Note: normally we would just feature detect Components.utils here, but
|
||||
// unfortunately that throws an ugly warning in content if we do.
|
||||
if (typeof Window === "undefined" && typeof Components !== "undefined" && Components.utils) {
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,32 +1,73 @@
|
|||
const prefConfig = {
|
||||
// Prefs listed with "invalidates: true" will prevent the prerendered version
|
||||
class _PrerenderData {
|
||||
constructor(options) {
|
||||
this.initialPrefs = options.initialPrefs;
|
||||
this.initialSections = options.initialSections;
|
||||
this._setValidation(options.validation);
|
||||
}
|
||||
|
||||
get validation() {
|
||||
return this._validation;
|
||||
}
|
||||
|
||||
set validation(value) {
|
||||
this._setValidation(value);
|
||||
}
|
||||
|
||||
get invalidatingPrefs() {
|
||||
return this._invalidatingPrefs;
|
||||
}
|
||||
|
||||
// This is needed so we can use it in the constructor
|
||||
_setValidation(value = []) {
|
||||
this._validation = value;
|
||||
this._invalidatingPrefs = value.reduce((result, next) => {
|
||||
if (typeof next === "string") {
|
||||
result.push(next);
|
||||
return result;
|
||||
} else if (next && next.oneOf) {
|
||||
return result.concat(next.oneOf);
|
||||
}
|
||||
throw new Error("Your validation configuration is not properly configured");
|
||||
}, []);
|
||||
}
|
||||
|
||||
arePrefsValid(getPref) {
|
||||
for (const prefs of this.validation) {
|
||||
// {oneOf: ["foo", "bar"]}
|
||||
if (prefs && prefs.oneOf && !prefs.oneOf.some(name => getPref(name) === this.initialPrefs[name])) {
|
||||
return false;
|
||||
|
||||
// "foo"
|
||||
} else if (getPref(prefs) !== this.initialPrefs[prefs]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.PrerenderData = new _PrerenderData({
|
||||
initialPrefs: {
|
||||
"migrationExpired": true,
|
||||
"showTopSites": true,
|
||||
"showSearch": true,
|
||||
"topSitesCount": 6,
|
||||
"feeds.section.topstories": true,
|
||||
"feeds.section.highlights": true
|
||||
},
|
||||
// Prefs listed as invalidating will prevent the prerendered version
|
||||
// of AS from being used if their value is something other than what is listed
|
||||
// here. This is required because some preferences cause the page layout to be
|
||||
// too different for the prerendered version to be used. Unfortunately, this
|
||||
// will result in users who have modified some of their preferences not being
|
||||
// able to get the benefits of prerendering.
|
||||
"migrationExpired": {value: true},
|
||||
"showTopSites": {
|
||||
value: true,
|
||||
invalidates: true
|
||||
},
|
||||
"showSearch": {
|
||||
value: true,
|
||||
invalidates: true
|
||||
},
|
||||
"topSitesCount": {value: 6},
|
||||
"feeds.section.topstories": {
|
||||
value: true,
|
||||
invalidates: true
|
||||
}
|
||||
};
|
||||
|
||||
this.PrerenderData = {
|
||||
invalidatingPrefs: Object.keys(prefConfig).filter(key => prefConfig[key].invalidates),
|
||||
initialPrefs: Object.keys(prefConfig).reduce((obj, key) => {
|
||||
obj[key] = prefConfig[key].value;
|
||||
return obj;
|
||||
}, {}), // This creates an object of the form {prefName: value}
|
||||
validation: [
|
||||
"showTopSites",
|
||||
"showSearch",
|
||||
// This means if either of these are set to their default values,
|
||||
// prerendering can be used.
|
||||
{oneOf: ["feeds.section.topstories", "feeds.section.highlights"]}
|
||||
],
|
||||
initialSections: [
|
||||
{
|
||||
enabled: true,
|
||||
|
@ -35,8 +76,16 @@ this.PrerenderData = {
|
|||
order: 1,
|
||||
title: {id: "header_recommended_by", values: {provider: "Pocket"}},
|
||||
topics: [{}]
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
id: "highlights",
|
||||
icon: "highlights",
|
||||
order: 2,
|
||||
title: {id: "header_highlights"}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["PrerenderData"];
|
||||
this._PrerenderData = _PrerenderData;
|
||||
this.EXPORTED_SYMBOLS = ["PrerenderData", "_PrerenderData"];
|
||||
|
|
|
@ -233,6 +233,19 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
}
|
||||
return section;
|
||||
});
|
||||
case at.SECTION_UPDATE_CARD:
|
||||
return prevState.map(section => {
|
||||
if (section && section.id === action.data.id && section.rows) {
|
||||
const newRows = section.rows.map(card => {
|
||||
if (card.url === action.data.url) {
|
||||
return Object.assign({}, card, action.data.options);
|
||||
}
|
||||
return card;
|
||||
});
|
||||
return Object.assign({}, section, {rows: newRows});
|
||||
}
|
||||
return section;
|
||||
});
|
||||
case at.PLACES_BOOKMARK_ADDED:
|
||||
if (!action.data) {
|
||||
return prevState;
|
||||
|
|
|
@ -113,7 +113,8 @@
|
|||
"showTopSites": true,
|
||||
"showSearch": true,
|
||||
"topSitesCount": 6,
|
||||
"feeds.section.topstories": true
|
||||
"feeds.section.topstories": true,
|
||||
"feeds.section.highlights": true
|
||||
}
|
||||
},
|
||||
"Dialog": {
|
||||
|
@ -137,6 +138,17 @@
|
|||
{}
|
||||
],
|
||||
"initialized": false
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"id": "header_highlights"
|
||||
},
|
||||
"rows": [],
|
||||
"order": 2,
|
||||
"enabled": true,
|
||||
"id": "highlights",
|
||||
"icon": "highlights",
|
||||
"initialized": false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -94,7 +94,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", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "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_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_REGISTER", "SECTION_UPDATE", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "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", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "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_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
|
||||
actionTypes[type] = type;
|
||||
}
|
||||
|
||||
|
@ -583,6 +583,19 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
}
|
||||
return section;
|
||||
});
|
||||
case at.SECTION_UPDATE_CARD:
|
||||
return prevState.map(section => {
|
||||
if (section && section.id === action.data.id && section.rows) {
|
||||
const newRows = section.rows.map(card => {
|
||||
if (card.url === action.data.url) {
|
||||
return Object.assign({}, card, action.data.options);
|
||||
}
|
||||
return card;
|
||||
});
|
||||
return Object.assign({}, section, { rows: newRows });
|
||||
}
|
||||
return section;
|
||||
});
|
||||
case at.PLACES_BOOKMARK_ADDED:
|
||||
if (!action.data) {
|
||||
return prevState;
|
||||
|
@ -657,8 +670,10 @@ module.exports = {
|
|||
|
||||
|
||||
/* istanbul ignore if */
|
||||
// Note: normally we would just feature detect Components.utils here, but
|
||||
// unfortunately that throws an ugly warning in content if we do.
|
||||
|
||||
if (typeof Components !== "undefined" && Components.utils) {
|
||||
if (typeof Window === "undefined" && typeof Components !== "undefined" && Components.utils) {
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
}
|
||||
|
||||
|
@ -1046,10 +1061,10 @@ module.exports._unconnected = LinkMenu;
|
|||
const ReactDOM = __webpack_require__(11);
|
||||
const Base = __webpack_require__(12);
|
||||
const { Provider } = __webpack_require__(3);
|
||||
const initStore = __webpack_require__(28);
|
||||
const initStore = __webpack_require__(29);
|
||||
const { reducers } = __webpack_require__(6);
|
||||
const DetectUserSessionStart = __webpack_require__(30);
|
||||
const { addSnippetsSubscriber } = __webpack_require__(31);
|
||||
const DetectUserSessionStart = __webpack_require__(31);
|
||||
const { addSnippetsSubscriber } = __webpack_require__(32);
|
||||
const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
|
||||
|
||||
new DetectUserSessionStart().sendEventOrAddListener();
|
||||
|
@ -1092,6 +1107,7 @@ const ManualMigration = __webpack_require__(22);
|
|||
const PreferencesPane = __webpack_require__(23);
|
||||
const Sections = __webpack_require__(24);
|
||||
const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
|
||||
const { PrerenderData } = __webpack_require__(28);
|
||||
|
||||
// Add the locale data for pluralization and relative-time formatting for now,
|
||||
// this just uses english locale data. We can make this more sophisticated if
|
||||
|
@ -1103,6 +1119,10 @@ function addLocaleDataForReactIntl({ locale, textDirection }) {
|
|||
}
|
||||
|
||||
class Base extends React.Component {
|
||||
componentWillMount() {
|
||||
this.sendNewTabRehydrated(this.props.App);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Request state AFTER the first render to ensure we don't cause the
|
||||
// prerendered DOM to be unmounted. Otherwise, NEW_TAB_STATE_REQUEST is
|
||||
|
@ -1117,7 +1137,10 @@ class Base extends React.Component {
|
|||
document.getElementById("favicon").href += "#";
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
componentWillUpdate({ App }) {
|
||||
this.sendNewTabRehydrated(App);
|
||||
|
||||
// Early loads might not have locale yet, so wait until we do
|
||||
if (App.locale && App.locale !== this.props.App.locale) {
|
||||
addLocaleDataForReactIntl(App);
|
||||
|
@ -1131,11 +1154,25 @@ class Base extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// The NEW_TAB_REHYDRATED event is used to inform feeds that their
|
||||
// data has been consumed e.g. for counting the number of tabs that
|
||||
// have rendered that data.
|
||||
sendNewTabRehydrated(App) {
|
||||
if (App && App.initialized && !this.renderNotified) {
|
||||
this.props.dispatch(ac.SendToMain({ type: at.NEW_TAB_REHYDRATED, data: {} }));
|
||||
this.renderNotified = true;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const { locale, strings, initialized } = props.App;
|
||||
const prefs = props.Prefs.values;
|
||||
|
||||
const shouldBeFixedToTop = PrerenderData.arePrefsValid(name => prefs[name]);
|
||||
|
||||
const outerClassName = `outer-wrapper${shouldBeFixedToTop ? " fixed-to-top" : ""}`;
|
||||
|
||||
if (!props.isPrerendered && !initialized) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1148,7 +1185,7 @@ class Base extends React.Component {
|
|||
{ key: "STATIC", locale: locale, messages: strings },
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: "outer-wrapper" },
|
||||
{ className: outerClassName },
|
||||
React.createElement(
|
||||
"main",
|
||||
null,
|
||||
|
@ -1165,6 +1202,7 @@ class Base extends React.Component {
|
|||
}
|
||||
|
||||
module.exports = connect(state => ({ App: state.App, Prefs: state.Prefs }))(Base);
|
||||
module.exports._unconnected = Base;
|
||||
|
||||
/***/ }),
|
||||
/* 13 */
|
||||
|
@ -2642,6 +2680,8 @@ class Card extends React.Component {
|
|||
const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
|
||||
// Display "now" as "trending" until we have new strings #3402
|
||||
const { icon, intlID } = cardContextTypes[link.type === "now" ? "trending" : link.type] || {};
|
||||
const hasImage = link.image || link.hasImage;
|
||||
const imageStyle = { backgroundImage: link.image ? `url(${link.image})` : "none" };
|
||||
|
||||
return React.createElement(
|
||||
"li",
|
||||
|
@ -2652,10 +2692,14 @@ class Card extends React.Component {
|
|||
React.createElement(
|
||||
"div",
|
||||
{ className: "card" },
|
||||
link.image && React.createElement("div", { className: "card-preview-image", style: { backgroundImage: `url(${link.image})` } }),
|
||||
hasImage && React.createElement(
|
||||
"div",
|
||||
{ className: "card-preview-image-outer" },
|
||||
React.createElement("div", { className: `card-preview-image${link.image ? " loaded" : ""}`, style: imageStyle })
|
||||
),
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: `card-details${link.image ? "" : " no-image"}` },
|
||||
{ className: `card-details${hasImage ? "" : " no-image"}` },
|
||||
link.hostname && React.createElement(
|
||||
"div",
|
||||
{ className: "card-host-name" },
|
||||
|
@ -2663,7 +2707,7 @@ class Card extends React.Component {
|
|||
),
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: `card-text${link.image ? "" : " no-image"}${link.hostname ? "" : " no-host-name"}${icon ? "" : " no-context"}` },
|
||||
{ className: `card-text${hasImage ? "" : " no-image"}${link.hostname ? "" : " no-host-name"}${icon ? "" : " no-context"}` },
|
||||
React.createElement(
|
||||
"h4",
|
||||
{ className: "card-title", dir: "auto" },
|
||||
|
@ -2792,11 +2836,102 @@ module.exports.Topic = Topic;
|
|||
|
||||
/***/ }),
|
||||
/* 28 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
class _PrerenderData {
|
||||
constructor(options) {
|
||||
this.initialPrefs = options.initialPrefs;
|
||||
this.initialSections = options.initialSections;
|
||||
this._setValidation(options.validation);
|
||||
}
|
||||
|
||||
get validation() {
|
||||
return this._validation;
|
||||
}
|
||||
|
||||
set validation(value) {
|
||||
this._setValidation(value);
|
||||
}
|
||||
|
||||
get invalidatingPrefs() {
|
||||
return this._invalidatingPrefs;
|
||||
}
|
||||
|
||||
// This is needed so we can use it in the constructor
|
||||
_setValidation(value = []) {
|
||||
this._validation = value;
|
||||
this._invalidatingPrefs = value.reduce((result, next) => {
|
||||
if (typeof next === "string") {
|
||||
result.push(next);
|
||||
return result;
|
||||
} else if (next && next.oneOf) {
|
||||
return result.concat(next.oneOf);
|
||||
}
|
||||
throw new Error("Your validation configuration is not properly configured");
|
||||
}, []);
|
||||
}
|
||||
|
||||
arePrefsValid(getPref) {
|
||||
for (const prefs of this.validation) {
|
||||
// {oneOf: ["foo", "bar"]}
|
||||
if (prefs && prefs.oneOf && !prefs.oneOf.some(name => getPref(name) === this.initialPrefs[name])) {
|
||||
return false;
|
||||
|
||||
// "foo"
|
||||
} else if (getPref(prefs) !== this.initialPrefs[prefs]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var PrerenderData = new _PrerenderData({
|
||||
initialPrefs: {
|
||||
"migrationExpired": true,
|
||||
"showTopSites": true,
|
||||
"showSearch": true,
|
||||
"topSitesCount": 6,
|
||||
"feeds.section.topstories": true,
|
||||
"feeds.section.highlights": true
|
||||
},
|
||||
// Prefs listed as invalidating will prevent the prerendered version
|
||||
// of AS from being used if their value is something other than what is listed
|
||||
// here. This is required because some preferences cause the page layout to be
|
||||
// too different for the prerendered version to be used. Unfortunately, this
|
||||
// will result in users who have modified some of their preferences not being
|
||||
// able to get the benefits of prerendering.
|
||||
validation: ["showTopSites", "showSearch",
|
||||
// This means if either of these are set to their default values,
|
||||
// prerendering can be used.
|
||||
{ oneOf: ["feeds.section.topstories", "feeds.section.highlights"] }],
|
||||
initialSections: [{
|
||||
enabled: true,
|
||||
icon: "pocket",
|
||||
id: "topstories",
|
||||
order: 1,
|
||||
title: { id: "header_recommended_by", values: { provider: "Pocket" } },
|
||||
topics: [{}]
|
||||
}, {
|
||||
enabled: true,
|
||||
id: "highlights",
|
||||
icon: "highlights",
|
||||
order: 2,
|
||||
title: { id: "header_highlights" }
|
||||
}]
|
||||
});
|
||||
module.exports = {
|
||||
PrerenderData,
|
||||
_PrerenderData
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 29 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {/* eslint-env mozilla/frame-script */
|
||||
|
||||
const { createStore, combineReducers, applyMiddleware } = __webpack_require__(29);
|
||||
const { createStore, combineReducers, applyMiddleware } = __webpack_require__(30);
|
||||
const { actionTypes: at, actionCreators: ac, actionUtils: au } = __webpack_require__(0);
|
||||
|
||||
const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
|
||||
|
@ -2906,13 +3041,13 @@ module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
|
|||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
|
||||
|
||||
/***/ }),
|
||||
/* 29 */
|
||||
/* 30 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = Redux;
|
||||
|
||||
/***/ }),
|
||||
/* 30 */
|
||||
/* 31 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {const { actionTypes: at } = __webpack_require__(0);
|
||||
|
@ -2982,7 +3117,7 @@ module.exports = class DetectUserSessionStart {
|
|||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
|
||||
|
||||
/***/ }),
|
||||
/* 31 */
|
||||
/* 32 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {const DATABASE_NAME = "snippets_db";
|
||||
|
|
|
@ -168,9 +168,11 @@ a {
|
|||
|
||||
.outer-wrapper {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding: 40px 32px 32px;
|
||||
height: 100%; }
|
||||
height: 100%;
|
||||
flex-grow: 1; }
|
||||
.outer-wrapper.fixed-to-top {
|
||||
height: auto; }
|
||||
|
||||
main {
|
||||
margin: auto;
|
||||
|
@ -976,14 +978,30 @@ main {
|
|||
opacity: 1; }
|
||||
.card-outer:hover .card-title, .card-outer:focus .card-title, .card-outer.active .card-title {
|
||||
color: #0060DF; }
|
||||
.card-outer .card-preview-image {
|
||||
.card-outer .card-preview-image-outer {
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background: linear-gradient(135deg, #B1B1B3, #D7D7DB);
|
||||
height: 122px;
|
||||
border-bottom: 1px solid #D7D7DB;
|
||||
border-radius: 3px 3px 0 0; }
|
||||
border-radius: 3px 3px 0 0;
|
||||
overflow: hidden; }
|
||||
.card-outer .card-preview-image-outer:dir(rtl) {
|
||||
background: linear-gradient(225deg, #B1B1B3, #D7D7DB); }
|
||||
.card-outer .card-preview-image-outer::after {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
bottom: 0;
|
||||
content: " ";
|
||||
position: absolute;
|
||||
width: 100%; }
|
||||
.card-outer .card-preview-image-outer .card-preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
transition: opacity 1s;
|
||||
opacity: 0; }
|
||||
.card-outer .card-preview-image-outer .card-preview-image.loaded {
|
||||
opacity: 1; }
|
||||
.card-outer .card-details {
|
||||
padding: 15px 16px 12px; }
|
||||
.card-outer .card-details.no-image {
|
||||
|
|
До Ширина: | Высота: | Размер: 17 KiB После Ширина: | Высота: | Размер: 17 KiB |
До Ширина: | Высота: | Размер: 13 KiB После Ширина: | Высота: | Размер: 6.9 KiB |
До Ширина: | Высота: | Размер: 32 KiB |
До Ширина: | Высота: | Размер: 28 KiB |
До Ширина: | Высота: | Размер: 22 KiB |
До Ширина: | Высота: | Размер: 17 KiB |
До Ширина: | Высота: | Размер: 11 KiB |
Двоичные данные
browser/extensions/activity-stream/data/content/tippytop/images/amazon@2x.png
Normal file
После Ширина: | Высота: | Размер: 8.9 KiB |
Двоичные данные
browser/extensions/activity-stream/data/content/tippytop/images/avito-ru@2x.png
Normal file
После Ширина: | Высота: | Размер: 5.1 KiB |
До Ширина: | Высота: | Размер: 11 KiB |
До Ширина: | Высота: | Размер: 16 KiB После Ширина: | Высота: | Размер: 25 KiB |
До Ширина: | Высота: | Размер: 15 KiB |
До Ширина: | Высота: | Размер: 16 KiB После Ширина: | Высота: | Размер: 10 KiB |
До Ширина: | Высота: | Размер: 10 KiB После Ширина: | Высота: | Размер: 15 KiB |
Двоичные данные
browser/extensions/activity-stream/data/content/tippytop/images/leboncoin-fr@2x.png
Normal file
После Ширина: | Высота: | Размер: 12 KiB |
До Ширина: | Высота: | Размер: 13 KiB |
Двоичные данные
browser/extensions/activity-stream/data/content/tippytop/images/ok-ru@2x.png
Normal file
После Ширина: | Высота: | Размер: 6.0 KiB |
До Ширина: | Высота: | Размер: 52 KiB |
До Ширина: | Высота: | Размер: 7.4 KiB После Ширина: | Высота: | Размер: 8.9 KiB |
До Ширина: | Высота: | Размер: 16 KiB После Ширина: | Высота: | Размер: 7.3 KiB |
До Ширина: | Высота: | Размер: 16 KiB После Ширина: | Высота: | Размер: 4.2 KiB |
До Ширина: | Высота: | Размер: 9.4 KiB После Ширина: | Высота: | Размер: 15 KiB |
До Ширина: | Высота: | Размер: 21 KiB |
Двоичные данные
browser/extensions/activity-stream/data/content/tippytop/images/wikipedia-org@2x.png
Normal file
После Ширина: | Высота: | Размер: 26 KiB |
Двоичные данные
browser/extensions/activity-stream/data/content/tippytop/images/wykop-pl@2x.png
Normal file
После Ширина: | Высота: | Размер: 7.1 KiB |
До Ширина: | Высота: | Размер: 19 KiB |
До Ширина: | Высота: | Размер: 7.5 KiB После Ширина: | Высота: | Размер: 4.1 KiB |
|
@ -11,33 +11,13 @@
|
|||
},
|
||||
{
|
||||
"title": "amazon",
|
||||
"url": "https://www.amazon.ca/",
|
||||
"image_url": "amazon-ca@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "amazon",
|
||||
"url": "https://www.amazon.co.uk/",
|
||||
"image_url": "amazon-uk@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "amazon",
|
||||
"url": "https://www.amazon.com/",
|
||||
"image_url": "amazon-com@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "amazon",
|
||||
"url": "https://www.amazon.de/",
|
||||
"image_url": "amazon-de@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "amazon",
|
||||
"url": "https://www.amazon.fr/",
|
||||
"image_url": "amazon-fr@2x.png"
|
||||
"urls": ["https://www.amazon.ca/", "https://www.amazon.co.uk/", "https://www.amazon.com/", "https://www.amazon.de/", "https://www.amazon.fr/"],
|
||||
"image_url": "amazon@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "avito",
|
||||
"url": "https://www.avito.ru/",
|
||||
"image_url": "avito@2x.png"
|
||||
"image_url": "avito-ru@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "bbc",
|
||||
|
@ -46,14 +26,9 @@
|
|||
},
|
||||
{
|
||||
"title": "ebay",
|
||||
"urls": ["https://www.ebay.com", "https://www.ebay.co.uk/"],
|
||||
"urls": ["https://www.ebay.com", "https://www.ebay.co.uk/", "https://ebay.de"],
|
||||
"image_url": "ebay@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "ebay",
|
||||
"url": "https://ebay.de",
|
||||
"image_url": "ebay-de@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "facebook",
|
||||
"url": "https://www.facebook.com/",
|
||||
|
@ -62,12 +37,12 @@
|
|||
{
|
||||
"title": "leboncoin",
|
||||
"url": "http://www.leboncoin.fr/",
|
||||
"image_url": "leboncoin@2x.png"
|
||||
"image_url": "leboncoin-fr@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "ok",
|
||||
"url": "https://www.ok.ru/",
|
||||
"image_url": "ok@2x.png"
|
||||
"image_url": "ok-ru@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "olx",
|
||||
|
@ -92,12 +67,12 @@
|
|||
{
|
||||
"title": "wikipedia",
|
||||
"url": "https://www.wikipedia.org/",
|
||||
"image_url": "wikipedia-com@2x.png"
|
||||
"image_url": "wikipedia-org@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "wykop",
|
||||
"url": "https://www.wykop.pl/",
|
||||
"image_url": "wykop@2x.png"
|
||||
"image_url": "wykop-pl@2x.png"
|
||||
},
|
||||
{
|
||||
"title": "youtube",
|
||||
|
|
|
@ -5997,8 +5997,8 @@
|
|||
"header_recommended_by": "{provider} öneriyor",
|
||||
"header_bookmarks_placeholder": "Henüz hiç yer iminiz yok.",
|
||||
"header_stories_from": "kaynak:",
|
||||
"type_label_visited": "Ziyaret edildi",
|
||||
"type_label_bookmarked": "Yer imlerine eklendi",
|
||||
"type_label_visited": "Ziyaret etmiştiniz",
|
||||
"type_label_bookmarked": "Yer imlerinizde",
|
||||
"type_label_synced": "Başka bir cihazdan eşitlendi",
|
||||
"type_label_recommended": "Popüler",
|
||||
"type_label_open": "Açık",
|
||||
|
@ -6075,7 +6075,7 @@
|
|||
"pocket_description": "Mozilla ailesinin yeni üyesi Pocket’ın yardımıyla, gözünüzden kaçabilecek kaliteli içerikleri keşfedin.",
|
||||
"highlights_empty_state": "Gezinmeye başlayın. Son zamanlarda baktığınız veya yer imlerinize eklediğiniz bazı güzel makaleleri, videoları ve diğer sayfaları burada göstereceğiz.",
|
||||
"topstories_empty_state": "Hepsini bitirdiniz. Yeni {provider} haberleri için daha fazla yine gelin. Beklemek istemiyor musunuz? İlginç yazılara ulaşmak için popüler konulardan birini seçebilirsiniz.",
|
||||
"manual_migration_explanation2": "Öteki tarayıcılarınızdaki yer işaretlerinizi, geçmişinizi ve parolalarınızı Firefox’a taşıyabilirsiniz.",
|
||||
"manual_migration_explanation2": "Öteki tarayıcılarınızdaki yer imlerinizi, geçmişinizi ve parolalarınızı Firefox’a aktarabilirsiniz.",
|
||||
"manual_migration_cancel_button": "Gerek yok",
|
||||
"manual_migration_import_button": "Olur, aktaralım"
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:unpack>false</em:unpack>
|
||||
<em:version>2017.09.11.1306-373d9fc</em:version>
|
||||
<em:version>2017.09.12.1376-781e5de5</em:version>
|
||||
<em:name>Activity Stream</em:name>
|
||||
<em:description>A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you're looking for in Firefox.</em:description>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
|
|
@ -60,7 +60,8 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
|
|||
*/
|
||||
middleware(store) {
|
||||
return next => action => {
|
||||
if (!this.channel) {
|
||||
const skipMain = action.meta && action.meta.skipMain;
|
||||
if (!this.channel && !skipMain) {
|
||||
next(action);
|
||||
return;
|
||||
}
|
||||
|
@ -69,7 +70,10 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
|
|||
} else if (au.isBroadcastToContent(action)) {
|
||||
this.broadcast(action);
|
||||
}
|
||||
next(action);
|
||||
|
||||
if (!skipMain) {
|
||||
next(action);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -136,10 +140,10 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
|
|||
this.channel.addMessageListener(this.incomingMessageName, this.onMessage);
|
||||
|
||||
// Some pages might have already loaded, so we won't get the usual message
|
||||
for (const {loaded, portID} of this.channel.messagePorts) {
|
||||
const simulatedMsg = {target: {portID}};
|
||||
for (const target of this.channel.messagePorts) {
|
||||
const simulatedMsg = {target};
|
||||
this.onNewTabInit(simulatedMsg);
|
||||
if (loaded) {
|
||||
if (target.loaded) {
|
||||
this.onNewTabLoad(simulatedMsg);
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +172,10 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
|
|||
* @param {obj} msg The messsage from a page that was just initialized
|
||||
*/
|
||||
onNewTabInit(msg) {
|
||||
this.onActionFromContent({type: at.NEW_TAB_INIT}, msg.target.portID);
|
||||
this.onActionFromContent({
|
||||
type: at.NEW_TAB_INIT,
|
||||
data: {url: msg.target.url}
|
||||
}, msg.target.portID);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,6 +15,8 @@ const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
|
||||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Screenshots",
|
||||
"resource://activity-stream/lib/Screenshots.jsm");
|
||||
|
||||
const HIGHLIGHTS_MAX_LENGTH = 9;
|
||||
const HIGHLIGHTS_UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
|
||||
|
@ -26,6 +28,7 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
this.highlightsLastUpdated = 0;
|
||||
this.highlights = [];
|
||||
this.dedupe = new Dedupe(this._dedupeKey);
|
||||
this.imageCache = new Map();
|
||||
}
|
||||
|
||||
_dedupeKey(site) {
|
||||
|
@ -62,10 +65,16 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
continue;
|
||||
}
|
||||
|
||||
// If we already have the image for the card in the cache, use that
|
||||
// immediately. Then asynchronously fetch the image (refreshes the cache).
|
||||
const image = this.imageCache.get(page.url);
|
||||
this.fetchImage(page.url, page.preview_image_url);
|
||||
|
||||
// We want the page, so update various fields for UI
|
||||
Object.assign(page, {
|
||||
image,
|
||||
hasImage: true, // We always have an image - fall back to a screenshot
|
||||
hostname,
|
||||
image: page.preview_image_url,
|
||||
type: page.bookmarkGuid ? "bookmark" : page.type
|
||||
});
|
||||
|
||||
|
@ -81,6 +90,23 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
|
||||
SectionsManager.updateSection(SECTION_ID, {rows: this.highlights}, this.highlightsLastUpdated === 0 || broadcast);
|
||||
this.highlightsLastUpdated = Date.now();
|
||||
// Clearing the image cache here is okay. The asynchronous fetchImage calls
|
||||
// get scheduled after the body of fetchHighlights has been executed, so they
|
||||
// then fill up the cache again for the next fetchHighlights call.
|
||||
this.imageCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an image for a given highlight, store it in the image cache, and
|
||||
* update the card with the new image. If the highlight has a preview image
|
||||
* then use that, else fall back to a screenshot of the page.
|
||||
*/
|
||||
async fetchImage(url, imageUrl) {
|
||||
const image = await Screenshots.getScreenshotForURL(imageUrl || url);
|
||||
if (image) {
|
||||
this.imageCache.set(url, image);
|
||||
}
|
||||
SectionsManager.updateSectionCard(SECTION_ID, url, {image}, true);
|
||||
}
|
||||
|
||||
onAction(action) {
|
||||
|
|
|
@ -17,14 +17,8 @@ this.PrefsFeed = class PrefsFeed {
|
|||
|
||||
// If the any prefs are set to something other than what the prerendered version
|
||||
// of AS expects, we can't use it.
|
||||
_setPrerenderPref() {
|
||||
for (const prefName of PrerenderData.invalidatingPrefs) {
|
||||
if (this._prefs.get(prefName) !== PrerenderData.initialPrefs[prefName]) {
|
||||
this._prefs.set("prerender", false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._prefs.set("prerender", true);
|
||||
_setPrerenderPref(name) {
|
||||
this._prefs.set("prerender", PrerenderData.arePrefsValid(pref => this._prefs.get(pref)));
|
||||
}
|
||||
|
||||
_checkPrerender(name) {
|
||||
|
@ -37,7 +31,7 @@ this.PrefsFeed = class PrefsFeed {
|
|||
if (this._prefMap.has(name)) {
|
||||
this.store.dispatch(ac.BroadcastToContent({type: at.PREF_CHANGED, data: {name, value}}));
|
||||
}
|
||||
this._checkPrerender(name, value);
|
||||
this._checkPrerender(name);
|
||||
}
|
||||
|
||||
init() {
|
||||
|
|
|
@ -19,6 +19,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "MIMEService",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
|
||||
const GREY_10 = "#F9F9FA";
|
||||
|
||||
this.Screenshots = {
|
||||
/**
|
||||
* Convert bytes to a string using extremely fast String.fromCharCode without
|
||||
|
@ -41,7 +43,7 @@ this.Screenshots = {
|
|||
async getScreenshotForURL(url) {
|
||||
let screenshot = null;
|
||||
try {
|
||||
await BackgroundPageThumbs.captureIfMissing(url);
|
||||
await BackgroundPageThumbs.captureIfMissing(url, {backgroundColor: GREY_10});
|
||||
const imgPath = PageThumbs.getThumbnailPath(url);
|
||||
|
||||
// OS.File object used to easily read off-thread
|
||||
|
|
|
@ -139,6 +139,26 @@ const SectionsManager = {
|
|||
o => !this.CONTEXT_MENU_PREFS[o] || Services.prefs.getBoolPref(this.CONTEXT_MENU_PREFS[o]));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a specific section card by its url. This allows an action to be
|
||||
* broadcast to all existing pages to update a specific card without having to
|
||||
* also force-update the rest of the section's cards and state on those pages.
|
||||
*
|
||||
* @param id The id of the section with the card to be updated
|
||||
* @param url The url of the card to update
|
||||
* @param options The options to update for the card
|
||||
* @param shouldBroadcast Whether or not to broadcast the update
|
||||
*/
|
||||
updateSectionCard(id, url, options, shouldBroadcast) {
|
||||
if (this.sections.has(id)) {
|
||||
const card = this.sections.get(id).rows.find(elem => elem.url === url);
|
||||
if (card) {
|
||||
Object.assign(card, options);
|
||||
}
|
||||
this.emit(this.UPDATE_SECTION_CARD, id, url, options, shouldBroadcast);
|
||||
}
|
||||
},
|
||||
onceInitialized(callback) {
|
||||
if (this.initialized) {
|
||||
callback();
|
||||
|
@ -160,6 +180,7 @@ for (const action of [
|
|||
"ENABLE_SECTION",
|
||||
"DISABLE_SECTION",
|
||||
"UPDATE_SECTION",
|
||||
"UPDATE_SECTION_CARD",
|
||||
"INIT",
|
||||
"UNINIT"
|
||||
]) {
|
||||
|
@ -174,12 +195,14 @@ class SectionsFeed {
|
|||
this.onAddSection = this.onAddSection.bind(this);
|
||||
this.onRemoveSection = this.onRemoveSection.bind(this);
|
||||
this.onUpdateSection = this.onUpdateSection.bind(this);
|
||||
this.onUpdateSectionCard = this.onUpdateSectionCard.bind(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
SectionsManager.on(SectionsManager.ADD_SECTION, this.onAddSection);
|
||||
SectionsManager.on(SectionsManager.REMOVE_SECTION, this.onRemoveSection);
|
||||
SectionsManager.on(SectionsManager.UPDATE_SECTION, this.onUpdateSection);
|
||||
SectionsManager.on(SectionsManager.UPDATE_SECTION_CARD, this.onUpdateSectionCard);
|
||||
// Catch any sections that have already been added
|
||||
SectionsManager.sections.forEach((section, id) =>
|
||||
this.onAddSection(SectionsManager.ADD_SECTION, id, section));
|
||||
|
@ -191,6 +214,7 @@ class SectionsFeed {
|
|||
SectionsManager.off(SectionsManager.ADD_SECTION, this.onAddSection);
|
||||
SectionsManager.off(SectionsManager.REMOVE_SECTION, this.onRemoveSection);
|
||||
SectionsManager.off(SectionsManager.UPDATE_SECTION, this.onUpdateSection);
|
||||
SectionsManager.off(SectionsManager.UPDATE_SECTION_CARD, this.onUpdateSectionCard);
|
||||
}
|
||||
|
||||
onAddSection(event, id, options) {
|
||||
|
@ -210,6 +234,13 @@ class SectionsFeed {
|
|||
}
|
||||
}
|
||||
|
||||
onUpdateSectionCard(event, id, url, options, shouldBroadcast = false) {
|
||||
if (options) {
|
||||
const action = {type: at.SECTION_UPDATE_CARD, data: {id, url, options}};
|
||||
this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : action);
|
||||
}
|
||||
}
|
||||
|
||||
onAction(action) {
|
||||
switch (action.type) {
|
||||
case at.INIT:
|
||||
|
|
|
@ -189,17 +189,21 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
* addSession - Start tracking a new session
|
||||
*
|
||||
* @param {string} id the portID of the open session
|
||||
*
|
||||
* @param {string} the URL being loaded for this session (optional)
|
||||
* @return {obj} Session object
|
||||
*/
|
||||
addSession(id) {
|
||||
addSession(id, url) {
|
||||
const session = {
|
||||
session_id: String(gUUIDGenerator.generateUUID()),
|
||||
page: "about:newtab", // TODO: Handle about:home here
|
||||
// "unknown" will be overwritten when appropriate
|
||||
page: url ? url : "unknown",
|
||||
// "unexpected" will be overwritten when appropriate
|
||||
perf: {load_trigger_type: "unexpected"}
|
||||
};
|
||||
|
||||
if (url) {
|
||||
session.page = url;
|
||||
}
|
||||
this.sessions.set(id, session);
|
||||
return session;
|
||||
}
|
||||
|
@ -242,10 +246,11 @@ 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) || this.addSession(portID);
|
||||
Object.assign(ping, {
|
||||
session_id: session.session_id,
|
||||
page: session.page
|
||||
});
|
||||
Object.assign(ping, {session_id: session.session_id});
|
||||
|
||||
if (session.page) {
|
||||
Object.assign(ping, {page: session.page});
|
||||
}
|
||||
}
|
||||
return ping;
|
||||
}
|
||||
|
@ -355,7 +360,7 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
this.init();
|
||||
break;
|
||||
case at.NEW_TAB_INIT:
|
||||
this.addSession(au.getPortIdOfSender(action));
|
||||
this.addSession(au.getPortIdOfSender(action), action.data.url);
|
||||
break;
|
||||
case at.NEW_TAB_UNLOAD:
|
||||
this.endSession(au.getPortIdOfSender(action));
|
||||
|
|
|
@ -57,7 +57,7 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
let frecent = await NewTabUtils.activityStreamLinks.getTopSites();
|
||||
const notBlockedDefaultSites = DEFAULT_TOP_SITES.filter(site => !NewTabUtils.blockedLinks.isBlocked({url: site.url}));
|
||||
const defaultUrls = notBlockedDefaultSites.map(site => site.url);
|
||||
let pinned = NewTabUtils.pinnedLinks.links;
|
||||
let pinned = this._getPinnedWithData(frecent);
|
||||
pinned = pinned.map(site => site && Object.assign({}, site, {
|
||||
isDefault: defaultUrls.indexOf(site.url) !== -1,
|
||||
hostname: shortURL(site)
|
||||
|
@ -122,14 +122,18 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
}
|
||||
this.lastUpdated = Date.now();
|
||||
}
|
||||
_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;
|
||||
_getPinnedWithData(links) {
|
||||
// Augment the pinned links with any other extra data we have for them already in the store.
|
||||
// Alternatively you can pass in some links that you know have data you want the pinned links
|
||||
// to also have. This is useful for start up to make sure pinned links have favicons
|
||||
// (See github ticket #3428 fore more details)
|
||||
let originalLinks = links ? links : this.store.getState().TopSites.rows;
|
||||
const pinned = NewTabUtils.pinnedLinks.links;
|
||||
return pinned.map(pinnedLink => {
|
||||
if (pinnedLink) {
|
||||
const hostname = shortURL(pinnedLink);
|
||||
return Object.assign(links.find(link => link && link.url === pinnedLink.url) || {hostname}, pinnedLink);
|
||||
const originalLink = originalLinks.find(link => link && link.url === pinnedLink.url);
|
||||
return Object.assign(pinnedLink, originalLink || {hostname});
|
||||
}
|
||||
return pinnedLink;
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ const baseKeys = {
|
|||
addon_version: Joi.string().required(),
|
||||
locale: Joi.string().required(),
|
||||
session_id: Joi.string(),
|
||||
page: Joi.valid(["about:home", "about:newtab"]),
|
||||
page: Joi.valid(["about:home", "about:newtab", "unknown"]),
|
||||
user_prefs: Joi.number().integer().required()
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
const {PrerenderData, _PrerenderData} = require("common/PrerenderData.jsm");
|
||||
|
||||
describe("_PrerenderData", () => {
|
||||
describe("properties", () => {
|
||||
it("should set .initialPrefs", () => {
|
||||
const initialPrefs = {foo: true};
|
||||
const instance = new _PrerenderData({initialPrefs});
|
||||
|
||||
assert.equal(instance.initialPrefs, initialPrefs);
|
||||
});
|
||||
it("should set .initialSections", () => {
|
||||
const initialSections = [{id: "foo"}];
|
||||
const instance = new _PrerenderData({initialSections});
|
||||
|
||||
assert.equal(instance.initialSections, initialSections);
|
||||
});
|
||||
it("should set .validation and .invalidatingPrefs in the constructor", () => {
|
||||
const validation = ["foo", "bar", {oneOf: ["baz", "qux"]}];
|
||||
const instance = new _PrerenderData({validation});
|
||||
|
||||
assert.equal(instance.validation, validation);
|
||||
assert.deepEqual(instance.invalidatingPrefs, ["foo", "bar", "baz", "qux"]);
|
||||
});
|
||||
it("should also set .invalidatingPrefs when .validation is set", () => {
|
||||
const validation = ["foo", "bar", {oneOf: ["baz", "qux"]}];
|
||||
const instance = new _PrerenderData({validation});
|
||||
|
||||
const newValidation = ["foo", {oneOf: ["blah", "gloo"]}];
|
||||
instance.validation = newValidation;
|
||||
assert.equal(instance.validation, newValidation);
|
||||
assert.deepEqual(instance.invalidatingPrefs, ["foo", "blah", "gloo"]);
|
||||
});
|
||||
it("should throw if an invalid validation config is set", () => {
|
||||
// {stuff: []} is not a valid configuration type
|
||||
assert.throws(() => new _PrerenderData({validation: ["foo", {stuff: ["bar"]}]}));
|
||||
});
|
||||
});
|
||||
describe("#arePrefsValid", () => {
|
||||
let FAKE_PREFS;
|
||||
const getPrefs = pref => FAKE_PREFS[pref];
|
||||
beforeEach(() => {
|
||||
FAKE_PREFS = {};
|
||||
});
|
||||
it("should return true if all prefs match", () => {
|
||||
FAKE_PREFS = {foo: true, bar: false};
|
||||
const instance = new _PrerenderData({
|
||||
initialPrefs: FAKE_PREFS,
|
||||
validation: ["foo", "bar"]
|
||||
});
|
||||
assert.isTrue(instance.arePrefsValid(getPrefs));
|
||||
});
|
||||
it("should return true if all *invalidating* prefs match", () => {
|
||||
FAKE_PREFS = {foo: true, bar: false};
|
||||
const instance = new _PrerenderData({
|
||||
initialPrefs: {foo: true, bar: true},
|
||||
validation: ["foo"]
|
||||
});
|
||||
|
||||
assert.isTrue(instance.arePrefsValid(getPrefs));
|
||||
});
|
||||
it("should return true if one each oneOf group matches", () => {
|
||||
FAKE_PREFS = {foo: false, floo: true, bar: false, blar: true};
|
||||
const instance = new _PrerenderData({
|
||||
initialPrefs: {foo: true, floo: true, bar: true, blar: true},
|
||||
validation: [{oneOf: ["foo", "floo"]}, {oneOf: ["bar", "blar"]}]
|
||||
});
|
||||
|
||||
assert.isTrue(instance.arePrefsValid(getPrefs));
|
||||
});
|
||||
it("should return false if an invalidating pref is mismatched", () => {
|
||||
FAKE_PREFS = {foo: true, bar: false};
|
||||
const instance = new _PrerenderData({
|
||||
initialPrefs: {foo: true, bar: true},
|
||||
validation: ["foo", "bar"]
|
||||
});
|
||||
|
||||
assert.isFalse(instance.arePrefsValid(getPrefs));
|
||||
});
|
||||
it("should return false if none of the oneOf group matches", () => {
|
||||
FAKE_PREFS = {foo: true, bar: false, baz: false};
|
||||
const instance = new _PrerenderData({
|
||||
initialPrefs: {foo: true, bar: true, baz: true},
|
||||
validation: ["foo", {oneOf: ["bar", "baz"]}]
|
||||
});
|
||||
|
||||
assert.isFalse(instance.arePrefsValid(getPrefs));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// This is the instance used by Activity Stream
|
||||
describe("PrerenderData", () => {
|
||||
it("should set initial values for all invalidating prefs", () => {
|
||||
PrerenderData.invalidatingPrefs.forEach(pref => {
|
||||
assert.property(PrerenderData.initialPrefs, pref);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -328,6 +328,30 @@ describe("Reducers", () => {
|
|||
const updatedSection = newState.find(section => section.id === "foo_bar_2");
|
||||
assert.propertyVal(updatedSection, "initialized", true);
|
||||
});
|
||||
it("should have no effect on SECTION_UPDATE_CARD if the id or url doesn't exist", () => {
|
||||
const noIdAction = {type: at.SECTION_UPDATE_CARD, data: {id: "non-existent", url: "www.foo.bar", options: {title: "New title"}}};
|
||||
const noIdState = Sections(oldState, noIdAction);
|
||||
const noUrlAction = {type: at.SECTION_UPDATE_CARD, data: {id: "foo_bar_2", url: "www.non-existent.url", options: {title: "New title"}}};
|
||||
const noUrlState = Sections(oldState, noUrlAction);
|
||||
assert.deepEqual(noIdState, oldState);
|
||||
assert.deepEqual(noUrlState, oldState);
|
||||
});
|
||||
it("should update the card with the correct data on SECTION_UPDATE_CARD", () => {
|
||||
const action = {type: at.SECTION_UPDATE_CARD, data: {id: "foo_bar_2", url: "www.other.url", options: {title: "Fake new title"}}};
|
||||
const newState = Sections(oldState, action);
|
||||
const updatedSection = newState.find(section => section.id === "foo_bar_2");
|
||||
const updatedCard = updatedSection.rows.find(card => card.url === "www.other.url");
|
||||
assert.propertyVal(updatedCard, "title", "Fake new title");
|
||||
});
|
||||
it("should only update the cards belonging to the right section on SECTION_UPDATE_CARD", () => {
|
||||
const action = {type: at.SECTION_UPDATE_CARD, data: {id: "foo_bar_2", url: "www.other.url", options: {title: "Fake new title"}}};
|
||||
const newState = Sections(oldState, action);
|
||||
newState.forEach((section, i) => {
|
||||
if (section.id !== "foo_bar_2") {
|
||||
assert.deepEqual(section, oldState[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
it("should allow action.data to set .initialized", () => {
|
||||
const data = {rows: [], initialized: false, id: "foo_bar_2"};
|
||||
const action = {type: at.SECTION_UPDATE, data};
|
||||
|
|
|
@ -82,13 +82,22 @@ describe("ActivityStreamMessageChannel", () => {
|
|||
});
|
||||
it("should simluate init for existing ports", () => {
|
||||
sinon.stub(mm, "onActionFromContent");
|
||||
RPmessagePorts.push({loaded: false, portID: "inited"});
|
||||
RPmessagePorts.push({loaded: true, portID: "loaded"});
|
||||
|
||||
RPmessagePorts.push({
|
||||
url: "about:monkeys",
|
||||
loaded: false,
|
||||
portID: "inited"
|
||||
});
|
||||
RPmessagePorts.push({
|
||||
url: "about:sheep",
|
||||
loaded: true,
|
||||
portID: "loaded"
|
||||
});
|
||||
|
||||
mm.createChannel();
|
||||
|
||||
assert.calledWith(mm.onActionFromContent.firstCall, {type: at.NEW_TAB_INIT}, "inited");
|
||||
assert.calledWith(mm.onActionFromContent.secondCall, {type: at.NEW_TAB_INIT}, "loaded");
|
||||
assert.calledWith(mm.onActionFromContent.firstCall, {type: at.NEW_TAB_INIT, data: {url: "about:monkeys"}}, "inited");
|
||||
assert.calledWith(mm.onActionFromContent.secondCall, {type: at.NEW_TAB_INIT, data: {url: "about:sheep"}}, "loaded");
|
||||
});
|
||||
it("should simluate load for loaded ports", () => {
|
||||
sinon.stub(mm, "onActionFromContent");
|
||||
|
@ -146,10 +155,15 @@ describe("ActivityStreamMessageChannel", () => {
|
|||
});
|
||||
describe("#onNewTabInit", () => {
|
||||
it("should dispatch a NEW_TAB_INIT action", () => {
|
||||
const t = {portID: "foo"};
|
||||
const t = {portID: "foo", url: "about:monkeys"};
|
||||
sinon.stub(mm, "onActionFromContent");
|
||||
|
||||
mm.onNewTabInit({target: t});
|
||||
assert.calledWith(mm.onActionFromContent, {type: at.NEW_TAB_INIT}, "foo");
|
||||
|
||||
assert.calledWith(mm.onActionFromContent, {
|
||||
type: at.NEW_TAB_INIT,
|
||||
data: {url: "about:monkeys"}
|
||||
}, "foo");
|
||||
});
|
||||
});
|
||||
describe("#onNewTabLoad", () => {
|
||||
|
@ -245,6 +259,17 @@ describe("ActivityStreamMessageChannel", () => {
|
|||
store.dispatch({type: "ADD", data: 10});
|
||||
assert.equal(store.getState(), 10);
|
||||
});
|
||||
it("should not call next if skipMain is true", () => {
|
||||
store.dispatch({type: "ADD", data: 10, meta: {skipMain: true}});
|
||||
assert.equal(store.getState(), 0);
|
||||
|
||||
sinon.stub(mm, "send");
|
||||
const action = ac.SendToContent({type: "ADD", data: 10, meta: {skipMain: true}}, "foo");
|
||||
mm.createChannel();
|
||||
store.dispatch(action);
|
||||
assert.calledWith(mm.send, action);
|
||||
assert.equal(store.getState(), 0);
|
||||
});
|
||||
it("should call .send if the action is SendToContent", () => {
|
||||
sinon.stub(mm, "send");
|
||||
const action = ac.SendToContent({type: "FOO"}, "foo");
|
||||
|
|
|
@ -5,8 +5,9 @@ const {actionTypes: at} = require("common/Actions.jsm");
|
|||
const {Dedupe} = require("common/Dedupe.jsm");
|
||||
|
||||
const FAKE_LINKS = new Array(9).fill(null).map((v, i) => ({url: `http://www.site${i}.com`}));
|
||||
const FAKE_IMAGE = "data123";
|
||||
|
||||
describe("Top Sites Feed", () => {
|
||||
describe("Highlights Feed", () => {
|
||||
let HighlightsFeed;
|
||||
let HIGHLIGHTS_UPDATE_TIME;
|
||||
let SECTION_ID;
|
||||
|
@ -15,6 +16,7 @@ describe("Top Sites Feed", () => {
|
|||
let sandbox;
|
||||
let links;
|
||||
let clock;
|
||||
let fakeScreenshot;
|
||||
let fakeNewTabUtils;
|
||||
let sectionsManagerStub;
|
||||
let shortURLStub;
|
||||
|
@ -28,13 +30,16 @@ describe("Top Sites Feed", () => {
|
|||
enableSection: sinon.spy(),
|
||||
disableSection: sinon.spy(),
|
||||
updateSection: sinon.spy(),
|
||||
updateSectionCard: sinon.spy(),
|
||||
sections: new Map([["highlights", {}]])
|
||||
};
|
||||
fakeScreenshot = {getScreenshotForURL: sandbox.spy(() => Promise.resolve(FAKE_IMAGE))};
|
||||
shortURLStub = sinon.stub().callsFake(site => site.url.match(/\/([^/]+)/)[1]);
|
||||
globals.set("NewTabUtils", fakeNewTabUtils);
|
||||
({HighlightsFeed, HIGHLIGHTS_UPDATE_TIME, SECTION_ID} = injector({
|
||||
"lib/ShortURL.jsm": {shortURL: shortURLStub},
|
||||
"lib/SectionsManager.jsm": {SectionsManager: sectionsManagerStub},
|
||||
"lib/Screenshots.jsm": {Screenshots: fakeScreenshot},
|
||||
"common/Dedupe.jsm": {Dedupe}
|
||||
}));
|
||||
feed = new HighlightsFeed();
|
||||
|
@ -69,11 +74,24 @@ describe("Top Sites Feed", () => {
|
|||
});
|
||||
});
|
||||
describe("#fetchHighlights", () => {
|
||||
it("should add hostname and image to each link", async () => {
|
||||
links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
|
||||
it("should add hostname and hasImage to each link", async () => {
|
||||
links = [{url: "https://mozilla.org"}];
|
||||
await feed.fetchHighlights();
|
||||
assert.equal(feed.highlights[0].hostname, "mozilla.org");
|
||||
assert.equal(feed.highlights[0].image, links[0].preview_image_url);
|
||||
assert.equal(feed.highlights[0].hasImage, true);
|
||||
});
|
||||
it("should add the image from the imageCache if it exists to the link", async () => {
|
||||
links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
|
||||
feed.imageCache = new Map([["https://mozilla.org", FAKE_IMAGE]]);
|
||||
await feed.fetchHighlights();
|
||||
assert.equal(feed.highlights[0].image, FAKE_IMAGE);
|
||||
});
|
||||
it("should call fetchImage with the correct arguments for each link", async () => {
|
||||
links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
|
||||
sinon.spy(feed, "fetchImage");
|
||||
await feed.fetchHighlights();
|
||||
assert.calledOnce(feed.fetchImage);
|
||||
assert.calledWith(feed.fetchImage, links[0].url, links[0].preview_image_url);
|
||||
});
|
||||
it("should not include any links already in Top Sites", async () => {
|
||||
links = [
|
||||
|
@ -115,6 +133,38 @@ describe("Top Sites Feed", () => {
|
|||
await feed.fetchHighlights();
|
||||
assert.equal(feed.highlights[0].type, "bookmark");
|
||||
});
|
||||
it("should clear the imageCache at the end", async () => {
|
||||
links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jpg"}];
|
||||
feed.imageCache = new Map([["https://mozilla.org", FAKE_IMAGE]]);
|
||||
// Stops fetchImage adding to the cache
|
||||
feed.fetchImage = () => {};
|
||||
await feed.fetchHighlights();
|
||||
assert.equal(feed.imageCache.size, 0);
|
||||
});
|
||||
});
|
||||
describe("#fetchImage", () => {
|
||||
const FAKE_URL = "https://mozilla.org";
|
||||
const FAKE_IMAGE_URL = "https://mozilla.org/preview.jpg";
|
||||
it("should capture the image, if available", async () => {
|
||||
await feed.fetchImage(FAKE_URL, FAKE_IMAGE_URL);
|
||||
assert.calledOnce(fakeScreenshot.getScreenshotForURL);
|
||||
assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_IMAGE_URL);
|
||||
});
|
||||
it("should fall back to capturing a screenshot", async () => {
|
||||
await feed.fetchImage(FAKE_URL, undefined);
|
||||
assert.calledOnce(fakeScreenshot.getScreenshotForURL);
|
||||
assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_URL);
|
||||
});
|
||||
it("should store the image in the imageCache", async () => {
|
||||
feed.imageCache.clear();
|
||||
await feed.fetchImage(FAKE_URL, FAKE_IMAGE_URL);
|
||||
assert.equal(feed.imageCache.get(FAKE_URL), FAKE_IMAGE);
|
||||
});
|
||||
it("should call SectionsManager.updateSectionCard with the right arguments", async () => {
|
||||
await feed.fetchImage(FAKE_URL, FAKE_IMAGE_URL);
|
||||
assert.calledOnce(sectionsManagerStub.updateSectionCard);
|
||||
assert.calledWith(sectionsManagerStub.updateSectionCard, "highlights", FAKE_URL, {image: FAKE_IMAGE}, true);
|
||||
});
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
it("should disable its section", () => {
|
||||
|
|
|
@ -57,7 +57,7 @@ describe("PrefsFeed", () => {
|
|||
});
|
||||
it("should set prerender pref to false if a pref does not match its initial value", () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
FAKE_PREFS.set("feeds.section.topstories", false);
|
||||
FAKE_PREFS.set("showSearch", false);
|
||||
feed.onAction({type: at.INIT});
|
||||
assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, false);
|
||||
});
|
||||
|
@ -70,16 +70,16 @@ describe("PrefsFeed", () => {
|
|||
it("should set the prerender pref to false if a pref in invalidatingPrefs is changed from its original value", () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
|
||||
feed._prefs.set("feeds.section.topstories", false);
|
||||
feed.onPrefChanged("feeds.section.topstories", false);
|
||||
feed._prefs.set("showSearch", false);
|
||||
feed.onPrefChanged("showSearch", false);
|
||||
assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, false);
|
||||
});
|
||||
it("should set the prerender pref back to true if the invalidatingPrefs are changed back to their original values", () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
FAKE_PREFS.set("feeds.section.topstories", false);
|
||||
FAKE_PREFS.set("showSearch", false);
|
||||
|
||||
feed._prefs.set("feeds.section.topstories", true);
|
||||
feed.onPrefChanged("feeds.section.topstories", true);
|
||||
feed._prefs.set("showSearch", true);
|
||||
feed.onPrefChanged("showSearch", true);
|
||||
assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,9 @@ const {MAIN_MESSAGE_TYPE, CONTENT_MESSAGE_TYPE} = require("common/Actions.jsm");
|
|||
|
||||
const FAKE_ID = "FAKE_ID";
|
||||
const FAKE_OPTIONS = {icon: "FAKE_ICON", title: "FAKE_TITLE"};
|
||||
const FAKE_ROWS = [{url: "1"}, {url: "2"}, {"url": "3"}];
|
||||
const FAKE_ROWS = [{url: "1.example.com"}, {url: "2.example.com"}, {"url": "3.example.com"}];
|
||||
const FAKE_URL = "2.example.com";
|
||||
const FAKE_CARD_OPTIONS = {title: "Some fake title"};
|
||||
|
||||
describe("SectionsManager", () => {
|
||||
let globals;
|
||||
|
@ -183,6 +185,24 @@ describe("SectionsManager", () => {
|
|||
assert.calledWith(SectionsManager.once, SectionsManager.INIT, callback);
|
||||
});
|
||||
});
|
||||
describe("#updateSectionCard", () => {
|
||||
it("should emit an UPDATE_SECTION_CARD event with correct arguments", () => {
|
||||
SectionsManager.addSection(FAKE_ID, Object.assign({}, FAKE_OPTIONS, {rows: FAKE_ROWS}));
|
||||
const spy = sinon.spy();
|
||||
SectionsManager.on(SectionsManager.UPDATE_SECTION_CARD, spy);
|
||||
SectionsManager.updateSectionCard(FAKE_ID, FAKE_URL, FAKE_CARD_OPTIONS, true);
|
||||
assert.calledOnce(spy);
|
||||
assert.calledWith(spy, SectionsManager.UPDATE_SECTION_CARD, FAKE_ID,
|
||||
FAKE_URL, FAKE_CARD_OPTIONS, true);
|
||||
});
|
||||
it("should do nothing if the section doesn't exist", () => {
|
||||
SectionsManager.removeSection(FAKE_ID);
|
||||
const spy = sinon.spy();
|
||||
SectionsManager.on(SectionsManager.UPDATE_SECTION_CARD, spy);
|
||||
SectionsManager.updateSectionCard(FAKE_ID, FAKE_URL, FAKE_CARD_OPTIONS, true);
|
||||
assert.notCalled(spy);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("SectionsFeed", () => {
|
||||
|
@ -204,11 +224,12 @@ describe("SectionsFeed", () => {
|
|||
it("should bind appropriate listeners", () => {
|
||||
sinon.spy(SectionsManager, "on");
|
||||
feed.init();
|
||||
assert.calledThrice(SectionsManager.on);
|
||||
assert.callCount(SectionsManager.on, 4);
|
||||
for (const [event, listener] of [
|
||||
[SectionsManager.ADD_SECTION, feed.onAddSection],
|
||||
[SectionsManager.REMOVE_SECTION, feed.onRemoveSection],
|
||||
[SectionsManager.UPDATE_SECTION, feed.onUpdateSection]
|
||||
[SectionsManager.UPDATE_SECTION, feed.onUpdateSection],
|
||||
[SectionsManager.UPDATE_SECTION_CARD, feed.onUpdateSectionCard]
|
||||
]) {
|
||||
assert.calledWith(SectionsManager.on, event, listener);
|
||||
}
|
||||
|
@ -231,11 +252,12 @@ describe("SectionsFeed", () => {
|
|||
sinon.spy(SectionsManager, "off");
|
||||
feed.init();
|
||||
feed.uninit();
|
||||
assert.calledThrice(SectionsManager.off);
|
||||
assert.callCount(SectionsManager.off, 4);
|
||||
for (const [event, listener] of [
|
||||
[SectionsManager.ADD_SECTION, feed.onAddSection],
|
||||
[SectionsManager.REMOVE_SECTION, feed.onRemoveSection],
|
||||
[SectionsManager.UPDATE_SECTION, feed.onUpdateSection]
|
||||
[SectionsManager.UPDATE_SECTION, feed.onUpdateSection],
|
||||
[SectionsManager.UPDATE_SECTION_CARD, feed.onUpdateSectionCard]
|
||||
]) {
|
||||
assert.calledWith(SectionsManager.off, event, listener);
|
||||
}
|
||||
|
@ -271,7 +293,7 @@ describe("SectionsFeed", () => {
|
|||
});
|
||||
});
|
||||
describe("#onUpdateSection", () => {
|
||||
it("should do nothing if no rows are provided", () => {
|
||||
it("should do nothing if no options are provided", () => {
|
||||
feed.onUpdateSection(null, FAKE_ID, null);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
});
|
||||
|
@ -291,6 +313,27 @@ describe("SectionsFeed", () => {
|
|||
assert.equal(action.meta.to, CONTENT_MESSAGE_TYPE);
|
||||
});
|
||||
});
|
||||
describe("#onUpdateSectionCard", () => {
|
||||
it("should do nothing if no options are provided", () => {
|
||||
feed.onUpdateSectionCard(null, FAKE_ID, FAKE_URL, null);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
});
|
||||
it("should dispatch a SECTION_UPDATE_CARD action with the correct data", () => {
|
||||
feed.onUpdateSectionCard(null, FAKE_ID, FAKE_URL, FAKE_CARD_OPTIONS);
|
||||
const action = feed.store.dispatch.firstCall.args[0];
|
||||
assert.equal(action.type, "SECTION_UPDATE_CARD");
|
||||
assert.deepEqual(action.data, {id: FAKE_ID, url: FAKE_URL, options: FAKE_CARD_OPTIONS});
|
||||
// Should be not broadcast by default, so meta should not exist
|
||||
assert.notOk(action.meta);
|
||||
});
|
||||
it("should broadcast the action only if shouldBroadcast is true", () => {
|
||||
feed.onUpdateSectionCard(null, FAKE_ID, FAKE_URL, FAKE_CARD_OPTIONS, true);
|
||||
const action = feed.store.dispatch.firstCall.args[0];
|
||||
// Should be broadcast
|
||||
assert.equal(action.meta.from, MAIN_MESSAGE_TYPE);
|
||||
assert.equal(action.meta.to, CONTENT_MESSAGE_TYPE);
|
||||
});
|
||||
});
|
||||
describe("#onAction", () => {
|
||||
it("should bind this.init to SectionsManager.INIT on INIT", () => {
|
||||
sinon.spy(SectionsManager, "once");
|
||||
|
|
|
@ -99,10 +99,15 @@ describe("TelemetryFeed", () => {
|
|||
assert.calledOnce(global.gUUIDGenerator.generateUUID);
|
||||
assert.equal(session.session_id, global.gUUIDGenerator.generateUUID.firstCall.returnValue);
|
||||
});
|
||||
it("should set the page", () => {
|
||||
it("should set the page if a url parameter is given", () => {
|
||||
const session = instance.addSession("foo", "about:monkeys");
|
||||
|
||||
assert.propertyVal(session, "page", "about:monkeys");
|
||||
});
|
||||
it("should set the page prop to 'unknown' if no URL parameter given", () => {
|
||||
const session = instance.addSession("foo");
|
||||
|
||||
assert.equal(session.page, "about:newtab"); // This is hardcoded for now.
|
||||
assert.propertyVal(session, "page", "unknown");
|
||||
});
|
||||
it("should set the perf type when lacking timestamp", () => {
|
||||
const session = instance.addSession("foo");
|
||||
|
@ -178,11 +183,12 @@ describe("TelemetryFeed", () => {
|
|||
const ping = await instance.createPing();
|
||||
assert.validate(ping, BasePing);
|
||||
assert.notProperty(ping, "session_id");
|
||||
assert.notProperty(ping, "page");
|
||||
});
|
||||
it("should create a valid base ping with session info if a portID is supplied", async () => {
|
||||
// Add a session
|
||||
const portID = "foo";
|
||||
instance.addSession(portID);
|
||||
instance.addSession(portID, "about:home");
|
||||
const sessionID = instance.sessions.get(portID).session_id;
|
||||
|
||||
// Create a ping referencing the session
|
||||
|
@ -191,13 +197,13 @@ describe("TelemetryFeed", () => {
|
|||
|
||||
// Make sure we added the right session-related stuff to the ping
|
||||
assert.propertyVal(ping, "session_id", sessionID);
|
||||
assert.propertyVal(ping, "page", "about:newtab");
|
||||
assert.propertyVal(ping, "page", "about:home");
|
||||
});
|
||||
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(ping, "page", "unknown");
|
||||
assert.propertyVal(instance.sessions.get("foo").perf, "load_trigger_type", "unexpected");
|
||||
});
|
||||
it("should create a base ping with user_prefs", async () => {
|
||||
|
@ -490,11 +496,11 @@ describe("TelemetryFeed", () => {
|
|||
|
||||
instance.onAction(ac.SendToMain({
|
||||
type: at.NEW_TAB_INIT,
|
||||
data: {}
|
||||
data: {url: "about:monkeys"}
|
||||
}, "port123"));
|
||||
|
||||
assert.calledOnce(stub);
|
||||
assert.calledWith(stub, "port123");
|
||||
assert.calledWith(stub, "port123", "about:monkeys");
|
||||
});
|
||||
it("should call .endSession() on a NEW_TAB_UNLOAD action", () => {
|
||||
const stub = sandbox.stub(instance, "endSession");
|
||||
|
|
|
@ -385,6 +385,17 @@ describe("Top Sites Feed", () => {
|
|||
data: [Object.assign({}, site1, {hostname: "foo.com"})]
|
||||
}));
|
||||
});
|
||||
it("should compare against links if available, instead of getting from store", () => {
|
||||
const frecentSite = {url: "foo.com", faviconSize: 32, favicon: "favicon.png"};
|
||||
const pinnedSite1 = {url: "bar.com"};
|
||||
const pinnedSite2 = {url: "foo.com"};
|
||||
fakeNewTabUtils.pinnedLinks.links = [pinnedSite1, pinnedSite2];
|
||||
feed.store = {getState() { return {TopSites: {rows: sinon.spy()}}; }};
|
||||
let result = feed._getPinnedWithData([frecentSite]);
|
||||
assert.deepEqual(result[0], pinnedSite1);
|
||||
assert.deepEqual(result[1], Object.assign({}, frecentSite, pinnedSite2));
|
||||
assert.notCalled(feed.store.getState().TopSites.rows);
|
||||
});
|
||||
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 = {
|
||||
|
|