diff --git a/browser/base/content/test/static/browser_all_files_referenced.js b/browser/base/content/test/static/browser_all_files_referenced.js
index 206cb17a757b..e45d3545bc79 100644
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -87,6 +87,10 @@ var whitelist = [
{file: "resource://app/modules/NewTabSearchProvider.jsm"},
{file: "resource://app/modules/NewTabWebChannel.jsm"},
+ // browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
+ // This will used when Bug 1397875 lands
+ {file: "resource://activity-stream/data/content/activity-stream-prerendered.html"},
+
// layout/mathml/nsMathMLChar.cpp
{file: "resource://gre/res/fonts/mathfontSTIXGeneral.properties"},
{file: "resource://gre/res/fonts/mathfontUnicode.properties"},
diff --git a/browser/components/search/test/browser_searchEngine_behaviors.js b/browser/components/search/test/browser_searchEngine_behaviors.js
index cf1b7681b8a5..999c12948e8b 100644
--- a/browser/components/search/test/browser_searchEngine_behaviors.js
+++ b/browser/components/search/test/browser_searchEngine_behaviors.js
@@ -95,14 +95,9 @@ function promiseStateChangeURI() {
function promiseContentSearchReady(browser) {
return ContentTask.spawn(browser, {}, async function(args) {
- return new Promise(resolve => {
- content.addEventListener("ContentSearchService", function listener(aEvent) {
- if (aEvent.detail.type == "State") {
- content.removeEventListener("ContentSearchService", listener);
- resolve();
- }
- });
- });
+ await ContentTaskUtils.waitForCondition(() => content.wrappedJSObject.gContentSearchController &&
+ content.wrappedJSObject.gContentSearchController.defaultEngine
+ );
});
}
diff --git a/browser/extensions/activity-stream/common/Actions.jsm b/browser/extensions/activity-stream/common/Actions.jsm
index 75f2fbb4b497..fd500f48525a 100644
--- a/browser/extensions/activity-stream/common/Actions.jsm
+++ b/browser/extensions/activity-stream/common/Actions.jsm
@@ -39,6 +39,7 @@ for (const type of [
"NEW_TAB_INIT",
"NEW_TAB_INITIAL_STATE",
"NEW_TAB_LOAD",
+ "NEW_TAB_STATE_REQUEST",
"NEW_TAB_UNLOAD",
"OPEN_LINK",
"OPEN_NEW_WINDOW",
diff --git a/browser/extensions/activity-stream/common/PerfService.jsm b/browser/extensions/activity-stream/common/PerfService.jsm
index a340fd6b57ee..4a5255169c62 100644
--- a/browser/extensions/activity-stream/common/PerfService.jsm
+++ b/browser/extensions/activity-stream/common/PerfService.jsm
@@ -1,26 +1,28 @@
/* globals Services */
"use strict";
-let usablePerfObj;
-
-let Cu;
-const isRunningInChrome = typeof Window === "undefined";
-
/* istanbul ignore if */
-if (isRunningInChrome) {
- Cu = Components.utils;
-} else {
- Cu = {import() {}};
+if (typeof Components !== "undefined" && Components.utils) {
+ Components.utils.import("resource://gre/modules/Services.jsm");
}
-Cu.import("resource://gre/modules/Services.jsm");
+let usablePerfObj;
/* istanbul ignore if */
-if (isRunningInChrome) {
+/* istanbul ignore else */
+if (typeof Services !== "undefined") {
// Borrow the high-resolution timer from the hidden window....
usablePerfObj = Services.appShell.hiddenDOMWindow.performance;
-} else { // we must be running in content space
+} else if (typeof performance !== "undefined") {
+ // we must be running in content space
usablePerfObj = performance;
+} else {
+ // This is a dummy object so this file doesn't crash in the node prerendering
+ // task.
+ usablePerfObj = {
+ now() {},
+ mark() {}
+ };
}
this._PerfService = function _PerfService(options) {
diff --git a/browser/extensions/activity-stream/common/PrerenderData.jsm b/browser/extensions/activity-stream/common/PrerenderData.jsm
new file mode 100644
index 000000000000..6a1d327cc0d8
--- /dev/null
+++ b/browser/extensions/activity-stream/common/PrerenderData.jsm
@@ -0,0 +1,42 @@
+const prefConfig = {
+ // Prefs listed with "invalidates: true" 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}
+ initialSections: [
+ {
+ enabled: true,
+ icon: "pocket",
+ id: "topstories",
+ order: 1,
+ title: {id: "header_recommended_by", values: {provider: "Pocket"}},
+ topics: [{}]
+ }
+ ]
+};
+
+this.EXPORTED_SYMBOLS = ["PrerenderData"];
diff --git a/browser/extensions/activity-stream/common/Reducers.jsm b/browser/extensions/activity-stream/common/Reducers.jsm
index 2c1f6782ab10..a60bf90bbb23 100644
--- a/browser/extensions/activity-stream/common/Reducers.jsm
+++ b/browser/extensions/activity-stream/common/Reducers.jsm
@@ -5,6 +5,9 @@
const {actionTypes: at} = Components.utils.import("resource://activity-stream/common/Actions.jsm", {});
+// Locales that should be displayed RTL
+const RTL_LIST = ["ar", "he", "fa", "ur"];
+
const TOP_SITES_DEFAULT_LENGTH = 6;
const TOP_SITES_SHOWMORE_LENGTH = 12;
@@ -16,6 +19,8 @@ const INITIAL_STATE = {
locale: "",
// Localized strings with defaults
strings: null,
+ // The text direction for the locale
+ textDirection: "",
// The version of the system-addon
version: null
},
@@ -48,7 +53,8 @@ function App(prevState = INITIAL_STATE.App, action) {
let {locale, strings} = action.data;
return Object.assign({}, prevState, {
locale,
- strings
+ strings,
+ textDirection: RTL_LIST.indexOf(locale.split("-")[0]) >= 0 ? "rtl" : "ltr"
});
}
default:
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
new file mode 100644
index 000000000000..750c31383c6b
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/activity-stream-initial-state.js
@@ -0,0 +1,142 @@
+// Note - this is a generated file.
+ window.gActivityStreamPrerenderedState = {
+ "TopSites": {
+ "initialized": false,
+ "rows": []
+ },
+ "App": {
+ "initialized": false,
+ "locale": "en-PRERENDER",
+ "strings": {
+ "newtab_page_title": " ",
+ "default_label_loading": " ",
+ "header_top_sites": " ",
+ "header_stories": " ",
+ "header_highlights": " ",
+ "header_visit_again": " ",
+ "header_bookmarks": " ",
+ "header_recommended_by": " ",
+ "header_bookmarks_placeholder": " ",
+ "header_stories_from": " ",
+ "type_label_visited": " ",
+ "type_label_bookmarked": " ",
+ "type_label_synced": " ",
+ "type_label_recommended": " ",
+ "type_label_open": " ",
+ "type_label_topic": " ",
+ "type_label_now": " ",
+ "menu_action_bookmark": " ",
+ "menu_action_remove_bookmark": " ",
+ "menu_action_copy_address": " ",
+ "menu_action_email_link": " ",
+ "menu_action_open_new_window": " ",
+ "menu_action_open_private_window": " ",
+ "menu_action_dismiss": " ",
+ "menu_action_delete": " ",
+ "menu_action_pin": " ",
+ "menu_action_unpin": " ",
+ "confirm_history_delete_p1": " ",
+ "confirm_history_delete_notice_p2": " ",
+ "menu_action_save_to_pocket": " ",
+ "search_for_something_with": " ",
+ "search_button": " ",
+ "search_header": " ",
+ "search_web_placeholder": "Search the Web",
+ "search_settings": " ",
+ "section_info_option": " ",
+ "section_info_send_feedback": " ",
+ "section_info_privacy_notice": " ",
+ "welcome_title": " ",
+ "welcome_body": " ",
+ "welcome_label": " ",
+ "time_label_less_than_minute": " ",
+ "time_label_minute": " ",
+ "time_label_hour": " ",
+ "time_label_day": " ",
+ "settings_pane_button_label": " ",
+ "settings_pane_header": " ",
+ "settings_pane_body2": " ",
+ "settings_pane_search_header": " ",
+ "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": " ",
+ "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": " ",
+ "settings_pane_done_button": " ",
+ "edit_topsites_button_text": " ",
+ "edit_topsites_button_label": " ",
+ "edit_topsites_showmore_button": " ",
+ "edit_topsites_showless_button": " ",
+ "edit_topsites_done_button": " ",
+ "edit_topsites_pin_button": " ",
+ "edit_topsites_unpin_button": " ",
+ "edit_topsites_edit_button": " ",
+ "edit_topsites_dismiss_button": " ",
+ "edit_topsites_add_button": " ",
+ "topsites_form_add_header": " ",
+ "topsites_form_edit_header": " ",
+ "topsites_form_title_placeholder": " ",
+ "topsites_form_url_placeholder": " ",
+ "topsites_form_add_button": " ",
+ "topsites_form_save_button": " ",
+ "topsites_form_cancel_button": " ",
+ "topsites_form_url_validation": " ",
+ "pocket_read_more": " ",
+ "pocket_read_even_more": " ",
+ "pocket_feedback_header": " ",
+ "pocket_description": " ",
+ "highlights_empty_state": " ",
+ "topstories_empty_state": " ",
+ "manual_migration_explanation2": " ",
+ "manual_migration_cancel_button": " ",
+ "manual_migration_import_button": " "
+ },
+ "textDirection": "ltr",
+ "version": null
+ },
+ "Snippets": {
+ "initialized": false
+ },
+ "Prefs": {
+ "initialized": true,
+ "values": {
+ "migrationExpired": true,
+ "showTopSites": true,
+ "showSearch": true,
+ "topSitesCount": 6,
+ "feeds.section.topstories": true
+ }
+ },
+ "Dialog": {
+ "visible": false,
+ "data": {}
+ },
+ "Sections": [
+ {
+ "title": {
+ "id": "header_recommended_by",
+ "values": {
+ "provider": "Pocket"
+ }
+ },
+ "rows": [],
+ "order": 1,
+ "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
new file mode 100644
index 000000000000..2c4ea5d6e0eb
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 c0320b63f8c4..b814b7c5bba9 100644
--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -65,12 +65,6 @@
/************************************************************************/
/******/ ([
/* 0 */
-/***/ (function(module, exports) {
-
-module.exports = React;
-
-/***/ }),
-/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -100,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_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_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"]) {
actionTypes[type] = type;
}
@@ -191,9 +185,7 @@ function UserEvent(data) {
* @param {int} importContext (For testing) Override the import context for testing.
* @return {object} An action. For UI code, a SendToMain action.
*/
-function UndesiredEvent(data) {
- let importContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalImportContext;
-
+function UndesiredEvent(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_UNDESIRED_EVENT,
data
@@ -208,9 +200,7 @@ function UndesiredEvent(data) {
* @param {int} importContext (For testing) Override the import context for testing.
* @return {object} An action. For UI code, a SendToMain action.
*/
-function PerfEvent(data) {
- let importContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalImportContext;
-
+function PerfEvent(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_PERFORMANCE_EVENT,
data
@@ -225,9 +215,7 @@ function PerfEvent(data) {
* @param {int} importContext (For testing) Override the import context for testing.
* #return {object} An action. For UI code, a SendToMain action.
*/
-function ImpressionStats(data) {
- let importContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalImportContext;
-
+function ImpressionStats(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_IMPRESSION_STATS,
data
@@ -235,9 +223,7 @@ function ImpressionStats(data) {
return importContext === UI_CODE ? SendToMain(action) : action;
}
-function SetPref(name, value) {
- let importContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : globalImportContext;
-
+function SetPref(name, value, importContext = globalImportContext) {
const action = { type: actionTypes.SET_PREF, data: { name, value } };
return importContext === UI_CODE ? SendToMain(action) : action;
}
@@ -296,6 +282,12 @@ module.exports = {
CONTENT_MESSAGE_TYPE
};
+/***/ }),
+/* 1 */
+/***/ (function(module, exports) {
+
+module.exports = React;
+
/***/ }),
/* 2 */
/***/ (function(module, exports) {
@@ -310,18 +302,46 @@ module.exports = ReactRedux;
/***/ }),
/* 4 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
-"use strict";
+var g;
+
+// This works in non-strict mode
+g = (function() {
+ return this;
+})();
+
+try {
+ // This works if eval is allowed (see CSP)
+ g = g || Function("return this")() || (1,eval)("this");
+} catch(e) {
+ // This works if the window reference is available
+ if(typeof window === "object")
+ g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
-module.exports = {
- TOP_SITES_SOURCE: "TOP_SITES",
- TOP_SITES_CONTEXT_MENU_OPTIONS: ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"]
-};
-
/***/ }),
/* 5 */
+/***/ (function(module, exports) {
+
+module.exports = {
+ TOP_SITES_SOURCE: "TOP_SITES",
+ TOP_SITES_CONTEXT_MENU_OPTIONS: ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"],
+ // minimum size necessary to show a rich icon instead of a screenshot
+ MIN_RICH_FAVICON_SIZE: 96,
+ // minimum size necessary to show any icon in the top left corner with a screenshot
+ MIN_CORNER_FAVICON_SIZE: 32
+};
+
+/***/ }),
+/* 6 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -330,10 +350,10 @@ module.exports = {
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-var _require = __webpack_require__(1);
-
-const at = _require.actionTypes;
+const { actionTypes: at } = __webpack_require__(0);
+// Locales that should be displayed RTL
+const RTL_LIST = ["ar", "he", "fa", "ur"];
const TOP_SITES_DEFAULT_LENGTH = 6;
const TOP_SITES_SHOWMORE_LENGTH = 12;
@@ -346,6 +366,8 @@ const INITIAL_STATE = {
locale: "",
// Localized strings with defaults
strings: null,
+ // The text direction for the locale
+ textDirection: "",
// The version of the system-addon
version: null
},
@@ -367,10 +389,7 @@ const INITIAL_STATE = {
Sections: []
};
-function App() {
- let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.App;
- let action = arguments[1];
-
+function App(prevState = INITIAL_STATE.App, action) {
switch (action.type) {
case at.INIT:
return Object.assign({}, prevState, action.data || {}, { initialized: true });
@@ -379,13 +398,11 @@ function App() {
if (!action.data) {
return prevState;
}
- var _action$data = action.data;
- let locale = _action$data.locale,
- strings = _action$data.strings;
-
+ let { locale, strings } = action.data;
return Object.assign({}, prevState, {
locale,
- strings
+ strings,
+ textDirection: RTL_LIST.indexOf(locale.split("-")[0]) >= 0 ? "rtl" : "ltr"
});
}
default:
@@ -428,10 +445,7 @@ function insertPinned(links, pinned) {
return newLinks;
}
-function TopSites() {
- let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.TopSites;
- let action = arguments[1];
-
+function TopSites(prevState = INITIAL_STATE.TopSites, action) {
let hasMatch;
let newRows;
let pinned;
@@ -456,11 +470,7 @@ function TopSites() {
}
newRows = prevState.rows.map(site => {
if (site && site.url === action.data.url) {
- var _action$data2 = action.data;
- const bookmarkGuid = _action$data2.bookmarkGuid,
- bookmarkTitle = _action$data2.bookmarkTitle,
- dateAdded = _action$data2.dateAdded;
-
+ const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
return Object.assign({}, site, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded });
}
return site;
@@ -497,10 +507,7 @@ function TopSites() {
}
}
-function Dialog() {
- let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Dialog;
- let action = arguments[1];
-
+function Dialog(prevState = INITIAL_STATE.Dialog, action) {
switch (action.type) {
case at.DIALOG_OPEN:
return Object.assign({}, prevState, { visible: true, data: action.data });
@@ -513,10 +520,7 @@ function Dialog() {
}
}
-function Prefs() {
- let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Prefs;
- let action = arguments[1];
-
+function Prefs(prevState = INITIAL_STATE.Prefs, action) {
let newValues;
switch (action.type) {
case at.PREFS_INITIAL_VALUES:
@@ -530,10 +534,7 @@ function Prefs() {
}
}
-function Sections() {
- let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Sections;
- let action = arguments[1];
-
+function Sections(prevState = INITIAL_STATE.Sections, action) {
let hasMatch;
let newState;
switch (action.type) {
@@ -590,11 +591,7 @@ function Sections() {
rows: section.rows.map(item => {
// find the item within the rows that is attempted to be bookmarked
if (item.url === action.data.url) {
- var _action$data3 = action.data;
- const bookmarkGuid = _action$data3.bookmarkGuid,
- bookmarkTitle = _action$data3.bookmarkTitle,
- dateAdded = _action$data3.dateAdded;
-
+ const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
Object.assign(item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded });
if (!item.type || item.type === "history") {
item.type = "bookmark";
@@ -631,10 +628,7 @@ function Sections() {
}
}
-function Snippets() {
- let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Snippets;
- let action = arguments[1];
-
+function Snippets(prevState = INITIAL_STATE.Snippets, action) {
switch (action.type) {
case at.SNIPPETS_DATA:
return Object.assign({}, prevState, { initialized: true }, action.data);
@@ -655,34 +649,36 @@ module.exports = {
};
/***/ }),
-/* 6 */
+/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* globals Services */
-let usablePerfObj;
-
-let Cu;
-const isRunningInChrome = typeof Window === "undefined";
-
/* istanbul ignore if */
-if (isRunningInChrome) {
- Cu = Components.utils;
-} else {
- Cu = { import() {} };
+
+if (typeof Components !== "undefined" && Components.utils) {
+ Components.utils.import("resource://gre/modules/Services.jsm");
}
-Cu.import("resource://gre/modules/Services.jsm");
+let usablePerfObj;
/* istanbul ignore if */
-if (isRunningInChrome) {
+/* istanbul ignore else */
+if (typeof Services !== "undefined") {
// Borrow the high-resolution timer from the hidden window....
usablePerfObj = Services.appShell.hiddenDOMWindow.performance;
-} else {
+} else if (typeof performance !== "undefined") {
// we must be running in content space
usablePerfObj = performance;
+} else {
+ // This is a dummy object so this file doesn't crash in the node prerendering
+ // task.
+ usablePerfObj = {
+ now() {},
+ mark() {}
+ };
}
var _PerfService = function _PerfService(options) {
@@ -778,47 +774,43 @@ module.exports = {
};
/***/ }),
-/* 7 */
+/* 8 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-const React = __webpack_require__(0);
+const React = __webpack_require__(1);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
-var _require = __webpack_require__(1);
-
-const ac = _require.actionCreators,
- at = _require.actionTypes;
-
-
-const LinkMenu = __webpack_require__(8);
-
-var _require2 = __webpack_require__(4);
-
-const TOP_SITES_SOURCE = _require2.TOP_SITES_SOURCE,
- TOP_SITES_CONTEXT_MENU_OPTIONS = _require2.TOP_SITES_CONTEXT_MENU_OPTIONS;
+const LinkMenu = __webpack_require__(9);
+const { TOP_SITES_SOURCE, TOP_SITES_CONTEXT_MENU_OPTIONS, MIN_RICH_FAVICON_SIZE, MIN_CORNER_FAVICON_SIZE } = __webpack_require__(5);
const TopSiteLink = props => {
- const link = props.link;
-
+ const { link } = props;
const topSiteOuterClassName = `top-site-outer${props.className ? ` ${props.className}` : ""}`;
- const tippyTopIcon = link.tippyTopIcon;
-
+ const { tippyTopIcon, faviconSize } = link;
let imageClassName;
let imageStyle;
- if (tippyTopIcon) {
- imageClassName = "tippy-top-icon";
+ let showSmallFavicon = false;
+ let smallFaviconStyle;
+ if (tippyTopIcon || faviconSize >= MIN_RICH_FAVICON_SIZE) {
+ // styles and class names for top sites with rich icons
+ imageClassName = "top-site-icon rich-icon";
imageStyle = {
backgroundColor: link.backgroundColor,
- backgroundImage: `url(${tippyTopIcon})`
+ backgroundImage: `url(${tippyTopIcon || link.favicon})`
};
} else {
+ // styles and class names for top sites with screenshot + small icon in top left corner
imageClassName = `screenshot${link.screenshot ? " active" : ""}`;
imageStyle = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" };
+
+ // only show a favicon in top left if it's greater than 32x32
+ if (faviconSize >= MIN_CORNER_FAVICON_SIZE) {
+ showSmallFavicon = true;
+ smallFaviconStyle = { backgroundImage: `url(${link.favicon})` };
+ }
}
return React.createElement(
"li",
@@ -834,7 +826,8 @@ const TopSiteLink = props => {
{ className: "letter-fallback" },
props.title[0]
),
- React.createElement("div", { className: imageClassName, style: imageStyle })
+ React.createElement("div", { className: imageClassName, style: imageStyle }),
+ showSmallFavicon && React.createElement("div", { className: "top-site-icon default-icon", style: smallFaviconStyle })
),
React.createElement(
"div",
@@ -895,8 +888,7 @@ class TopSite extends React.Component {
this.setState({ showContextMenu });
}
onDismissButtonClick() {
- const link = this.props.link;
-
+ const { link } = this.props;
if (link.isPinned) {
this.props.dispatch(ac.SendToMain({
type: at.TOP_SITES_UNPIN,
@@ -910,10 +902,7 @@ class TopSite extends React.Component {
this.userEvent("BLOCK");
}
onPinButtonClick() {
- var _props = this.props;
- const link = _props.link,
- index = _props.index;
-
+ const { link, index } = this.props;
if (link.isPinned) {
this.props.dispatch(ac.SendToMain({
type: at.TOP_SITES_UNPIN,
@@ -932,9 +921,8 @@ class TopSite extends React.Component {
this.props.onEdit(this.props.index);
}
render() {
- const props = this.props;
- const link = props.link;
-
+ const { props } = this;
+ const { link } = props;
const isContextMenuOpen = this.state.showContextMenu && this.state.activeTile === props.index;
const title = link.label || link.hostname;
return React.createElement(
@@ -993,45 +981,26 @@ module.exports.TopSiteLink = TopSiteLink;
module.exports.TopSitePlaceholder = TopSitePlaceholder;
/***/ }),
-/* 8 */
+/* 9 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(2);
-
-const injectIntl = _require.injectIntl;
-
+const React = __webpack_require__(1);
+const { injectIntl } = __webpack_require__(2);
const ContextMenu = __webpack_require__(17);
-
-var _require2 = __webpack_require__(1);
-
-const ac = _require2.actionCreators;
-
+const { actionCreators: ac } = __webpack_require__(0);
const linkMenuOptions = __webpack_require__(18);
const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
class LinkMenu extends React.Component {
getOptions() {
const props = this.props;
- const site = props.site,
- index = props.index,
- source = props.source;
+ const { site, index, source } = props;
// Handle special case of default site
-
const propOptions = !site.isDefault ? props.options : DEFAULT_SITE_MENU_OPTIONS;
const options = propOptions.map(o => linkMenuOptions[o](site, index, source)).map(option => {
- const action = option.action,
- impression = option.impression,
- id = option.id,
- type = option.type,
- userEvent = option.userEvent;
-
+ const { action, impression, id, type, userEvent } = option;
if (!type && id) {
option.label = props.intl.formatMessage(option);
option.onClick = () => {
@@ -1069,72 +1038,39 @@ class LinkMenu extends React.Component {
module.exports = injectIntl(LinkMenu);
module.exports._unconnected = LinkMenu;
-/***/ }),
-/* 9 */
-/***/ (function(module, exports) {
-
-var g;
-
-// This works in non-strict mode
-g = (function() {
- return this;
-})();
-
-try {
- // This works if eval is allowed (see CSP)
- g = g || Function("return this")() || (1,eval)("this");
-} catch(e) {
- // This works if the window reference is available
- if(typeof window === "object")
- g = window;
-}
-
-// g can still be undefined, but nothing to do about it...
-// We return undefined, instead of nothing here, so it's
-// easier to handle this case. if(!global) { ...}
-
-module.exports = g;
-
-
/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
+/* WEBPACK VAR INJECTION */(function(global) {const React = __webpack_require__(1);
const ReactDOM = __webpack_require__(11);
const Base = __webpack_require__(12);
-
-var _require = __webpack_require__(3);
-
-const Provider = _require.Provider;
-
+const { Provider } = __webpack_require__(3);
const initStore = __webpack_require__(28);
-
-var _require2 = __webpack_require__(5);
-
-const reducers = _require2.reducers;
-
+const { reducers } = __webpack_require__(6);
const DetectUserSessionStart = __webpack_require__(30);
-
-var _require3 = __webpack_require__(31);
-
-const addSnippetsSubscriber = _require3.addSnippetsSubscriber;
-
+const { addSnippetsSubscriber } = __webpack_require__(31);
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
new DetectUserSessionStart().sendEventOrAddListener();
-const store = initStore(reducers);
+const store = initStore(reducers, global.gActivityStreamPrerenderedState);
+
+// If we are starting in a prerendered state, we must wait until the first render
+// to request state rehydration (see Base.jsx). If we are NOT in a prerendered state,
+// we can request it immedately.
+if (!global.gActivityStreamPrerenderedState) {
+ store.dispatch(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+}
ReactDOM.render(React.createElement(
Provider,
{ store: store },
- React.createElement(Base, null)
+ React.createElement(Base, { isPrerendered: !!global.gActivityStreamPrerenderedState })
), document.getElementById("root"));
addSnippetsSubscriber(store);
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
/***/ }),
/* 11 */
@@ -1146,52 +1082,42 @@ module.exports = ReactDOM;
/* 12 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const addLocaleData = _require2.addLocaleData,
- IntlProvider = _require2.IntlProvider;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { addLocaleData, IntlProvider } = __webpack_require__(2);
const TopSites = __webpack_require__(13);
const Search = __webpack_require__(19);
const ConfirmDialog = __webpack_require__(21);
const ManualMigration = __webpack_require__(22);
const PreferencesPane = __webpack_require__(23);
const Sections = __webpack_require__(24);
-
-// Locales that should be displayed RTL
-const RTL_LIST = ["ar", "he", "fa", "ur"];
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
// 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
// more features are needed.
-function addLocaleDataForReactIntl(_ref) {
- let locale = _ref.locale;
-
+function addLocaleDataForReactIntl({ locale, textDirection }) {
addLocaleData([{ locale, parentLocale: "en" }]);
document.documentElement.lang = locale;
- document.documentElement.dir = RTL_LIST.indexOf(locale.split("-")[0]) >= 0 ? "rtl" : "ltr";
+ document.documentElement.dir = textDirection;
}
class Base extends React.Component {
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
+ // dispatched right after the store is ready.
+ if (this.props.isPrerendered) {
+ this.props.dispatch(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+ }
+
// Also wait for the preloaded page to show, so the tab's title and favicon updates
addEventListener("visibilitychange", () => {
this.updateTitle(this.props.App);
document.getElementById("favicon").href += "#";
}, { once: true });
}
- componentWillUpdate(_ref2) {
- let App = _ref2.App;
-
+ componentWillUpdate({ App }) {
// Early loads might not have locale yet, so wait until we do
if (App.locale && App.locale !== this.props.App.locale) {
addLocaleDataForReactIntl(App);
@@ -1199,9 +1125,7 @@ class Base extends React.Component {
}
}
- updateTitle(_ref3) {
- let strings = _ref3.strings;
-
+ updateTitle({ strings }) {
if (strings) {
document.title = strings.newtab_page_title;
}
@@ -1209,19 +1133,19 @@ class Base extends React.Component {
render() {
const props = this.props;
- var _props$App = props.App;
- const locale = _props$App.locale,
- strings = _props$App.strings,
- initialized = _props$App.initialized;
-
+ const { locale, strings, initialized } = props.App;
const prefs = props.Prefs.values;
- if (!initialized || !strings) {
+
+ if (!props.isPrerendered && !initialized) {
return null;
}
+ // Note: the key on IntlProvider must be static in order to not blow away
+ // all elements on a locale change (such as after preloading).
+ // See https://github.com/yahoo/react-intl/issues/695 for more info.
return React.createElement(
IntlProvider,
- { key: locale, locale: locale, messages: strings },
+ { key: "STATIC", locale: locale, messages: strings },
React.createElement(
"div",
{ className: "outer-wrapper" },
@@ -1234,7 +1158,7 @@ class Base extends React.Component {
React.createElement(Sections, null),
React.createElement(ConfirmDialog, null)
),
- React.createElement(PreferencesPane, null)
+ initialized && React.createElement(PreferencesPane, null)
)
);
}
@@ -1246,28 +1170,13 @@ module.exports = connect(state => ({ App: state.App, Prefs: state.Prefs }))(Base
/* 13 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { FormattedMessage } = __webpack_require__(2);
const TopSitesPerfTimer = __webpack_require__(14);
const TopSitesEdit = __webpack_require__(15);
-
-var _require3 = __webpack_require__(7);
-
-const TopSite = _require3.TopSite,
- TopSitePlaceholder = _require3.TopSitePlaceholder;
-
+const { TopSite, TopSitePlaceholder } = __webpack_require__(8);
const TopSites = props => {
const realTopSites = props.TopSites.rows.slice(0, props.TopSitesCount);
@@ -1307,23 +1216,10 @@ module.exports._unconnected = TopSites;
/* 14 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(1);
-
-const ac = _require2.actionCreators,
- at = _require2.actionTypes;
-
-var _require3 = __webpack_require__(6);
-
-const perfSvc = _require3.perfService;
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
+const { perfService: perfSvc } = __webpack_require__(7);
/**
* A proxy class that uses double requestAnimationFrame from
@@ -1343,7 +1239,6 @@ const perfSvc = _require3.perfService;
* @class TopSitesPerfTimer
* @extends {React.Component}
*/
-
class TopSitesPerfTimer extends React.Component {
constructor(props) {
super(props);
@@ -1440,38 +1335,15 @@ module.exports._unconnected = TopSitesPerfTimer;
/* 15 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(2);
-
-const FormattedMessage = _require.FormattedMessage,
- injectIntl = _require.injectIntl;
-
-var _require2 = __webpack_require__(1);
-
-const ac = _require2.actionCreators,
- at = _require2.actionTypes;
-
+const React = __webpack_require__(1);
+const { FormattedMessage, injectIntl } = __webpack_require__(2);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
const TopSiteForm = __webpack_require__(16);
+const { TopSite, TopSitePlaceholder } = __webpack_require__(8);
-var _require3 = __webpack_require__(7);
-
-const TopSite = _require3.TopSite,
- TopSitePlaceholder = _require3.TopSitePlaceholder;
-
-var _require4 = __webpack_require__(5);
-
-const TOP_SITES_DEFAULT_LENGTH = _require4.TOP_SITES_DEFAULT_LENGTH,
- TOP_SITES_SHOWMORE_LENGTH = _require4.TOP_SITES_SHOWMORE_LENGTH;
-
-var _require5 = __webpack_require__(4);
-
-const TOP_SITES_SOURCE = _require5.TOP_SITES_SOURCE;
-
+const { TOP_SITES_DEFAULT_LENGTH, TOP_SITES_SHOWMORE_LENGTH } = __webpack_require__(6);
+const { TOP_SITES_SOURCE } = __webpack_require__(5);
class TopSitesEdit extends React.Component {
constructor(props) {
@@ -1623,6 +1495,7 @@ class TopSitesEdit extends React.Component {
url: this.props.TopSites.rows[this.state.editIndex].url,
index: this.state.editIndex,
editMode: true,
+ link: this.props.TopSites.rows[this.state.editIndex],
onClose: this.onFormClose,
dispatch: this.props.dispatch,
intl: this.props.intl })
@@ -1639,24 +1512,11 @@ module.exports._unconnected = TopSitesEdit;
/* 16 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(1);
-
-const ac = _require.actionCreators,
- at = _require.actionTypes;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage;
-
-var _require3 = __webpack_require__(4);
-
-const TOP_SITES_SOURCE = _require3.TOP_SITES_SOURCE;
+const React = __webpack_require__(1);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
+const { FormattedMessage } = __webpack_require__(2);
+const { TOP_SITES_SOURCE } = __webpack_require__(5);
class TopSiteForm extends React.Component {
constructor(props) {
@@ -1710,6 +1570,13 @@ class TopSiteForm extends React.Component {
if (this.state.label !== "") {
site.label = this.state.label;
}
+ // Unpin links if the URL changed.
+ if (this.props.link.isPinned && this.props.link.url !== site.url) {
+ this.props.dispatch(ac.SendToMain({
+ type: at.TOP_SITES_UNPIN,
+ data: { site: { url: this.props.link.url } }
+ }));
+ }
this.props.dispatch(ac.SendToMain({
type: at.TOP_SITES_PIN,
data: { site, index: this.props.index }
@@ -1832,10 +1699,7 @@ module.exports = TopSiteForm;
/* 17 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
+const React = __webpack_require__(1);
class ContextMenu extends React.Component {
constructor(props) {
@@ -1885,8 +1749,7 @@ class ContextMenuItem extends React.Component {
this.props.option.onClick();
}
onKeyDown(event) {
- const option = this.props.option;
-
+ const { option } = this.props;
switch (event.key) {
case "Tab":
// tab goes down in context menu, shift + tab goes up in context menu
@@ -1903,8 +1766,7 @@ class ContextMenuItem extends React.Component {
}
}
render() {
- const option = this.props.option;
-
+ const { option } = this.props;
return React.createElement(
"li",
{ role: "menuitem", className: "context-menu-item" },
@@ -1926,20 +1788,13 @@ module.exports.ContextMenuItem = ContextMenuItem;
/* 18 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-var _require = __webpack_require__(1);
-
-const at = _require.actionTypes,
- ac = _require.actionCreators;
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
/**
* List of functions that return items that can be included as menu options in a
* LinkMenu. All functions take the site as the first parameter, and optionally
* the index of the site.
*/
-
module.exports = {
Separator: () => ({ type: "separator" }),
RemoveBookmark: site => ({
@@ -2052,25 +1907,11 @@ module.exports.CheckPinTopSite = (site, index) => site.isPinned ? module.exports
/* globals ContentSearchUIController */
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage,
- injectIntl = _require2.injectIntl;
-
-var _require3 = __webpack_require__(1);
-
-const ac = _require3.actionCreators;
-
-var _require4 = __webpack_require__(20);
-
-const IS_NEWTAB = _require4.IS_NEWTAB;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { FormattedMessage, injectIntl } = __webpack_require__(2);
+const { actionCreators: ac } = __webpack_require__(0);
+const { IS_NEWTAB } = __webpack_require__(20);
class Search extends React.Component {
constructor(props) {
@@ -2160,42 +2001,28 @@ class Search extends React.Component {
}
}
-module.exports = connect()(injectIntl(Search));
+// initialized is passed to props so that Search will rerender when it receives strings
+module.exports = connect(state => ({ locale: state.App.locale }))(injectIntl(Search));
module.exports._unconnected = Search;
/***/ }),
/* 20 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-module.exports = {
+/* WEBPACK VAR INJECTION */(function(global) {module.exports = {
// constant to know if the page is about:newtab or about:home
- IS_NEWTAB: document.documentURI === "about:newtab"
+ IS_NEWTAB: global.document && global.document.documentURI === "about:newtab"
};
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
/***/ }),
/* 21 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage;
-
-var _require3 = __webpack_require__(1);
-
-const actionTypes = _require3.actionTypes,
- ac = _require3.actionCreators;
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { FormattedMessage } = __webpack_require__(2);
+const { actionTypes, actionCreators: ac } = __webpack_require__(0);
/**
* ConfirmDialog component.
@@ -2216,7 +2043,6 @@ const actionTypes = _require3.actionTypes,
* confirm_button_string_id: "menu_action_delete"
* },
*/
-
const ConfirmDialog = React.createClass({
displayName: "ConfirmDialog",
@@ -2298,23 +2124,10 @@ module.exports.Dialog = ConfirmDialog;
/* 22 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage;
-
-var _require3 = __webpack_require__(1);
-
-const at = _require3.actionTypes,
- ac = _require3.actionCreators;
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { FormattedMessage } = __webpack_require__(2);
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
/**
* Manual migration component used to start the profile import wizard.
@@ -2324,7 +2137,6 @@ const at = _require3.actionTypes,
* 3. After 3 active days
* 4. User clicks "Cancel" on the import wizard (currently not implemented).
*/
-
class ManualMigration extends React.Component {
constructor(props) {
super(props);
@@ -2376,30 +2188,11 @@ module.exports._unconnected = ManualMigration;
/* 23 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const injectIntl = _require2.injectIntl,
- FormattedMessage = _require2.FormattedMessage;
-
-var _require3 = __webpack_require__(1);
-
-const ac = _require3.actionCreators,
- at = _require3.actionTypes;
-
-var _require4 = __webpack_require__(5);
-
-const TOP_SITES_DEFAULT_LENGTH = _require4.TOP_SITES_DEFAULT_LENGTH,
- TOP_SITES_SHOWMORE_LENGTH = _require4.TOP_SITES_SHOWMORE_LENGTH;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { injectIntl, FormattedMessage } = __webpack_require__(2);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
+const { TOP_SITES_DEFAULT_LENGTH, TOP_SITES_SHOWMORE_LENGTH } = __webpack_require__(6);
const getFormattedMessage = message => typeof message === "string" ? React.createElement(
"span",
@@ -2447,9 +2240,7 @@ class PreferencesPane extends React.Component {
}
handlePrefChange(event) {
const target = event.target;
- const name = target.name,
- checked = target.checked;
-
+ const { name, checked } = target;
let value = checked;
if (name === "topSitesCount") {
value = checked ? TOP_SITES_SHOWMORE_LENGTH : TOP_SITES_DEFAULT_LENGTH;
@@ -2516,15 +2307,14 @@ class PreferencesPane extends React.Component {
React.createElement(PreferencesInput, { className: "showMoreTopSites", prefName: "topSitesCount", 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((_ref) => {
- let id = _ref.id,
- title = _ref.title,
- enabled = _ref.enabled,
- pref = _ref.pref;
- return 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 });
- })
+ 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,
+ titleString: { id: "settings_pane_snippets_header" },
+ descString: { id: "settings_pane_snippets_body" } })
),
React.createElement(
"section",
@@ -2549,31 +2339,15 @@ module.exports.PreferencesInput = PreferencesInput;
/* 24 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const injectIntl = _require2.injectIntl,
- FormattedMessage = _require2.FormattedMessage;
+/* WEBPACK VAR INJECTION */(function(global) {var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { injectIntl, FormattedMessage } = __webpack_require__(2);
const Card = __webpack_require__(25);
-const PlaceholderCard = Card.PlaceholderCard;
-
+const { PlaceholderCard } = Card;
const Topics = __webpack_require__(27);
-
-var _require3 = __webpack_require__(1);
-
-const ac = _require3.actionCreators;
-
+const { actionCreators: ac } = __webpack_require__(0);
const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange";
@@ -2618,8 +2392,7 @@ class Section extends React.Component {
}
_dispatchImpressionStats() {
- const props = this.props;
-
+ const { props } = this;
const maxCards = 3 * props.maxRows;
props.dispatch(ac.ImpressionStats({
source: props.eventSource,
@@ -2632,8 +2405,7 @@ class Section extends React.Component {
// changes while the page is hidden (i.e. preloaded or on a hidden tab),
// only send the event if the page becomes visible again.
sendImpressionStatsOrAddListener() {
- const props = this.props;
-
+ const { props } = this;
if (!props.dispatch) {
return;
@@ -2666,8 +2438,7 @@ class Section extends React.Component {
}
componentDidUpdate(prevProps) {
- const props = this.props;
-
+ const { props } = this;
if (
// Don't send impression stats for the empty state
props.rows.length &&
@@ -2689,22 +2460,13 @@ class Section extends React.Component {
}
render() {
- var _props = this.props;
- const id = _props.id,
- eventSource = _props.eventSource,
- title = _props.title,
- icon = _props.icon,
- rows = _props.rows,
- infoOption = _props.infoOption,
- emptyState = _props.emptyState,
- dispatch = _props.dispatch,
- maxRows = _props.maxRows,
- contextMenuOptions = _props.contextMenuOptions,
- intl = _props.intl,
- initialized = _props.initialized;
-
+ const {
+ id, eventSource, title, icon, rows,
+ infoOption, emptyState, dispatch, maxRows,
+ contextMenuOptions, intl, initialized
+ } = this.props;
const maxCards = CARDS_PER_ROW * maxRows;
- const shouldShowTopics = id === "topstories" && this.props.topics && this.props.topics.length > 0 && this.props.read_more_endpoint;
+ const shouldShowTopics = id === "topstories" && this.props.topics && this.props.topics.length > 0;
const infoOptionIconA11yAttrs = {
"aria-haspopup": "true",
@@ -2816,28 +2578,17 @@ module.exports = connect(state => ({ Sections: state.Sections }))(Sections);
module.exports._unconnected = Sections;
module.exports.SectionIntl = SectionIntl;
module.exports._unconnectedSection = Section;
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)))
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
/***/ }),
/* 25 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-const LinkMenu = __webpack_require__(8);
-
-var _require = __webpack_require__(2);
-
-const FormattedMessage = _require.FormattedMessage;
-
+const React = __webpack_require__(1);
+const LinkMenu = __webpack_require__(9);
+const { FormattedMessage } = __webpack_require__(2);
const cardContextTypes = __webpack_require__(26);
-
-var _require2 = __webpack_require__(1);
-
-const ac = _require2.actionCreators,
- at = _require2.actionTypes;
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
/**
* Card component.
@@ -2848,7 +2599,6 @@ const ac = _require2.actionCreators,
* this class. Each card will then get a context menu which reflects the actions that
* can be done on this Card.
*/
-
class Card extends React.Component {
constructor(props) {
super(props);
@@ -2866,12 +2616,7 @@ class Card extends React.Component {
}
onLinkClick(event) {
event.preventDefault();
- const altKey = event.altKey,
- button = event.button,
- ctrlKey = event.ctrlKey,
- metaKey = event.metaKey,
- shiftKey = event.shiftKey;
-
+ const { altKey, button, ctrlKey, metaKey, shiftKey } = event;
this.props.dispatch(ac.SendToMain({
type: at.OPEN_LINK,
data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } })
@@ -2892,21 +2637,11 @@ class Card extends React.Component {
this.setState({ showContextMenu });
}
render() {
- var _props = this.props;
- const index = _props.index,
- link = _props.link,
- dispatch = _props.dispatch,
- contextMenuOptions = _props.contextMenuOptions,
- eventSource = _props.eventSource;
- const props = this.props;
-
+ const { index, link, dispatch, contextMenuOptions, eventSource } = this.props;
+ const { props } = this;
const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
-
- var _ref = link.type ? cardContextTypes[link.type] : {};
-
- const icon = _ref.icon,
- intlID = _ref.intlID;
-
+ // Display "now" as "trending" until we have new strings #3402
+ const { icon, intlID } = cardContextTypes[link.type === "now" ? "trending" : link.type] || {};
return React.createElement(
"li",
@@ -2983,10 +2718,7 @@ module.exports.PlaceholderCard = PlaceholderCard;
/***/ }),
/* 26 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
+/***/ (function(module, exports) {
module.exports = {
history: {
@@ -3011,22 +2743,12 @@ module.exports = {
/* 27 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(2);
-
-const FormattedMessage = _require.FormattedMessage;
-
+const React = __webpack_require__(1);
+const { FormattedMessage } = __webpack_require__(2);
class Topic extends React.Component {
render() {
- var _props = this.props;
- const url = _props.url,
- name = _props.name;
-
+ const { url, name } = this.props;
return React.createElement(
"li",
null,
@@ -3041,10 +2763,7 @@ class Topic extends React.Component {
class Topics extends React.Component {
render() {
- var _props2 = this.props;
- const topics = _props2.topics,
- read_more_endpoint = _props2.read_more_endpoint;
-
+ const { topics, read_more_endpoint } = this.props;
return React.createElement(
"div",
{ className: "topic" },
@@ -3058,7 +2777,7 @@ class Topics extends React.Component {
null,
topics.map(t => React.createElement(Topic, { key: t.name, url: t.url, name: t.name }))
),
- React.createElement(
+ read_more_endpoint && React.createElement(
"a",
{ className: "topic-read-more", href: read_more_endpoint },
React.createElement(FormattedMessage, { id: "pocket_read_even_more" })
@@ -3075,21 +2794,10 @@ module.exports.Topic = Topic;
/* 28 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-/* eslint-env mozilla/frame-script */
-
-var _require = __webpack_require__(29);
-
-const createStore = _require.createStore,
- combineReducers = _require.combineReducers,
- applyMiddleware = _require.applyMiddleware;
-
-var _require2 = __webpack_require__(1);
-
-const au = _require2.actionUtils;
+/* WEBPACK VAR INJECTION */(function(global) {/* eslint-env mozilla/frame-script */
+const { createStore, combineReducers, applyMiddleware } = __webpack_require__(29);
+const { actionTypes: at, actionCreators: ac, actionUtils: au } = __webpack_require__(0);
const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
@@ -3131,30 +2839,71 @@ const messageMiddleware = store => next => action => {
next(action);
};
+const rehydrationMiddleware = store => next => action => {
+ if (store._didRehydrate) {
+ return next(action);
+ }
+
+ const isMergeStoreAction = action.type === MERGE_STORE_ACTION;
+ const isRehydrationRequest = action.type === at.NEW_TAB_STATE_REQUEST;
+
+ if (isRehydrationRequest) {
+ store._didRequestInitialState = true;
+ return next(action);
+ }
+
+ if (isMergeStoreAction) {
+ store._didRehydrate = true;
+ return next(action);
+ }
+
+ // If init happened after our request was made, we need to re-request
+ if (store._didRequestInitialState && action.type === at.INIT) {
+ return next(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+ }
+
+ if (au.isBroadcastToContent(action) || au.isSendToContent(action)) {
+ // Note that actions received before didRehydrate will not be dispatched
+ // because this could negatively affect preloading and the the state
+ // will be replaced by rehydration anyway.
+ return null;
+ }
+
+ return next(action);
+};
+
/**
* initStore - Create a store and listen for incoming actions
*
* @param {object} reducers An object containing Redux reducers
+ * @param {object} intialState (optional) The initial state of the store, if desired
* @return {object} A redux store
*/
-module.exports = function initStore(reducers) {
- const store = createStore(mergeStateReducer(combineReducers(reducers)), applyMiddleware(messageMiddleware));
+module.exports = function initStore(reducers, initialState) {
+ const store = createStore(mergeStateReducer(combineReducers(reducers)), initialState, global.addMessageListener && applyMiddleware(rehydrationMiddleware, messageMiddleware));
- addMessageListener(INCOMING_MESSAGE_NAME, msg => {
- try {
- store.dispatch(msg.data);
- } catch (ex) {
- console.error("Content msg:", msg, "Dispatch error: ", ex); // eslint-disable-line no-console
- dump(`Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${ex.stack}`);
- }
- });
+ store._didRehydrate = false;
+ store._didRequestInitialState = false;
+
+ if (global.addMessageListener) {
+ global.addMessageListener(INCOMING_MESSAGE_NAME, msg => {
+ try {
+ store.dispatch(msg.data);
+ } catch (ex) {
+ console.error("Content msg:", msg, "Dispatch error: ", ex); // eslint-disable-line no-console
+ dump(`Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${ex.stack}`);
+ }
+ });
+ }
return store;
};
+module.exports.rehydrationMiddleware = rehydrationMiddleware;
module.exports.MERGE_STORE_ACTION = MERGE_STORE_ACTION;
module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME;
module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
/***/ }),
/* 29 */
@@ -3166,28 +2915,17 @@ module.exports = Redux;
/* 30 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-
-
-var _require = __webpack_require__(1);
-
-const at = _require.actionTypes;
-
-var _require2 = __webpack_require__(6);
-
-const perfSvc = _require2.perfService;
-
+/* WEBPACK VAR INJECTION */(function(global) {const { actionTypes: at } = __webpack_require__(0);
+const { perfService: perfSvc } = __webpack_require__(7);
const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange";
module.exports = class DetectUserSessionStart {
- constructor() {
- let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
-
+ constructor(options = {}) {
// Overrides for testing
- this.sendAsyncMessage = options.sendAsyncMessage || window.sendAsyncMessage;
- this.document = options.document || document;
+ this.sendAsyncMessage = options.sendAsyncMessage || global.sendAsyncMessage;
+ this.document = options.document || global.document;
this._perfService = options.perfService || perfSvc;
this._onVisibilityChange = this._onVisibilityChange.bind(this);
}
@@ -3241,23 +2979,21 @@ module.exports = class DetectUserSessionStart {
}
}
};
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
/***/ }),
/* 31 */
/***/ (function(module, exports, __webpack_require__) {
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {
-
-const DATABASE_NAME = "snippets_db";
+/* WEBPACK VAR INJECTION */(function(global) {const DATABASE_NAME = "snippets_db";
const DATABASE_VERSION = 1;
const SNIPPETS_OBJECTSTORE_NAME = "snippets";
const SNIPPETS_UPDATE_INTERVAL_MS = 14400000; // 4 hours.
-var _require = __webpack_require__(1);
+const SNIPPETS_ENABLED_EVENT = "Snippets:Enabled";
+const SNIPPETS_DISABLED_EVENT = "Snippets:Disabled";
-const at = _require.actionTypes,
- ac = _require.actionCreators;
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
/**
* SnippetsMap - A utility for cacheing values related to the snippet. It has
@@ -3267,7 +3003,6 @@ const at = _require.actionTypes,
* previously cached data, if necessary.
*
*/
-
class SnippetsMap extends Map {
constructor(dispatch) {
super();
@@ -3533,6 +3268,14 @@ class SnippetsProvider {
} catch (e) {
this._showDefaultSnippets(e);
}
+
+ window.dispatchEvent(new Event(SNIPPETS_ENABLED_EVENT));
+ this.initialized = true;
+ }
+
+ uninit() {
+ window.dispatchEvent(new Event(SNIPPETS_DISABLED_EVENT));
+ this.initialized = false;
}
}
@@ -3542,19 +3285,29 @@ class SnippetsProvider {
* Snippet data.
*
* @param {obj} store The redux store
- * @return {obj} Returns the snippets instance and unsubscribe function
+ * @return {obj} Returns the snippets instance and unsubscribe function
*/
function addSnippetsSubscriber(store) {
const snippets = new SnippetsProvider(store.dispatch);
- const unsubscribe = store.subscribe(() => {
+
+ let initializing = false;
+
+ store.subscribe(async () => {
const state = store.getState();
- if (state.Snippets.initialized) {
- if (state.Snippets.onboardingFinished) {
- snippets.init({ appData: state.Snippets });
+ // 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;
}
- unsubscribe();
+ } else if (state.Snippets.initialized === false && snippets.initialized) {
+ snippets.uninit();
}
});
+
// These values are returned for testing purposes
return snippets;
}
@@ -3565,7 +3318,7 @@ module.exports = {
SnippetsProvider,
SNIPPETS_UPDATE_INTERVAL_MS
};
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)))
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
/***/ })
/******/ ]);
\ No newline at end of file
diff --git a/browser/extensions/activity-stream/data/content/activity-stream.css b/browser/extensions/activity-stream/data/content/activity-stream.css
index e1252e029597..fe238faf8245 100644
--- a/browser/extensions/activity-stream/data/content/activity-stream.css
+++ b/browser/extensions/activity-stream/data/content/activity-stream.css
@@ -64,7 +64,9 @@ input {
.icon.icon-historyItem {
background-image: url("assets/glyph-historyItem-16.svg"); }
.icon.icon-trending {
- background-image: url("assets/glyph-trending-16.svg"); }
+ background-image: url("assets/glyph-trending-16.svg");
+ transform: translateY(2px);
+ /* trending bolt is visually top heavy */ }
.icon.icon-now {
background-image: url("chrome://browser/skin/history.svg"); }
.icon.icon-topsites {
@@ -317,17 +319,26 @@ main {
opacity: 0; }
.top-sites-list .top-site-outer .screenshot.active {
opacity: 1; }
- .top-sites-list .top-site-outer .tippy-top-icon {
+ .top-sites-list .top-site-outer .top-site-icon {
position: absolute;
- top: 0;
- left: 0;
- height: 100%;
- width: 100%;
border-radius: 6px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
background-position: center center;
- background-size: 96px;
- background-repeat: no-repeat; }
+ background-repeat: no-repeat;
+ background-color: #F9F9FA; }
+ .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;
+ height: 42px;
+ width: 42px;
+ background-size: 32px; }
.top-sites-list .top-site-outer .title {
font: message-box;
height: 30px;
@@ -1017,11 +1028,10 @@ main {
display: flex; }
.card-outer .card-context-icon {
fill: rgba(12, 12, 13, 0.6);
- font-size: 13px;
- margin-inline-end: 6px;
- display: block; }
+ margin-inline-end: 6px; }
.card-outer .card-context-label {
flex-grow: 1;
+ line-height: 16px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
diff --git a/browser/extensions/activity-stream/data/content/activity-stream.html b/browser/extensions/activity-stream/data/content/activity-stream.html
index 6630864c64ba..aafd603cd7b3 100644
--- a/browser/extensions/activity-stream/data/content/activity-stream.html
+++ b/browser/extensions/activity-stream/data/content/activity-stream.html
@@ -1,5 +1,5 @@
-
+
diff --git a/browser/extensions/activity-stream/data/locales.json b/browser/extensions/activity-stream/data/locales.json
index e5b670873298..d1098e9658f2 100644
--- a/browser/extensions/activity-stream/data/locales.json
+++ b/browser/extensions/activity-stream/data/locales.json
@@ -232,9 +232,11 @@
"settings_pane_visit_again_header": "Təkrar ziyarət et",
"settings_pane_visit_again_body": "Firefox tarixçənizdən yadda saxlamaq və ya geri qayıtmaq istəyə biləcəyiniz hissələri göstərəcək.",
"settings_pane_highlights_header": "Seçilmişlər",
+ "settings_pane_highlights_body2": "Son ziyarət etdiyiniz və ya əlfəcinlədiyiniz maraqlı məzmunlara rahat qayıdın.",
"settings_pane_highlights_options_bookmarks": "Əlfəcinlər",
"settings_pane_highlights_options_visited": "Baxılmış Saytlar",
"settings_pane_snippets_header": "Hissələr",
+ "settings_pane_snippets_body": "Mozilladan Firefox, internet mədəniyyəti və digər yeniliklər haqqında qısa bildirişlər oxuyun.",
"settings_pane_done_button": "Oldu",
"edit_topsites_button_text": "Redaktə et",
"edit_topsites_button_label": "Qabaqcıl Saytlar bölümünüzü fərdiləşdirin",
@@ -257,6 +259,8 @@
"pocket_read_more": "Məşhur Mövzular:",
"pocket_read_even_more": "Daha çox hekayə gör",
"pocket_feedback_header": "25 milyon nəfərin dəstəyi ilə internetin ən yaxşıları.",
+ "pocket_description": "Mozilla ailəsinin yeni üzvü olan Pocket ilə yüksək keyfiyyətli məzmunları kəşf edin.",
+ "highlights_empty_state": "İnternetdə gəzməyə başlayın, burada ziyarət edəcəyiniz və ya əlfəcinləyəcəyiniz məqalə, video və digər səhifələri göstərəcəyik.",
"topstories_empty_state": "Hamısını oxudunuz. Yeni {provider} məqalələri üçün daha sonra təkrar yoxlayın. Gözləyə bilmirsiz? Məşhur mövzu seçərək internetdən daha çox gözəl məqalələr tapın.",
"manual_migration_explanation2": "Firefox səyyahını digər səyyahlardan olan əlfəcin, tarixçə və parollar ilə yoxlayın.",
"manual_migration_cancel_button": "Xeyr, Təşəkkürlər",
@@ -414,9 +418,11 @@
"settings_pane_visit_again_header": "Посещаване",
"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": "Настройки на най-посещаваните",
@@ -440,6 +446,7 @@
"pocket_read_even_more": "Повече статии",
"pocket_feedback_header": "Най-доброто от интернет, подбрано от над 25 милиона души.",
"pocket_description": "Открийте висококачествено съдържание, което иначе може да пропуснете, с помощта на Pocket, вече част от Mozilla.",
+ "highlights_empty_state": "Разглеждайте и тук ще ви покажем някои от най-добрите статии, видео и други страници, които сте посетили или отметнали наскоро.",
"topstories_empty_state": "Разгледахте всичко. Проверете по-късно за повече истории от {provider}. Нямате търпение? Изберете популярна тема, за да откриете повече истории из цялата Мрежа.",
"manual_migration_explanation2": "Опитайте Firefox с отметките, историята и паролите от друг четец.",
"manual_migration_cancel_button": "Не, благодаря",
@@ -1055,10 +1062,10 @@
"menu_action_email_link": "Link per E-Mail versenden…",
"menu_action_open_new_window": "In neuem Fenster öffnen",
"menu_action_open_private_window": "In neuem privaten Fenster öffnen",
- "menu_action_dismiss": "Schließen",
+ "menu_action_dismiss": "Entfernen",
"menu_action_delete": "Aus Chronik löschen",
"menu_action_pin": "Anheften",
- "menu_action_unpin": "Lösen",
+ "menu_action_unpin": "Ablösen",
"confirm_history_delete_p1": "Soll wirklich jede Instanz dieser Seite aus Ihrer Chronik gelöscht werden?",
"confirm_history_delete_notice_p2": "Diese Aktion kann nicht rückgängig gemacht werden.",
"menu_action_save_to_pocket": "Bei Pocket speichern",
@@ -1077,8 +1084,8 @@
"time_label_minute": "{number} m",
"time_label_hour": "{number} h",
"time_label_day": "{number} t",
- "settings_pane_button_label": "Neuer-Tab-Seite anpassen",
- "settings_pane_header": "Einstellungen zum neuen Tab",
+ "settings_pane_button_label": "Einstellungen für neue Tabs anpassen",
+ "settings_pane_header": "Einstellungen für neue Tabs",
"settings_pane_body2": "Wählen Sie aus, was auf dieser Seite angezeigt wird.",
"settings_pane_search_header": "Suche",
"settings_pane_search_body": "Suchen Sie aus einem neuen Tab im Internet.",
@@ -1108,8 +1115,8 @@
"edit_topsites_add_button": "Hinzufügen",
"topsites_form_add_header": "Neue meistbesuchte Seite",
"topsites_form_edit_header": "Meistbesuchte Seite bearbeiten",
- "topsites_form_title_placeholder": "Titel eingeben",
- "topsites_form_url_placeholder": "Eine URL eingeben oder einfügen",
+ "topsites_form_title_placeholder": "Name eingeben",
+ "topsites_form_url_placeholder": "Eine Adresse eingeben oder einfügen",
"topsites_form_add_button": "Hinzufügen",
"topsites_form_save_button": "Speichern",
"topsites_form_cancel_button": "Abbrechen",
@@ -1119,7 +1126,7 @@
"pocket_feedback_header": "Das Beste aus dem Web, zusammengetragen von 25 Millionen Menschen.",
"pocket_description": "Entdecken Sie qualitativ hochwertige Inhalte mithilfe von Pocket (jetzt Teil von von Mozilla), die Sie ansonsten verpassen würden.",
"highlights_empty_state": "Surfen Sie los und wir zeigen Ihnen hier tolle Artikel, Videos und andere Seiten, die Sie kürzlich besucht oder als Lesezeichen gespeichert haben.",
- "topstories_empty_state": "Jetzt kennen Sie die Neuigkeiten. Schauen Sie später wieder vorbei, um neue Informationen von {provider} zu erhalten. Können sie nicht warten? Wählen Sie ein beliebtes Thema und lesen Sie weitere interessante Geschichten aus dem Internet.",
+ "topstories_empty_state": "Jetzt kennen Sie die Neuigkeiten. Schauen Sie später wieder vorbei, um neue Informationen von {provider} zu erhalten. Können Sie nicht warten? Wählen Sie ein beliebtes Thema und lesen Sie weitere interessante Geschichten aus dem Internet.",
"manual_migration_explanation2": "Probieren Sie Firefox aus und importieren Sie die Lesezeichen, Chronik und Passwörter eines anderen Browsers.",
"manual_migration_cancel_button": "Nein, danke",
"manual_migration_import_button": "Jetzt importieren"
@@ -1763,6 +1770,7 @@
"default_label_loading": "Cargando…",
"header_top_sites": "Sitios favoritos",
"header_stories": "Historias populares",
+ "header_highlights": "Destacados",
"header_visit_again": "Visitar de nuevo",
"header_bookmarks": "Marcadores recientes",
"header_recommended_by": "Recomendado por {provider}",
@@ -1794,6 +1802,8 @@
"search_web_placeholder": "Buscar en la Web",
"search_settings": "Cambiar ajustes de búsqueda",
"section_info_option": "Info",
+ "section_info_send_feedback": "Enviar comentario",
+ "section_info_privacy_notice": "Aviso de privacidad",
"welcome_title": "Bienvenido a la nueva pestaña",
"welcome_body": "Firefox utilizará este espacio para mostrarte los marcadores, artículos y vídeos más relevantes y las páginas que has visitado recientemente, para que puedas acceder más rápido.",
"welcome_label": "Identificar lo más destacado para ti",
@@ -1803,7 +1813,7 @@
"time_label_day": "{number}d",
"settings_pane_button_label": "Personalizar la página Nueva pestaña",
"settings_pane_header": "Preferencias de nueva pestaña",
- "settings_pane_body": "Elige qué quieres ver al abrir una nueva pestaña",
+ "settings_pane_body2": "Elige lo quieras ver en esta página.",
"settings_pane_search_header": "Buscar",
"settings_pane_search_body": "Busca en la Web desde tu nueva pestaña.",
"settings_pane_topsites_header": "Sitios populares",
@@ -1813,8 +1823,12 @@
"settings_pane_bookmarks_body": "Tus marcadores recién creados, fácilmente accesibles.",
"settings_pane_visit_again_header": "Visitar de nuevo",
"settings_pane_visit_again_body": "Firefox te mostrará partes de tu historial de navegación que te gustaría recordar o volver a visitar.",
- "settings_pane_pocketstories_header": "Historias populares",
- "settings_pane_pocketstories_body": "Pocket, que forma parte de la familia de Mozilla, te ayudará a encontrar contenido de alta calidad que puede que no encuentres de otra forma.",
+ "settings_pane_highlights_header": "Destacados",
+ "settings_pane_highlights_body2": "Vuelve a encontrar todas las cosas interesantes que hayas visitado o marcado recientemente.",
+ "settings_pane_highlights_options_bookmarks": "Marcadores",
+ "settings_pane_highlights_options_visited": "Sitios visitados",
+ "settings_pane_snippets_header": "Fragmentos de código",
+ "settings_pane_snippets_body": "Lee actualizaciones breves de Mozilla sobre Firefox, la cultura de internet y el típico meme aleatorio.",
"settings_pane_done_button": "Hecho",
"edit_topsites_button_text": "Editar",
"edit_topsites_button_label": "Personalizar la sección de Sitios populares",
@@ -1837,10 +1851,10 @@
"pocket_read_more": "Temas populares:",
"pocket_read_even_more": "Ver más historias",
"pocket_feedback_header": "Lo mejor de la web, confirmado por más de 25 millones de personas.",
- "pocket_feedback_body": "Pocket, que forma parte de la familia de Mozilla, te ayudará a encontrar contenido de alta calidad que puede que no encuentres de otra forma.",
- "pocket_send_feedback": "Enviar comentario",
+ "pocket_description": "Gracias a Pocket, que ahora forma parte de Mozilla, podrás descubrir contenido de alta calidad que de otra forma te perderías.",
+ "highlights_empty_state": "Empieza a navegar y nosotros te mostraremos aquí algunos de los mejores artículos, videos y otras páginas que hayas visitado recientemente o agregado a marcadores.",
"topstories_empty_state": "Ya estás al día. Vuelve luego y busca más historias de {provider}. ¿No puedes esperar? Selecciona un tema popular y encontrás más historias alucinantes por toda la web.",
- "manual_migration_explanation": "Prueba Firefox con tusmarcadores y sitios favoritos importados desde otro navegador.",
+ "manual_migration_explanation2": "Prueba Firefox con los marcadores, historial y contraseñas de otro navegador.",
"manual_migration_cancel_button": "No, gracias",
"manual_migration_import_button": "Importar ahora"
},
@@ -2363,7 +2377,7 @@
"settings_pane_visit_again_header": "Visiter à nouveau",
"settings_pane_visit_again_body": "Firefox affichera des extraits de votre historique de navigation dont vous pourriez vouloir vous souvenir ou que vous pourriez vouloir revisiter.",
"settings_pane_highlights_header": "Éléments-clés",
- "settings_pane_highlights_body2": "Retrouvez des pages inintéressantes que vous avez déjà visitées récemment ou ajoutées aux marque-pages.",
+ "settings_pane_highlights_body2": "Retrouvez des pages intéressantes que vous avez visitées récemment ou ajoutées aux marque-pages.",
"settings_pane_highlights_options_bookmarks": "Marque-pages",
"settings_pane_highlights_options_visited": "Sites visités",
"settings_pane_snippets_header": "Brèves",
@@ -3007,6 +3021,7 @@
"default_label_loading": "Betöltés…",
"header_top_sites": "Népszerű oldalak",
"header_stories": "Népszerű történetek",
+ "header_highlights": "Kiemelések",
"header_visit_again": "Látogasson el ismét",
"header_bookmarks": "Friss könyvjelzők",
"header_recommended_by": "A(z) {provider} ajánlásával",
@@ -3038,6 +3053,8 @@
"search_web_placeholder": "Keresés a weben",
"search_settings": "Keresési beállítások módosítása",
"section_info_option": "Információ",
+ "section_info_send_feedback": "Visszajelzés küldése",
+ "section_info_privacy_notice": "Adatvédelmi nyilatkozat",
"welcome_title": "Üdvözöljük az új lapon",
"welcome_body": "A Firefox ezt a területet a leginkább releváns könyvjelzők, cikkek, videók és nemrég látogatott oldalak megjelenítésére fogja használni, így könnyedén visszatalálhat hozzájuk.",
"welcome_label": "A kiemeléseinek azonosítása",
@@ -3047,7 +3064,7 @@
"time_label_day": "{number} n",
"settings_pane_button_label": "Az Új lap oldal személyre szabása",
"settings_pane_header": "Új lap beállításai",
- "settings_pane_body": "Válassza ki, hogy mit lát, amikor megnyit egy új lapot.",
+ "settings_pane_body2": "Válassza ki, hogy mit akar látni ezen az oldalon.",
"settings_pane_search_header": "Keresés",
"settings_pane_search_body": "Keresés a weben az új lapon.",
"settings_pane_topsites_header": "Népszerű oldalak",
@@ -3057,8 +3074,12 @@
"settings_pane_bookmarks_body": "A frissen létrehozott könyvjelzői egy praktikus helyen.",
"settings_pane_visit_again_header": "Látogasson el ismét",
"settings_pane_visit_again_body": "A Firefox megjeleníti a böngészési előzményeinek azt a részét, amelyet lehet hogy meg szeretne jegyezni, vagy ahová vissza akar térni.",
- "settings_pane_pocketstories_header": "Népszerű történetek",
- "settings_pane_pocketstories_body": "A Pocket a Mozilla család tagja, segít az olyan jó minőségű tartalmak fellelésében, melyekkel egyébként nem is találkozott volna.",
+ "settings_pane_highlights_header": "Kiemelések",
+ "settings_pane_highlights_body2": "Találjon vissza azokhoz az érdekes dolgokhoz, amelyeket meglátogatott vagy könyvjelzőzött.",
+ "settings_pane_highlights_options_bookmarks": "Könyvjelzők",
+ "settings_pane_highlights_options_visited": "Látogatott helyek",
+ "settings_pane_snippets_header": "Töredékek",
+ "settings_pane_snippets_body": "Olvasson rövid és érdekes híreket a Mozillától, a Firefoxról, az internetes kultúráról, és időnként kapjon mémeket.",
"settings_pane_done_button": "Kész",
"edit_topsites_button_text": "Szerkesztés",
"edit_topsites_button_label": "A Népszerű oldalak rész testreszabása",
@@ -3081,10 +3102,10 @@
"pocket_read_more": "Népszerű témák:",
"pocket_read_even_more": "További történetek",
"pocket_feedback_header": "A web legjava, több mint 25 millió ember válogatásában.",
- "pocket_feedback_body": "A Pocket a Mozilla család tagja, segít az olyan jó minőségű tartalmak fellelésében, melyekkel egyébként nem is találkozott volna.",
- "pocket_send_feedback": "Visszajelzés küldése",
+ "pocket_description": "Fedezzen fel olyan, magas minőségű tartalmakat, amelyek egyébként elkerülnék a figyelmét, a Pocket segítségével, amely most már a Mozilla része.",
+ "highlights_empty_state": "Kezdjen el böngészni, és itt fognak megjelenni azok a nagyszerű cikkek, videók és más lapok, amelyeket nemrég meglátogatott vagy könyvjelzőzött.",
"topstories_empty_state": "Már felzárkózott. Nézzen vissza később a legújabb {provider} hírekért. Nem tud várni? Válasszon egy népszerű témát, hogy még több sztorit találjon a weben.",
- "manual_migration_explanation": "Próbálja ki a Firefoxot egy másik böngészőben lévő kedvenc oldalaival és könyvjelzőivel.",
+ "manual_migration_explanation2": "Próbálja ki a Firefoxot másik böngészőből származó könyvjelzőkkel, előzményekkel és jelszavakkal.",
"manual_migration_cancel_button": "Köszönöm, nem",
"manual_migration_import_button": "Importálás most"
},
@@ -3123,6 +3144,7 @@
"default_label_loading": "Memuat…",
"header_top_sites": "Situs Teratas",
"header_stories": "Cerita Utama",
+ "header_highlights": "Sorotan",
"header_visit_again": "Kunjungi Lagi",
"header_bookmarks": "Markah Terbaru",
"header_recommended_by": "Disarankan oleh {provider}",
@@ -3154,6 +3176,8 @@
"search_web_placeholder": "Cari di Web",
"search_settings": "Ubah Pengaturan Pencarian",
"section_info_option": "Info",
+ "section_info_send_feedback": "Kirim Umpan Balik",
+ "section_info_privacy_notice": "Kebijakan Privasi",
"welcome_title": "Selamat datang di tab baru",
"welcome_body": "Firefox akan menggunakan ruang ini untuk menampilkan markah, artikel, video, dan laman yang baru-baru ini dikunjungi, yang paling relevan agar Anda bisa kembali mengunjunginya dengan mudah.",
"welcome_label": "Mengidentifikasi Sorotan Anda",
@@ -3163,7 +3187,7 @@
"time_label_day": "{number} hr",
"settings_pane_button_label": "Ubahsuai laman Tab Baru Anda",
"settings_pane_header": "Preferensi Tab Baru",
- "settings_pane_body": "Pilih apa yang Anda lihat ketika Anda membuka tab baru.",
+ "settings_pane_body2": "Pilih apa yang Anda lihat di halaman ini.",
"settings_pane_search_header": "Pencarian",
"settings_pane_search_body": "Cari Web dari tab baru Anda.",
"settings_pane_topsites_header": "Situs Teratas",
@@ -3173,8 +3197,12 @@
"settings_pane_bookmarks_body": "Markah Anda dibuat di lokasi yang praktis.",
"settings_pane_visit_again_header": "Kunjungi Lagi",
"settings_pane_visit_again_body": "Firefox akan menunjukkan bagian dari riwayat penjelajahan yang mungkin ingin Anda ingat atau kunjungi lagi.",
- "settings_pane_pocketstories_header": "Cerita Utama",
- "settings_pane_pocketstories_body": "Pocket, bagian dari keluarga Mozilla, akan membantu hubungkan Anda dengan konten berkualitas tinggi yang tak dapat Anda temukan di tempat lain.",
+ "settings_pane_highlights_header": "Sorotan",
+ "settings_pane_highlights_body2": "Temukan jalan kembali ke hal menarik yang baru saja Anda kunjungi atau dimarkah.",
+ "settings_pane_highlights_options_bookmarks": "Markah",
+ "settings_pane_highlights_options_visited": "Situs Terkunjungi",
+ "settings_pane_snippets_header": "Catatan Kecil",
+ "settings_pane_snippets_body": "Baca info pendek terbaru dari Mozilla tentang Firefox, budaya internet dan beberapa meme acak.",
"settings_pane_done_button": "Selesai",
"edit_topsites_button_text": "Sunting",
"edit_topsites_button_label": "Ubahsuai bagian Situs Teratas Anda",
@@ -3197,10 +3225,10 @@
"pocket_read_more": "Topik Populer:",
"pocket_read_even_more": "Lihat Cerita Lainnya",
"pocket_feedback_header": "Yang terbaik dari Web, dikurasi lebih dari 25 juta orang.",
- "pocket_feedback_body": "Pocket, bagian dari keluarga Mozilla, akan membantu hubungkan Anda dengan konten berkualitas tinggi yang tak dapat Anda temukan di tempat lain.",
- "pocket_send_feedback": "Kirim Umpanbalik",
+ "pocket_description": "Temukan konten berkualitas tinggi yang mungkin Anda lewatkan dengan bantuan Pocket, yang sekarang menjadi bagian dari Mozilla.",
+ "highlights_empty_state": "Mulai menjelajah, dan kami akan menampilkan beberapa artikel bagus, video, dan halaman lain yang baru saja Anda kunjungi atau termarkah di sini.",
"topstories_empty_state": "Maaf Anda tercegat. Periksa lagi nanti untuk lebih banyak cerita terbaik dari {provider}. Tidak mau menunggu? Pilih topik populer untuk menemukan lebih banyak cerita hebat dari seluruh web.",
- "manual_migration_explanation": "Cobalah Firefox dengan situs dan markah kesukaan Anda dari peramban yang lain.",
+ "manual_migration_explanation2": "Coba Firefox dengan markah, riwayat, dan sandi dari peramban lain.",
"manual_migration_cancel_button": "Tidak, Terima kasih",
"manual_migration_import_button": "Impor Sekarang"
},
@@ -3488,6 +3516,7 @@
"default_label_loading": "Asali…",
"header_top_sites": "Ismal ifazen",
"header_stories": "Tiqsiɣin ifazen",
+ "header_highlights": "Asebrureq",
"header_visit_again": "Rzu tikelt-nniḍen",
"header_bookmarks": "Ticraḍ n melmi kan",
"header_recommended_by": "Iwelleh-it-id {provider}",
@@ -3519,6 +3548,8 @@
"search_web_placeholder": "Nadi di Web",
"search_settings": "Snifel iγewwaṛen n unadi",
"section_info_option": "Talɣut",
+ "section_info_send_feedback": "Azen tikti",
+ "section_info_privacy_notice": "Tasertit n tbaḍnit",
"welcome_title": "Ansuf ar yiccer amaynut",
"welcome_body": "Firefox ad iseqdec tallunt akken ad d-yesken akk ticraḍ n isebtar iwulmen, imagraden, tividyutin, akked isebtar aniɣer terziḍ melmi kan, ihi tzemreḍ ad d-uɣaleḍ ɣer-sen s wudem fessusen.",
"welcome_label": "Asulu n iferdisen tisura",
@@ -3528,7 +3559,7 @@
"time_label_day": "{number}n wussan",
"settings_pane_button_label": "Sagen asebter n yiccer-ik amaynut",
"settings_pane_header": "Ismenyifen n yiccer amaynut",
- "settings_pane_body": "Fren ayen ara twaliḍ ticki teldiḍ iccer imaynut.",
+ "settings_pane_body2": "Fren ayen ad twaliḍ deg usebter-agi.",
"settings_pane_search_header": "Nadi",
"settings_pane_search_body": "Nadi di Web seg iccer-ik amaynut.",
"settings_pane_topsites_header": "Ismal ifazen",
@@ -3538,8 +3569,12 @@
"settings_pane_bookmarks_body": "Ticraḍ yettwarnan melmi kan deg iwen n umdiq ɣef afus.",
"settings_pane_visit_again_header": "Rzu tikelt-nniḍen",
"settings_pane_visit_again_body": "Firefox ad d-yesken tukkist n umazray-ik n tunigin i tzemreḍ ad twalid tikelt-nniḍen.",
- "settings_pane_pocketstories_header": "Tiqsiɣin ifazen",
- "settings_pane_pocketstories_body": "Pocket, aɛeggal n twaxult n Mozilla, ak-d-yefk afus ad twaliḍ agbur n tɣara meqqren i tzemred ad tzegleḍ.",
+ "settings_pane_highlights_header": "Asebrureq",
+ "settings_pane_highlights_body2": "Aff abrid-ik γer wayen i tḥemmleḍ i γef terziḍ yakan neγ tcerḍeḍ-t.",
+ "settings_pane_highlights_options_bookmarks": "Ticraḍ n isebtar",
+ "settings_pane_highlights_options_visited": "Ismal yettwarzan",
+ "settings_pane_snippets_header": "Tiwzillin",
+ "settings_pane_snippets_body": "Wali issalen n Mozilla γef Firefox, adlis internet, akked issalen nniṣen sya γer da.",
"settings_pane_done_button": "Immed",
"edit_topsites_button_text": "Ẓreg",
"edit_topsites_button_label": "Sagen tigezmi n ismal ifazen",
@@ -3562,10 +3597,10 @@
"pocket_read_more": "Isental ittwasnen aṭas:",
"pocket_read_even_more": "Wali ugar n teqsiḍin",
"pocket_feedback_header": "D amezwaru n Web, ittwafren sγur ugar 25 imelyan n imdanen.",
- "pocket_feedback_body": "Pocket, aɛeggal n twaxult n Mozilla, ak-d-yefk afus ad twaliḍ agbur n tɣara meqqren i tzemred ad tzegleḍ.",
- "pocket_send_feedback": "Azen tikti",
+ "pocket_description": "S lmendad n Pocket n Mozillan wali aqbur ifazen aṭas, s ttawil-a werǧin ad tzegleḍ taγawsa.",
+ "highlights_empty_state": "Bdu tuniginn sakin nekkni ad k-n-sken imagraden, tividyutin, akked isebtar nniḍen i γef terziḍ yakan neγ i tceṛḍeḍ dagi.",
"topstories_empty_state": "Ulac wiyaḍ. Uɣal-d ticki s wugar n imagraden seg {provider}. Ur tebɣiḍ ara ad terǧuḍ? Fren asentel seg wid yettwasnen akken ad twaliḍ imagraden yelhan di Web.",
- "manual_migration_explanation": "Ɛreḍ Firefox s ismal-ik inurifen akked ticraḍ seg iminig-nniḍen.",
+ "manual_migration_explanation2": "Σreḍ Firefox s ticṛaḍ n isebtar, amazray akked awalen uffiren sγur ilinigen nniḍen.",
"manual_migration_cancel_button": "Ala, tanemmirt",
"manual_migration_import_button": "Kter tura"
},
@@ -3628,9 +3663,11 @@
"settings_pane_visit_again_header": "Қайтадан шолу",
"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": "Топ сайттар санатын баптау",
@@ -3653,7 +3690,10 @@
"pocket_read_more": "Әйгілі тақырыптар:",
"pocket_read_even_more": "Көбірек хикаяларды қарау",
"pocket_feedback_header": "Интернеттің ең жақсысы, 25 миллион адаммен танылған.",
+ "pocket_description": "Ол болмаса, сіз жіберіп алатын мүмкіндігі бар жоғары сапалы құраманы Pocket көмегімен табыңыз, ол енді Mozilla-ның бөлігі болып табылады.",
+ "highlights_empty_state": "Шолуды бастаңыз, сіз жақында шолған немесе бетбелгілерге қосқан тамаша мақалалар, видеолар немесе басқа парақтардың кейбіреулері осында көрсетіледі.",
"topstories_empty_state": "Дайын. {provider} ұсынған көбірек мақалаларды алу үшін кейінірек тексеріңіз. Күте алмайсыз ба? Интернеттен көбірек тамаша мақалаларды алу үшін әйгілі теманы таңдаңыз.",
+ "manual_migration_explanation2": "Firefox-ты басқа браузер бетбелгілері, тарихы және парольдерімен қолданып көріңіз.",
"manual_migration_cancel_button": "Жоқ, рахмет",
"manual_migration_import_button": "Қазір импорттау"
},
@@ -4376,6 +4416,7 @@
"pocket_read_more": "Populære emner:",
"pocket_read_even_more": "Vis flere saker",
"pocket_feedback_header": "Det beste av nettet, kurert av over 25 millioner mennesker.",
+ "pocket_description": "Oppdag høykvalitetsinnhold som du ellers ville gå glipp av, ved hjelp av Pocket, som nå er en del av Mozilla.",
"highlights_empty_state": "Begynn å surfe, og vi viser noen av de beste artiklene, videoer og andre sider du nylig har besøkt eller bokmerket her.",
"topstories_empty_state": "Du har tatt igjen. Kom tilbake senere for flere topphistorier fra {provider}. Kan du ikke vente? Velg et populært emne for å finne flere gode artikler fra hele Internett.",
"manual_migration_explanation2": "Prøv Firefox med bokmerkene, historikk og passord fra en annen nettleser.",
@@ -4991,7 +5032,7 @@
"menu_action_email_link": "Trametter la colliaziun per e-mail…",
"menu_action_open_new_window": "Avrir en ina nova fanestra",
"menu_action_open_private_window": "Avrir en ina nova fanestra privata",
- "menu_action_dismiss": "Serrar",
+ "menu_action_dismiss": "Sbittar",
"menu_action_delete": "Stizzar da la cronologia",
"menu_action_pin": "Fixar",
"menu_action_unpin": "Betg pli fixar",
@@ -5371,6 +5412,8 @@
"settings_pane_highlights_body2": "Najdite pot nazaj do zanimivih strani, ki ste jih nedavno obiskali ali dodali med zaznamke.",
"settings_pane_highlights_options_bookmarks": "Zaznamki",
"settings_pane_highlights_options_visited": "Obiskane strani",
+ "settings_pane_snippets_header": "Izrezki",
+ "settings_pane_snippets_body": "Spremljajte kratke novice o Mozilli in Firefoxu, kulturi interneta in si občasno oglejte kak meme.",
"settings_pane_done_button": "Končano",
"edit_topsites_button_text": "Uredi",
"edit_topsites_button_label": "Prilagodite odsek Glavne strani",
@@ -5384,8 +5427,8 @@
"edit_topsites_add_button": "Dodaj",
"topsites_form_add_header": "Nova glavna stran",
"topsites_form_edit_header": "Uredi glavno stran",
- "topsites_form_title_placeholder": "Vnesite naslov",
- "topsites_form_url_placeholder": "Vnesite ali prilepite URL",
+ "topsites_form_title_placeholder": "Vnesite ime",
+ "topsites_form_url_placeholder": "Vnesite ali prilepite spletni naslov",
"topsites_form_add_button": "Dodaj",
"topsites_form_save_button": "Shrani",
"topsites_form_cancel_button": "Prekliči",
@@ -5725,6 +5768,7 @@
"default_label_loading": "వస్తోంది…",
"header_top_sites": "మేటి సైట్లు",
"header_stories": "ముఖ్య కథనాలు",
+ "header_highlights": "విశేషాలు",
"header_visit_again": "మళ్లీ సందర్శించండి",
"header_bookmarks": "ఇటీవలి ఇష్టాంశములు",
"header_recommended_by": "{provider}చే సిఫార్సు చేయబడినది",
@@ -5756,6 +5800,8 @@
"search_web_placeholder": "జాలంలో వెతకండి",
"search_settings": "శోధన అమరికలు మార్చు",
"section_info_option": "సమాచారం",
+ "section_info_send_feedback": "అభిప్రాయాన్ని పంపండి",
+ "section_info_privacy_notice": "గోప్యతా విధానం",
"welcome_title": "కొత్త ట్యాబుకు స్వాగతం",
"welcome_body": "సముచితమైన మీ ఇష్టాంశాలను, వ్యాసాలను, వీడియోలను, ఇంకా మీరు ఇటీవలే చూసిన పేజీలను మీకు తేలిగ్గా అందుబాటులో ఉంచేందుకు Firefox ఈ జాగాని వాడుకుంటుంది.",
"welcome_label": "మీ ముఖ్యాంశాలను గుర్తిస్తున్నది",
@@ -5765,7 +5811,7 @@
"time_label_day": "{number}రో",
"settings_pane_button_label": "మీ కొత్త ట్యాబు పేజీని మలచుకోండి",
"settings_pane_header": "కొత్త ట్యాబు అభిరుచులు",
- "settings_pane_body": "మీరు కొత్త ట్యాబు తెరిచినప్పుడు ఏం చూడాలో ఎంచుకోండి.",
+ "settings_pane_body2": "మీరు ఈ పేజీలో చూసేదాన్ని ఎంచుకోండి.",
"settings_pane_search_header": "వెతకడం",
"settings_pane_search_body": "కొత్త ట్యాబు నుండే జాలంలో వెతకండి.",
"settings_pane_topsites_header": "మేటి సైట్లు",
@@ -5775,8 +5821,9 @@
"settings_pane_bookmarks_body": "ఒక సులభ స్థానంలో మీ క్రొత్తగా సృష్టించిన బుక్మార్క్లు.",
"settings_pane_visit_again_header": "మళ్లీ సందర్శించండి",
"settings_pane_visit_again_body": "మీరు బ్రౌజింగ్ చరిత్రలో గుర్తుంచుకోవాల్సిన లేదా తిరిగి పొందవలసిన భాగాలను చూపిస్తుంది.",
- "settings_pane_pocketstories_header": "ముఖ్య కథనాలు",
- "settings_pane_pocketstories_body": "Mozilla కుటుంబం యొక్క Pocket, మీరు కనుగొనలేకపోయే అధిక-నాణ్యత విషయముకి మిమ్మల్ని అనుసంధానించడానికి సహాయపడుతుంది.",
+ "settings_pane_highlights_header": "విశేషాలు",
+ "settings_pane_highlights_options_bookmarks": "ఇష్టాంశాలు",
+ "settings_pane_highlights_options_visited": "చూసిన సైటులు",
"settings_pane_done_button": "పూర్తయింది",
"edit_topsites_button_text": "మార్చు",
"edit_topsites_button_label": "మీ మేటి సైట్ల విభాగాన్ని మలచుకోండి",
@@ -5799,10 +5846,7 @@
"pocket_read_more": "ప్రముఖ అంశాలు:",
"pocket_read_even_more": "మరిన్ని కథలను వీక్షించండి",
"pocket_feedback_header": "వెబ్లో అత్యుత్తమమైనది, 25 మిలియన్లకు పైగా ప్రజలు పర్యవేక్షించినవి.",
- "pocket_feedback_body": "Mozilla కుటుంబం యొక్క Pocket, మీరు కనుగొనలేకపోయే అధిక-నాణ్యత విషయముకి మిమ్మల్ని అనుసంధానించడానికి సహాయపడుతుంది.",
- "pocket_send_feedback": "అభిప్రాయాన్ని పంపండి",
"topstories_empty_state": "మీరు పట్టుబడ్డారు. {provider} నుండి మరింత అగ్ర కథనాల కోసం తరువాత తనిఖీ చేయండి. వేచి ఉండలేరా? జాలములోని అంతటి నుండి మరింత గొప్ప కథనాలను కనుగొనడానికి ప్రసిద్ధ అంశం ఎంచుకోండి.",
- "manual_migration_explanation": "మరొక విహరణి నుండి మీకు ఇష్టమైన సైట్లు మరియు ఇష్టంశాలతో Firefox ను ప్రయత్నించండి.",
"manual_migration_cancel_button": "అడిగినందుకు ధన్యవాదాలు, వద్దు",
"manual_migration_import_button": "ఇప్పుడే దిగుమతి చేయండి"
},
@@ -5865,6 +5909,7 @@
"settings_pane_visit_again_header": "เยี่ยมชมอีกครั้ง",
"settings_pane_highlights_header": "รายการเด่น",
"settings_pane_highlights_options_bookmarks": "ที่คั่นหน้า",
+ "settings_pane_highlights_options_visited": "ไซต์ที่เยี่ยมชมแล้ว",
"settings_pane_done_button": "เสร็จสิ้น",
"edit_topsites_button_text": "แก้ไข",
"edit_topsites_button_label": "ปรับแต่งส่วนไซต์เด่นของคุณ",
@@ -5887,6 +5932,7 @@
"pocket_read_more": "หัวข้อยอดนิยม:",
"pocket_read_even_more": "ดูเรื่องราวเพิ่มเติม",
"pocket_feedback_header": "ที่สุดของเว็บ จัดรายการโดยผู้คนกว่า 25 ล้านคน",
+ "manual_migration_explanation2": "ลอง Firefox ด้วยที่คั่นหน้า, ประวัติ และรหัสผ่านจากเบราว์เซอร์อื่น",
"manual_migration_cancel_button": "ไม่ ขอบคุณ",
"manual_migration_import_button": "นำเข้าตอนนี้"
},
@@ -6294,8 +6340,8 @@
"menu_action_remove_bookmark": "移除书签",
"menu_action_copy_address": "复制地址",
"menu_action_email_link": "用邮件发送链接…",
- "menu_action_open_new_window": "在新窗口中打开",
- "menu_action_open_private_window": "在新的隐私窗口中打开",
+ "menu_action_open_new_window": "新建窗口打开",
+ "menu_action_open_private_window": "新建隐私浏览窗口打开",
"menu_action_dismiss": "隐藏",
"menu_action_delete": "从历史记录中删除",
"menu_action_pin": "固定",
diff --git a/browser/extensions/activity-stream/install.rdf.in b/browser/extensions/activity-stream/install.rdf.in
index b49a47d9af44..0190ca2d90ea 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.08.0882-3dbf720c
+ 2017.09.11.1306-373d9fc
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/ActivityStream.jsm b/browser/extensions/activity-stream/lib/ActivityStream.jsm
index 442db4980079..3a32d86a6f9d 100644
--- a/browser/extensions/activity-stream/lib/ActivityStream.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStream.jsm
@@ -77,6 +77,10 @@ const PREFS_CONFIG = new Map([
title: "Number of days to show the manual migration message",
value: 4
}],
+ ["prerender", {
+ title: "Use the prerendered version of activity-stream.html. This is set automatically by PrefsFeed.jsm.",
+ value: true
+ }],
["showSearch", {
title: "Show the Search bar on the New Tab page",
value: true
diff --git a/browser/extensions/activity-stream/lib/HighlightsFeed.jsm b/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
index 139a905b2ad7..5c846dd77631 100644
--- a/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
+++ b/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
@@ -10,6 +10,7 @@ const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.j
const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {});
const {SectionsManager} = Cu.import("resource://activity-stream/lib/SectionsManager.jsm", {});
+const {TOP_SITES_SHOWMORE_LENGTH} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
@@ -17,6 +18,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
const HIGHLIGHTS_MAX_LENGTH = 9;
const HIGHLIGHTS_UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
+const MANY_EXTRA_LENGTH = HIGHLIGHTS_MAX_LENGTH * 5 + TOP_SITES_SHOWMORE_LENGTH;
const SECTION_ID = "highlights";
this.HighlightsFeed = class HighlightsFeed {
@@ -43,18 +45,39 @@ this.HighlightsFeed = class HighlightsFeed {
}
async fetchHighlights(broadcast = false) {
- this.highlights = await NewTabUtils.activityStreamLinks.getHighlights();
- for (let highlight of this.highlights) {
- highlight.hostname = shortURL(Object.assign({}, highlight, {url: highlight.url}));
- highlight.image = highlight.preview_image_url;
- if (highlight.bookmarkGuid) {
- highlight.type = "bookmark";
- }
- }
+ // Request more than the expected length to allow for items being removed by
+ // deduping against Top Sites or multiple history from the same domain, etc.
+ const manyPages = await NewTabUtils.activityStreamLinks.getHighlights({numItems: MANY_EXTRA_LENGTH});
// Remove any Highlights that are in Top Sites already
- const deduped = this.dedupe.group(this.store.getState().TopSites.rows, this.highlights);
- this.highlights = deduped[1];
+ const deduped = this.dedupe.group(this.store.getState().TopSites.rows, manyPages)[1];
+
+ // 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;
+ }
+
+ // We want the page, so update various fields for UI
+ Object.assign(page, {
+ hostname,
+ image: page.preview_image_url,
+ type: page.bookmarkGuid ? "bookmark" : page.type
+ });
+
+ // Add the "bookmark" or not-skipped "history"
+ this.highlights.push(page);
+ hosts.add(hostname);
+
+ // 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();
diff --git a/browser/extensions/activity-stream/lib/NewTabInit.jsm b/browser/extensions/activity-stream/lib/NewTabInit.jsm
index 850f5a4d57dd..fcf5d34a6a39 100644
--- a/browser/extensions/activity-stream/lib/NewTabInit.jsm
+++ b/browser/extensions/activity-stream/lib/NewTabInit.jsm
@@ -12,12 +12,30 @@ const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-str
* newly opened tabs.
*/
this.NewTabInit = class NewTabInit {
+ constructor() {
+ this._queue = new Set();
+ }
+ reply(target) {
+ const action = {type: at.NEW_TAB_INITIAL_STATE, data: this.store.getState()};
+ this.store.dispatch(ac.SendToContent(action, target));
+ }
onAction(action) {
- let newAction;
switch (action.type) {
- case at.NEW_TAB_LOAD:
- newAction = {type: at.NEW_TAB_INITIAL_STATE, data: this.store.getState()};
- this.store.dispatch(ac.SendToContent(newAction, action.meta.fromTarget));
+ case at.NEW_TAB_STATE_REQUEST:
+ // If localization hasn't been loaded yet, we should wait for it.
+ if (!this.store.getState().App.strings) {
+ this._queue.add(action.meta.fromTarget);
+ return;
+ }
+ this.reply(action.meta.fromTarget);
+ break;
+ case at.LOCALE_UPDATED:
+ // If the queue is full because we were waiting for strings,
+ // dispatch them now.
+ if (this._queue.size > 0 && this.store.getState().App.strings) {
+ this._queue.forEach(target => this.reply(target));
+ this._queue.clear();
+ }
break;
}
}
diff --git a/browser/extensions/activity-stream/lib/PrefsFeed.jsm b/browser/extensions/activity-stream/lib/PrefsFeed.jsm
index 640bd9c231d5..fe0c74111f40 100644
--- a/browser/extensions/activity-stream/lib/PrefsFeed.jsm
+++ b/browser/extensions/activity-stream/lib/PrefsFeed.jsm
@@ -7,17 +7,39 @@ 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", {});
this.PrefsFeed = class PrefsFeed {
constructor(prefMap) {
this._prefMap = prefMap;
this._prefs = new Prefs();
}
+
+ // 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);
+ }
+
+ _checkPrerender(name) {
+ if (PrerenderData.invalidatingPrefs.includes(name)) {
+ this._setPrerenderPref();
+ }
+ }
+
onPrefChanged(name, value) {
if (this._prefMap.has(name)) {
this.store.dispatch(ac.BroadcastToContent({type: at.PREF_CHANGED, data: {name, value}}));
}
+ this._checkPrerender(name, value);
}
+
init() {
this._prefs.observeBranch(this);
@@ -29,6 +51,8 @@ this.PrefsFeed = class PrefsFeed {
// Set the initial state of all prefs in redux
this.store.dispatch(ac.BroadcastToContent({type: at.PREFS_INITIAL_VALUES, data: values}));
+
+ this._setPrerenderPref();
}
removeListeners() {
this._prefs.ignoreBranch(this);
diff --git a/browser/extensions/activity-stream/lib/SectionsManager.jsm b/browser/extensions/activity-stream/lib/SectionsManager.jsm
index 83eb391fba14..e28e20866b1c 100644
--- a/browser/extensions/activity-stream/lib/SectionsManager.jsm
+++ b/browser/extensions/activity-stream/lib/SectionsManager.jsm
@@ -48,7 +48,7 @@ const BUILT_IN_SECTIONS = {
icon: "highlights",
title: {id: "header_highlights"},
maxRows: 3,
- availableContextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
+ availableContextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"],
emptyState: {
message: {id: "highlights_empty_state"},
icon: "highlights"
diff --git a/browser/extensions/activity-stream/lib/SnippetsFeed.jsm b/browser/extensions/activity-stream/lib/SnippetsFeed.jsm
index 18984da9c26e..aba53e9156a2 100644
--- a/browser/extensions/activity-stream/lib/SnippetsFeed.jsm
+++ b/browser/extensions/activity-stream/lib/SnippetsFeed.jsm
@@ -121,7 +121,7 @@ this.SnippetsFeed = class SnippetsFeed {
Services.prefs.removeObserver(TELEMETRY_PREF, this._refresh);
Services.prefs.removeObserver(FXA_USERNAME_PREF, this._refresh);
Services.obs.removeObserver(this, SEARCH_ENGINE_OBSERVER_TOPIC);
- this.store.dispatch({type: at.SNIPPETS_RESET});
+ this.store.dispatch(ac.BroadcastToContent({type: at.SNIPPETS_RESET}));
}
showFirefoxAccounts(browser) {
diff --git a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
index fdaba48a3098..8b3105a2587b 100644
--- a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
@@ -21,6 +21,7 @@ const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
const DEFAULT_SITES_PREF = "default.sites";
const DEFAULT_TOP_SITES = [];
const FRECENCY_THRESHOLD = 100; // 1 visit (skip first-run/one-time pages)
+const MIN_FAVICON_SIZE = 96;
this.TopSitesFeed = class TopSitesFeed {
constructor() {
@@ -95,13 +96,13 @@ this.TopSitesFeed = class TopSitesFeed {
}
}
- // Now, get a tippy top icon or screenshot for every item
+ // Now, get a tippy top icon, a rich icon, or screenshot for every item
for (let link of links) {
if (!link) { continue; }
- // Check for tippy top icon.
+ // Check for tippy top icon or a rich icon.
link = this._tippyTopProvider.processSite(link);
- if (link.tippyTopIcon) { continue; }
+ if (link.tippyTopIcon || link.faviconSize >= MIN_FAVICON_SIZE) { continue; }
// If no tippy top, then we get a screenshot.
if (currentScreenshots[link.url]) {
diff --git a/browser/extensions/activity-stream/test/unit/activity-stream-prerender.test.jsx b/browser/extensions/activity-stream/test/unit/activity-stream-prerender.test.jsx
new file mode 100644
index 000000000000..3bc399e5dd6e
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/activity-stream-prerender.test.jsx
@@ -0,0 +1,70 @@
+const prerender = require("content-src/activity-stream-prerender");
+const {prerenderStore} = prerender;
+const {PrerenderData} = require("common/PrerenderData.jsm");
+
+describe("prerenderStore", () => {
+ it("should create a store", () => {
+ const store = prerenderStore();
+
+ assert.isFunction(store.getState);
+ });
+ it("should start uninitialized", () => {
+ const store = prerenderStore();
+
+ const state = store.getState();
+ assert.equal(state.App.initialized, false);
+ });
+ it("should set the right locale, strings, and text direction", () => {
+ const strings = {foo: "foo"};
+
+ const store = prerenderStore("en-FOO", strings);
+
+ const state = store.getState();
+ assert.equal(state.App.locale, "en-FOO");
+ assert.equal(state.App.strings, strings);
+ assert.equal(state.App.textDirection, "ltr");
+ });
+ it("should add the right initial prefs", () => {
+ const store = prerenderStore();
+
+ const state = store.getState();
+ assert.equal(state.Prefs.values, PrerenderData.initialPrefs);
+ });
+ it("should add TopStories as the first section", () => {
+ const store = prerenderStore();
+
+ const state = store.getState();
+ // TopStories
+ const firstSection = state.Sections[0];
+ assert.equal(firstSection.id, "topstories");
+ // it should start uninitialized
+ assert.equal(firstSection.initialized, false);
+ });
+});
+
+describe("prerender", () => {
+ it("should set the locale and get the right strings of whatever is passed in", () => {
+ const {store} = prerender("en-US");
+
+ const state = store.getState();
+ assert.equal(state.App.locale, "en-US");
+ assert.equal(state.App.strings.newtab_page_title, "New Tab");
+ });
+ it("should throw if an unknown locale is passed in", () => {
+ assert.throws(() => prerender("en-FOO"));
+ });
+ it("should set the locale to en-PRERENDER and have empty strings if no locale is passed in", () => {
+ const {store} = prerender();
+
+ const state = store.getState();
+ assert.equal(state.App.locale, "en-PRERENDER");
+ assert.equal(state.App.strings.newtab_page_title, " ");
+ });
+ // # TODO: Remove when #3370 is resolved.
+ it("should render a real English string for search_web_placeholder", () => {
+ const {store} = prerender();
+
+ const state = store.getState();
+ assert.equal(state.App.strings.search_web_placeholder, "Search the Web");
+ });
+});
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 273db903503c..1f100344cb5d 100644
--- a/browser/extensions/activity-stream/test/unit/common/Reducers.test.js
+++ b/browser/extensions/activity-stream/test/unit/common/Reducers.test.js
@@ -26,12 +26,20 @@ describe("Reducers", () => {
const nextState = App(undefined, {type: at.LOCALE_UPDATED});
assert.equal(nextState, INITIAL_STATE.App);
});
- it("should set locale, strings on LOCALE_UPDATE", () => {
+ it("should set locale, strings and text direction on LOCALE_UPDATE", () => {
const strings = {};
const action = {type: "LOCALE_UPDATED", data: {locale: "zh-CN", strings}};
const nextState = App(undefined, action);
assert.propertyVal(nextState, "locale", "zh-CN");
assert.propertyVal(nextState, "strings", strings);
+ assert.propertyVal(nextState, "textDirection", "ltr");
+ });
+ it("should set rtl text direction for RTL locales", () => {
+ const action = {type: "LOCALE_UPDATED", data: {locale: "ar"}};
+
+ const nextState = App(undefined, action);
+
+ assert.propertyVal(nextState, "textDirection", "rtl");
});
});
describe("TopSites", () => {
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 d20aff9793e9..cb0e6156e53c 100644
--- a/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js
@@ -30,7 +30,7 @@ describe("Top Sites Feed", () => {
updateSection: sinon.spy(),
sections: new Map([["highlights", {}]])
};
- shortURLStub = sinon.stub().callsFake(site => site.url);
+ 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},
@@ -72,7 +72,7 @@ describe("Top Sites Feed", () => {
it("should add hostname and image to each link", async () => {
links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
await feed.fetchHighlights();
- assert.equal(feed.highlights[0].hostname, links[0].url);
+ assert.equal(feed.highlights[0].hostname, "mozilla.org");
assert.equal(feed.highlights[0].image, links[0].preview_image_url);
});
it("should not include any links already in Top Sites", async () => {
@@ -86,6 +86,30 @@ describe("Top Sites Feed", () => {
assert.equal(feed.highlights.length, 1);
assert.deepEqual(feed.highlights[0], links[0]);
});
+ it("should not include history of same hostname as a bookmark", async () => {
+ links = [
+ {url: "https://site.com/bookmark", type: "bookmark"},
+ {url: "https://site.com/history", type: "history"}
+ ];
+
+ await feed.fetchHighlights();
+
+ assert.equal(feed.highlights.length, 1);
+ assert.deepEqual(feed.highlights[0], links[0]);
+ });
+ it("should take the first history of a hostname", async () => {
+ links = [
+ {url: "https://site.com/first", type: "history"},
+ {url: "https://site.com/second", type: "history"},
+ {url: "https://other", type: "history"}
+ ];
+
+ await feed.fetchHighlights();
+
+ assert.equal(feed.highlights.length, 2);
+ 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();
diff --git a/browser/extensions/activity-stream/test/unit/lib/NewTabInit.test.js b/browser/extensions/activity-stream/test/unit/lib/NewTabInit.test.js
new file mode 100644
index 000000000000..ec0531d3316f
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/NewTabInit.test.js
@@ -0,0 +1,62 @@
+const {NewTabInit} = require("lib/NewTabInit.jsm");
+const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
+
+describe("NewTabInit", () => {
+ let instance;
+ let store;
+ let STATE;
+ beforeEach(() => {
+ STATE = {};
+ store = {getState: sinon.stub().returns(STATE), dispatch: sinon.stub()};
+ instance = new NewTabInit();
+ instance.store = store;
+ });
+ it("should reply with a copy of the state immediately if localization is ready", () => {
+ STATE.App = {strings: {}};
+
+ instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, 123));
+
+ const resp = ac.SendToContent({type: at.NEW_TAB_INITIAL_STATE, data: STATE}, 123);
+ assert.calledWith(store.dispatch, resp);
+ });
+ it("should not reply immediately if localization is not ready", () => {
+ STATE.App = {strings: null};
+
+ instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, 123));
+
+ assert.notCalled(store.dispatch);
+ });
+ it("should dispatch responses for queued targets when LOCALE_UPDATED is received", () => {
+ STATE.App = {strings: null};
+
+ // Send requests before strings are ready
+ instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "foo"));
+ instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "bar"));
+ instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "baz"));
+ assert.notCalled(store.dispatch);
+
+ // Update strings
+ STATE.App = {strings: {}};
+ instance.onAction({type: at.LOCALE_UPDATED});
+
+ assert.calledThrice(store.dispatch);
+ const action = {type: at.NEW_TAB_INITIAL_STATE, data: STATE};
+ assert.calledWith(store.dispatch, ac.SendToContent(action, "foo"));
+ assert.calledWith(store.dispatch, ac.SendToContent(action, "bar"));
+ assert.calledWith(store.dispatch, ac.SendToContent(action, "baz"));
+ });
+ it("should clear targets from the queue once they have been sent", () => {
+ STATE.App = {strings: null};
+ instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "foo"));
+ instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "bar"));
+ instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "baz"));
+
+ STATE.App = {strings: {}};
+ instance.onAction({type: at.LOCALE_UPDATED});
+ assert.calledThrice(store.dispatch);
+
+ store.dispatch.reset();
+ instance.onAction({type: at.LOCALE_UPDATED});
+ assert.notCalled(store.dispatch);
+ });
+});
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 d47a7477e392..eff02248a333 100644
--- a/browser/extensions/activity-stream/test/unit/lib/PrefsFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/PrefsFeed.test.js
@@ -1,16 +1,20 @@
const {PrefsFeed} = require("lib/PrefsFeed.jsm");
const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
+const {PrerenderData} = require("common/PrerenderData.jsm");
+const {initialPrefs} = PrerenderData;
-const FAKE_PREFS = new Map([["foo", {value: 1}], ["bar", {value: 2}]]);
+const PRERENDER_PREF_NAME = "prerender";
describe("PrefsFeed", () => {
let feed;
+ let FAKE_PREFS;
beforeEach(() => {
+ FAKE_PREFS = new Map([["foo", 1], ["bar", 2]]);
feed = new PrefsFeed(FAKE_PREFS);
feed.store = {dispatch: sinon.spy()};
feed._prefs = {
- get: sinon.spy(item => FAKE_PREFS.get(item).value),
- set: sinon.spy(),
+ get: sinon.spy(item => FAKE_PREFS.get(item)),
+ set: sinon.spy((name, value) => FAKE_PREFS.set(name, value)),
observe: sinon.spy(),
observeBranch: sinon.spy(),
ignore: sinon.spy(),
@@ -41,4 +45,42 @@ describe("PrefsFeed", () => {
feed.onPrefChanged("foo", 2);
assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({type: at.PREF_CHANGED, data: {name: "foo", value: 2}}));
});
+ describe("INIT prerendering", () => {
+ it("should set a prerender pref on init", () => {
+ feed.onAction({type: at.INIT});
+ assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME);
+ });
+ it("should set prerender pref to true if prefs match initial values", () => {
+ Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
+ feed.onAction({type: at.INIT});
+ assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, true);
+ });
+ 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);
+ feed.onAction({type: at.INIT});
+ assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, false);
+ });
+ });
+ describe("onPrefChanged prerendering", () => {
+ it("should not change the prerender pref if the pref is not included in invalidatingPrefs", () => {
+ feed.onPrefChanged("foo123", true);
+ assert.notCalled(feed._prefs.set);
+ });
+ 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);
+ 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);
+
+ feed._prefs.set("feeds.section.topstories", true);
+ feed.onPrefChanged("feeds.section.topstories", true);
+ assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, true);
+ });
+ });
});
diff --git a/browser/extensions/activity-stream/test/unit/lib/SnippetsFeed.test.js b/browser/extensions/activity-stream/test/unit/lib/SnippetsFeed.test.js
index 6d1887775a10..16c302135970 100644
--- a/browser/extensions/activity-stream/test/unit/lib/SnippetsFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/SnippetsFeed.test.js
@@ -1,5 +1,5 @@
const {SnippetsFeed} = require("lib/SnippetsFeed.jsm");
-const {actionTypes: at} = require("common/Actions.jsm");
+const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm");
const {GlobalOverrider} = require("test/unit/utils");
const WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000;
@@ -78,13 +78,13 @@ describe("SnippetsFeed", () => {
feed.onAction({type: at.UNINIT});
assert.calledOnce(feed.uninit);
});
- it("should dispatch a SNIPPETS_RESET on uninit", () => {
+ it("should broadcast a SNIPPETS_RESET on uninit", () => {
const feed = new SnippetsFeed();
feed.store = {dispatch: sandbox.stub()};
feed.uninit();
- assert.calledWith(feed.store.dispatch, {type: at.SNIPPETS_RESET});
+ assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({type: at.SNIPPETS_RESET}));
});
it("should dispatch an update event when the Search observer is called", async () => {
const feed = new SnippetsFeed();
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 329520811967..05256a890e34 100644
--- a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
@@ -295,6 +295,22 @@ describe("Top Sites Feed", () => {
assert.calledOnce(feed.store.dispatch);
assert.notCalled(feed.getScreenshot);
});
+ it("should skip getting screenshot if there is an icon of size greater than 96x96 and no tippy top", async () => {
+ sandbox.stub(feed, "getScreenshot");
+ feed.getLinksWithDefaults = () => [{
+ url: "foo.com",
+ favicon: "data:foo",
+ faviconSize: 196
+ }];
+ feed._tippyTopProvider.processSite = site => {
+ site.tippyTopIcon = null;
+ site.backgroundColor = null;
+ return site;
+ };
+ await feed.refresh(action);
+ assert.calledOnce(feed.store.dispatch);
+ assert.notCalled(feed.getScreenshot);
+ });
});
describe("getScreenshot", () => {
it("should call Screenshots.getScreenshotForURL with the right url", async () => {
diff --git a/browser/extensions/activity-stream/test/unit/lib/init-store.test.js b/browser/extensions/activity-stream/test/unit/lib/init-store.test.js
index 4f01e6718e22..f887619fe948 100644
--- a/browser/extensions/activity-stream/test/unit/lib/init-store.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/init-store.test.js
@@ -1,6 +1,7 @@
const initStore = require("content-src/lib/init-store");
+const {MERGE_STORE_ACTION, rehydrationMiddleware} = initStore;
const {GlobalOverrider, addNumberReducer} = require("test/unit/utils");
-const {actionCreators: ac} = require("common/Actions.jsm");
+const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm");
describe("initStore", () => {
let globals;
@@ -16,20 +17,33 @@ describe("initStore", () => {
assert.ok(store);
assert.property(store.getState(), "number");
});
- it("should add a listener for incoming actions", () => {
+ it("should add a listener that dispatches actions", () => {
assert.calledWith(global.addMessageListener, initStore.INCOMING_MESSAGE_NAME);
- const callback = global.addMessageListener.firstCall.args[1];
+ const listener = global.addMessageListener.firstCall.args[1];
globals.sandbox.spy(store, "dispatch");
const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: "FOO"}};
- callback(message);
+
+ listener(message);
+
assert.calledWith(store.dispatch, message.data);
});
+ it("should not throw if addMessageListener is not defined", () => {
+ // Note: this is being set/restored by GlobalOverrider
+ delete global.addMessageListener;
+
+ assert.doesNotThrow(() => initStore({number: addNumberReducer}));
+ });
+ it("should initialize with an initial state if provided as the second argument", () => {
+ store = initStore({number: addNumberReducer}, {number: 42});
+
+ assert.equal(store.getState().number, 42);
+ });
it("should log errors from failed messages", () => {
const callback = global.addMessageListener.firstCall.args[1];
globals.sandbox.stub(global.console, "error");
globals.sandbox.stub(store, "dispatch").throws(Error("failed"));
- const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: "FOO"}};
+ const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: MERGE_STORE_ACTION}};
callback(message);
assert.calledOnce(global.console.error);
@@ -38,13 +52,60 @@ describe("initStore", () => {
store.dispatch({type: initStore.MERGE_STORE_ACTION, data: {number: 42}});
assert.deepEqual(store.getState(), {number: 42});
});
- it("should send out SendToMain ations", () => {
+ it("should send out SendToMain actions", () => {
const action = ac.SendToMain({type: "FOO"});
store.dispatch(action);
assert.calledWith(global.sendAsyncMessage, initStore.OUTGOING_MESSAGE_NAME, action);
});
- it("should not send out other types of ations", () => {
+ it("should not send out other types of actions", () => {
store.dispatch({type: "FOO"});
assert.notCalled(global.sendAsyncMessage);
});
+ describe("rehydrationMiddleware", () => {
+ it("should allow NEW_TAB_STATE_REQUEST to go through", () => {
+ const action = ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST});
+ const next = sinon.spy();
+ rehydrationMiddleware(store)(next)(action);
+ assert.calledWith(next, action);
+ });
+ it("should dispatch an additional NEW_TAB_STATE_REQUEST if INIT was received after a request", () => {
+ const requestAction = ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST});
+ const next = sinon.spy();
+
+ rehydrationMiddleware(store)(next)(requestAction);
+
+ next.reset();
+ rehydrationMiddleware(store)(next)({type: at.INIT});
+ assert.calledWith(next, requestAction);
+ });
+ it("should allow MERGE_STORE_ACTION to go through", () => {
+ const action = {type: MERGE_STORE_ACTION};
+ const next = sinon.spy();
+ rehydrationMiddleware(store)(next)(action);
+ assert.calledWith(next, action);
+ });
+ it("should not allow actions from main to go through before MERGE_STORE_ACTION was received", () => {
+ const next = sinon.spy();
+
+ rehydrationMiddleware(store)(next)(ac.BroadcastToContent({type: "FOO"}));
+ rehydrationMiddleware(store)(next)(ac.SendToContent({type: "FOO"}, 123));
+
+ assert.notCalled(next);
+ });
+ it("should allow all local actions to go through", () => {
+ const action = {type: "FOO"};
+ const next = sinon.spy();
+ rehydrationMiddleware(store)(next)(action);
+ assert.calledWith(next, action);
+ });
+ it("should allow actions from main to go through after MERGE_STORE_ACTION has been received", () => {
+ const next = sinon.spy();
+ rehydrationMiddleware(store)(next)({type: MERGE_STORE_ACTION});
+ next.reset();
+
+ const action = ac.SendToContent({type: "FOO"}, 123);
+ rehydrationMiddleware(store)(next)(action);
+ assert.calledWith(next, action);
+ });
+ });
});