--- a/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
+++ b/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
@@ -8,18 +8,30 @@
<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
<link rel="stylesheet" href="resource://activity-stream/data/content/activity-stream.css" />
</head>
<body class="activity-stream">
<div id="root"><div class="outer-wrapper fixed-to-top" data-reactroot="" data-reactid="1" data-react-checksum="544412221"><main data-reactid="2"><div class="search-wrapper" data-reactid="3"><label for="newtab-search-text" class="search-label" data-reactid="4"><span class="sr-only" data-reactid="5"><span data-reactid="6">Search the Web</span></span></label><input type="search" id="newtab-search-text" maxlength="256" placeholder="Search the Web" title="Search the Web" data-reactid="7"/><button id="searchSubmit" class="search-button" title=" " data-reactid="8"><span class="sr-only" data-reactid="9"><span data-reactid="10"> </span></span></button></div><section class="top-sites" data-reactid="11"><h3 class="section-title" data-reactid="12"><span class="icon icon-small-spacer icon-topsites" data-reactid="13"></span><span data-reactid="14"> </span></h3><ul class="top-sites-list" data-reactid="15"><li class="top-site-outer placeholder" data-reactid="16"><a data-reactid="17"><div class="tile" aria-hidden="true" data-reactid="18"><span class="letter-fallback" data-reactid="19"></span><div class="screenshot" style="background-image:none;" data-reactid="20"></div></div><div class="title " data-reactid="21"><span dir="auto" data-reactid="22"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="23"><a data-reactid="24"><div class="tile" aria-hidden="true" data-reactid="25"><span class="letter-fallback" data-reactid="26"></span><div class="screenshot" style="background-image:none;" data-reactid="27"></div></div><div class="title " data-reactid="28"><span dir="auto" data-reactid="29"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="30"><a data-reactid="31"><div class="tile" aria-hidden="true" data-reactid="32"><span class="letter-fallback" data-reactid="33"></span><div class="screenshot" style="background-image:none;" data-reactid="34"></div></div><div class="title " data-reactid="35"><span dir="auto" data-reactid="36"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="37"><a data-reactid="38"><div class="tile" aria-hidden="true" data-reactid="39"><span class="letter-fallback" data-reactid="40"></span><div class="screenshot" style="background-image:none;" data-reactid="41"></div></div><div class="title " data-reactid="42"><span dir="auto" data-reactid="43"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="44"><a data-reactid="45"><div class="tile" aria-hidden="true" data-reactid="46"><span class="letter-fallback" data-reactid="47"></span><div class="screenshot" style="background-image:none;" data-reactid="48"></div></div><div class="title " data-reactid="49"><span dir="auto" data-reactid="50"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="51"><a data-reactid="52"><div class="tile" aria-hidden="true" data-reactid="53"><span class="letter-fallback" data-reactid="54"></span><div class="screenshot" style="background-image:none;" data-reactid="55"></div></div><div class="title " data-reactid="56"><span dir="auto" data-reactid="57"></span></div></a></li></ul><div class="edit-topsites-wrapper" data-reactid="58"><div class="edit-topsites-button" data-reactid="59"><button class="edit" title=" " data-reactid="60"><span data-reactid="61"> </span></button></div></div></section><div class="sections-list" data-reactid="62"><section data-reactid="63"><div class="section-top-bar" data-reactid="64"><h3 class="section-title" data-reactid="65"><span class="icon icon-small-spacer icon-pocket" data-reactid="66"></span><span data-reactid="67"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="68"><li class="card-outer placeholder" data-reactid="69"><a data-reactid="70"><div class="card" data-reactid="71"><div class="card-details no-image" data-reactid="72"><div class="card-text no-context no-description no-host-name no-image" data-reactid="73"><h4 class="card-title" dir="auto" data-reactid="74"></h4><p class="card-description" dir="auto" data-reactid="75"></p></div><div class="card-context" data-reactid="76"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="77"><a data-reactid="78"><div class="card" data-reactid="79"><div class="card-details no-image" data-reactid="80"><div class="card-text no-context no-description no-host-name no-image" data-reactid="81"><h4 class="card-title" dir="auto" data-reactid="82"></h4><p class="card-description" dir="auto" data-reactid="83"></p></div><div class="card-context" data-reactid="84"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="85"><a data-reactid="86"><div class="card" data-reactid="87"><div class="card-details no-image" data-reactid="88"><div class="card-text no-context no-description no-host-name no-image" data-reactid="89"><h4 class="card-title" dir="auto" data-reactid="90"></h4><p class="card-description" dir="auto" data-reactid="91"></p></div><div class="card-context" data-reactid="92"></div></div></div></a></li></ul><div class="topic" data-reactid="93"><span data-reactid="94"><span data-reactid="95"> </span></span><ul data-reactid="96"><li data-reactid="97"><a class="topic-link" data-reactid="98"></a></li></ul></div></section><section data-reactid="99"><div class="section-top-bar" data-reactid="100"><h3 class="section-title" data-reactid="101"><span class="icon icon-small-spacer icon-highlights" data-reactid="102"></span><span data-reactid="103"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="104"><li class="card-outer placeholder" data-reactid="105"><a data-reactid="106"><div class="card" data-reactid="107"><div class="card-details no-image" data-reactid="108"><div class="card-text no-context no-description no-host-name no-image" data-reactid="109"><h4 class="card-title" dir="auto" data-reactid="110"></h4><p class="card-description" dir="auto" data-reactid="111"></p></div><div class="card-context" data-reactid="112"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="113"><a data-reactid="114"><div class="card" data-reactid="115"><div class="card-details no-image" data-reactid="116"><div class="card-text no-context no-description no-host-name no-image" data-reactid="117"><h4 class="card-title" dir="auto" data-reactid="118"></h4><p class="card-description" dir="auto" data-reactid="119"></p></div><div class="card-context" data-reactid="120"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="121"><a data-reactid="122"><div class="card" data-reactid="123"><div class="card-details no-image" data-reactid="124"><div class="card-text no-context no-description no-host-name no-image" data-reactid="125"><h4 class="card-title" dir="auto" data-reactid="126"></h4><p class="card-description" dir="auto" data-reactid="127"></p></div><div class="card-context" data-reactid="128"></div></div></div></a></li></ul></section></div><!-- react-empty: 129 --></main></div></div>
<div id="snippets-container">
<div id="snippets"></div>
</div>
-<script src="resource://activity-stream/data/content/activity-stream-initial-state.js"></script>
- <script src="chrome://browser/content/contentSearchUI.js"></script>
- <script src="resource://activity-stream/vendor/react.js"></script>
- <script src="resource://activity-stream/vendor/react-dom.js"></script>
- <script src="resource://activity-stream/vendor/react-intl.js"></script>
- <script src="resource://activity-stream/vendor/redux.js"></script>
- <script src="resource://activity-stream/vendor/react-redux.js"></script>
- <script src="resource://activity-stream/data/content/activity-stream.bundle.js"></script>
+ <script>
+// Don't directly load the following scripts as part of html to let the page
+// finish loading to render the content sooner.
+for (const src of [
+ "resource://activity-stream/data/content/activity-stream-initial-state.js",
+ "chrome://browser/content/contentSearchUI.js",
+ "resource://activity-stream/vendor/react.js",
+ "resource://activity-stream/vendor/react-dom.js",
+ "resource://activity-stream/vendor/react-intl.js",
+ "resource://activity-stream/vendor/redux.js",
+ "resource://activity-stream/vendor/react-redux.js",
+ "resource://activity-stream/data/content/activity-stream.bundle.js"
+]) {
+ // These dynamically inserted scripts by default are async, but we need them
+ // to load in the desired order (i.e., bundle last).
+ const script = document.body.appendChild(document.createElement("script"));
+ script.async = false;
+ script.src = src;
+}
+ </script>
</body>
</html>
--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -2254,17 +2254,17 @@ const getFormattedMessage = message => t
"span",
null,
message
) : React.createElement(FormattedMessage, message);
const PreferencesInput = props => React.createElement(
"section",
null,
- React.createElement("input", { type: "checkbox", id: props.prefName, name: props.prefName, checked: props.value, onChange: props.onChange, className: props.className }),
+ React.createElement("input", { type: "checkbox", id: props.prefName, name: props.prefName, checked: props.value, disabled: props.disabled, onChange: props.onChange, className: props.className }),
React.createElement(
"label",
{ htmlFor: props.prefName, className: props.labelClassName },
getFormattedMessage(props.titleString)
),
props.descString && React.createElement(
"p",
{ className: "prefs-input-description" },
@@ -2354,18 +2354,19 @@ class PreferencesPane extends React.Pure
),
React.createElement(PreferencesInput, { className: "showSearch", prefName: "showSearch", value: prefs.showSearch, onChange: this.handlePrefChange,
titleString: { id: "settings_pane_search_header" }, descString: { id: "settings_pane_search_body" } }),
React.createElement("hr", null),
React.createElement(PreferencesInput, { className: "showTopSites", prefName: "showTopSites", value: prefs.showTopSites, onChange: this.handlePrefChange,
titleString: { id: "settings_pane_topsites_header" }, descString: { id: "settings_pane_topsites_body" } }),
React.createElement(
"div",
- { className: "options" },
- React.createElement(PreferencesInput, { className: "showMoreTopSites", prefName: "topSitesCount", value: prefs.topSitesCount !== TOP_SITES_DEFAULT_LENGTH, onChange: this.handlePrefChange,
+ { className: `options${prefs.showTopSites ? "" : " disabled"}` },
+ React.createElement(PreferencesInput, { className: "showMoreTopSites", prefName: "topSitesCount", disabled: !prefs.showTopSites,
+ value: prefs.topSitesCount !== TOP_SITES_DEFAULT_LENGTH, onChange: this.handlePrefChange,
titleString: { id: "settings_pane_topsites_options_showmore" }, labelClassName: "icon icon-topsites" })
),
sections.filter(section => !section.shouldHidePref).map(({ id, title, enabled, pref }) => React.createElement(PreferencesInput, { key: id, className: "showSection", prefName: pref && pref.feed || id,
value: enabled, onChange: pref && pref.feed ? this.handlePrefChange : this.handleSectionChange,
titleString: pref && pref.titleString || title, descString: pref && pref.descString })),
React.createElement("hr", null),
React.createElement(PreferencesInput, { className: "showSnippets", prefName: "feeds.snippets",
value: prefs["feeds.snippets"], onChange: this.handlePrefChange,
--- a/browser/extensions/activity-stream/data/content/activity-stream.css
+++ b/browser/extensions/activity-stream/data/content/activity-stream.css
@@ -331,19 +331,19 @@ main {
.top-sites-list .top-site-outer .rich-icon {
top: 0;
offset-inline-start: 0;
height: 100%;
width: 100%;
background-size: 96px; }
.top-sites-list .top-site-outer .default-icon {
z-index: 1;
- top: -6px;
- offset-inline-start: -6px;
+ bottom: -6px;
height: 42px;
+ offset-inline-end: -6px;
width: 42px;
background-size: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px; }
.top-sites-list .top-site-outer .title {
font: message-box;
@@ -807,16 +807,18 @@ main {
line-height: 19px; }
.prefs-pane .prefs-modal-inner-wrapper .options {
background: #F9F9FA;
border: 1px solid #D7D7DB;
border-radius: 2px;
margin: -10px 0 20px;
margin-inline-start: 30px;
padding: 10px; }
+ .prefs-pane .prefs-modal-inner-wrapper .options.disabled {
+ opacity: 0.5; }
.prefs-pane .prefs-modal-inner-wrapper .options label {
background-position-x: 35px;
background-position-y: 2.5px;
background-repeat: no-repeat;
display: inline-block;
font-size: 14px;
font-weight: normal;
height: auto;
@@ -838,18 +840,18 @@ main {
position: fixed;
width: 400px; }
.prefs-pane .actions button {
margin-inline-end: 20px; }
.prefs-pane [type='checkbox']:not(:checked),
.prefs-pane [type='checkbox']:checked {
offset-inline-start: -9999px;
position: absolute; }
- .prefs-pane [type='checkbox']:not(:checked) + label,
- .prefs-pane [type='checkbox']:checked + label {
+ .prefs-pane [type='checkbox']:not(:disabled):not(:checked) + label,
+ .prefs-pane [type='checkbox']:not(:disabled):checked + label {
cursor: pointer;
padding: 0 30px;
position: relative; }
.prefs-pane [type='checkbox']:not(:checked) + label::before,
.prefs-pane [type='checkbox']:checked + label::before {
background: #FFF;
border: 1px solid #B1B1B3;
border-radius: 3px;
@@ -870,20 +872,20 @@ main {
width: 21px;
-moz-context-properties: fill, stroke;
fill: #0060DF;
stroke: none; }
.prefs-pane [type='checkbox']:not(:checked) + label::after {
opacity: 0; }
.prefs-pane [type='checkbox']:checked + label::after {
opacity: 1; }
- .prefs-pane [type='checkbox'] + label:hover::before {
+ .prefs-pane [type='checkbox']:not(:disabled) + label:hover::before {
border: 1px solid #0060DF; }
- .prefs-pane [type='checkbox']:checked:focus + label::before,
- .prefs-pane [type='checkbox']:not(:checked):focus + label::before {
+ .prefs-pane [type='checkbox']:not(:disabled):checked:focus + label::before,
+ .prefs-pane [type='checkbox']:not(:disabled):not(:checked):focus + label::before {
border: 1px dotted #0060DF; }
.prefs-pane-button button {
background-color: transparent;
border: 0;
cursor: pointer;
fill: rgba(12, 12, 13, 0.6);
padding: 15px;
--- a/browser/extensions/activity-stream/data/content/activity-stream.html
+++ b/browser/extensions/activity-stream/data/content/activity-stream.html
@@ -8,17 +8,29 @@
<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
<link rel="stylesheet" href="resource://activity-stream/data/content/activity-stream.css" />
</head>
<body class="activity-stream">
<div id="root"></div>
<div id="snippets-container">
<div id="snippets"></div>
</div>
- <script src="chrome://browser/content/contentSearchUI.js"></script>
- <script src="resource://activity-stream/vendor/react.js"></script>
- <script src="resource://activity-stream/vendor/react-dom.js"></script>
- <script src="resource://activity-stream/vendor/react-intl.js"></script>
- <script src="resource://activity-stream/vendor/redux.js"></script>
- <script src="resource://activity-stream/vendor/react-redux.js"></script>
- <script src="resource://activity-stream/data/content/activity-stream.bundle.js"></script>
+ <script>
+// Don't directly load the following scripts as part of html to let the page
+// finish loading to render the content sooner.
+for (const src of [
+ "chrome://browser/content/contentSearchUI.js",
+ "resource://activity-stream/vendor/react.js",
+ "resource://activity-stream/vendor/react-dom.js",
+ "resource://activity-stream/vendor/react-intl.js",
+ "resource://activity-stream/vendor/redux.js",
+ "resource://activity-stream/vendor/react-redux.js",
+ "resource://activity-stream/data/content/activity-stream.bundle.js"
+]) {
+ // These dynamically inserted scripts by default are async, but we need them
+ // to load in the desired order (i.e., bundle last).
+ const script = document.body.appendChild(document.createElement("script"));
+ script.async = false;
+ script.src = src;
+}
+ </script>
</body>
</html>
--- a/browser/extensions/activity-stream/data/locales.json
+++ b/browser/extensions/activity-stream/data/locales.json
@@ -6058,33 +6058,33 @@
"menu_action_pin": "Sabitle",
"menu_action_unpin": "Sabitleneni kaldır",
"confirm_history_delete_p1": "Bu sayfanın tüm kayıtlarını geçmişinizden silmek istediğinizden emin misiniz?",
"confirm_history_delete_notice_p2": "Bu işlem geri alınamaz.",
"menu_action_save_to_pocket": "Pocket’a kaydet",
"search_for_something_with": "{search_term} terimini şununla ara:",
"search_button": "Ara",
"search_header": "{search_engine_name} Araması",
- "search_web_placeholder": "Web'de ara",
+ "search_web_placeholder": "Web’de ara",
"search_settings": "Arama ayarlarını değiştir",
"section_info_option": "Bilgi",
"section_info_send_feedback": "Görüş gönder",
"section_info_privacy_notice": "Gizlilik bildirimi",
"welcome_title": "Yeni sekmeye hoş geldiniz",
"welcome_body": "Firefox son zamanlarda ziyaret ettiğiniz ve sık kullandığınız yer imlerini, makaleleri, videoları ve sayfaları onlara tekrar kolayca geri dönebilmeniz için bu alanda gösterecektir.",
"welcome_label": "Öne Çıkanlar'ınızı tanıyın",
"time_label_less_than_minute": "<1 dk",
"time_label_minute": "{number} dk",
"time_label_hour": "{number} sa",
"time_label_day": "{number} g",
"settings_pane_button_label": "Yeni Sekme sayfanızı özelleştirin",
"settings_pane_header": "Yeni Sekme Tercihleri",
"settings_pane_body2": "Bu sayfada görmek istediklerinizi seçin.",
"settings_pane_search_header": "Arama",
- "settings_pane_search_body": "Yeni sekme üzerinden web'de arama yapın.",
+ "settings_pane_search_body": "Yeni sekme üzerinden web’de arama yapın.",
"settings_pane_topsites_header": "Sık Kullandıklarınız",
"settings_pane_topsites_body": "En sık ziyaret ettiğiniz web sitelerine erişin.",
"settings_pane_topsites_options_showmore": "İki satır göster",
"settings_pane_bookmarks_header": "Son Yer İmleri",
"settings_pane_bookmarks_body": "Yeni eklediğiniz yer imlerini bir araya topladık.",
"settings_pane_visit_again_header": "Yeniden Ziyaret Edin",
"settings_pane_visit_again_body": "Firefox, gezinti geçmişinizden hatırlamak veya yeniden ziyaret etmek isteyebileceğiniz sayfaları burada gösterecek.",
"settings_pane_highlights_header": "Öne çıkanlar",
--- a/browser/extensions/activity-stream/install.rdf.in
+++ b/browser/extensions/activity-stream/install.rdf.in
@@ -3,17 +3,17 @@
#filter substitution
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>activity-stream@mozilla.org</em:id>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:unpack>false</em:unpack>
- <em:version>2017.09.14.1322-706b3303</em:version>
+ <em:version>2017.09.16.0001-2fc82208</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>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
--- a/browser/extensions/activity-stream/lib/ActivityStream.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStream.jsm
@@ -47,22 +47,21 @@ const PREFS_CONFIG = new Map([
}],
["feeds.section.topstories.options", {
title: "Configuration options for top stories feed",
// This is a dynamic pref as it depends on the feed being shown or not
getValue: args => JSON.stringify({
api_key_pref: "extensions.pocket.oAuthConsumerKey",
// Use the opposite value as what default value the feed would have used
hidden: !PREFS_CONFIG.get("feeds.section.topstories").getValue(args),
- learn_more_endpoint: "https://getpocket.cdn.mozilla.net/firefox_learnmore?src=ff_newtab",
provider_header: "pocket_feedback_header",
provider_description: "pocket_description",
provider_icon: "pocket",
provider_name: "Pocket",
- read_more_endpoint: "https://getpocket.cdn.mozilla.net/explore/trending?src=ff_new_tab",
+ read_more_endpoint: "https://getpocket.cdn.mozilla.net/explore/trending?src=fx_new_tab",
stories_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=2&consumer_key=$apiKey&locale_lang=${args.locale}`,
stories_referrer: "https://getpocket.com/recommendations",
info_link: "https://www.mozilla.org/privacy/firefox/#pocketstories",
topics_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/trending-topics?version=2&consumer_key=$apiKey&locale_lang=${args.locale}`,
show_spocs: false,
personalized: false
})
}],
--- a/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
+++ b/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
@@ -25,17 +25,16 @@ const HIGHLIGHTS_UPDATE_TIME = 15 * 60 *
const MANY_EXTRA_LENGTH = HIGHLIGHTS_MAX_LENGTH * 5 + TOP_SITES_SHOWMORE_LENGTH;
const SECTION_ID = "highlights";
this.HighlightsFeed = class HighlightsFeed {
constructor() {
this.highlightsLastUpdated = 0;
this.highlights = [];
this.dedupe = new Dedupe(this._dedupeKey);
- this.imageCache = new Map();
}
_dedupeKey(site) {
return site && site.url;
}
init() {
SectionsManager.onceInitialized(this.postInit.bind(this));
@@ -69,30 +68,40 @@ this.HighlightsFeed = class HighlightsFe
// Remove adult highlights if we need to
const checkedAdult = this.store.getState().Prefs.values.filterAdult ?
filterAdult(manyPages) : manyPages;
// Remove any Highlights that are in Top Sites already
const [, deduped] = this.dedupe.group(this.store.getState().TopSites.rows, checkedAdult);
+ // Store existing images in case we need to reuse them
+ const currentImages = {};
+ for (const site of this.highlights) {
+ if (site && site.image) {
+ currentImages[site.url] = site.image;
+ }
+ }
+
// Keep all "bookmark"s and at most one (most recent) "history" per host
this.highlights = [];
const hosts = new Set();
for (const page of deduped) {
const hostname = shortURL(page);
// Skip this history page if we already something from the same host
if (page.type === "history" && hosts.has(hostname)) {
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);
+ // If we already have the image for the card, use that immediately. Else
+ // asynchronously fetch the image.
+ const image = currentImages[page.url];
+ if (!image) {
+ 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,
type: page.bookmarkGuid ? "bookmark" : page.type
});
@@ -104,33 +113,32 @@ this.HighlightsFeed = class HighlightsFe
// Skip the rest if we have enough items
if (this.highlights.length === HIGHLIGHTS_MAX_LENGTH) {
break;
}
}
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.
+ * Fetch an image for a given highlight and update the card with it. If no
+ * image is available then fallback to fetching a screenshot. Update the card
+ * in `this.highlights` so that the image is cached for the next refresh.
*/
async fetchImage(url, imageUrl) {
const image = await Screenshots.getScreenshotForURL(imageUrl || url);
+ SectionsManager.updateSectionCard(SECTION_ID, url, {image}, true);
if (image) {
- this.imageCache.set(url, image);
+ const highlight = this.highlights.find(site => site.url === url);
+ if (highlight) {
+ highlight.image = image;
+ }
}
- SectionsManager.updateSectionCard(SECTION_ID, url, {image}, true);
}
onAction(action) {
switch (action.type) {
case at.INIT:
this.init();
break;
case at.NEW_TAB_LOAD:
--- a/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js
@@ -108,24 +108,27 @@ describe("Highlights Feed", () => {
assert.calledOnce(fakeNewTabUtils.activityStreamLinks.getHighlights);
});
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].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]]);
+ it("should add an existing image if it exists to the link without calling fetchImage", async () => {
+ links = [{url: "https://mozilla.org", image: FAKE_IMAGE}];
+ feed.highlights = links;
+ sinon.spy(feed, "fetchImage");
await feed.fetchHighlights();
assert.equal(feed.highlights[0].image, FAKE_IMAGE);
+ assert.notCalled(feed.fetchImage);
});
- it("should call fetchImage with the correct arguments for each link", async () => {
+ it("should call fetchImage with the correct arguments for new links", async () => {
links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
+ feed.highlights = [];
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 = [
{url: "https://mozilla.org"},
@@ -161,24 +164,16 @@ describe("Highlights Feed", () => {
assert.deepEqual(feed.highlights[0], links[0]);
assert.deepEqual(feed.highlights[1], links[2]);
});
it("should set type to bookmark if there is a bookmarkGuid", async () => {
links = [{url: "https://mozilla.org", type: "history", bookmarkGuid: "1234567890"}];
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);
- });
it("should not filter out adult pages when pref is false", async() => {
await feed.fetchHighlights();
assert.notCalled(filterAdultStub);
});
it("should filter out adult pages when pref is true", async() => {
feed.store.state.Prefs.values.filterAdult = true;
@@ -197,26 +192,27 @@ describe("Highlights Feed", () => {
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);
});
+ it("should update the card in feed.highlights with the image", async () => {
+ feed.highlights = [{url: FAKE_URL}];
+ await feed.fetchImage(FAKE_URL, FAKE_IMAGE_URL);
+ const highlight = feed.highlights.find(({url}) => url === FAKE_URL);
+ assert.propertyVal(highlight, "image", FAKE_IMAGE);
+ });
});
describe("#uninit", () => {
it("should disable its section", () => {
feed.onAction({type: at.UNINIT});
assert.calledOnce(sectionsManagerStub.disableSection);
assert.calledWith(sectionsManagerStub.disableSection, SECTION_ID);
});
});