diff --git a/browser/extensions/activity-stream/common/Actions.jsm b/browser/extensions/activity-stream/common/Actions.jsm index dc823172eb9d..fa37e85b3216 100644 --- a/browser/extensions/activity-stream/common/Actions.jsm +++ b/browser/extensions/activity-stream/common/Actions.jsm @@ -31,6 +31,7 @@ for (const type of [ "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", + "DISABLE_ONBOARDING", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", @@ -49,8 +50,8 @@ for (const type of [ "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", + "PLACES_LINKS_DELETED", "PLACES_LINK_BLOCKED", - "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", diff --git a/browser/extensions/activity-stream/common/PrerenderData.jsm b/browser/extensions/activity-stream/common/PrerenderData.jsm index b9360662c646..9b040fe0c950 100644 --- a/browser/extensions/activity-stream/common/PrerenderData.jsm +++ b/browser/extensions/activity-stream/common/PrerenderData.jsm @@ -74,8 +74,7 @@ this.PrerenderData = new _PrerenderData({ icon: "pocket", id: "topstories", order: 1, - title: {id: "header_recommended_by", values: {provider: "Pocket"}}, - topics: [{}] + title: {id: "header_recommended_by", values: {provider: "Pocket"}} }, { enabled: true, diff --git a/browser/extensions/activity-stream/common/Reducers.jsm b/browser/extensions/activity-stream/common/Reducers.jsm index 5dbd926f7a3f..a6f83d5f4823 100644 --- a/browser/extensions/activity-stream/common/Reducers.jsm +++ b/browser/extensions/activity-stream/common/Reducers.jsm @@ -288,7 +288,9 @@ function Sections(prevState = INITIAL_STATE.Sections, action) { return item; }) })); - case at.PLACES_LINK_DELETED: + case at.PLACES_LINKS_DELETED: + return prevState.map(section => Object.assign({}, section, + {rows: section.rows.filter(site => !action.data.includes(site.url))})); case at.PLACES_LINK_BLOCKED: return prevState.map(section => Object.assign({}, section, {rows: section.rows.filter(site => site.url !== action.data.url)})); diff --git a/browser/extensions/activity-stream/data/content/activity-stream-initial-state.js b/browser/extensions/activity-stream/data/content/activity-stream-initial-state.js index 572b9ff068e3..24e71dce86c3 100644 --- a/browser/extensions/activity-stream/data/content/activity-stream-initial-state.js +++ b/browser/extensions/activity-stream/data/content/activity-stream-initial-state.js @@ -138,9 +138,6 @@ "enabled": true, "icon": "pocket", "id": "topstories", - "topics": [ - {} - ], "initialized": false }, { diff --git a/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html b/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html index 484493d185a1..798f92331f47 100644 --- a/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html +++ b/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html @@ -9,7 +9,7 @@ -

+

    diff --git a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js index 1dbbaa5ff4dd..27b66f8c20b2 100644 --- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js +++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js @@ -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_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_OPTIONS_CHANGED", "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_CANCEL_EDIT", "TOP_SITES_EDIT", "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", "DISABLE_ONBOARDING", "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", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_DELETED", "PLACES_LINK_BLOCKED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_OPTIONS_CHANGED", "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_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) { actionTypes[type] = type; } @@ -638,7 +638,8 @@ function Sections(prevState = INITIAL_STATE.Sections, action) { return item; }) })); - case at.PLACES_LINK_DELETED: + case at.PLACES_LINKS_DELETED: + return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => !action.data.includes(site.url)) })); case at.PLACES_LINK_BLOCKED: return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => site.url !== action.data.url) })); default: @@ -1511,10 +1512,14 @@ class TopSitesEdit extends React.PureComponent { "section", { className: "edit-topsites-inner-wrapper" }, React.createElement( - "h3", - { className: "section-title" }, - React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }), - React.createElement(FormattedMessage, { id: "header_top_sites" }) + "div", + { className: "section-top-bar" }, + React.createElement( + "h3", + { className: "section-title" }, + React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }), + React.createElement(FormattedMessage, { id: "header_top_sites" }) + ) ), React.createElement( "ul", @@ -2569,7 +2574,10 @@ class Section extends React.PureComponent { contextMenuOptions, intl, initialized } = this.props; const maxCards = CARDS_PER_ROW * maxRows; - const shouldShowTopics = id === "topstories" && this.props.topics && this.props.topics.length > 0; + + // Show topics only for top stories and if it's not initialized yet (so + // content doesn't shift when it is loaded) or has loaded with topics + const shouldShowTopics = id === "topstories" && (!this.props.topics || this.props.topics.length > 0); const infoOptionIconA11yAttrs = { "aria-haspopup": "true", @@ -2694,6 +2702,9 @@ const { FormattedMessage } = __webpack_require__(2); const cardContextTypes = __webpack_require__(26); const { actionCreators: ac, actionTypes: at } = __webpack_require__(0); +// Keep track of pending image loads to only request once +const gImageLoading = new Map(); + /** * Card component. * Cards are found within a Section component and contain information about a link such @@ -2706,11 +2717,47 @@ const { actionCreators: ac, actionTypes: at } = __webpack_require__(0); class Card extends React.PureComponent { constructor(props) { super(props); - this.state = { showContextMenu: false, activeCard: null }; + this.state = { + activeCard: null, + imageLoaded: false, + showContextMenu: false + }; this.onMenuButtonClick = this.onMenuButtonClick.bind(this); this.onMenuUpdate = this.onMenuUpdate.bind(this); this.onLinkClick = this.onLinkClick.bind(this); } + + /** + * Helper to conditionally load an image and update state when it loads. + */ + async maybeLoadImage() { + // No need to load if it's already loaded or no image + const { image } = this.props.link; + if (!this.state.imageLoaded && image) { + // Initialize a promise to share a load across multiple card updates + if (!gImageLoading.has(image)) { + const loaderPromise = new Promise((resolve, reject) => { + const loader = new Image(); + loader.addEventListener("load", resolve); + loader.addEventListener("error", reject); + loader.src = image; + }); + + // Save and remove the promise only while it's pending + gImageLoading.set(image, loaderPromise); + loaderPromise.catch(ex => ex).then(() => gImageLoading.delete(image)).catch(); + } + + // Wait for the image whether just started loading or reused promise + await gImageLoading.get(image); + + // Only update state if we're still waiting to load the original image + if (this.props.link.image === image && !this.state.imageLoaded) { + this.setState({ imageLoaded: true }); + } + } + } + onMenuButtonClick(event) { event.preventDefault(); this.setState({ @@ -2742,6 +2789,18 @@ class Card extends React.PureComponent { onMenuUpdate(showContextMenu) { this.setState({ showContextMenu }); } + componentDidMount() { + this.maybeLoadImage(); + } + componentDidUpdate() { + this.maybeLoadImage(); + } + componentWillReceiveProps(nextProps) { + // Clear the image state if changing images + if (nextProps.link.image !== this.props.link.image) { + this.setState({ imageLoaded: false }); + } + } render() { const { index, link, dispatch, contextMenuOptions, eventSource, shouldSendImpressionStats } = this.props; const { props } = this; @@ -2763,7 +2822,7 @@ class Card extends React.PureComponent { 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-preview-image${this.state.imageLoaded ? " loaded" : ""}`, style: imageStyle }) ), React.createElement( "div", @@ -2894,7 +2953,7 @@ class Topics extends React.PureComponent { React.createElement( "ul", null, - topics.map(t => React.createElement(Topic, { key: t.name, url: t.url, name: t.name })) + topics && topics.map(t => React.createElement(Topic, { key: t.name, url: t.url, name: t.name })) ), read_more_endpoint && React.createElement( "a", @@ -2985,8 +3044,7 @@ var PrerenderData = new _PrerenderData({ icon: "pocket", id: "topstories", order: 1, - title: { id: "header_recommended_by", values: { provider: "Pocket" } }, - topics: [{}] + title: { id: "header_recommended_by", values: { provider: "Pocket" } } }, { enabled: true, id: "highlights", @@ -3257,6 +3315,10 @@ class SnippetsMap extends Map { await this.set("blockList", blockList); } + disableOnboarding() { + this._dispatch(ac.SendToMain({ type: at.DISABLE_ONBOARDING })); + } + showFirefoxAccounts() { this._dispatch(ac.SendToMain({ type: at.SHOW_FIREFOX_ACCOUNTS })); } @@ -3395,7 +3457,6 @@ class SnippetsProvider { if (needsUpdate && this.appData.snippetsURL) { this.snippetsMap.set("snippets-last-update", Date.now()); try { - // TODO: timeout? const response = await fetch(this.appData.snippetsURL); if (response.status === 200) { const payload = await response.text(); @@ -3409,10 +3470,18 @@ class SnippetsProvider { } } - _showDefaultSnippets() { + _noSnippetFallback() { // TODO } + _forceOnboardingVisibility(shouldBeVisible) { + const onboardingEl = document.getElementById("onboarding-notification-bar"); + + if (onboardingEl) { + onboardingEl.style.display = shouldBeVisible ? "" : "none"; + } + } + _showRemoteSnippets() { const snippetsEl = document.getElementById(this.elementId); const payload = this.snippetsMap.get("snippets"); @@ -3480,15 +3549,18 @@ class SnippetsProvider { try { this._showRemoteSnippets(); } catch (e) { - this._showDefaultSnippets(e); + this._noSnippetFallback(e); } window.dispatchEvent(new Event(SNIPPETS_ENABLED_EVENT)); + + this._forceOnboardingVisibility(true); this.initialized = true; } uninit() { window.dispatchEvent(new Event(SNIPPETS_DISABLED_EVENT)); + this._forceOnboardingVisibility(false); this.initialized = false; } } @@ -3508,16 +3580,16 @@ function addSnippetsSubscriber(store) { store.subscribe(async () => { const state = store.getState(); - // state.Snippets.initialized: Should snippets be initialised? - // snippets.initialized: Is SnippetsProvider currently initialised? - if (state.Snippets.initialized && !snippets.initialized && state.Snippets.onboardingFinished) { - // Don't call init multiple times - if (!initializing) { - initializing = true; - await snippets.init({ appData: state.Snippets }); - initializing = false; - } - } else if (state.Snippets.initialized === false && snippets.initialized) { + // state.Prefs.values["feeds.snippets"]: Should snippets be shown? + // state.Snippets.initialized Is the snippets data initialized? + // snippets.initialized: Is SnippetsProvider currently initialised? + if (state.Prefs.values["feeds.snippets"] && state.Snippets.initialized && !snippets.initialized && + // Don't call init multiple times + !initializing) { + initializing = true; + await snippets.init({ appData: state.Snippets }); + initializing = false; + } else if (state.Prefs.values["feeds.snippets"] === false && snippets.initialized) { snippets.uninit(); } }); diff --git a/browser/extensions/activity-stream/data/content/activity-stream.css b/browser/extensions/activity-stream/data/content/activity-stream.css index 81e33d2d0e80..6c5d239a2d5a 100644 --- a/browser/extensions/activity-stream/data/content/activity-stream.css +++ b/browser/extensions/activity-stream/data/content/activity-stream.css @@ -457,26 +457,24 @@ main { padding-inline-start: 3px; } .topsite-form .form-wrapper { + margin: auto; + max-width: 350px; padding: 15px 0; } .topsite-form .form-wrapper .field { - margin-inline-start: 205px; position: relative; } .topsite-form .form-wrapper .url input:not(:placeholder-shown):dir(rtl) { direction: ltr; text-align: right; } .topsite-form .form-wrapper .section-title { - margin-bottom: 5px; - margin-inline-start: 205px; } + margin-bottom: 5px; } .topsite-form .form-wrapper input[type='text'] { border: solid 1px rgba(12, 12, 13, 0.2); border-radius: 2px; margin: 5px 0; padding: 7px; - width: 350px; } + width: 100%; } .topsite-form .form-wrapper input[type='text']:focus { border: solid 1px rgba(12, 12, 13, 0.4); } - .topsite-form .form-wrapper input[type='text']::placeholder { - font-style: italic; } .topsite-form .form-wrapper .invalid input[type='text'] { border: solid 1px #D70022; box-shadow: 0 0 0 2px rgba(215, 0, 34, 0.35); } @@ -676,28 +674,19 @@ main { width: 100%; height: 36px; } .search-wrapper input { - border: 0; - box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); border: 1px solid rgba(0, 0, 0, 0.15); - box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); border-radius: 3px; - border-radius: 4px; + box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); color: inherit; padding: 0; padding-inline-end: 36px; padding-inline-start: 35px; width: 100%; font-size: 15px; } - .search-wrapper input:focus { - border-color: #0060DF; - box-shadow: 0 0 0 2px #0060DF; - z-index: 1; } - .search-wrapper input:focus + .search-button { - z-index: 1; - background-color: #0060DF; - background-image: url("chrome://browser/skin/forward.svg"); - fill: #FFF; - -moz-context-properties: fill; } + .search-wrapper:active input, + .search-wrapper input:focus { + border-color: #0A84FF; + box-shadow: 0 0 0 2px #0A84FF; } .search-wrapper .search-label { background: url("chrome://browser/skin/search-glass.svg") no-repeat 12px center/16px; fill: rgba(12, 12, 13, 0.4); @@ -705,8 +694,7 @@ main { position: absolute; offset-inline-start: 0; height: 100%; - width: 35px; - z-index: 2; } + width: 35px; } .search-wrapper .search-button { background: url("chrome://browser/skin/forward.svg") no-repeat center center; border-radius: 0 3px 3px 0; @@ -718,11 +706,11 @@ main { height: 100%; offset-inline-end: 0; position: absolute; } - .search-wrapper .search-button:hover { - z-index: 1; - background-color: #0060DF; - fill: #FFF; + .search-wrapper .search-button:focus, .search-wrapper .search-button:hover { + background-color: rgba(12, 12, 13, 0.1); cursor: pointer; } + .search-wrapper .search-button:active { + background-color: rgba(12, 12, 13, 0.15); } .search-wrapper .search-button:dir(rtl) { transform: scaleX(-1); } .search-wrapper .contentSearchSuggestionTable { @@ -1009,13 +997,11 @@ main { .card-outer:-moz-any(:hover, :focus, .active):not(.placeholder) .card-title { color: #0060DF; } .card-outer .card-preview-image-outer { + background-color: #F9F9FA; position: relative; - background: linear-gradient(135deg, #B1B1B3, #D7D7DB); height: 122px; 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; @@ -1028,8 +1014,8 @@ main { background-size: cover; background-position: center; background-repeat: no-repeat; - transition: opacity 1s; - opacity: 0; } + opacity: 0; + transition: opacity 1s cubic-bezier(0.07, 0.95, 0, 1); } .card-outer .card-preview-image-outer .card-preview-image.loaded { opacity: 1; } .card-outer .card-details { diff --git a/browser/extensions/activity-stream/data/locales.json b/browser/extensions/activity-stream/data/locales.json index f24bf2d1c53f..fb0979a9b847 100644 --- a/browser/extensions/activity-stream/data/locales.json +++ b/browser/extensions/activity-stream/data/locales.json @@ -580,7 +580,51 @@ "time_label_hour": "{number}e", "time_label_day": "{number}d", "settings_pane_button_label": "Personelait ho pajenn Ivinell Nevez", - "settings_pane_header": "Gwellvezioù an ivinell nevez" + "settings_pane_header": "Gwellvezioù an ivinell nevez", + "settings_pane_body2": "Dibabit petra a welit war ar bajenn-mañ.", + "settings_pane_search_header": "Klask", + "settings_pane_search_body": "Klask er web adalek an ivinell nevez.", + "settings_pane_topsites_header": "Lec'hiennoù gwellañ", + "settings_pane_topsites_body": "Kit war al lec'hiennoù gweladennet ar muiañ ganeoc'h.", + "settings_pane_topsites_options_showmore": "Diskouez daou vann", + "settings_pane_bookmarks_header": "Sinedoù nevez", + "settings_pane_bookmarks_body": "Ho sinedoù nevez strollet en ul lec'h aes da dizhout.", + "settings_pane_visit_again_header": "Gweladenniñ en-dro", + "settings_pane_visit_again_body": "Firefox a ziskouezo deoc'h ul lodenn eus ho roll istor a c'hallfec'h kaout c'hoant da zerc'hel soñj pe da zistreiñ eno.", + "settings_pane_highlights_header": "Mareoù pouezus", + "settings_pane_highlights_body2": "Adkavit an traoù dedennus gweladennet pe lakaet er sinedoù nevez ’zo.", + "settings_pane_highlights_options_bookmarks": "Sinedoù", + "settings_pane_highlights_options_visited": "Lec'hiennoù gweladennet", + "settings_pane_snippets_header": "Notennigoù", + "settings_pane_snippets_body": "Lennit an hizivadurioù berr ha dous graet gant Mozilla evit Firefox, sevenadur ar genrouedad, hag ur mem dre-zegouezh ur wech an amzer.", + "settings_pane_done_button": "Graet", + "edit_topsites_button_text": "Embann", + "edit_topsites_button_label": "Personelaat ar gevrenn “lec'hiennoù gweladennet ar muiañ”", + "edit_topsites_showmore_button": "Diskouez muioc'h", + "edit_topsites_showless_button": "Diskouez nebeutoc'h", + "edit_topsites_done_button": "Graet", + "edit_topsites_pin_button": "Spilhennañ al lec'hienn-mañ", + "edit_topsites_unpin_button": "Dispilhennañ al lec'hienn-mañ", + "edit_topsites_edit_button": "Embann al lec'hienn-mañ", + "edit_topsites_dismiss_button": "Dilemel al lec'hienn-mañ", + "edit_topsites_add_button": "Ouzhpennañ", + "topsites_form_add_header": "Lec'hiennoù gwellañ nevez", + "topsites_form_edit_header": "Embann al Lec'hiennoù Gwellañ", + "topsites_form_title_placeholder": "Enankañ un titl", + "topsites_form_url_placeholder": "Skrivit pe pegit un URL", + "topsites_form_add_button": "Ouzhpennañ", + "topsites_form_save_button": "Enrollañ", + "topsites_form_cancel_button": "Nullañ", + "topsites_form_url_validation": "URL talvoudek azgoulennet", + "pocket_read_more": "Danvezioù brudet:", + "pocket_read_even_more": "Gwelet muioc'h a istorioù", + "pocket_feedback_header": "Ar gwellañ eus ar web, dibabet gant ouzhpenn 25 milion a dud.", + "pocket_description": "Dizoloit pennadoù eus an dibab ho pije gellout c'hwitout a-hent all warno, a-drugarez da bPocket, hag a zo bremañ ul lodenn deus Mozilla.", + "highlights_empty_state": "Krogit da verdeiñ hag e tiskouezimp deoc’h pennadoù, videoioù ha pajennoù all gweladennet pe lakaet er sinedoù nevez ’zo.", + "topstories_empty_state": "Aet oc'h betek penn. Distroit diwezhatoc'h evit muioc’h a istorioù digant {provider}. N’oc'h ket evit gortoz? Dibabit un danvez brudet evit klask muioc’h a bennadoù dedennus eus pep lec’h er web.", + "manual_migration_explanation2": "Amprouit Firefox gant sinedoù, roll istor ha gerioù-tremen ur merdeer all.", + "manual_migration_cancel_button": "N'am bo ket", + "manual_migration_import_button": "Emporzhiañ bremañ" }, "ca": { "newtab_page_title": "Pestanya nova", @@ -955,6 +999,7 @@ "default_label_loading": "Indlæser…", "header_top_sites": "Mest besøgte websider", "header_stories": "Tophistorier", + "header_highlights": "Højdepunkter", "header_visit_again": "Besøg igen", "header_bookmarks": "Seneste bogmærker", "header_recommended_by": "Anbefalet af {provider}", @@ -986,6 +1031,7 @@ "search_web_placeholder": "Søg på internettet", "search_settings": "Skift søgeindstillinger", "section_info_option": "Info", + "section_info_send_feedback": "Send feedback", "welcome_title": "Velkommen til nyt faneblad", "welcome_body": "Firefox vil bruge denne plads til at vise dine mest relevante bogmærker, artikler, videoer og sider, du har besøgt for nylig - så kan du nemmere finde dem.", "welcome_label": "Finder dine højdepunkter", @@ -995,7 +1041,6 @@ "time_label_day": "{number} d.", "settings_pane_button_label": "Tilpas siden Nyt faneblad", "settings_pane_header": "Indstillinger for Nyt faneblad", - "settings_pane_body": "Vælg, hvad der vises, når du åbner et nyt faneblad.", "settings_pane_search_header": "Søgning", "settings_pane_search_body": "Søg på nettet fra Nyt faneblad.", "settings_pane_topsites_header": "Mest besøgte websider", @@ -1005,8 +1050,6 @@ "settings_pane_bookmarks_body": "Dine seneste bogmærker samlet ét sted.", "settings_pane_visit_again_header": "Besøg igen", "settings_pane_visit_again_body": "Firefox viser dig dele af din browserhistorik, som du måske vil huske på eller vende tilbage til.", - "settings_pane_pocketstories_header": "Tophistorier", - "settings_pane_pocketstories_body": "Pocket, en del af Mozilla-familien, hjælper dig med at opdage indhold af høj kvalitet, som du måske ellers ikke ville have fundet.", "settings_pane_done_button": "Færdig", "edit_topsites_button_text": "Rediger", "edit_topsites_button_label": "Tilpas afsnittet Mest besøgte websider", @@ -1029,10 +1072,7 @@ "pocket_read_more": "Populære emner:", "pocket_read_even_more": "Se flere historier", "pocket_feedback_header": "Det bedste fra nettet, udvalgt af mere end 25 millioner mennesker.", - "pocket_feedback_body": "Pocket, en del af Mozilla-familien, hjælper dig med at opdage indhold af høj kvalitet, som du måske ellers ikke ville have fundet.", - "pocket_send_feedback": "Send feedback", "topstories_empty_state": "Der er ikke flere nye historier. Kom tilbage senere for at se flere tophistorier fra {provider}. Kan du ikke vente? Vælg et populært emne og find flere spændende historier fra hele verden.", - "manual_migration_explanation": "Prøv Firefox med dine favorit-websteder og bogmærker fra en anden browser.", "manual_migration_cancel_button": "Nej tak", "manual_migration_import_button": "Importer nu" }, @@ -3788,8 +3828,8 @@ "kk": { "newtab_page_title": "Жаңа бет", "default_label_loading": "Жүктелуде…", - "header_top_sites": "Топ сайттар", - "header_stories": "Топ хикаялар", + "header_top_sites": "Үздік сайттар", + "header_stories": "Үздік хикаялар", "header_highlights": "Ерекше жаңалықтар", "header_visit_again": "Қайтадан шолу", "header_bookmarks": "Соңғы бетбелгілер", @@ -3815,7 +3855,7 @@ "menu_action_unpin": "Бекітуді алып тастау", "confirm_history_delete_p1": "Бұл парақтың барлық кездесулерін шолу тарихыңыздан өшіруді қалайсыз ба?", "confirm_history_delete_notice_p2": "Бұл әрекетті болдырмау мүмкін болмайды.", - "menu_action_save_to_pocket": "Pocket-ке сақтау", + "menu_action_save_to_pocket": "Pocket ішіне сақтау", "search_for_something_with": "{search_term} ұғымын көмегімен іздеу:", "search_button": "Іздеу", "search_header": "{search_engine_name} іздеуі", @@ -3833,10 +3873,10 @@ "time_label_day": "{number} күн", "settings_pane_button_label": "Жаңа бетті баптаңыз", "settings_pane_header": "Жаңа бет баптаулары", - "settings_pane_body2": "Бұл парақта не көргіңіз келетінді таңдаңыз.", + "settings_pane_body2": "Бұл бетте не көргіңіз келетінді таңдаңыз.", "settings_pane_search_header": "Іздеу", "settings_pane_search_body": "Жаңа беттен интернеттен іздеңіз.", - "settings_pane_topsites_header": "Топ сайттар", + "settings_pane_topsites_header": "Үздік сайттар", "settings_pane_topsites_body": "Көбірек қаралатын сайттарға қатынау.", "settings_pane_topsites_options_showmore": "Екі жолды көрсету", "settings_pane_bookmarks_header": "Соңғы бетбелгілер", @@ -3848,7 +3888,7 @@ "settings_pane_highlights_options_bookmarks": "Бетбелгілер", "settings_pane_highlights_options_visited": "Ашылған сайттар", "settings_pane_snippets_header": "Үзінділер", - "settings_pane_snippets_body": "Mozilla-дан Firefox және интернет мәдениеті туралы қысқа жаңалықтарды, және кездейсоқ мемдерді оқыңыз.", + "settings_pane_snippets_body": "Mozilla ұсынған Firefox және интернет мәдениеті туралы қысқа жаңалықтарды, және кездейсоқ мемдерді оқыңыз.", "settings_pane_done_button": "Дайын", "edit_topsites_button_text": "Түзету", "edit_topsites_button_label": "Топ сайттар санатын баптау", @@ -3874,7 +3914,7 @@ "pocket_description": "Ол болмаса, сіз жіберіп алатын мүмкіндігі бар жоғары сапалы құраманы Pocket көмегімен табыңыз, ол енді Mozilla-ның бөлігі болып табылады.", "highlights_empty_state": "Шолуды бастаңыз, сіз жақында шолған немесе бетбелгілерге қосқан тамаша мақалалар, видеолар немесе басқа парақтардың кейбіреулері осында көрсетіледі.", "topstories_empty_state": "Дайын. {provider} ұсынған көбірек мақалаларды алу үшін кейінірек тексеріңіз. Күте алмайсыз ба? Интернеттен көбірек тамаша мақалаларды алу үшін әйгілі теманы таңдаңыз.", - "manual_migration_explanation2": "Firefox-ты басқа браузер бетбелгілері, тарихы және парольдерімен қолданып көріңіз.", + "manual_migration_explanation2": "Firefox қолданбасын басқа браузер бетбелгілері, тарихы және парольдерімен қолданып көріңіз.", "manual_migration_cancel_button": "Жоқ, рахмет", "manual_migration_import_button": "Қазір импорттау" }, @@ -4203,13 +4243,15 @@ "manual_migration_import_button": "Importuoti dabar" }, "lv": { - "newtab_page_title": "Jauna cilne" + "newtab_page_title": "Jauna cilne", + "default_label_loading": "Notiek ielāde…" }, "mk": { "newtab_page_title": "Ново јазиче", "default_label_loading": "Се вчитува…", - "header_top_sites": "Врвни мрежни места", - "header_stories": "Врвни написи", + "header_top_sites": "Популарни мрежни места", + "header_stories": "Популарни написи", + "header_highlights": "Интереси", "header_visit_again": "Посети повторно", "header_bookmarks": "Скорешни обележувачи", "header_recommended_by": "Препорачано од {provider}", @@ -4232,7 +4274,7 @@ "menu_action_delete": "Избриши од историја", "menu_action_pin": "Прикачи", "menu_action_unpin": "Откачи", - "confirm_history_delete_p1": "Дали сте сигурни дека сакате да ја избришете оваа страница отсекаде во Вашата историја на прелистување?", + "confirm_history_delete_p1": "Дали сте сигурни дека сакате да ја избришете оваа страница отсекаде во вашата историја на прелистување?", "confirm_history_delete_notice_p2": "Ова дејство не може да се одврати.", "menu_action_save_to_pocket": "Зачувај во Pocket", "search_for_something_with": "Пребарај за {search_term} со:", @@ -4241,30 +4283,36 @@ "search_web_placeholder": "Пребарајте на Интернет", "search_settings": "Промени поставувања за пребарување", "section_info_option": "Инфо", + "section_info_send_feedback": "Испрати мислење", + "section_info_privacy_notice": "Белешка за приватност", "welcome_title": "Добредојдовте во новото јазиче", "welcome_body": "Firefox ќе го искористи овој простор за да Ви ги прикаже најрелевантните обележувачи, написи, видеа и страници што сте ги посетиле, за да можете лесно да им се навратите.", - "welcome_label": "Ги откривам Вашите интереси", + "welcome_label": "Ги откривам вашите Интереси", "time_label_less_than_minute": "< 1 м", "time_label_minute": "{number} м", "time_label_hour": "{number} ч", "time_label_day": "{number} д", - "settings_pane_button_label": "Прилагодете ја страницата на Вашето Ново јазиче", + "settings_pane_button_label": "Прилагодете ја страницата на вашето Ново јазиче", "settings_pane_header": "Преференци за Ново јазиче", - "settings_pane_body": "Изберете што ќе гледате кога ќе отворите ново јазиче.", + "settings_pane_body2": "Изберете што ќе гледате на оваа страница.", "settings_pane_search_header": "Пребарување", - "settings_pane_search_body": "Пребарајте низ Интернет од Вашето ново јазиче.", + "settings_pane_search_body": "Пребарајте низ Интернет од вашето ново јазиче.", "settings_pane_topsites_header": "Врвни мрежни места", "settings_pane_topsites_body": "Пристапете до мрежните места што ги посетувате најмногу.", "settings_pane_topsites_options_showmore": "Прикажи два реда", "settings_pane_bookmarks_header": "Скорешни обележувачи", "settings_pane_bookmarks_body": "Вашите нови обележувачи во едно згодно место.", "settings_pane_visit_again_header": "Посети повторно", - "settings_pane_visit_again_body": "Firefox ќе прикаже делови од Вашата историја на прелистување кои можеби би сакале да ги запомните или пак да им се навратите.", - "settings_pane_pocketstories_header": "Врвни написи", - "settings_pane_pocketstories_body": "Pocket, дел од семејството на Mozilla, ќе Ви помогне да стигнете до високо-квалитетни содржини кои можеби не би ги откриле на друг начин.", + "settings_pane_visit_again_body": "Firefox ќе прикаже делови од вашата историја на прелистување кои можеби би сакале да ги запомните или пак да им се навратите.", + "settings_pane_highlights_header": "Интереси", + "settings_pane_highlights_body2": "Навратете се на интересни места што неодамна сте ги посетиле или обележале.", + "settings_pane_highlights_options_bookmarks": "Обележувачи", + "settings_pane_highlights_options_visited": "Посетени мрежни места", + "settings_pane_snippets_header": "Исечоци", + "settings_pane_snippets_body": "Прочитајте кратки и слатки новости од Mozilla во врска со Firefox, Интернет-културата и повремените случајни меми.", "settings_pane_done_button": "Готово", "edit_topsites_button_text": "Уреди", - "edit_topsites_button_label": "Прилагодете ги Вашите Врвни мрежни места", + "edit_topsites_button_label": "Прилагодете ги вашите Популарни мрежни места", "edit_topsites_showmore_button": "Прикажи повеќе", "edit_topsites_showless_button": "Прикажи помалку", "edit_topsites_done_button": "Готово", @@ -4284,10 +4332,10 @@ "pocket_read_more": "Популарни теми:", "pocket_read_even_more": "Види повеќе написи", "pocket_feedback_header": "Најдоброто од Интернет, одбрано од повеќе од 25 милиони луѓе.", - "pocket_feedback_body": "Pocket, дел од семејството на Mozilla, ќе Ви помогне да стигнете до високо-квалитетни содржини кои можеби не би ги откриле на друг начин.", - "pocket_send_feedback": "Остави коментар", + "pocket_description": "Откријте високо-квалитетни содржини, коишто инаку би можеле да ги пропуштите, со помош на Pocket, кој сега е дел од Mozilla.", + "highlights_empty_state": "Започнете со прелистување и ние овде ќе ви прикажеме некои од одличните написи, видеа и други страници што неодамна сте ги поселите или обележале.", "topstories_empty_state": "Имате видено сѐ! Навратете се подоцна за нови содржини од {provider}. Не можете да чекате? Изберете популарна тема и откријте уште одлични содржини ширум Интернет.", - "manual_migration_explanation": "Пробајте го Firefox со Вашите омилени мрежни места и обележувачи од друг прелистувач.", + "manual_migration_explanation2": "Пробајте го Firefox со обележувачите, историјата и лозинките на друг прелистувач.", "manual_migration_cancel_button": "Не, благодарам", "manual_migration_import_button": "Увези сега" }, @@ -6092,7 +6140,7 @@ "header_stories": "เรื่องราวเด่น", "header_highlights": "รายการเด่น", "header_visit_again": "เยี่ยมชมอีกครั้ง", - "header_bookmarks": "ที่คั่นหน้าเมื่อเร็ว ๆ นี้", + "header_bookmarks": "ที่คั่นหน้าล่าสุด", "header_recommended_by": "แนะนำโดย {provider}", "header_bookmarks_placeholder": "คุณยังไม่มีที่คั่นหน้าใด ๆ", "header_stories_from": "จาก", @@ -6139,7 +6187,7 @@ "settings_pane_topsites_header": "ไซต์เด่น", "settings_pane_topsites_body": "เข้าถึงเว็บไซต์ที่คุณเยี่ยมชมมากที่สุด", "settings_pane_topsites_options_showmore": "แสดงสองแถว", - "settings_pane_bookmarks_header": "ที่คั่นหน้าเมื่อเร็ว ๆ นี้", + "settings_pane_bookmarks_header": "ที่คั่นหน้าล่าสุด", "settings_pane_bookmarks_body": "ที่คั่นหน้าที่สร้างใหม่ของคุณในตำแหน่งที่ตั้งเดียวที่สะดวก", "settings_pane_visit_again_header": "เยี่ยมชมอีกครั้ง", "settings_pane_highlights_header": "รายการเด่น", @@ -6617,8 +6665,8 @@ "settings_pane_highlights_body2": "根据您最近访问的页面和添加的书签推荐您感兴趣的东西。", "settings_pane_highlights_options_bookmarks": "书签", "settings_pane_highlights_options_visited": "访问过的网站", - "settings_pane_snippets_header": "板报", - "settings_pane_snippets_body": "阅读和了解 Mozilla 就 Firefox、互联网文化等提供的一些简短而有趣的更新。", + "settings_pane_snippets_header": "只言片语", + "settings_pane_snippets_body": "阅读 Mozilla 就 Firefox、互联网文化、偶尔还有模因提供的一些简短而有趣的小文章。", "settings_pane_done_button": "完成", "edit_topsites_button_text": "编辑", "edit_topsites_button_label": "定制您的“常用网站”区域", @@ -6641,7 +6689,7 @@ "pocket_read_more": "热门主题:", "pocket_read_even_more": "查看更多文章", "pocket_feedback_header": "由超过 2500 万人挑选出来的网上精华内容。", - "pocket_description": "借助 Pocket(目前所属 Mozilla)发现有趣的高品质内容。", + "pocket_description": "借助 Pocket(目前属 Mozilla 旗下)发现您不容错过的高品质内容。", "highlights_empty_state": "开始浏览旅程吧,之后这里会显示您最近看过或加了书签的精彩文章、视频以及其他页面。", "topstories_empty_state": "所有文章都读完啦!晚点再来,{provider} 将推荐更多热门文章。等不及了?选择一个热门话题,找到更多网上的好文章。", "manual_migration_explanation2": "把在其他浏览器中保存的书签、历史记录和密码带到 Firefox 吧。", diff --git a/browser/extensions/activity-stream/install.rdf.in b/browser/extensions/activity-stream/install.rdf.in index 6d13034a391f..1d46f75e7951 100644 --- a/browser/extensions/activity-stream/install.rdf.in +++ b/browser/extensions/activity-stream/install.rdf.in @@ -8,7 +8,7 @@ 2 true false - 2017.09.22.1389-2ee94db4 + 2017.09.27.1211-43262ffa Activity Stream 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. true diff --git a/browser/extensions/activity-stream/lib/HighlightsFeed.jsm b/browser/extensions/activity-stream/lib/HighlightsFeed.jsm index d82f6f4d13d5..5732e25c885f 100644 --- a/browser/extensions/activity-stream/lib/HighlightsFeed.jsm +++ b/browser/extensions/activity-stream/lib/HighlightsFeed.jsm @@ -160,7 +160,7 @@ this.HighlightsFeed = class HighlightsFeed { break; case at.MIGRATION_COMPLETED: case at.PLACES_HISTORY_CLEARED: - case at.PLACES_LINK_DELETED: + case at.PLACES_LINKS_DELETED: case at.PLACES_LINK_BLOCKED: this.fetchHighlights(true); break; diff --git a/browser/extensions/activity-stream/lib/PlacesFeed.jsm b/browser/extensions/activity-stream/lib/PlacesFeed.jsm index e4359ae96912..ecdf6c1f3d9c 100644 --- a/browser/extensions/activity-stream/lib/PlacesFeed.jsm +++ b/browser/extensions/activity-stream/lib/PlacesFeed.jsm @@ -42,11 +42,24 @@ class HistoryObserver extends Observer { * @param {obj} uri A URI object representing the link's url * {str} uri.spec The URI as a string */ - onDeleteURI(uri) { - this.dispatch({ - type: at.PLACES_LINK_DELETED, - data: {url: uri.spec} - }); + async onDeleteURI(uri) { + // Add to an existing array of links if we haven't dispatched yet + const {spec} = uri; + if (this._deletedLinks) { + this._deletedLinks.push(spec); + } else { + // Store an array of synchronously deleted links + this._deletedLinks = [spec]; + + // Only dispatch a single action when we've gotten all deleted urls + await Promise.resolve().then(() => { + this.dispatch({ + type: at.PLACES_LINKS_DELETED, + data: this._deletedLinks + }); + delete this._deletedLinks; + }); + } } /** diff --git a/browser/extensions/activity-stream/lib/PrefsFeed.jsm b/browser/extensions/activity-stream/lib/PrefsFeed.jsm index a5391acbde97..5d23ce9e8ced 100644 --- a/browser/extensions/activity-stream/lib/PrefsFeed.jsm +++ b/browser/extensions/activity-stream/lib/PrefsFeed.jsm @@ -8,6 +8,9 @@ const {utils: Cu} = Components; const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {}); const {Prefs} = Cu.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {}); const {PrerenderData} = Cu.import("resource://activity-stream/common/PrerenderData.jsm", {}); +Cu.import("resource://gre/modules/Services.jsm"); + +const ONBOARDING_FINISHED_PREF = "browser.onboarding.notification.finished"; this.PrefsFeed = class PrefsFeed { constructor(prefMap) { @@ -27,11 +30,30 @@ this.PrefsFeed = class PrefsFeed { } } + _initOnboardingPref() { + const snippetsEnabled = this._prefs.get("feeds.snippets"); + if (!snippetsEnabled) { + this.setOnboardingDisabledDefault(true); + } + } + + setOnboardingDisabledDefault(value) { + const branch = Services.prefs.getDefaultBranch(""); + branch.setBoolPref(ONBOARDING_FINISHED_PREF, value); + } + onPrefChanged(name, value) { if (this._prefMap.has(name)) { this.store.dispatch(ac.BroadcastToContent({type: at.PREF_CHANGED, data: {name, value}})); } + this._checkPrerender(name); + + if (name === "feeds.snippets") { + // If snippets are disabled, onboarding notifications should also be + // disabled because they look like snippets. + this.setOnboardingDisabledDefault(!value); + } } init() { @@ -47,6 +69,7 @@ this.PrefsFeed = class PrefsFeed { this.store.dispatch(ac.BroadcastToContent({type: at.PREFS_INITIAL_VALUES, data: values})); this._setPrerenderPref(); + this._initOnboardingPref(); } removeListeners() { this._prefs.ignoreBranch(this); @@ -62,6 +85,9 @@ this.PrefsFeed = class PrefsFeed { case at.SET_PREF: this._prefs.set(action.data.name, action.data.value); break; + case at.DISABLE_ONBOARDING: + this.setOnboardingDisabledDefault(true); + break; } } }; diff --git a/browser/extensions/activity-stream/lib/SnippetsFeed.jsm b/browser/extensions/activity-stream/lib/SnippetsFeed.jsm index aba53e9156a2..c95f338f1668 100644 --- a/browser/extensions/activity-stream/lib/SnippetsFeed.jsm +++ b/browser/extensions/activity-stream/lib/SnippetsFeed.jsm @@ -17,8 +17,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge", // Url to fetch snippets, in the urlFormatter service format. const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl"; const TELEMETRY_PREF = "datareporting.healthreport.uploadEnabled"; -const ONBOARDING_FINISHED_PREF = "browser.onboarding.notification.finished"; const FXA_USERNAME_PREF = "services.sync.username"; +const ONBOARDING_FINISHED_PREF = "browser.onboarding.notification.finished"; // Prefix for any target matching a search engine. const TARGET_SEARCHENGINE_PREFIX = "searchEngine-"; diff --git a/browser/extensions/activity-stream/lib/TelemetryFeed.jsm b/browser/extensions/activity-stream/lib/TelemetryFeed.jsm index c7f96352400b..f83c82f03f8f 100644 --- a/browser/extensions/activity-stream/lib/TelemetryFeed.jsm +++ b/browser/extensions/activity-stream/lib/TelemetryFeed.jsm @@ -21,6 +21,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"); +const ACTIVITY_STREAM_ID = "activity-stream"; const ACTIVITY_STREAM_ENDPOINT_PREF = "browser.newtabpage.activity-stream.telemetry.ping.endpoint"; // This is a mapping table between the user preferences and its encoding code @@ -160,12 +161,10 @@ this.TelemetryFeed = class TelemetryFeed { * Lazily initialize PingCentre to send pings */ get pingCentre() { - const ACTIVITY_STREAM_ID = "activity-stream"; Object.defineProperty(this, "pingCentre", { value: new PingCentre({ topic: ACTIVITY_STREAM_ID, - filter: ACTIVITY_STREAM_ID, overrideEndpointPref: ACTIVITY_STREAM_ENDPOINT_PREF }) }); @@ -363,7 +362,8 @@ this.TelemetryFeed = class TelemetryFeed { async sendEvent(event_object) { if (this.telemetryEnabled) { - this.pingCentre.sendPing(event_object); + this.pingCentre.sendPing(event_object, + {filter: ACTIVITY_STREAM_ID}); } } diff --git a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm index d6f670e44c37..04dafcd4cd85 100644 --- a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm +++ b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm @@ -264,8 +264,8 @@ this.TopSitesFeed = class TopSitesFeed { // All these actions mean we need new top sites case at.MIGRATION_COMPLETED: case at.PLACES_HISTORY_CLEARED: - case at.PLACES_LINK_DELETED: case at.PLACES_LINK_BLOCKED: + case at.PLACES_LINKS_DELETED: this.frecentCache.expire(); this.refresh(); break; diff --git a/browser/extensions/activity-stream/test/unit/common/Reducers.test.js b/browser/extensions/activity-stream/test/unit/common/Reducers.test.js index 3916b870975e..6d97e29c437d 100644 --- a/browser/extensions/activity-stream/test/unit/common/Reducers.test.js +++ b/browser/extensions/activity-stream/test/unit/common/Reducers.test.js @@ -358,13 +358,22 @@ describe("Reducers", () => { }); it("should remove blocked and deleted urls from all rows in all sections", () => { const blockAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "www.foo.bar"}}; - const deleteAction = {type: at.PLACES_LINK_DELETED, data: {url: "www.foo.bar"}}; + const deleteAction = {type: at.PLACES_LINKS_DELETED, data: ["www.foo.bar"]}; const newBlockState = Sections(oldState, blockAction); const newDeleteState = Sections(oldState, deleteAction); newBlockState.concat(newDeleteState).forEach(section => { assert.deepEqual(section.rows, [{url: "www.other.url"}]); }); }); + it("should remove all deleted urls", () => { + const deleteAction = {type: at.PLACES_LINKS_DELETED, data: ["www.foo.bar", "www.other.url"]}; + + const newState = Sections(oldState, deleteAction); + + newState.forEach(section => { + assert.lengthOf(section.rows, 0); + }); + }); it("should not update state for empty action.data on PLACES_BOOKMARK_ADDED", () => { const nextState = Sections(undefined, {type: at.PLACES_BOOKMARK_ADDED}); assert.equal(nextState, INITIAL_STATE.Sections); diff --git a/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js b/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js index 01fccfb7f636..61566df30a32 100644 --- a/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js @@ -296,10 +296,10 @@ describe("Highlights Feed", () => { assert.calledOnce(feed.fetchHighlights); assert.calledWith(feed.fetchHighlights, true); }); - it("should fetch highlights on PLACES_LINK_DELETED", async () => { + it("should fetch highlights on PLACES_LINKS_DELETED", async () => { await feed.fetchHighlights(); feed.fetchHighlights = sinon.spy(); - feed.onAction({type: at.PLACES_LINK_DELETED}); + feed.onAction({type: at.PLACES_LINKS_DELETED}); assert.calledOnce(feed.fetchHighlights); assert.calledWith(feed.fetchHighlights, true); }); diff --git a/browser/extensions/activity-stream/test/unit/lib/PlacesFeed.test.js b/browser/extensions/activity-stream/test/unit/lib/PlacesFeed.test.js index ec469881bf7e..4f93c84b9183 100644 --- a/browser/extensions/activity-stream/test/unit/lib/PlacesFeed.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/PlacesFeed.test.js @@ -179,9 +179,20 @@ describe("PlacesFeed", () => { assert.property(observer, "QueryInterface"); }); describe("#onDeleteURI", () => { - it("should dispatch a PLACES_LINK_DELETED action with the right url", () => { + it("should dispatch a PLACES_LINKS_DELETED action with the right url", async() => { + await observer.onDeleteURI({spec: "foo.com"}); + + assert.calledWith(dispatch, {type: at.PLACES_LINKS_DELETED, data: ["foo.com"]}); + }); + it("should dispatch a PLACES_LINKS_DELETED action with multiple urls", async() => { + const promise = observer.onDeleteURI({spec: "bar.com"}); observer.onDeleteURI({spec: "foo.com"}); - assert.calledWith(dispatch, {type: at.PLACES_LINK_DELETED, data: {url: "foo.com"}}); + await promise; + + const result = dispatch.firstCall.args[0].data; + assert.lengthOf(result, 2); + assert.equal(result[0], "bar.com"); + assert.equal(result[1], "foo.com"); }); }); describe("#onClearHistory", () => { diff --git a/browser/extensions/activity-stream/test/unit/lib/PrefsFeed.test.js b/browser/extensions/activity-stream/test/unit/lib/PrefsFeed.test.js index cd2a1cbf657b..28d194c876bf 100644 --- a/browser/extensions/activity-stream/test/unit/lib/PrefsFeed.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/PrefsFeed.test.js @@ -4,6 +4,7 @@ const {PrerenderData} = require("common/PrerenderData.jsm"); const {initialPrefs} = PrerenderData; const PRERENDER_PREF_NAME = "prerender"; +const ONBOARDING_FINISHED_PREF = "browser.onboarding.notification.finished"; describe("PrefsFeed", () => { let feed; @@ -62,6 +63,44 @@ describe("PrefsFeed", () => { assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, false); }); }); + describe("Onboarding", () => { + let sandbox; + let defaultBranch; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + defaultBranch = {setBoolPref: sandbox.stub()}; + sandbox.stub(global.Services.prefs, "getDefaultBranch").returns(defaultBranch); + }); + afterEach(() => { + sandbox.restore(); + }); + it("should set ONBOARDING_FINISHED_PREF to true if prefs.feeds.snippets if false", () => { + FAKE_PREFS.set("feeds.snippets", false); + feed.onAction({type: at.INIT}); + assert.calledWith(defaultBranch.setBoolPref, ONBOARDING_FINISHED_PREF, true); + }); + it("should not set ONBOARDING_FINISHED_PREF if prefs.feeds.snippets is true", () => { + FAKE_PREFS.set("feeds.snippets", true); + feed.onAction({type: at.INIT}); + assert.notCalled(defaultBranch.setBoolPref); + }); + it("should set ONBOARDING_FINISHED_PREF to true if the feeds.snippets pref changes to false", () => { + feed.onPrefChanged("feeds.snippets", false); + assert.calledWith(defaultBranch.setBoolPref, ONBOARDING_FINISHED_PREF, true); + }); + it("should set ONBOARDING_FINISHED_PREF to false if the feeds.snippets pref changes to true", () => { + feed.onPrefChanged("feeds.snippets", true); + assert.calledWith(defaultBranch.setBoolPref, ONBOARDING_FINISHED_PREF, false); + }); + it("should not set ONBOARDING_FINISHED_PREF if an unrelated pref changes", () => { + feed.onPrefChanged("foo", true); + assert.notCalled(defaultBranch.setBoolPref); + }); + it("should set ONBOARDING_FINISHED_PREF to true if a DISABLE_ONBOARDING action was received", () => { + feed.onAction({type: at.DISABLE_ONBOARDING}); + assert.calledWith(defaultBranch.setBoolPref, ONBOARDING_FINISHED_PREF, true); + }); + }); describe("onPrefChanged prerendering", () => { it("should not change the prerender pref if the pref is not included in invalidatingPrefs", () => { feed.onPrefChanged("foo123", true); diff --git a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js index b13dbfc329d9..4cb86e6ca46c 100644 --- a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js @@ -507,9 +507,9 @@ describe("Top Sites Feed", () => { assert.calledOnce(feed.refresh); assert.equal(feed.refresh.firstCall.args[0], null); }); - it("should call refresh without a target on PLACES_LINK_DELETED action", async () => { + it("should call refresh without a target on PLACES_LINKS_DELETED action", async () => { sinon.stub(feed, "refresh"); - await feed.onAction({type: at.PLACES_LINK_DELETED}); + await feed.onAction({type: at.PLACES_LINKS_DELETED}); assert.calledOnce(feed.refresh); assert.equal(feed.refresh.firstCall.args[0], null); }); diff --git a/browser/extensions/activity-stream/test/unit/unit-entry.js b/browser/extensions/activity-stream/test/unit/unit-entry.js index e0cdd4fd0cd8..0538666a60f8 100644 --- a/browser/extensions/activity-stream/test/unit/unit-entry.js +++ b/browser/extensions/activity-stream/test/unit/unit-entry.js @@ -27,6 +27,8 @@ overrider.set({ ContentSearchUIController: function() {}, // NB: This is a function/constructor dump() {}, fetch() {}, + // eslint-disable-next-line object-shorthand + Image: function() {}, // NB: This is a function/constructor Preferences: FakePrefs, Services: { locale: { diff --git a/browser/modules/PingCentre.jsm b/browser/modules/PingCentre.jsm index 8f3b79fa6245..dace651f99e2 100644 --- a/browser/modules/PingCentre.jsm +++ b/browser/modules/PingCentre.jsm @@ -38,7 +38,6 @@ class PingCentre { } this._topic = options.topic; - this._filter = options.filter; this._prefs = Services.prefs.getBranch(""); this._setPingEndpoint(options.topic, options.overrideEndpointPref); @@ -92,12 +91,12 @@ class PingCentre { this._fhrEnabled = this._prefs.getBoolPref(prefKey); } - _createExperimentsString(activeExperiments) { + _createExperimentsString(activeExperiments, filter) { let experimentsString = ""; for (let experimentID in activeExperiments) { if (!activeExperiments[experimentID] || !activeExperiments[experimentID].branch || - (this._filter && !experimentID.includes(this._filter))) { + (filter && !experimentID.includes(filter))) { continue; } let expString = `${experimentID}:${activeExperiments[experimentID].branch}`; @@ -106,9 +105,10 @@ class PingCentre { return experimentsString; } - async sendPing(data) { + async sendPing(data, options) { + let filter = options && options.filter; let experiments = TelemetryEnvironment.getActiveExperiments(); - let experimentsString = this._createExperimentsString(experiments); + let experimentsString = this._createExperimentsString(experiments, filter); if (!this.enabled) { return Promise.resolve(); }