Bug 1435419 - Add webextension actions, add/save consistency and bug fixes to Activity Stream. r=k88hudson

MozReview-Commit-ID: AXayKIY4HfZ

--HG--
extra : rebase_source : 08a54c3930df122615503f407f6e42bee1070c61
This commit is contained in:
Ed Lee 2018-02-02 14:09:17 -08:00
parent b15f168038
commit 11eea41e9c
73 changed files with 717 additions and 545 deletions

View File

@ -146,10 +146,14 @@ function onBrowserReady() {
if (rows <= 0) {
Services.prefs.setBoolPref("browser.newtabpage.activity-stream.showTopSites", false);
} else {
// Assume we want a full row (6 sites per row)
Services.prefs.setIntPref("browser.newtabpage.activity-stream.topSitesCount", rows * 6);
Services.prefs.setIntPref("browser.newtabpage.activity-stream.topSitesRows", rows);
}
});
// Old activity stream topSitesCount pref showed 6 per row
migratePref("browser.newtabpage.activity-stream.topSitesCount", count => {
Services.prefs.setIntPref("browser.newtabpage.activity-stream.topSitesRows", Math.ceil(count / 6));
});
}
/**

View File

@ -85,7 +85,9 @@ for (const type of [
"TOP_SITES_PIN",
"TOP_SITES_UNPIN",
"TOP_SITES_UPDATED",
"UNINIT"
"UNINIT",
"WEBEXT_CLICK",
"WEBEXT_DISMISS"
]) {
actionTypes[type] = type;
}
@ -99,7 +101,7 @@ function _RouteMessage(action, options) {
}
// For each of these fields, if they are passed as an option,
// add them to the action. If they are not defined, remove them.
["from", "to", "toTarget", "fromTarget", "skipOrigin"].forEach(o => {
["from", "to", "toTarget", "fromTarget", "skipMain", "skipLocal"].forEach(o => {
if (typeof options[o] !== "undefined") {
meta[o] = options[o];
} else if (meta[o]) {
@ -110,23 +112,37 @@ function _RouteMessage(action, options) {
}
/**
* SendToMain - Creates a message that will be sent to the Main process.
* AlsoToMain - Creates a message that will be dispatched locally and also sent to the Main process.
*
* @param {object} action Any redux action (required)
* @param {object} options
* @param {bool} skipLocal Used by OnlyToMain to skip the main reducer
* @param {string} fromTarget The id of the content port from which the action originated. (optional)
* @return {object} An action with added .meta properties
*/
function AlsoToMain(action, fromTarget, skipLocal) {
return _RouteMessage(action, {
from: CONTENT_MESSAGE_TYPE,
to: MAIN_MESSAGE_TYPE,
fromTarget,
skipLocal
});
}
/**
* OnlyToMain - Creates a message that will be sent to the Main process and skip the local reducer.
*
* @param {object} action Any redux action (required)
* @param {object} options
* @param {string} fromTarget The id of the content port from which the action originated. (optional)
* @return {object} An action with added .meta properties
*/
function SendToMain(action, fromTarget) {
return _RouteMessage(action, {
from: CONTENT_MESSAGE_TYPE,
to: MAIN_MESSAGE_TYPE,
fromTarget
});
function OnlyToMain(action, fromTarget) {
return AlsoToMain(action, fromTarget, true);
}
/**
* BroadcastToContent - Creates a message that will be sent to ALL content processes.
* BroadcastToContent - Creates a message that will be dispatched to main and sent to ALL content processes.
*
* @param {object} action Any redux action (required)
* @return {object} An action with added .meta properties
@ -139,30 +155,45 @@ function BroadcastToContent(action) {
}
/**
* SendToContent - Creates a message that will be sent to a particular Content process.
* AlsoToOneContent - Creates a message that will be will be dispatched to the main store
* and also sent to a particular Content process.
*
* @param {object} action Any redux action (required)
* @param {string} target The id of a content port
* @param {bool} skipMain Used by OnlyToOneContent to skip the main process
* @return {object} An action with added .meta properties
*/
function AlsoToOneContent(action, target, skipMain) {
if (!target) {
throw new Error("You must provide a target ID as the second parameter of AlsoToOneContent. If you want to send to all content processes, use BroadcastToContent");
}
return _RouteMessage(action, {
from: MAIN_MESSAGE_TYPE,
to: CONTENT_MESSAGE_TYPE,
toTarget: target,
skipMain
});
}
/**
* OnlyToOneContent - Creates a message that will be sent to a particular Content process
* and skip the main reducer.
*
* @param {object} action Any redux action (required)
* @param {string} target The id of a content port
* @return {object} An action with added .meta properties
*/
function SendToContent(action, target) {
if (!target) {
throw new Error("You must provide a target ID as the second parameter of SendToContent. If you want to send to all content processes, use BroadcastToContent");
}
return _RouteMessage(action, {
from: MAIN_MESSAGE_TYPE,
to: CONTENT_MESSAGE_TYPE,
toTarget: target
});
function OnlyToOneContent(action, target) {
return AlsoToOneContent(action, target, true);
}
/**
* SendToPreloaded - Creates a message that will be sent to the preloaded tab.
* AlsoToPreloaded - Creates a message that dispatched to the main reducer and also sent to the preloaded tab.
*
* @param {object} action Any redux action (required)
* @return {object} An action with added .meta properties
*/
function SendToPreloaded(action) {
function AlsoToPreloaded(action) {
return _RouteMessage(action, {
from: MAIN_MESSAGE_TYPE,
to: PRELOAD_MESSAGE_TYPE
@ -174,10 +205,10 @@ function SendToPreloaded(action) {
* be sent from the UI during a user session.
*
* @param {object} data Fields to include in the ping (source, etc.)
* @return {object} An SendToMain action
* @return {object} An AlsoToMain action
*/
function UserEvent(data) {
return SendToMain({
return AlsoToMain({
type: actionTypes.TELEMETRY_USER_EVENT,
data
});
@ -188,14 +219,14 @@ function UserEvent(data) {
*
* @param {object} data Fields to include in the ping (value, etc.)
* @param {int} importContext (For testing) Override the import context for testing.
* @return {object} An action. For UI code, a SendToMain action.
* @return {object} An action. For UI code, a AlsoToMain action.
*/
function UndesiredEvent(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_UNDESIRED_EVENT,
data
};
return importContext === UI_CODE ? SendToMain(action) : action;
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
/**
@ -203,14 +234,14 @@ function UndesiredEvent(data, importContext = globalImportContext) {
*
* @param {object} data Fields to include in the ping (value, etc.)
* @param {int} importContext (For testing) Override the import context for testing.
* @return {object} An action. For UI code, a SendToMain action.
* @return {object} An action. For UI code, a AlsoToMain action.
*/
function PerfEvent(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_PERFORMANCE_EVENT,
data
};
return importContext === UI_CODE ? SendToMain(action) : action;
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
/**
@ -218,19 +249,27 @@ function PerfEvent(data, importContext = globalImportContext) {
*
* @param {object} data Fields to include in the ping
* @param {int} importContext (For testing) Override the import context for testing.
* #return {object} An action. For UI code, a SendToMain action.
* #return {object} An action. For UI code, a AlsoToMain action.
*/
function ImpressionStats(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_IMPRESSION_STATS,
data
};
return importContext === UI_CODE ? SendToMain(action) : action;
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
function SetPref(name, value, importContext = globalImportContext) {
const action = {type: actionTypes.SET_PREF, data: {name, value}};
return importContext === UI_CODE ? SendToMain(action) : action;
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
function WebExtEvent(type, data, importContext = globalImportContext) {
if (!data || !data.source) {
throw new Error("WebExtEvent actions should include a property \"source\", the id of the webextension that should receive the event.");
}
const action = {type, data};
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
this.actionTypes = actionTypes;
@ -241,10 +280,13 @@ this.actionCreators = {
UndesiredEvent,
PerfEvent,
ImpressionStats,
SendToContent,
SendToMain,
SendToPreloaded,
SetPref
AlsoToOneContent,
OnlyToOneContent,
AlsoToMain,
OnlyToMain,
AlsoToPreloaded,
SetPref,
WebExtEvent
};
// These are helpers to test for certain kinds of actions
@ -264,7 +306,7 @@ this.actionUtils = {
}
return false;
},
isSendToContent(action) {
isSendToOneContent(action) {
if (!action.meta) {
return false;
}

View File

@ -2,9 +2,7 @@
"use strict";
/* istanbul ignore if */
// Note: normally we would just feature detect Components.utils here, but
// unfortunately that throws an ugly warning in content if we do.
if (typeof Window === "undefined" && typeof Components !== "undefined" && Components.utils) {
if (typeof ChromeUtils !== "undefined") {
ChromeUtils.import("resource://gre/modules/Services.jsm");
}

View File

@ -51,7 +51,7 @@ this.PrerenderData = new _PrerenderData({
"migrationExpired": true,
"showTopSites": true,
"showSearch": true,
"topSitesCount": 12,
"topSitesRows": 2,
"collapseTopSites": false,
"section.highlights.collapsed": false,
"section.topstories.collapsed": false,
@ -67,7 +67,7 @@ this.PrerenderData = new _PrerenderData({
validation: [
"showTopSites",
"showSearch",
"topSitesCount",
"topSitesRows",
"collapseTopSites",
"section.highlights.collapsed",
"section.topstories.collapsed",

View File

@ -6,8 +6,8 @@
const {actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});
const {Dedupe} = ChromeUtils.import("resource://activity-stream/common/Dedupe.jsm", {});
const TOP_SITES_DEFAULT_LENGTH = 6;
const TOP_SITES_SHOWMORE_LENGTH = 12;
const TOP_SITES_DEFAULT_ROWS = 2;
const TOP_SITES_MAX_SITES_PER_ROW = 6;
const dedupe = new Dedupe(site => site && site.url);
@ -24,12 +24,8 @@ const INITIAL_STATE = {
initialized: false,
// The history (and possibly default) links
rows: [],
// Used in content only to dispatch action from
// context menu to TopSitesEdit.
editForm: {
visible: false,
index: -1
}
// Used in content only to dispatch action to TopSiteForm.
editForm: null
},
Prefs: {
initialized: false,
@ -95,9 +91,9 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
}
return Object.assign({}, prevState, {initialized: true, rows: action.data});
case at.TOP_SITES_EDIT:
return Object.assign({}, prevState, {editForm: {visible: true, index: action.data.index}});
return Object.assign({}, prevState, {editForm: {index: action.data.index}});
case at.TOP_SITES_CANCEL_EDIT:
return Object.assign({}, prevState, {editForm: {visible: false}});
return Object.assign({}, prevState, {editForm: null});
case at.SCREENSHOT_UPDATED:
newRows = prevState.rows.map(row => {
if (row && row.url === action.data.url) {
@ -323,10 +319,10 @@ function PreferencesPane(prevState = INITIAL_STATE.PreferencesPane, action) {
}
this.INITIAL_STATE = INITIAL_STATE;
this.TOP_SITES_DEFAULT_LENGTH = TOP_SITES_DEFAULT_LENGTH;
this.TOP_SITES_SHOWMORE_LENGTH = TOP_SITES_SHOWMORE_LENGTH;
this.TOP_SITES_DEFAULT_ROWS = TOP_SITES_DEFAULT_ROWS;
this.TOP_SITES_MAX_SITES_PER_ROW = TOP_SITES_MAX_SITES_PER_ROW;
this.reducers = {TopSites, App, Snippets, Prefs, Dialog, Sections, PreferencesPane};
this.insertPinned = insertPinned;
this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE", "insertPinned", "TOP_SITES_DEFAULT_LENGTH", "TOP_SITES_SHOWMORE_LENGTH"];
this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE", "insertPinned", "TOP_SITES_DEFAULT_ROWS", "TOP_SITES_MAX_SITES_PER_ROW"];

View File

@ -106,7 +106,7 @@ const actionTypes = {};
/* harmony export (immutable) */ __webpack_exports__["b"] = actionTypes;
for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "DISABLE_ONBOARDING", "INIT", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_DELETED", "PLACES_LINK_BLOCKED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "DISABLE_ONBOARDING", "INIT", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_DELETED", "PLACES_LINK_BLOCKED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
actionTypes[type] = type;
}
@ -119,7 +119,7 @@ function _RouteMessage(action, options) {
}
// For each of these fields, if they are passed as an option,
// add them to the action. If they are not defined, remove them.
["from", "to", "toTarget", "fromTarget", "skipOrigin"].forEach(o => {
["from", "to", "toTarget", "fromTarget", "skipMain", "skipLocal"].forEach(o => {
if (typeof options[o] !== "undefined") {
meta[o] = options[o];
} else if (meta[o]) {
@ -130,23 +130,37 @@ function _RouteMessage(action, options) {
}
/**
* SendToMain - Creates a message that will be sent to the Main process.
* AlsoToMain - Creates a message that will be dispatched locally and also sent to the Main process.
*
* @param {object} action Any redux action (required)
* @param {object} options
* @param {bool} skipLocal Used by OnlyToMain to skip the main reducer
* @param {string} fromTarget The id of the content port from which the action originated. (optional)
* @return {object} An action with added .meta properties
*/
function AlsoToMain(action, fromTarget, skipLocal) {
return _RouteMessage(action, {
from: CONTENT_MESSAGE_TYPE,
to: MAIN_MESSAGE_TYPE,
fromTarget,
skipLocal
});
}
/**
* OnlyToMain - Creates a message that will be sent to the Main process and skip the local reducer.
*
* @param {object} action Any redux action (required)
* @param {object} options
* @param {string} fromTarget The id of the content port from which the action originated. (optional)
* @return {object} An action with added .meta properties
*/
function SendToMain(action, fromTarget) {
return _RouteMessage(action, {
from: CONTENT_MESSAGE_TYPE,
to: MAIN_MESSAGE_TYPE,
fromTarget
});
function OnlyToMain(action, fromTarget) {
return AlsoToMain(action, fromTarget, true);
}
/**
* BroadcastToContent - Creates a message that will be sent to ALL content processes.
* BroadcastToContent - Creates a message that will be dispatched to main and sent to ALL content processes.
*
* @param {object} action Any redux action (required)
* @return {object} An action with added .meta properties
@ -159,30 +173,45 @@ function BroadcastToContent(action) {
}
/**
* SendToContent - Creates a message that will be sent to a particular Content process.
* AlsoToOneContent - Creates a message that will be will be dispatched to the main store
* and also sent to a particular Content process.
*
* @param {object} action Any redux action (required)
* @param {string} target The id of a content port
* @param {bool} skipMain Used by OnlyToOneContent to skip the main process
* @return {object} An action with added .meta properties
*/
function AlsoToOneContent(action, target, skipMain) {
if (!target) {
throw new Error("You must provide a target ID as the second parameter of AlsoToOneContent. If you want to send to all content processes, use BroadcastToContent");
}
return _RouteMessage(action, {
from: MAIN_MESSAGE_TYPE,
to: CONTENT_MESSAGE_TYPE,
toTarget: target,
skipMain
});
}
/**
* OnlyToOneContent - Creates a message that will be sent to a particular Content process
* and skip the main reducer.
*
* @param {object} action Any redux action (required)
* @param {string} target The id of a content port
* @return {object} An action with added .meta properties
*/
function SendToContent(action, target) {
if (!target) {
throw new Error("You must provide a target ID as the second parameter of SendToContent. If you want to send to all content processes, use BroadcastToContent");
}
return _RouteMessage(action, {
from: MAIN_MESSAGE_TYPE,
to: CONTENT_MESSAGE_TYPE,
toTarget: target
});
function OnlyToOneContent(action, target) {
return AlsoToOneContent(action, target, true);
}
/**
* SendToPreloaded - Creates a message that will be sent to the preloaded tab.
* AlsoToPreloaded - Creates a message that dispatched to the main reducer and also sent to the preloaded tab.
*
* @param {object} action Any redux action (required)
* @return {object} An action with added .meta properties
*/
function SendToPreloaded(action) {
function AlsoToPreloaded(action) {
return _RouteMessage(action, {
from: MAIN_MESSAGE_TYPE,
to: PRELOAD_MESSAGE_TYPE
@ -194,10 +223,10 @@ function SendToPreloaded(action) {
* be sent from the UI during a user session.
*
* @param {object} data Fields to include in the ping (source, etc.)
* @return {object} An SendToMain action
* @return {object} An AlsoToMain action
*/
function UserEvent(data) {
return SendToMain({
return AlsoToMain({
type: actionTypes.TELEMETRY_USER_EVENT,
data
});
@ -208,14 +237,14 @@ function UserEvent(data) {
*
* @param {object} data Fields to include in the ping (value, etc.)
* @param {int} importContext (For testing) Override the import context for testing.
* @return {object} An action. For UI code, a SendToMain action.
* @return {object} An action. For UI code, a AlsoToMain action.
*/
function UndesiredEvent(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_UNDESIRED_EVENT,
data
};
return importContext === UI_CODE ? SendToMain(action) : action;
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
/**
@ -223,14 +252,14 @@ function UndesiredEvent(data, importContext = globalImportContext) {
*
* @param {object} data Fields to include in the ping (value, etc.)
* @param {int} importContext (For testing) Override the import context for testing.
* @return {object} An action. For UI code, a SendToMain action.
* @return {object} An action. For UI code, a AlsoToMain action.
*/
function PerfEvent(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_PERFORMANCE_EVENT,
data
};
return importContext === UI_CODE ? SendToMain(action) : action;
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
/**
@ -238,19 +267,27 @@ function PerfEvent(data, importContext = globalImportContext) {
*
* @param {object} data Fields to include in the ping
* @param {int} importContext (For testing) Override the import context for testing.
* #return {object} An action. For UI code, a SendToMain action.
* #return {object} An action. For UI code, a AlsoToMain action.
*/
function ImpressionStats(data, importContext = globalImportContext) {
const action = {
type: actionTypes.TELEMETRY_IMPRESSION_STATS,
data
};
return importContext === UI_CODE ? SendToMain(action) : action;
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
function SetPref(name, value, importContext = globalImportContext) {
const action = { type: actionTypes.SET_PREF, data: { name, value } };
return importContext === UI_CODE ? SendToMain(action) : action;
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
function WebExtEvent(type, data, importContext = globalImportContext) {
if (!data || !data.source) {
throw new Error("WebExtEvent actions should include a property \"source\", the id of the webextension that should receive the event.");
}
const action = { type, data };
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
var actionCreators = {
@ -259,10 +296,13 @@ var actionCreators = {
UndesiredEvent,
PerfEvent,
ImpressionStats,
SendToContent,
SendToMain,
SendToPreloaded,
SetPref
AlsoToOneContent,
OnlyToOneContent,
AlsoToMain,
OnlyToMain,
AlsoToPreloaded,
SetPref,
WebExtEvent
};
// These are helpers to test for certain kinds of actions
@ -283,7 +323,7 @@ var actionUtils = {
}
return false;
},
isSendToContent(action) {
isSendToOneContent(action) {
if (!action.meta) {
return false;
}
@ -399,7 +439,7 @@ class Dedupe {
}
// CONCATENATED MODULE: ./system-addon/common/Reducers.jsm
/* unused harmony export insertPinned */
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return reducers; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return reducers; });
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -407,11 +447,11 @@ class Dedupe {
const TOP_SITES_DEFAULT_LENGTH = 6;
/* harmony export (immutable) */ __webpack_exports__["a"] = TOP_SITES_DEFAULT_LENGTH;
const TOP_SITES_DEFAULT_ROWS = 2;
/* unused harmony export TOP_SITES_DEFAULT_ROWS */
const TOP_SITES_SHOWMORE_LENGTH = 12;
/* harmony export (immutable) */ __webpack_exports__["b"] = TOP_SITES_SHOWMORE_LENGTH;
const TOP_SITES_MAX_SITES_PER_ROW = 6;
/* harmony export (immutable) */ __webpack_exports__["a"] = TOP_SITES_MAX_SITES_PER_ROW;
@ -430,12 +470,8 @@ const INITIAL_STATE = {
initialized: false,
// The history (and possibly default) links
rows: [],
// Used in content only to dispatch action from
// context menu to TopSitesEdit.
editForm: {
visible: false,
index: -1
}
// Used in content only to dispatch action to TopSiteForm.
editForm: null
},
Prefs: {
initialized: false,
@ -507,9 +543,9 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
}
return Object.assign({}, prevState, { initialized: true, rows: action.data });
case Actions["b" /* actionTypes */].TOP_SITES_EDIT:
return Object.assign({}, prevState, { editForm: { visible: true, index: action.data.index } });
return Object.assign({}, prevState, { editForm: { index: action.data.index } });
case Actions["b" /* actionTypes */].TOP_SITES_CANCEL_EDIT:
return Object.assign({}, prevState, { editForm: { visible: false } });
return Object.assign({}, prevState, { editForm: null });
case Actions["b" /* actionTypes */].SCREENSHOT_UPDATED:
newRows = prevState.rows.map(row => {
if (row && row.url === action.data.url) {
@ -853,7 +889,7 @@ const LinkMenuOptions = {
RemoveBookmark: site => ({
id: "menu_action_remove_bookmark",
icon: "bookmark-added",
action: Actions["a" /* actionCreators */].SendToMain({
action: Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].DELETE_BOOKMARK_BY_ID,
data: site.bookmarkGuid
}),
@ -862,7 +898,7 @@ const LinkMenuOptions = {
AddBookmark: site => ({
id: "menu_action_bookmark",
icon: "bookmark-hollow",
action: Actions["a" /* actionCreators */].SendToMain({
action: Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].BOOKMARK_URL,
data: { url: site.url, title: site.title, type: site.type }
}),
@ -871,7 +907,7 @@ const LinkMenuOptions = {
OpenInNewWindow: site => ({
id: "menu_action_open_new_window",
icon: "new-window",
action: Actions["a" /* actionCreators */].SendToMain({
action: Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].OPEN_NEW_WINDOW,
data: { url: site.url, referrer: site.referrer }
}),
@ -880,7 +916,7 @@ const LinkMenuOptions = {
OpenInPrivateWindow: site => ({
id: "menu_action_open_private_window",
icon: "new-window-private",
action: Actions["a" /* actionCreators */].SendToMain({
action: Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].OPEN_PRIVATE_WINDOW,
data: { url: site.url, referrer: site.referrer }
}),
@ -889,7 +925,7 @@ const LinkMenuOptions = {
BlockUrl: (site, index, eventSource) => ({
id: "menu_action_dismiss",
icon: "dismiss",
action: Actions["a" /* actionCreators */].SendToMain({
action: Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].BLOCK_URL,
data: site.url
}),
@ -900,13 +936,26 @@ const LinkMenuOptions = {
}),
userEvent: "BLOCK"
}),
// This is an option for web extentions which will result in remove items from
// memory and notify the web extenion, rather than using the built-in block list.
WebExtDismiss: (site, index, eventSource) => ({
id: "menu_action_webext_dismiss",
string_id: "menu_action_dismiss",
icon: "dismiss",
action: Actions["a" /* actionCreators */].WebExtEvent(Actions["b" /* actionTypes */].WEBEXT_DISMISS, {
source: eventSource,
url: site.url,
action_position: index
})
}),
DeleteUrl: site => ({
id: "menu_action_delete",
icon: "delete",
action: {
type: Actions["b" /* actionTypes */].DIALOG_OPEN,
data: {
onConfirm: [Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].DELETE_HISTORY_URL, data: { url: site.url, forceBlock: site.bookmarkGuid } }), Actions["a" /* actionCreators */].UserEvent({ event: "DELETE" })],
onConfirm: [Actions["a" /* actionCreators */].AlsoToMain({ type: Actions["b" /* actionTypes */].DELETE_HISTORY_URL, data: { url: site.url, forceBlock: site.bookmarkGuid } }), Actions["a" /* actionCreators */].UserEvent({ event: "DELETE" })],
body_string_id: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
confirm_button_string_id: "menu_action_delete",
cancel_button_string_id: "topsites_form_cancel_button",
@ -918,7 +967,7 @@ const LinkMenuOptions = {
PinTopSite: (site, index) => ({
id: "menu_action_pin",
icon: "pin",
action: Actions["a" /* actionCreators */].SendToMain({
action: Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].TOP_SITES_PIN,
data: { site: { url: site.url }, index }
}),
@ -927,7 +976,7 @@ const LinkMenuOptions = {
UnpinTopSite: site => ({
id: "menu_action_unpin",
icon: "unpin",
action: Actions["a" /* actionCreators */].SendToMain({
action: Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].TOP_SITES_UNPIN,
data: { site: { url: site.url } }
}),
@ -936,7 +985,7 @@ const LinkMenuOptions = {
SaveToPocket: (site, index, eventSource) => ({
id: "menu_action_save_to_pocket",
icon: "pocket",
action: Actions["a" /* actionCreators */].SendToMain({
action: Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].SAVE_TO_POCKET,
data: { site: { url: site.url, title: site.title } }
}),
@ -976,9 +1025,9 @@ class LinkMenu__LinkMenu extends external__React__default.a.PureComponent {
const propOptions = !site.isDefault ? props.options : DEFAULT_SITE_MENU_OPTIONS;
const options = propOptions.map(o => LinkMenuOptions[o](site, index, source)).map(option => {
const { action, impression, id, type, userEvent } = option;
const { action, impression, id, string_id, type, userEvent } = option;
if (!type && id) {
option.label = props.intl.formatMessage(option);
option.label = props.intl.formatMessage({ id: string_id || id });
option.onClick = () => {
props.dispatch(action);
if (userEvent) {
@ -1454,7 +1503,7 @@ class ComponentPerfTimer extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.C
const firstRenderKey = `${this.props.id}_first_render_ts`;
// value has to be Int32.
const value = parseInt(this.perfSvc.getMostRecentAbsMarkStartByName(dataReadyKey) - this.perfSvc.getMostRecentAbsMarkStartByName(firstRenderKey), 10);
this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({
this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].OnlyToMain({
type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SAVE_SESSION_PERF_DATA,
// highlights_data_late_by_ms, topsites_data_late_by_ms.
data: { [`${this.props.id}_data_late_by_ms`]: value }
@ -1479,7 +1528,7 @@ class ComponentPerfTimer extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.C
const data = {};
data[key] = this.perfSvc.getMostRecentAbsMarkStartByName(key);
this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({
this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].OnlyToMain({
type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SAVE_SESSION_PERF_DATA,
data
}));
@ -1512,10 +1561,8 @@ class ComponentPerfTimer extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.C
/* istanbul ignore if */
// Note: normally we would just feature detect Components.utils here, but
// unfortunately that throws an ugly warning in content if we do.
if (typeof Window === "undefined" && typeof Components !== "undefined" && Components.utils) {
if (typeof ChromeUtils !== "undefined") {
ChromeUtils.import("resource://gre/modules/Services.jsm");
}
@ -1665,7 +1712,7 @@ Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
const store = Object(__WEBPACK_IMPORTED_MODULE_4_content_src_lib_init_store__["a" /* initStore */])(__WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__["c" /* reducers */], global.gActivityStreamPrerenderedState);
const store = Object(__WEBPACK_IMPORTED_MODULE_4_content_src_lib_init_store__["a" /* initStore */])(__WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__["b" /* reducers */], global.gActivityStreamPrerenderedState);
new __WEBPACK_IMPORTED_MODULE_3_content_src_lib_detect_user_session_start__["a" /* DetectUserSessionStart */](store).sendEventOrAddListener();
@ -1673,7 +1720,7 @@ new __WEBPACK_IMPORTED_MODULE_3_content_src_lib_detect_user_session_start__["a"
// 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(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
store.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].AlsoToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
}
__WEBPACK_IMPORTED_MODULE_7_react_dom___default.a.render(__WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
@ -1755,17 +1802,17 @@ class SnippetsMap extends Map {
const { blockList } = this;
if (!blockList.includes(id)) {
blockList.push(id);
this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SNIPPETS_BLOCKLIST_UPDATED, data: blockList }));
this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].AlsoToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SNIPPETS_BLOCKLIST_UPDATED, data: blockList }));
await this.set("blockList", blockList);
}
}
disableOnboarding() {
this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].DISABLE_ONBOARDING }));
this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].AlsoToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].DISABLE_ONBOARDING }));
}
showFirefoxAccounts() {
this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SHOW_FIREFOX_ACCOUNTS }));
this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].AlsoToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SHOW_FIREFOX_ACCOUNTS }));
}
/**
@ -2101,7 +2148,7 @@ var external__React__default = /*#__PURE__*/__webpack_require__.n(external__Reac
* data: {
* // Any sort of data needed to be passed around by actions.
* payload: site.url,
* // Primary button SendToMain action.
* // Primary button AlsoToMain action.
* action: "DELETE_HISTORY_URL",
* // Primary button USerEvent action.
* userEvent: "DELETE",
@ -2205,12 +2252,12 @@ class ManualMigration__ManualMigration extends external__React__default.a.PureCo
}
onLaunchTour() {
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].MIGRATION_START }));
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({ type: Actions["b" /* actionTypes */].MIGRATION_START }));
this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({ event: Actions["b" /* actionTypes */].MIGRATION_START }));
}
onCancelTour() {
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].MIGRATION_CANCEL }));
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({ type: Actions["b" /* actionTypes */].MIGRATION_CANCEL }));
this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({ event: Actions["b" /* actionTypes */].MIGRATION_CANCEL }));
}
@ -2243,16 +2290,12 @@ class ManualMigration__ManualMigration extends external__React__default.a.PureCo
}
const ManualMigration = Object(external__ReactRedux_["connect"])()(ManualMigration__ManualMigration);
// EXTERNAL MODULE: ./system-addon/common/Reducers.jsm + 1 modules
var Reducers = __webpack_require__(5);
// CONCATENATED MODULE: ./system-addon/content-src/components/PreferencesPane/PreferencesPane.jsx
const getFormattedMessage = message => typeof message === "string" ? external__React__default.a.createElement(
"span",
null,
@ -2314,8 +2357,8 @@ class PreferencesPane__PreferencesPane extends external__React__default.a.PureCo
handlePrefChange({ target: { name, checked } }) {
let value = checked;
if (name === "topSitesCount") {
value = checked ? Reducers["b" /* TOP_SITES_SHOWMORE_LENGTH */] : Reducers["a" /* TOP_SITES_DEFAULT_LENGTH */];
if (name === "topSitesRows") {
value = checked ? 2 : 1;
}
this.props.dispatch(Actions["a" /* actionCreators */].SetPref(name, value));
}
@ -2323,7 +2366,7 @@ class PreferencesPane__PreferencesPane extends external__React__default.a.PureCo
handleSectionChange({ target }) {
const id = target.name;
const type = target.checked ? Actions["b" /* actionTypes */].SECTION_ENABLE : Actions["b" /* actionTypes */].SECTION_DISABLE;
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type, data: id }));
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({ type, data: id }));
}
togglePane() {
@ -2394,9 +2437,9 @@ class PreferencesPane__PreferencesPane extends external__React__default.a.PureCo
descString: { id: "settings_pane_topsites_body" } },
external__React__default.a.createElement(PreferencesInput, {
className: "showMoreTopSites",
prefName: "topSitesCount",
prefName: "topSitesRows",
disabled: !prefs.showTopSites,
value: prefs.topSitesCount !== Reducers["a" /* TOP_SITES_DEFAULT_LENGTH */],
value: prefs.topSitesRows === 2,
onChange: this.handlePrefChange,
titleString: { id: "settings_pane_topsites_options_showmore" },
labelClassName: "icon icon-topsites" })
@ -2499,7 +2542,7 @@ var PrerenderData = new _PrerenderData({
"migrationExpired": true,
"showTopSites": true,
"showSearch": true,
"topSitesCount": 12,
"topSitesRows": 2,
"collapseTopSites": false,
"section.highlights.collapsed": false,
"section.topstories.collapsed": false,
@ -2512,7 +2555,7 @@ var PrerenderData = new _PrerenderData({
// too different for the prerendered version to be used. Unfortunately, this
// will result in users who have modified some of their preferences not being
// able to get the benefits of prerendering.
validation: ["showTopSites", "showSearch", "topSitesCount", "collapseTopSites", "section.highlights.collapsed", "section.topstories.collapsed",
validation: ["showTopSites", "showSearch", "topSitesRows", "collapseTopSites", "section.highlights.collapsed", "section.topstories.collapsed",
// This means if either of these are set to their default values,
// prerendering can be used.
{ oneOf: ["feeds.section.topstories", "feeds.section.highlights"] }],
@ -2651,6 +2694,9 @@ var CollapsibleSection = __webpack_require__(7);
// EXTERNAL MODULE: ./system-addon/content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx
var ComponentPerfTimer = __webpack_require__(8);
// EXTERNAL MODULE: ./system-addon/common/Reducers.jsm + 1 modules
var Reducers = __webpack_require__(5);
// CONCATENATED MODULE: ./system-addon/content-src/components/TopSites/TopSiteForm.jsx
@ -2660,16 +2706,16 @@ var ComponentPerfTimer = __webpack_require__(8);
class TopSiteForm_TopSiteForm extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
const { site } = props;
this.state = {
label: props.label || "",
url: props.url || "",
label: site ? site.label || site.hostname : "",
url: site ? site.url : "",
validationError: false
};
this.onLabelChange = this.onLabelChange.bind(this);
this.onUrlChange = this.onUrlChange.bind(this);
this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
this.onAddButtonClick = this.onAddButtonClick.bind(this);
this.onSaveButtonClick = this.onSaveButtonClick.bind(this);
this.onDoneButtonClick = this.onDoneButtonClick.bind(this);
this.onUrlInputMount = this.onUrlInputMount.bind(this);
}
@ -2688,41 +2734,26 @@ class TopSiteForm_TopSiteForm extends external__React__default.a.PureComponent {
this.props.onClose();
}
onAddButtonClick(ev) {
onDoneButtonClick(ev) {
ev.preventDefault();
if (this.validateForm()) {
let site = { url: this.cleanUrl() };
if (this.state.label !== "") {
site.label = this.state.label;
}
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
type: Actions["b" /* actionTypes */].TOP_SITES_INSERT,
data: { site }
}));
this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
source: TOP_SITES_SOURCE,
event: "TOP_SITES_ADD"
}));
this.props.onClose();
}
}
onSaveButtonClick(ev) {
ev.preventDefault();
if (this.validateForm()) {
let site = { url: this.cleanUrl() };
const site = { url: this.cleanUrl() };
const { index } = this.props;
if (this.state.label !== "") {
site.label = this.state.label;
}
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].TOP_SITES_PIN,
data: { site, index: this.props.index }
data: { site, index }
}));
this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
source: TOP_SITES_SOURCE,
event: "TOP_SITES_EDIT",
action_position: this.props.index
action_position: index
}));
this.props.onClose();
}
}
@ -2766,6 +2797,9 @@ class TopSiteForm_TopSiteForm extends external__React__default.a.PureComponent {
}
render() {
// For UI purposes, editing without an existing link is "add"
const showAsAdd = !this.props.site;
return external__React__default.a.createElement(
"form",
{ className: "topsite-form" },
@ -2778,7 +2812,7 @@ class TopSiteForm_TopSiteForm extends external__React__default.a.PureComponent {
external__React__default.a.createElement(
"h3",
{ className: "section-title" },
external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: this.props.editMode ? "topsites_form_edit_header" : "topsites_form_add_header" })
external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: showAsAdd ? "topsites_form_add_header" : "topsites_form_edit_header" })
),
external__React__default.a.createElement(
"div",
@ -2814,15 +2848,10 @@ class TopSiteForm_TopSiteForm extends external__React__default.a.PureComponent {
{ className: "cancel", type: "button", onClick: this.onCancelButtonClick },
external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "topsites_form_cancel_button" })
),
this.props.editMode && external__React__default.a.createElement(
external__React__default.a.createElement(
"button",
{ className: "done save", type: "submit", onClick: this.onSaveButtonClick },
external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "topsites_form_save_button" })
),
!this.props.editMode && external__React__default.a.createElement(
"button",
{ className: "done add", type: "submit", onClick: this.onAddButtonClick },
external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "topsites_form_add_button" })
{ className: "done", type: "submit", onClick: this.onDoneButtonClick },
external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: showAsAdd ? "topsites_form_add_button" : "topsites_form_save_button" })
)
)
);
@ -2830,10 +2859,8 @@ class TopSiteForm_TopSiteForm extends external__React__default.a.PureComponent {
}
TopSiteForm_TopSiteForm.defaultProps = {
label: "",
url: "",
index: 0,
editMode: false // by default we are in "Add New Top Site" mode
TopSite: null,
index: -1
};
// EXTERNAL MODULE: ./system-addon/content-src/components/LinkMenu/LinkMenu.jsx + 2 modules
var LinkMenu = __webpack_require__(6);
@ -2847,6 +2874,7 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument
class TopSite_TopSiteLink extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
@ -3051,11 +3079,6 @@ class TopSite_TopSitePlaceholder extends external__React__default.a.PureComponen
}
onEditButtonClick() {
if (this.props.onEdit) {
this.props.onEdit(this.props.index);
return;
}
this.props.dispatch({ type: Actions["b" /* actionTypes */].TOP_SITES_EDIT, data: { index: this.props.index } });
}
@ -3135,7 +3158,7 @@ class TopSite__TopSiteList extends external__React__default.a.PureComponent {
case "drop":
if (index !== this.state.draggedIndex) {
this.dropped = true;
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].TOP_SITES_INSERT,
data: { site: { url: this.state.draggedSite.url, label: this.state.draggedTitle }, index, draggedFromIndex: this.state.draggedIndex }
}));
@ -3148,7 +3171,7 @@ class TopSite__TopSiteList extends external__React__default.a.PureComponent {
_getTopSites() {
// Make a copy of the sites to truncate or extend to desired length
let topSites = this.props.TopSites.rows.slice();
topSites.length = this.props.TopSitesCount;
topSites.length = this.props.TopSitesRows * Reducers["a" /* TOP_SITES_MAX_SITES_PER_ROW */];
return topSites;
}
@ -3218,11 +3241,8 @@ class TopSite__TopSiteList extends external__React__default.a.PureComponent {
key: link ? link.url : holeIndex++,
index: i
};
topSitesUI.push(!link ? external__React__default.a.createElement(TopSite_TopSitePlaceholder, _extends({
onEdit: props.onEdit
}, slotProps, commonProps)) : external__React__default.a.createElement(TopSite_TopSite, _extends({
topSitesUI.push(!link ? external__React__default.a.createElement(TopSite_TopSitePlaceholder, _extends({}, slotProps, commonProps)) : external__React__default.a.createElement(TopSite_TopSite, _extends({
link: link,
onEdit: props.onEdit,
activeIndex: this.state.activeIndex,
onActivate: this.onActivate
}, slotProps, commonProps)));
@ -3247,6 +3267,7 @@ const TopSiteList = Object(external__ReactIntl_["injectIntl"])(TopSite__TopSiteL
/**
* Iterates through TopSites and counts types of images.
* @param acc Accumulator for reducer.
@ -3279,17 +3300,8 @@ function countTopSitesIconsTypes(topSites) {
}
class TopSites__TopSites extends external__React__default.a.PureComponent {
static get DEFAULT_STATE() {
return {
showAddForm: false,
showEditForm: false,
editIndex: -1 // Index of top site being edited
};
}
constructor(props) {
super(props);
this.state = TopSites__TopSites.DEFAULT_STATE;
this.onAddButtonClick = this.onAddButtonClick.bind(this);
this.onFormClose = this.onFormClose.bind(this);
}
@ -3302,7 +3314,7 @@ class TopSites__TopSites extends external__React__default.a.PureComponent {
const topSitesIconsStats = countTopSitesIconsTypes(topSites);
const topSitesPinned = topSites.filter(site => !!site.isPinned).length;
// Dispatch telemetry event with the count of TopSites images types.
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].SAVE_SESSION_PERF_DATA,
data: { topsites_icon_stats: topSitesIconsStats, topsites_pinned: topSitesPinned }
}));
@ -3312,7 +3324,7 @@ class TopSites__TopSites extends external__React__default.a.PureComponent {
* Return the TopSites to display based on prefs.
*/
_getTopSites() {
return this.props.TopSites.rows.slice(0, this.props.TopSitesCount);
return this.props.TopSites.rows.slice(0, this.props.TopSitesRows * Reducers["a" /* TOP_SITES_MAX_SITES_PER_ROW */]);
}
componentDidUpdate() {
@ -3324,15 +3336,15 @@ class TopSites__TopSites extends external__React__default.a.PureComponent {
}
onAddButtonClick() {
this.setState({ showAddForm: true });
this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
source: TOP_SITES_SOURCE,
event: "TOP_SITES_ADD_FORM_OPEN"
}));
// Negative index will prepend the TopSite at the beginning of the list
this.props.dispatch({ type: Actions["b" /* actionTypes */].TOP_SITES_EDIT, data: { index: -1 } });
}
onFormClose() {
this.setState(TopSites__TopSites.DEFAULT_STATE);
this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
source: TOP_SITES_SOURCE,
event: "TOP_SITES_EDIT_CLOSE"
@ -3346,21 +3358,15 @@ class TopSites__TopSites extends external__React__default.a.PureComponent {
header: { id: "settings_pane_topsites_header" },
body: { id: "settings_pane_topsites_body" }
};
const { showAddForm } = this.state;
const { editForm } = this.props.TopSites;
const showEditForm = editForm && editForm.visible || this.state.showEditForm;
let { editIndex } = this.state;
if (editIndex < 0 && editForm) {
editIndex = editForm.index;
}
const editSite = this.props.TopSites.rows[editIndex] || {};
const { editForm } = props.TopSites;
return external__React__default.a.createElement(
ComponentPerfTimer["a" /* ComponentPerfTimer */],
{ id: "topsites", initialized: props.TopSites.initialized, dispatch: props.dispatch },
external__React__default.a.createElement(
CollapsibleSection["a" /* CollapsibleSection */],
{ className: "top-sites", icon: "topsites", title: external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "header_top_sites" }), infoOption: infoOption, prefName: "collapseTopSites", Prefs: props.Prefs, dispatch: props.dispatch },
external__React__default.a.createElement(TopSiteList, { TopSites: props.TopSites, TopSitesCount: props.TopSitesCount, dispatch: props.dispatch, intl: props.intl }),
external__React__default.a.createElement(TopSiteList, { TopSites: props.TopSites, TopSitesRows: props.TopSitesRows, dispatch: props.dispatch, intl: props.intl }),
external__React__default.a.createElement(
"div",
{ className: "edit-topsites-wrapper" },
@ -3376,17 +3382,7 @@ class TopSites__TopSites extends external__React__default.a.PureComponent {
external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "edit_topsites_add_button" })
)
),
showAddForm && external__React__default.a.createElement(
"div",
{ className: "edit-topsites" },
external__React__default.a.createElement("div", { className: "modal-overlay", onClick: this.onFormClose }),
external__React__default.a.createElement(
"div",
{ className: "modal" },
external__React__default.a.createElement(TopSiteForm_TopSiteForm, { onClose: this.onFormClose, dispatch: this.props.dispatch, intl: this.props.intl })
)
),
showEditForm && external__React__default.a.createElement(
editForm && external__React__default.a.createElement(
"div",
{ className: "edit-topsites" },
external__React__default.a.createElement("div", { className: "modal-overlay", onClick: this.onFormClose }),
@ -3394,10 +3390,8 @@ class TopSites__TopSites extends external__React__default.a.PureComponent {
"div",
{ className: "modal" },
external__React__default.a.createElement(TopSiteForm_TopSiteForm, {
label: editSite.label || editSite.hostname || "",
url: editSite.url || "",
index: editIndex,
editMode: true,
site: props.TopSites.rows[editForm.index],
index: editForm.index,
onClose: this.onFormClose,
dispatch: this.props.dispatch,
intl: this.props.intl })
@ -3412,7 +3406,7 @@ class TopSites__TopSites extends external__React__default.a.PureComponent {
const TopSites = Object(external__ReactRedux_["connect"])(state => ({
TopSites: state.TopSites,
Prefs: state.Prefs,
TopSitesCount: state.Prefs.values.topSitesCount
TopSitesRows: state.Prefs.values.topSitesRows
}))(Object(external__ReactIntl_["injectIntl"])(TopSites__TopSites));
// CONCATENATED MODULE: ./system-addon/content-src/components/Base/Base.jsx
@ -3446,8 +3440,8 @@ class Base__Base extends external__React__default.a.PureComponent {
// 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(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].PAGE_PRERENDERED }));
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({ type: Actions["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({ type: Actions["b" /* actionTypes */].PAGE_PRERENDERED }));
}
}
@ -3460,7 +3454,7 @@ class Base__Base extends external__React__default.a.PureComponent {
// have rendered that data.
sendNewTabRehydrated(App) {
if (App && App.initialized && !this.renderNotified) {
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].NEW_TAB_REHYDRATED, data: {} }));
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({ type: Actions["b" /* actionTypes */].NEW_TAB_REHYDRATED, data: {} }));
this.renderNotified = true;
}
}
@ -3697,7 +3691,7 @@ class Section extends __WEBPACK_IMPORTED_MODULE_6_react___default.a.PureComponen
"ul",
{ className: "section-list", style: { padding: 0 } },
realRows.map((link, index) => link && __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_0_content_src_components_Card_Card__["a" /* Card */], { key: index, index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions,
eventSource: eventSource, shouldSendImpressionStats: this.props.shouldSendImpressionStats })),
eventSource: eventSource, shouldSendImpressionStats: this.props.shouldSendImpressionStats, isWebExtension: this.props.isWebExtension })),
placeholders > 0 && [...new Array(placeholders)].map((_, i) => __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_0_content_src_components_Card_Card__["b" /* PlaceholderCard */], { key: i }))
),
shouldShowEmptyState && __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
@ -3864,21 +3858,31 @@ class Card_Card extends external__React__default.a.PureComponent {
onLinkClick(event) {
event.preventDefault();
const { altKey, button, ctrlKey, metaKey, shiftKey } = event;
this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
this.props.dispatch(Actions["a" /* actionCreators */].AlsoToMain({
type: Actions["b" /* actionTypes */].OPEN_LINK,
data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } })
}));
this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
event: "CLICK",
source: this.props.eventSource,
action_position: this.props.index
}));
if (this.props.shouldSendImpressionStats) {
this.props.dispatch(Actions["a" /* actionCreators */].ImpressionStats({
if (this.props.isWebExtension) {
this.props.dispatch(Actions["a" /* actionCreators */].WebExtEvent(Actions["b" /* actionTypes */].WEBEXT_CLICK, {
source: this.props.eventSource,
click: 0,
tiles: [{ id: this.props.link.guid, pos: this.props.index }]
url: this.props.link.url,
action_position: this.props.index
}));
} else {
this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
event: "CLICK",
source: this.props.eventSource,
action_position: this.props.index
}));
if (this.props.shouldSendImpressionStats) {
this.props.dispatch(Actions["a" /* actionCreators */].ImpressionStats({
source: this.props.eventSource,
click: 0,
tiles: [{ id: this.props.link.guid, pos: this.props.index }]
}));
}
}
}
@ -4101,7 +4105,7 @@ class DetectUserSessionStart {
try {
let visibility_event_rcvd_ts = this._perfService.getMostRecentAbsMarkStartByName("visibility_event_rcvd_ts");
this._store.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({
this._store.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].AlsoToMain({
type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SAVE_SESSION_PERF_DATA,
data: { visibility_event_rcvd_ts }
}));
@ -4183,10 +4187,13 @@ function mergeStateReducer(mainReducer) {
* messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
*/
const messageMiddleware = store => next => action => {
const skipLocal = action.meta && action.meta.skipLocal;
if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isSendToMain(action)) {
sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
}
next(action);
if (!skipLocal) {
next(action);
}
};
const rehydrationMiddleware = store => next => action => {
@ -4209,10 +4216,10 @@ const rehydrationMiddleware = store => next => action => {
// If init happened after our request was made, we need to re-request
if (store._didRequestInitialState && action.type === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].INIT) {
return next(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
return next(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].AlsoToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
}
if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isBroadcastToContent(action) || __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isSendToContent(action) || __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isSendToPreloaded(action)) {
if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isBroadcastToContent(action) || __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isSendToOneContent(action) || __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isSendToPreloaded(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.

View File

@ -8,7 +8,7 @@
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:unpack>false</em:unpack>
<em:version>2018.01.30.1052-2ec746e0</em:version>
<em:version>2018.02.05.1095-9be4c3c6</em:version>
<em:name>Activity Stream</em:name>
<em:description>A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you're looking for in Firefox.</em:description>
<em:multiprocessCompatible>true</em:multiprocessCompatible>

View File

@ -106,9 +106,9 @@ const PREFS_CONFIG = new Map([
title: "Collapse the Top Sites section",
value: false
}],
["topSitesCount", {
title: "Number of Top Sites to display",
value: 12
["topSitesRows", {
title: "Number of rows of Top Sites to display",
value: 2
}],
["telemetry", {
title: "Enable system error and usage data collection",

View File

@ -26,7 +26,7 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
/**
* ActivityStreamMessageChannel - This module connects a Redux store to a RemotePageManager in Firefox.
* Call .createChannel to start the connection, and .destroyChannel to destroy it.
* You should use the BroadcastToContent, SendToContent, and SendToMain action creators
* You should use the BroadcastToContent, AlsoToOneContent, and AlsoToMain action creators
* in common/Actions.jsm to help you create actions that will be automatically routed
* to the correct location.
*
@ -51,7 +51,7 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
}
/**
* middleware - Redux middleware that looks for SendToContent and BroadcastToContent type
* middleware - Redux middleware that looks for AlsoToOneContent and BroadcastToContent type
* actions, and sends them out.
*
* @param {object} store A redux store
@ -64,7 +64,7 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
next(action);
return;
}
if (au.isSendToContent(action)) {
if (au.isSendToOneContent(action)) {
this.send(action);
} else if (au.isBroadcastToContent(action)) {
this.broadcast(action);
@ -85,7 +85,7 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
* @param {string} targetId The portID of the port that sent the message
*/
onActionFromContent(action, targetId) {
this.dispatch(ac.SendToMain(action, targetId));
this.dispatch(ac.AlsoToMain(action, targetId));
}
/**

View File

@ -10,7 +10,7 @@ const {actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/
const {shortURL} = ChromeUtils.import("resource://activity-stream/lib/ShortURL.jsm", {});
const {SectionsManager} = ChromeUtils.import("resource://activity-stream/lib/SectionsManager.jsm", {});
const {TOP_SITES_SHOWMORE_LENGTH} = ChromeUtils.import("resource://activity-stream/common/Reducers.jsm", {});
const {TOP_SITES_DEFAULT_ROWS, TOP_SITES_MAX_SITES_PER_ROW} = ChromeUtils.import("resource://activity-stream/common/Reducers.jsm", {});
const {Dedupe} = ChromeUtils.import("resource://activity-stream/common/Dedupe.jsm", {});
ChromeUtils.defineModuleGetter(this, "filterAdult",
@ -25,7 +25,7 @@ ChromeUtils.defineModuleGetter(this, "PageThumbs",
"resource://gre/modules/PageThumbs.jsm");
const HIGHLIGHTS_MAX_LENGTH = 9;
const MANY_EXTRA_LENGTH = HIGHLIGHTS_MAX_LENGTH * 5 + TOP_SITES_SHOWMORE_LENGTH;
const MANY_EXTRA_LENGTH = HIGHLIGHTS_MAX_LENGTH * 5 + TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW;
const SECTION_ID = "highlights";
this.HighlightsFeed = class HighlightsFeed {

View File

@ -23,7 +23,7 @@ this.NewTabInit = class NewTabInit {
}
const action = {type: at.NEW_TAB_INITIAL_STATE, data: this.store.getState()};
this.store.dispatch(ac.SendToContent(action, target));
this.store.dispatch(ac.AlsoToOneContent(action, target));
// Remember that this early tab has already gotten a rehydration response in
// case it thought we lost its initial REQUEST and asked again

View File

@ -247,7 +247,10 @@ class PlacesFeed {
* Open a link in a desired destination defaulting to action's event.
*/
openLink(action, where = "", isPrivate = false) {
const params = {private: isPrivate};
const params = {
private: isPrivate,
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})
};
// Always include the referrer (even for http links) if we have one
const {event, referrer} = action.data;

View File

@ -82,7 +82,7 @@ const BUILT_IN_SECTIONS = {
};
const SectionsManager = {
ACTIONS_TO_PROXY: ["SYSTEM_TICK", "NEW_TAB_LOAD"],
ACTIONS_TO_PROXY: ["WEBEXT_CLICK", "WEBEXT_DISMISS"],
CONTEXT_MENU_PREFS: {"SaveToPocket": "extensions.pocket.enabled"},
initialized: false,
sections: new Map(),
@ -223,6 +223,13 @@ const SectionsManager = {
this.emit(this.UPDATE_SECTION_CARD, id, url, options, shouldBroadcast);
}
},
removeSectionCard(sectionId, url) {
if (!this.sections.has(sectionId)) {
return;
}
const rows = this.sections.get(sectionId).rows.filter(row => row.url !== url);
this.updateSection(sectionId, {rows}, true);
},
onceInitialized(callback) {
if (this.initialized) {
callback();
@ -294,14 +301,14 @@ class SectionsFeed {
onUpdateSection(event, id, options, shouldBroadcast = false) {
if (options) {
const action = {type: at.SECTION_UPDATE, data: Object.assign(options, {id})};
this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.SendToPreloaded(action));
this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.AlsoToPreloaded(action));
}
}
onUpdateSectionCard(event, id, url, options, shouldBroadcast = false) {
if (options) {
const action = {type: at.SECTION_UPDATE_CARD, data: {id, url, options}};
this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.SendToPreloaded(action));
this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.AlsoToPreloaded(action));
}
}
@ -327,6 +334,11 @@ class SectionsFeed {
case at.PLACES_BOOKMARK_ADDED:
SectionsManager.updateBookmarkMetadata(action.data);
break;
case at.WEBEXT_DISMISS:
if (action.data) {
SectionsManager.removeSectionCard(action.data.source, action.data.url);
}
break;
case at.SECTION_DISABLE:
SectionsManager.disableSection(action.data);
break;

View File

@ -8,7 +8,7 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {actionCreators: ac, actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});
const {TippyTopProvider} = ChromeUtils.import("resource://activity-stream/lib/TippyTopProvider.jsm", {});
const {insertPinned, TOP_SITES_SHOWMORE_LENGTH} = ChromeUtils.import("resource://activity-stream/common/Reducers.jsm", {});
const {insertPinned, TOP_SITES_DEFAULT_ROWS, TOP_SITES_MAX_SITES_PER_ROW} = ChromeUtils.import("resource://activity-stream/common/Reducers.jsm", {});
const {Dedupe} = ChromeUtils.import("resource://activity-stream/common/Dedupe.jsm", {});
const {shortURL} = ChromeUtils.import("resource://activity-stream/lib/ShortURL.jsm", {});
@ -74,9 +74,8 @@ this.TopSitesFeed = class TopSitesFeed {
}
async getLinksWithDefaults(action) {
// Get at least SHOWMORE amount so toggling between 1 and 2 rows has sites
const numItems = Math.max(this.store.getState().Prefs.values.topSitesCount,
TOP_SITES_SHOWMORE_LENGTH);
// Get at least TOP_SITES_DEFAULT_ROWS (2) amount so toggling between 1 and 2 rows has sites
const numItems = Math.max(this.store.getState().Prefs.values.topSitesRows, TOP_SITES_DEFAULT_ROWS) * TOP_SITES_MAX_SITES_PER_ROW;
const frecent = (await this.frecentCache.request({
numItems,
topsiteFrecency: FRECENCY_THRESHOLD
@ -159,7 +158,7 @@ this.TopSitesFeed = class TopSitesFeed {
this.store.dispatch(ac.BroadcastToContent(newAction));
} else {
// Don't broadcast only update the state and update the preloaded tab.
this.store.dispatch(ac.SendToPreloaded(newAction));
this.store.dispatch(ac.AlsoToPreloaded(newAction));
}
}
@ -226,8 +225,13 @@ this.TopSitesFeed = class TopSitesFeed {
*/
pin(action) {
const {site, index} = action.data;
this._pinSiteAt(site, index);
this._broadcastPinnedSitesUpdated();
// If valid index provided, pin at that position
if (index >= 0) {
this._pinSiteAt(site, index);
this._broadcastPinnedSitesUpdated();
} else {
this.insert(action);
}
}
/**
@ -246,7 +250,7 @@ this.TopSitesFeed = class TopSitesFeed {
// Don't insert any pins past the end of the visible top sites. Otherwise,
// we can end up with a bunch of pinned sites that can never be unpinned again
// from the UI.
const {topSitesCount} = this.store.getState().Prefs.values;
const topSitesCount = this.store.getState().Prefs.values.topSitesRows * TOP_SITES_MAX_SITES_PER_ROW;
if (index >= topSitesCount) {
return;
}
@ -285,11 +289,17 @@ this.TopSitesFeed = class TopSitesFeed {
* Handle an insert (drop/add) action of a site.
*/
insert(action) {
let {index} = action.data;
// Treat invalid pin index values (e.g., -1, undefined) as insert in the first position
if (!(index > 0)) {
index = 0;
}
// Inserting a top site pins it in the specified slot, pushing over any link already
// pinned in the slot (unless it's the last slot, then it replaces).
this._insertPin(
action.data.site, action.data.index || 0,
action.data.draggedFromIndex !== undefined ? action.data.draggedFromIndex : this.store.getState().Prefs.values.topSitesCount);
action.data.site, index,
action.data.draggedFromIndex !== undefined ? action.data.draggedFromIndex : this.store.getState().Prefs.values.topSitesRows * TOP_SITES_MAX_SITES_PER_ROW);
this._broadcastPinnedSitesUpdated();
}

View File

@ -317,8 +317,8 @@ this.TopStoriesFeed = class TopStoriesFeed {
rows.splice(2, 0, spocs[0]);
// Send a content update to the target tab
const action = {type: at.SECTION_UPDATE, meta: {skipMain: true}, data: Object.assign({rows}, {id: SECTION_ID})};
this.store.dispatch(ac.SendToContent(action, target));
const action = {type: at.SECTION_UPDATE, data: Object.assign({rows}, {id: SECTION_ID})};
this.store.dispatch(ac.OnlyToOneContent(action, target));
return false;
};

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Lami tam obedo {provider}",
"header_bookmarks_placeholder": "Pud i pee ki alamabuk.",
"header_stories_from": "ki bot",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Yab jami ayera pi {title}",
"type_label_visited": "Kilimo",
"type_label_bookmarked": "Kiketo alamabuk",
"type_label_synced": "Kiribo ki i nyonyo mukene",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Yub kakube man",
"edit_topsites_dismiss_button": "Kwer kakube man",
"edit_topsites_add_button": "Medi",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Med Kakube maloyo",
"topsites_form_add_header": "Kakube maloyo manyen",
"topsites_form_edit_header": "Yub Kakube maloyo",
"topsites_form_title_placeholder": "Ket wiye",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "{provider} məsləhət görür",
"header_bookmarks_placeholder": "Hələlik heç əlfəcininiz yoxdur.",
"header_stories_from": "qaynaq:",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "{title} üçün kontekst menyusunu aç",
"type_label_visited": "Ziyarət edilib",
"type_label_bookmarked": "Əlfəcinlənib",
"type_label_synced": "Digər cihazdan sync edilib",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Bu saytı düzəlt",
"edit_topsites_dismiss_button": "Bu saytı çıxart",
"edit_topsites_add_button": "Əlavə et",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Qabaqcıl sayt əlavə et",
"topsites_form_add_header": "Yeni Qabaqcıl Saytlar",
"topsites_form_edit_header": "Qabaqcıl Saytları Dəyişdir",
"topsites_form_title_placeholder": "Başlıq daxil et",

View File

@ -2,7 +2,7 @@
window.gActivityStreamStrings = {
"newtab_page_title": "Нов раздел",
"default_label_loading": "Зареждане…",
"header_top_sites": "Най-посещавани",
"header_top_sites": "Често посещавани",
"header_stories": "Популярни",
"header_highlights": "Акценти",
"header_visit_again": "Посещаване",
@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Препоръчано от {provider}",
"header_bookmarks_placeholder": "Все още нямате отметки.",
"header_stories_from": "от",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Отваряне на контекстуалното меню на {title}",
"type_label_visited": "Посетена",
"type_label_bookmarked": "Отметната",
"type_label_synced": "Синхронизирана от друго устройство",
@ -54,8 +54,8 @@ window.gActivityStreamStrings = {
"settings_pane_body2": "Изберете какво да виждате на тази страница.",
"settings_pane_search_header": "Търсене",
"settings_pane_search_body": "Търсете в мрежата от нов раздел.",
"settings_pane_topsites_header": "Най-посещавани",
"settings_pane_topsites_body": "Достъп до сайтовете, които посещавате най-често.",
"settings_pane_topsites_header": "Често посещавани",
"settings_pane_topsites_body": "Достъп до страниците, които посещавате най-често.",
"settings_pane_topsites_options_showmore": "Показване на два реда",
"settings_pane_bookmarks_header": "Последни отметки",
"settings_pane_bookmarks_body": "Всички нови отметки на едно място.",
@ -70,7 +70,7 @@ window.gActivityStreamStrings = {
"settings_pane_done_button": "Готово",
"settings_pane_topstories_options_sponsored": "Показване на платени статии",
"edit_topsites_button_text": "Редактиране",
"edit_topsites_button_label": "Настройки на най-посещаваните",
"edit_topsites_button_label": "Настройки на често посещаваните",
"edit_topsites_showmore_button": "Повече",
"edit_topsites_showless_button": "По-малко",
"edit_topsites_done_button": "Готово",
@ -79,9 +79,9 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Редактиране",
"edit_topsites_dismiss_button": "Изтриване",
"edit_topsites_add_button": "Добавяне",
"edit_topsites_add_button_tooltip": "Add Top Site",
"topsites_form_add_header": "Нов най-посещаван сайт",
"topsites_form_edit_header": "Редактиране на най-посещаван сайт",
"edit_topsites_add_button_tooltip": "Добавяне към често посещаваните страници",
"topsites_form_add_header": "Нов често посещавана страница",
"topsites_form_edit_header": "Редактиране на често посещавана страница",
"topsites_form_title_placeholder": "Заглавие",
"topsites_form_url_placeholder": "Адрес",
"topsites_form_add_button": "Добавяне",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "{provider} দ্বারা সুপারিশকৃত",
"header_bookmarks_placeholder": "এখনও কোন বুকমার্ক নেই।",
"header_stories_from": "থেকে",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "{title} থেকে কনটেক্সট মেনু খুলুন",
"type_label_visited": "পরিদর্শিত",
"type_label_bookmarked": "বুকমার্ক করা হয়েছে",
"type_label_synced": "অন্য ডিভাইস থেকে সিঙ্ক করা হয়েছে",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "সাইটটি সম্পাদনা করুন",
"edit_topsites_dismiss_button": "সাইটটি মুছে দিন",
"edit_topsites_add_button": "যোগ",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "টপ সাইট যোগ করুন",
"topsites_form_add_header": "নতুন শীর্ষ সাইট",
"topsites_form_edit_header": "শীর্ষ সাইট সম্পাদনা করুন",
"topsites_form_title_placeholder": "নাম দিন",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "{provider} দ্বারা সুপারিশকৃত",
"header_bookmarks_placeholder": "এখনও কোন বুকমার্ক নেই।",
"header_stories_from": "থেকে",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "{title} থেকে কনটেক্সট মেনু খুলুন",
"type_label_visited": "দেখা হয়েছে",
"type_label_bookmarked": "বুকমার্ক করা হয়েছে",
"type_label_synced": "অন্য ডিভাইস থেকে সিঙ্ক করা হয়েছে",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "সাইটটি সম্পাদনা করুন",
"edit_topsites_dismiss_button": "সাইটটি মুছে দিন",
"edit_topsites_add_button": "যুক্ত করুন",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "টপ সাইট যোগ করুন",
"topsites_form_add_header": "নতুন শীর্ষ সাইট",
"topsites_form_edit_header": "শীর্ষ সাইট সম্পাদনা করুন",
"topsites_form_title_placeholder": "একটি শিরোনাম লিখুন",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Argymhellwyd gan {provider}",
"header_bookmarks_placeholder": "Nid oes gennych unrhyw nodau tudalen eto.",
"header_stories_from": "oddi wrth",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Agor dewislen cynnwys {title}",
"type_label_visited": "Ymwelwyd",
"type_label_bookmarked": "Nod Tudalen",
"type_label_synced": "Cydweddwyd o ddyfais arall",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Golygu'r wefan",
"edit_topsites_dismiss_button": "Dileu'r wefan",
"edit_topsites_add_button": "Ychwanegu",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Ychwanegu Prif Wefan",
"topsites_form_add_header": "Hoff Wefan Newydd",
"topsites_form_edit_header": "Golygu'r Hoff Wefan",
"topsites_form_title_placeholder": "Rhoi teitl",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Rekomendita de {provider}",
"header_bookmarks_placeholder": "Vi ankoraŭ ne havas legosignojn.",
"header_stories_from": "el",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Malfermi kuntekstan menu por {title}",
"type_label_visited": "Vizitita",
"type_label_bookmarked": "Kun legosigno",
"type_label_synced": "Spegulitaj el alia aparato",
@ -79,9 +79,9 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Redakti ĉi tiun retejon",
"edit_topsites_dismiss_button": "Ignori ĉi tiun retejon",
"edit_topsites_add_button": "Aldoni",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Aldoni oftan retejon",
"topsites_form_add_header": "Nova ofta retejo",
"topsites_form_edit_header": "Redakti ofta retejo",
"topsites_form_edit_header": "Redakti oftan retejon",
"topsites_form_title_placeholder": "Tajpu titolon",
"topsites_form_url_placeholder": "Tajpu aŭ alguu retadreson",
"topsites_form_add_button": "Aldoni",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Recomendado por {provider}",
"header_bookmarks_placeholder": "Aún no tienes ningún marcador.",
"header_stories_from": "de",
"context_menu_button_sr": "Abrir menú de contexto para {title}",
"context_menu_button_sr": "Abrir menú contextual para {title}",
"type_label_visited": "Visitados",
"type_label_bookmarked": "Marcados",
"type_label_synced": "Sincronizado desde otro dispositivo",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Editar este sitio",
"edit_topsites_dismiss_button": "Descartar este sitio",
"edit_topsites_add_button": "Agregar",
"edit_topsites_add_button_tooltip": "Añadir sitio popular",
"edit_topsites_add_button_tooltip": "Agregar sitio popular",
"topsites_form_add_header": "Nuevo sitio popular",
"topsites_form_edit_header": "Editar sitio popular",
"topsites_form_title_placeholder": "Introducir un título",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "{provider} soovitab",
"header_bookmarks_placeholder": "Sul pole veel järjehoidjaid.",
"header_stories_from": "allikast",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Ava {title} kontekstimenüü",
"type_label_visited": "Külastatud",
"type_label_bookmarked": "Järjehoidjatest",
"type_label_synced": "Sünkroniseeritud teisest seadmest",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Muuda seda saiti",
"edit_topsites_dismiss_button": "Peida see sait",
"edit_topsites_add_button": "Lisa",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Top saidi lisamine",
"topsites_form_add_header": "Uue top saidi lisamine",
"topsites_form_edit_header": "Top saidi muutmine",
"topsites_form_title_placeholder": "Sisesta pealkiri",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "پیشنهاد شده توسط {provider}",
"header_bookmarks_placeholder": "هنوز هیچ نشانکی ندارید.",
"header_stories_from": "از",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "بازکردن فهرست زمینه برای {title}",
"type_label_visited": "مشاهده شده",
"type_label_bookmarked": "نشانک شده",
"type_label_synced": "هم‌گام شده از دستگاهی دیگر",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "ویرایش این سایت",
"edit_topsites_dismiss_button": "نادیده گرفتن این سایت",
"edit_topsites_add_button": "افزودن",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "اضافه کردن به سایت های برتر",
"topsites_form_add_header": "سایت برتر جدید",
"topsites_form_edit_header": "ویرایش سایت برتر",
"topsites_form_title_placeholder": "عنوان را وارد کنید",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "{provider} द्वारा अनुशंसित",
"header_bookmarks_placeholder": "आपके पास अभी तक कोई भी पुस्तचिन्ह नहीं है.",
"header_stories_from": "के द्वारा",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "{title} के लिए कॉन्टेक्स्ट मेनू खोलें",
"type_label_visited": "देखी गई",
"type_label_bookmarked": "पुस्तचिह्न लगाया हुआ",
"type_label_synced": "किसी अन्य उपकरण से समकालीन किया गया",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "इस साइट को संपादित करें",
"edit_topsites_dismiss_button": "इस साइट को ख़ारिज करें",
"edit_topsites_add_button": "जोड़ें",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "शीर्ष साइट जोड़ें",
"topsites_form_add_header": "नई शीर्ष साइट",
"topsites_form_edit_header": "शीर्ष साइट संपादित करें",
"topsites_form_title_placeholder": "एक शीर्षक दर्ज करें",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Consigliati da {provider}",
"header_bookmarks_placeholder": "Non è ancora disponibile alcun segnalibro.",
"header_stories_from": "da",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Apri menu contestuale per {title}",
"type_label_visited": "Visitato",
"type_label_bookmarked": "Nei segnalibri",
"type_label_synced": "Sincronizzato da un altro dispositivo",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Modifica questo sito",
"edit_topsites_dismiss_button": "Ignora questo sito",
"edit_topsites_add_button": "Aggiungi",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Aggiungi sito principale",
"topsites_form_add_header": "Nuovi sito principale",
"topsites_form_edit_header": "Modifica sito principale",
"topsites_form_title_placeholder": "Inserire un titolo",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "რეკომენდებულია {provider}-ის მიერ",
"header_bookmarks_placeholder": "სანიშნები ჯერ არაა დამატებული.",
"header_stories_from": "მომწოდებელი:",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "კონტექსტური მენიუს გახსნა {title}",
"type_label_visited": "მონახულებული",
"type_label_bookmarked": "ჩანიშნული",
"type_label_synced": "სხვა მოწყობილობიდან დასინქრონებული",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "საიტის ჩასწორება",
"edit_topsites_dismiss_button": "საიტის დამალვა",
"edit_topsites_add_button": "დამატება",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "რჩეული საიტის დამატება",
"topsites_form_add_header": "ახალი საიტი რჩეულებში",
"topsites_form_edit_header": "რჩეული საიტების ჩასწორება",
"topsites_form_title_placeholder": "სათაურის შეყვანა",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Ұсынушы {provider}",
"header_bookmarks_placeholder": "Сізде әлі бетбелгілер жоқ.",
"header_stories_from": "ұсынған",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "{title} үшін контекст мәзірін ашу",
"type_label_visited": "Қаралған",
"type_label_bookmarked": "Бетбелгілерде",
"type_label_synced": "Басқа құрылғыдан синхрондалған",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Бұл сайтты түзету",
"edit_topsites_dismiss_button": "Бұл сайтты тайдыру",
"edit_topsites_add_button": "Қосу",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Топ сайт қосу",
"topsites_form_add_header": "Жаңа топ сайты",
"topsites_form_edit_header": "Топ сайтын түзету",
"topsites_form_title_placeholder": "Атауын енгізіңіз",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Rekomendavo „{provider}“",
"header_bookmarks_placeholder": "Jūs dar neturite adresyno įrašų.",
"header_stories_from": "iš",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Atverti kontekstinį {title} meniu",
"type_label_visited": "Aplankyti",
"type_label_bookmarked": "Adresyne",
"type_label_synced": "Sinchronizuoti iš kito įrenginio",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Redaguoti šią svetainę",
"edit_topsites_dismiss_button": "Paslėpti šią svetainę",
"edit_topsites_add_button": "Pridėti",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Pridėti mėgstamą svetainę",
"topsites_form_add_header": "Nauja mėgstama svetainė",
"topsites_form_edit_header": "Redaguoti mėgstamą svetainę",
"topsites_form_title_placeholder": "Įveskite pavadinimą",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Iesaka {provider}",
"header_bookmarks_placeholder": "Jums vēl nav nevienas grāmatzīmes.",
"header_stories_from": "no",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Atvērt izvēlni {title}",
"type_label_visited": "Apmeklēta",
"type_label_bookmarked": "Grāmatzīmēs",
"type_label_synced": "Atsūtīta no citas ierīces",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Rediģēt šo lapu",
"edit_topsites_dismiss_button": "Noraidīt šo lapu",
"edit_topsites_add_button": "Pievienot",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Pievienot populāru lapu",
"topsites_form_add_header": "Jauna populārā lapa",
"topsites_form_edit_header": "Rediģēt populārās lapas",
"topsites_form_title_placeholder": "Ievadiet nosaukumu",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Рекомендовано {provider}",
"header_bookmarks_placeholder": "У вас ещё нет каких-либо закладок.",
"header_stories_from": "от",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Открыть контекстное меню для {title}",
"type_label_visited": "Посещено",
"type_label_bookmarked": "В закладках",
"type_label_synced": "Синхронизировано с другого устройства",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Изменить этот сайт",
"edit_topsites_dismiss_button": "Скрыть этот сайт",
"edit_topsites_add_button": "Добавить",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Добавить в топ сайтов",
"topsites_form_add_header": "Новый сайт в топе",
"topsites_form_edit_header": "Изменить сайт из топа",
"topsites_form_title_placeholder": "Введите название",
@ -92,7 +92,7 @@ window.gActivityStreamStrings = {
"pocket_read_even_more": "Больше статей",
"pocket_feedback_header": "Лучшее из Интернета, отобранное более чем 25 миллионами людей.",
"pocket_description": "Откройте для себя высококачественный контент, который вы могли бы пропустить, с помощью Pocket, теперь ставшего частью Mozilla.",
"highlights_empty_state": "Начните веб-сёрфинг, и мы покажем вам здесь некоторые из замечательных статей, видеороликов и других страниц, которые вы недавно посетили или добавили в закладки.",
"highlights_empty_state": "Начните веб-сёрфинг, и мы покажем вам здесь некоторые из интересных статей, видеороликов и других страниц, которые вы недавно посетили или добавили в закладки.",
"topstories_empty_state": "Вы всё прочитали. Зайдите попозже, чтобы увидеть больше лучших статей от {provider}. Не можете ждать? Выберите популярную тему, чтобы найти больше интересных статей со всего Интернета.",
"manual_migration_explanation2": "Попробуйте Firefox с закладками, историей и паролями из другого браузера.",
"manual_migration_cancel_button": "Нет, спасибо",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Priporoča {provider}",
"header_bookmarks_placeholder": "Nimate še nobenih zaznamkov.",
"header_stories_from": "od",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Odpri priročni meni za {title}",
"type_label_visited": "Obiskano",
"type_label_bookmarked": "Med zaznamki",
"type_label_synced": "Sinhronizirano z druge naprave",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Uredi to stran",
"edit_topsites_dismiss_button": "Odstrani to stran",
"edit_topsites_add_button": "Dodaj",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Dodaj glavno stran",
"topsites_form_add_header": "Nova glavna stran",
"topsites_form_edit_header": "Uredi glavno stran",
"topsites_form_title_placeholder": "Vnesite ime",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Предложио {provider}",
"header_bookmarks_placeholder": "Још увек немате забелешке.",
"header_stories_from": "од",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Отвори мени поља за {title}",
"type_label_visited": "Посећено",
"type_label_bookmarked": "Забележено",
"type_label_synced": "Синхронизовано са другог уређаја",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "Уреди овај сајт",
"edit_topsites_dismiss_button": "Уклони овај сајт",
"edit_topsites_add_button": "Додај",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Додај омиљени сајт",
"topsites_form_add_header": "Нови омиљени сајт",
"topsites_form_edit_header": "Уреди популарне сајтове",
"topsites_form_title_placeholder": "Унесите наслов",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "{provider}చే సిఫార్సు చేయబడినది",
"header_bookmarks_placeholder": "మీకు ఇంకా ఎటువంటి ఇష్టాంశాలు లేవు.",
"header_stories_from": "నుండి",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "{title} కోసం సందర్భోచిత మెనుని తెరవండి",
"type_label_visited": "సందర్శించారు",
"type_label_bookmarked": "ఇష్టాంశంగా గుర్తుపెట్టారు",
"type_label_synced": "మరో పరికరం నుంచి సమకాలీకరించి తెచ్చుకున్నవి",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "ఈ సైటును మార్చు",
"edit_topsites_dismiss_button": "ఈ సైటుని తీసివేయి",
"edit_topsites_add_button": "జోడించు",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "అగ్రస్థాన సైటుని జోడించండి",
"topsites_form_add_header": "కొత్త మేటి సైటు",
"topsites_form_edit_header": "టాప్ సైట్ను సవరించండి",
"topsites_form_title_placeholder": "శీర్షికను నమోదు చేయండి",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "Inirekomenda ni {provider}",
"header_bookmarks_placeholder": "Wala kang anumang mga bookmark.",
"header_stories_from": "mula sa",
"context_menu_button_sr": "Open context menu for {title}",
"context_menu_button_sr": "Buksan ang menu ng konteksto para sa {title}",
"type_label_visited": "Binisita",
"type_label_bookmarked": "Bookmarked",
"type_label_synced": "Naka-sync mula sa ibang kagamitan",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "I-edit ang site na ito",
"edit_topsites_dismiss_button": "I-dismiss sa site na ito",
"edit_topsites_add_button": "Idagdag",
"edit_topsites_add_button_tooltip": "Add Top Site",
"edit_topsites_add_button_tooltip": "Magdagdag ng Nangungunang Site",
"topsites_form_add_header": "Bagong nangungunang site",
"topsites_form_edit_header": "I-edit ang nangungunang site",
"topsites_form_title_placeholder": "Magpasok ng isang pamagat",

View File

@ -3,98 +3,98 @@ window.gActivityStreamStrings = {
"newtab_page_title": "Yangi ichki oyna",
"default_label_loading": "Yuklanmoqda…",
"header_top_sites": "Ommabop saytlar",
"header_stories": "Top Stories",
"header_highlights": "Highlights",
"header_visit_again": "Visit Again",
"header_bookmarks": "Recent Bookmarks",
"header_recommended_by": "Recommended by {provider}",
"header_bookmarks_placeholder": "You dont have any bookmarks yet.",
"header_stories_from": "from",
"context_menu_button_sr": "Open context menu for {title}",
"type_label_visited": "Visited",
"type_label_bookmarked": "Bookmarked",
"type_label_synced": "Synced from another device",
"type_label_recommended": "Trending",
"type_label_open": "Open",
"type_label_topic": "Topic",
"type_label_now": "Now",
"menu_action_bookmark": "Bookmark",
"menu_action_remove_bookmark": "Remove Bookmark",
"menu_action_copy_address": "Copy Address",
"menu_action_email_link": "Email Link…",
"menu_action_open_new_window": "Open in a New Window",
"menu_action_open_private_window": "Open in a New Private Window",
"menu_action_dismiss": "Dismiss",
"menu_action_delete": "Delete from History",
"menu_action_pin": "Pin",
"menu_action_unpin": "Unpin",
"confirm_history_delete_p1": "Are you sure you want to delete every instance of this page from your history?",
"confirm_history_delete_notice_p2": "This action cannot be undone.",
"menu_action_save_to_pocket": "Save to Pocket",
"search_for_something_with": "Search for {search_term} with:",
"search_button": "Search",
"search_header": "{search_engine_name} Search",
"search_web_placeholder": "Search the Web",
"search_settings": "Change Search Settings",
"section_info_option": "Info",
"section_info_send_feedback": "Send Feedback",
"section_info_privacy_notice": "Privacy Notice",
"section_disclaimer_topstories": "The most interesting stories on the web, selected based on what you read. From Pocket, now part of Mozilla.",
"section_disclaimer_topstories_linktext": "Learn how it works.",
"section_disclaimer_topstories_buttontext": "Okay, got it",
"welcome_title": "Welcome to new tab",
"welcome_body": "Firefox will use this space to show your most relevant bookmarks, articles, videos, and pages youve recently visited, so you can get back to them easily.",
"welcome_label": "Identifying your Highlights",
"header_stories": "Ommabop maqolalar",
"header_highlights": "Ajratilgan saytlar",
"header_visit_again": "Yana tashrif buyuring",
"header_bookmarks": "Songgi xatchoplar",
"header_recommended_by": "{provider} tomonidan tavsiya qilingan",
"header_bookmarks_placeholder": "Sizda hali hech qanday xatchop yoq.",
"header_stories_from": "Sayti:",
"context_menu_button_sr": "{title} uchun menyu matnini ochish",
"type_label_visited": "Kirilgan",
"type_label_bookmarked": "Xatchopga qoshilgan",
"type_label_synced": "Boshqa qurilmadan sinxronlangan",
"type_label_recommended": "Trendda",
"type_label_open": "Ochiq",
"type_label_topic": "Mavzu",
"type_label_now": "Hozir",
"menu_action_bookmark": "Xatchop",
"menu_action_remove_bookmark": "Xatchopni olib tashlash",
"menu_action_copy_address": "Manzildan nusxa olish",
"menu_action_email_link": "E-pochta havolasi…",
"menu_action_open_new_window": "Yangi oynada ochish",
"menu_action_open_private_window": "Yangi maxfiy oynada ochish",
"menu_action_dismiss": "Rad etish",
"menu_action_delete": "Tarixdan ochirish",
"menu_action_pin": "Yopishtirish",
"menu_action_unpin": "Ajratish",
"confirm_history_delete_p1": "Ushbu sahifaning har bir nusxasini tarixingizdan ochirmoqchimisiz?",
"confirm_history_delete_notice_p2": "Bu amalni ortga qaytarib bolmaydi.",
"menu_action_save_to_pocket": "Pocket xizmatiga saqlash",
"search_for_something_with": "{search_term}ni",
"search_button": "Qidiruv",
"search_header": "{search_engine_name} Qidiruv bilan izlash",
"search_web_placeholder": "Internetda izlash",
"search_settings": "Qidiruv sozlamalarini ozgartrirish",
"section_info_option": "Malumot",
"section_info_send_feedback": "Fikr-mulohaza yuborish",
"section_info_privacy_notice": "Maxfiylik qaydlari",
"section_disclaimer_topstories": "Internetdagi eng qiziqarli maqolalar siz oqiyotgan malumotlar asosida. Hozirda Mozillaning qismiga aylangan Pocket xizmatidan.",
"section_disclaimer_topstories_linktext": "Uning qanday ishlashini organing.",
"section_disclaimer_topstories_buttontext": "Ok, tushundim",
"welcome_title": "Yangi ichki oynaga xush kelibsiz",
"welcome_body": "Firefox bu maydondan songgi tegishli xatchoplar, maqolalar, videolar va siz kirgan oxirgi saytlarni korsatish uchun foydalanadi. Demak, ularga kirish yanada osonlashadi.",
"welcome_label": "Ajratib korsatilgan saytlar aniqlanmoqda",
"time_label_less_than_minute": "<1m",
"time_label_minute": "{number}m",
"time_label_hour": "{number}h",
"time_label_day": "{number}d",
"settings_pane_button_label": "Customize your New Tab page",
"settings_pane_header": "New Tab Preferences",
"settings_pane_body2": "Choose what you see on this page.",
"settings_pane_search_header": "Search",
"settings_pane_search_body": "Search the Web from your new tab.",
"settings_pane_topsites_header": "Top Sites",
"settings_pane_topsites_body": "Access the websites you visit most.",
"settings_pane_topsites_options_showmore": "Show two rows",
"settings_pane_bookmarks_header": "Recent Bookmarks",
"settings_pane_bookmarks_body": "Your newly created bookmarks in one handy location.",
"settings_pane_visit_again_header": "Visit Again",
"settings_pane_visit_again_body": "Firefox will show you parts of your browsing history that you might want to remember or get back to.",
"settings_pane_highlights_header": "Highlights",
"settings_pane_highlights_body2": "Find your way back to interesting things youve recently visited or bookmarked.",
"settings_pane_highlights_options_bookmarks": "Bookmarks",
"settings_pane_highlights_options_visited": "Visited Sites",
"settings_pane_snippets_header": "Snippets",
"time_label_hour": "{number}s",
"time_label_day": "{number}k",
"settings_pane_button_label": "Yangi ichki oyna sahifasini sozlash",
"settings_pane_header": "Yangi ichki oyna parametrlari",
"settings_pane_body2": "Bu sahifada nimani korishni xohlasangiz, oshani tanlang.",
"settings_pane_search_header": "Izlash",
"settings_pane_search_body": "Yangi ichki oynada internetdan izlash.",
"settings_pane_topsites_header": "Ommabop saytlar",
"settings_pane_topsites_body": "Eng kop kirilgan saytlarga kirish.",
"settings_pane_topsites_options_showmore": "Ikki qatorda korsatish",
"settings_pane_bookmarks_header": "Songgi xatchoplar",
"settings_pane_bookmarks_body": "Yangi yaratilgan xatchoplar yagona qol uzatsa yetadigan joyda.",
"settings_pane_visit_again_header": "Yana tashrif buyuring",
"settings_pane_visit_again_body": "Firefox siz esalb qolmoqchi yoki yana kirmoqchi bolgan brauzer tarixi qismini korsatadi.",
"settings_pane_highlights_header": "Ajratilgan saytlar",
"settings_pane_highlights_body2": "Songgi kirilgan qiziqarli saytlar yoki xatchoplarga qaytish yolini toping.",
"settings_pane_highlights_options_bookmarks": "Xatchoplar",
"settings_pane_highlights_options_visited": "Kirilgan saytlar",
"settings_pane_snippets_header": "Parchalar",
"settings_pane_snippets_body": "Read short and sweet updates from Mozilla about Firefox, internet culture, and the occasional random meme.",
"settings_pane_done_button": "Done",
"settings_pane_topstories_options_sponsored": "Show Sponsored Stories",
"edit_topsites_button_text": "Edit",
"edit_topsites_button_label": "Customize your Top Sites section",
"edit_topsites_showmore_button": "Show More",
"edit_topsites_showless_button": "Show Fewer",
"edit_topsites_done_button": "Done",
"edit_topsites_pin_button": "Pin this site",
"edit_topsites_unpin_button": "Unpin this site",
"edit_topsites_edit_button": "Edit this site",
"edit_topsites_dismiss_button": "Dismiss this site",
"edit_topsites_add_button": "Add",
"edit_topsites_add_button_tooltip": "Add Top Site",
"topsites_form_add_header": "New Top Site",
"topsites_form_edit_header": "Edit Top Site",
"topsites_form_title_placeholder": "Enter a title",
"topsites_form_url_placeholder": "Type or paste a URL",
"topsites_form_add_button": "Add",
"topsites_form_save_button": "Save",
"topsites_form_cancel_button": "Cancel",
"topsites_form_url_validation": "Valid URL required",
"pocket_read_more": "Popular Topics:",
"pocket_read_even_more": "View More Stories",
"pocket_feedback_header": "The best of the web, curated by over 25 million people.",
"settings_pane_done_button": "Tayyor",
"settings_pane_topstories_options_sponsored": "Homiylik maqolalarini korsatish",
"edit_topsites_button_text": "Tahrirlash",
"edit_topsites_button_label": "Ommabop saytlar bolimini sozlash",
"edit_topsites_showmore_button": "Koproq",
"edit_topsites_showless_button": "Kamroq",
"edit_topsites_done_button": "Tayyor",
"edit_topsites_pin_button": "Saytni qistirish",
"edit_topsites_unpin_button": "Saytni ajratish",
"edit_topsites_edit_button": "Bu saytni tahrirlash",
"edit_topsites_dismiss_button": "Bu saytni bekor qilish",
"edit_topsites_add_button": "Qoshish",
"edit_topsites_add_button_tooltip": "Ommabop saytga qoshish",
"topsites_form_add_header": "Yangi ommabop sayt",
"topsites_form_edit_header": "Ommabop saytni tahrirlash",
"topsites_form_title_placeholder": "Nomini kiriting",
"topsites_form_url_placeholder": "URL manzilini kiriting",
"topsites_form_add_button": "Qoshish",
"topsites_form_save_button": "Saqlash",
"topsites_form_cancel_button": "Bekor qilish",
"topsites_form_url_validation": "URL manzilini bexato kiriting",
"pocket_read_more": "Mashhur mavzular:",
"pocket_read_even_more": "Yana maqolalar korish",
"pocket_feedback_header": "25 million odam tomonidan boshqariladigan eng zor veb.",
"pocket_description": "Discover high-quality content you might otherwise miss, with help from Pocket, now part of Mozilla.",
"highlights_empty_state": "Start browsing, and well show some of the great articles, videos, and other pages youve recently visited or bookmarked here.",
"topstories_empty_state": "Youve caught up. Check back later for more top stories from {provider}. Cant wait? Select a popular topic to find more great stories from around the web.",
"manual_migration_explanation2": "Try Firefox with the bookmarks, history and passwords from another browser.",
"manual_migration_cancel_button": "Yoʻq, kerak emas",
"manual_migration_import_button": "Import Now"
"manual_migration_import_button": "Hozir import qilish"
};

View File

@ -53,17 +53,17 @@ window.gActivityStreamStrings = {
"settings_pane_header": "Tùy chỉnh cho tab mới",
"settings_pane_body2": "Chọn những gì bạn thấy trên trang này.",
"settings_pane_search_header": "Tìm kiếm",
"settings_pane_search_body": "Search the Web from your new tab.",
"settings_pane_search_body": "Tìm kiếm Web từ thẻ mới của bạn.",
"settings_pane_topsites_header": "Các trang Web hàng đầu",
"settings_pane_topsites_body": "Truy cập vào các trang web mà bạn truy cập vào nhiều nhất.",
"settings_pane_topsites_options_showmore": "Hiển thị hai hàng",
"settings_pane_bookmarks_header": "Recent Bookmarks",
"settings_pane_bookmarks_header": "Trang đánh dấu gần đây",
"settings_pane_bookmarks_body": "Your newly created bookmarks in one handy location.",
"settings_pane_visit_again_header": "Visit Again",
"settings_pane_visit_again_header": "Truy cập lại",
"settings_pane_visit_again_body": "Firefox will show you parts of your browsing history that you might want to remember or get back to.",
"settings_pane_highlights_header": "Highlights",
"settings_pane_highlights_header": "Nổi bật",
"settings_pane_highlights_body2": "Find your way back to interesting things youve recently visited or bookmarked.",
"settings_pane_highlights_options_bookmarks": "Bookmarks",
"settings_pane_highlights_options_bookmarks": "Trang đánh dấu",
"settings_pane_highlights_options_visited": "Visited Sites",
"settings_pane_snippets_header": "Snippets",
"settings_pane_snippets_body": "Read short and sweet updates from Mozilla about Firefox, internet culture, and the occasional random meme.",

View File

@ -10,7 +10,7 @@ window.gActivityStreamStrings = {
"header_recommended_by": "{provider} 推薦",
"header_bookmarks_placeholder": "您還沒有任何書籤。",
"header_stories_from": "來自",
"context_menu_button_sr": "打开 {title} 的上下文菜单",
"context_menu_button_sr": "開啟 {title} 的右鍵選單",
"type_label_visited": "造訪過的網站",
"type_label_bookmarked": "已加入書籤",
"type_label_synced": "從其他裝置同步過來",
@ -79,7 +79,7 @@ window.gActivityStreamStrings = {
"edit_topsites_edit_button": "編輯此網站",
"edit_topsites_dismiss_button": "忽略此網站",
"edit_topsites_add_button": "新增",
"edit_topsites_add_button_tooltip": "添加常用网站",
"edit_topsites_add_button_tooltip": "新增熱門網站",
"topsites_form_add_header": "新增熱門網站",
"topsites_form_edit_header": "編輯熱門網站",
"topsites_form_title_placeholder": "輸入標題",

View File

@ -3,10 +3,7 @@ window.gActivityStreamPrerenderedState = {
"TopSites": {
"initialized": false,
"rows": [],
"editForm": {
"visible": false,
"index": -1
}
"editForm": null
},
"App": {
"initialized": false,
@ -21,7 +18,7 @@ window.gActivityStreamPrerenderedState = {
"migrationExpired": true,
"showTopSites": true,
"showSearch": true,
"topSitesCount": 12,
"topSitesRows": 2,
"collapseTopSites": false,
"section.highlights.collapsed": false,
"section.topstories.collapsed": false,

View File

@ -41,10 +41,10 @@ describe("ActionCreators", () => {
assert.isUndefined(action.meta.fromTarget);
});
});
describe("SendToMain", () => {
describe("AlsoToMain", () => {
it("should create the right action", () => {
const action = {type: "FOO", data: "BAR"};
const newAction = ac.SendToMain(action);
const newAction = ac.AlsoToMain(action);
assert.deepEqual(newAction, {
type: "FOO",
data: "BAR",
@ -53,24 +53,24 @@ describe("ActionCreators", () => {
});
it("should add the fromTarget if it was supplied", () => {
const action = {type: "FOO", data: "BAR"};
const newAction = ac.SendToMain(action, "port123");
const newAction = ac.AlsoToMain(action, "port123");
assert.equal(newAction.meta.fromTarget, "port123");
});
describe("isSendToMain", () => {
it("should return true if action is SendToMain", () => {
const newAction = ac.SendToMain({type: "FOO"});
it("should return true if action is AlsoToMain", () => {
const newAction = ac.AlsoToMain({type: "FOO"});
assert.isTrue(au.isSendToMain(newAction));
});
it("should return false if action is not SendToMain", () => {
it("should return false if action is not AlsoToMain", () => {
assert.isFalse(au.isSendToMain({type: "FOO"}));
});
});
});
describe("SendToContent", () => {
describe("AlsoToOneContent", () => {
it("should create the right action", () => {
const action = {type: "FOO", data: "BAR"};
const targetId = "abc123";
const newAction = ac.SendToContent(action, targetId);
const newAction = ac.AlsoToOneContent(action, targetId);
assert.deepEqual(newAction, {
type: "FOO",
data: "BAR",
@ -79,30 +79,30 @@ describe("ActionCreators", () => {
});
it("should throw if no targetId is provided", () => {
assert.throws(() => {
ac.SendToContent({type: "FOO"});
ac.AlsoToOneContent({type: "FOO"});
});
});
describe("isSendToContent", () => {
it("should return true if action is SendToContent", () => {
const newAction = ac.SendToContent({type: "FOO"}, "foo123");
assert.isTrue(au.isSendToContent(newAction));
describe("isSendToOneContent", () => {
it("should return true if action is AlsoToOneContent", () => {
const newAction = ac.AlsoToOneContent({type: "FOO"}, "foo123");
assert.isTrue(au.isSendToOneContent(newAction));
});
it("should return false if action is not SendToMain", () => {
assert.isFalse(au.isSendToContent({type: "FOO"}));
assert.isFalse(au.isSendToContent(ac.BroadcastToContent({type: "FOO"})));
it("should return false if action is not AlsoToMain", () => {
assert.isFalse(au.isSendToOneContent({type: "FOO"}));
assert.isFalse(au.isSendToOneContent(ac.BroadcastToContent({type: "FOO"})));
});
});
describe("isFromMain", () => {
it("should return true if action is SendToContent", () => {
const newAction = ac.SendToContent({type: "FOO"}, "foo123");
it("should return true if action is AlsoToOneContent", () => {
const newAction = ac.AlsoToOneContent({type: "FOO"}, "foo123");
assert.isTrue(au.isFromMain(newAction));
});
it("should return true if action is BroadcastToContent", () => {
const newAction = ac.BroadcastToContent({type: "FOO"});
assert.isTrue(au.isFromMain(newAction));
});
it("should return false if action is SendToMain", () => {
const newAction = ac.SendToMain({type: "FOO"});
it("should return false if action is AlsoToMain", () => {
const newAction = ac.AlsoToMain({type: "FOO"});
assert.isFalse(au.isFromMain(newAction));
});
});
@ -123,14 +123,14 @@ describe("ActionCreators", () => {
});
it("should return false if action is not BroadcastToContent", () => {
assert.isFalse(au.isBroadcastToContent({type: "FOO"}));
assert.isFalse(au.isBroadcastToContent(ac.SendToContent({type: "FOO"}, "foo123")));
assert.isFalse(au.isBroadcastToContent(ac.AlsoToOneContent({type: "FOO"}, "foo123")));
});
});
});
describe("SendToPreloaded", () => {
describe("AlsoToPreloaded", () => {
it("should create the right action", () => {
const action = {type: "FOO", data: "BAR"};
const newAction = ac.SendToPreloaded(action);
const newAction = ac.AlsoToPreloaded(action);
assert.deepEqual(newAction, {
type: "FOO",
data: "BAR",
@ -139,10 +139,10 @@ describe("ActionCreators", () => {
});
});
describe("isSendToPreloaded", () => {
it("should return true if action is SendToPreloaded", () => {
assert.isTrue(au.isSendToPreloaded(ac.SendToPreloaded({type: "FOO"})));
it("should return true if action is AlsoToPreloaded", () => {
assert.isTrue(au.isSendToPreloaded(ac.AlsoToPreloaded({type: "FOO"})));
});
it("should return false if action is not SendToPreloaded", () => {
it("should return false if action is not AlsoToPreloaded", () => {
assert.isFalse(au.isSendToPreloaded({type: "FOO"}));
assert.isFalse(au.isSendToPreloaded(ac.BroadcastToContent({type: "FOO"})));
});
@ -152,7 +152,7 @@ describe("ActionCreators", () => {
const data = {action: "foo"};
assert.equal(ac.UserEvent(data).data, data);
});
it("should wrap with SendToMain", () => {
it("should wrap with AlsoToMain", () => {
const action = ac.UserEvent({action: "foo"});
assert.isTrue(au.isSendToMain(action), "isSendToMain");
});
@ -162,10 +162,10 @@ describe("ActionCreators", () => {
const data = {action: "foo"};
assert.equal(ac.UndesiredEvent(data).data, data);
});
it("should wrap with SendToMain if in UI code", () => {
it("should wrap with AlsoToMain if in UI code", () => {
assert.isTrue(au.isSendToMain(ac.UndesiredEvent({action: "foo"})), "isSendToMain");
});
it("should not wrap with SendToMain if not in UI code", () => {
it("should not wrap with AlsoToMain if not in UI code", () => {
const action = ac.UndesiredEvent({action: "foo"}, BACKGROUND_PROCESS);
assert.isFalse(au.isSendToMain(action), "isSendToMain");
});
@ -175,10 +175,10 @@ describe("ActionCreators", () => {
const data = {action: "foo"};
assert.equal(ac.UndesiredEvent(data).data, data);
});
it("should wrap with SendToMain if in UI code", () => {
it("should wrap with AlsoToMain if in UI code", () => {
assert.isTrue(au.isSendToMain(ac.PerfEvent({action: "foo"})), "isSendToMain");
});
it("should not wrap with SendToMain if not in UI code", () => {
it("should not wrap with AlsoToMain if not in UI code", () => {
const action = ac.PerfEvent({action: "foo"}, BACKGROUND_PROCESS);
assert.isFalse(au.isSendToMain(action), "isSendToMain");
});
@ -188,21 +188,37 @@ describe("ActionCreators", () => {
const data = {action: "foo"};
assert.equal(ac.ImpressionStats(data).data, data);
});
it("should wrap with SendToMain if in UI code", () => {
it("should wrap with AlsoToMain if in UI code", () => {
assert.isTrue(au.isSendToMain(ac.ImpressionStats({action: "foo"})), "isSendToMain");
});
it("should not wrap with SendToMain if not in UI code", () => {
it("should not wrap with AlsoToMain if not in UI code", () => {
const action = ac.ImpressionStats({action: "foo"}, BACKGROUND_PROCESS);
assert.isFalse(au.isSendToMain(action), "isSendToMain");
});
});
describe("WebExtEvent", () => {
it("should set the provided type", () => {
const action = ac.WebExtEvent(at.WEBEXT_CLICK, {source: "MyExtension", url: "foo.com"});
assert.equal(action.type, at.WEBEXT_CLICK);
});
it("should set the provided data", () => {
const data = {source: "MyExtension", url: "foo.com"};
const action = ac.WebExtEvent(at.WEBEXT_CLICK, data);
assert.equal(action.data, data);
});
it("should throw if the 'source' property is missing", () => {
assert.throws(() => {
ac.WebExtEvent(at.WEBEXT_CLICK, {});
});
});
});
});
describe("ActionUtils", () => {
describe("getPortIdOfSender", () => {
it("should return the PortID from a SendToMain action", () => {
it("should return the PortID from a AlsoToMain action", () => {
const portID = "foo123";
const result = au.getPortIdOfSender(ac.SendToMain({type: "FOO"}, portID));
const result = au.getPortIdOfSender(ac.AlsoToMain({type: "FOO"}, portID));
assert.equal(result, portID);
});
});

View File

@ -35,18 +35,14 @@ describe("Reducers", () => {
const nextState = TopSites(undefined, {type: at.TOP_SITES_UPDATED});
assert.equal(nextState, INITIAL_STATE.TopSites);
});
it("should set editForm.visible to true on TOP_SITES_EDIT", () => {
const nextState = TopSites(undefined, {type: at.TOP_SITES_EDIT, data: {index: 3}});
assert.isTrue(nextState.editForm.visible);
});
it("should set editForm.site to action.data on TOP_SITES_EDIT", () => {
const data = {index: 7};
const nextState = TopSites(undefined, {type: at.TOP_SITES_EDIT, data});
assert.equal(nextState.editForm.index, data.index);
});
it("should set editForm.visible to false on TOP_SITES_CANCEL_EDIT", () => {
it("should set editForm to null on TOP_SITES_CANCEL_EDIT", () => {
const nextState = TopSites(undefined, {type: at.TOP_SITES_CANCEL_EDIT});
assert.isFalse(nextState.editForm.visible);
assert.isNull(nextState.editForm);
});
it("should add screenshots for SCREENSHOT_UPDATED", () => {
const oldState = {rows: [{url: "foo.com"}, {url: "bar.com"}]};

View File

@ -260,14 +260,14 @@ describe("ActivityStreamMessageChannel", () => {
const t = {portID: "foo", sendAsyncMessage: sinon.spy()};
mm.createChannel();
mm.channel.messagePorts = [t];
const action = ac.SendToContent({type: "HELLO"}, "foo");
const action = ac.AlsoToOneContent({type: "HELLO"}, "foo");
mm.send(action, "foo");
assert.calledWith(t.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
});
it("should not throw if the target isn't around", () => {
mm.createChannel();
// port is not added to the channel
const action = ac.SendToContent({type: "HELLO"}, "foo");
const action = ac.AlsoToOneContent({type: "HELLO"}, "foo");
assert.doesNotThrow(() => mm.send(action, "foo"));
});
@ -292,7 +292,7 @@ describe("ActivityStreamMessageChannel", () => {
};
mm.createChannel();
mm.channel.messagePorts.push(port);
const action = ac.SendToPreloaded({type: "HELLO", data: 10});
const action = ac.AlsoToPreloaded({type: "HELLO", data: 10});
mm.sendToPreloaded(action);
assert.calledWith(port.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
});
@ -308,7 +308,7 @@ describe("ActivityStreamMessageChannel", () => {
mm.createChannel();
mm.channel.messagePorts.push(port);
mm.channel.messagePorts.push(port);
mm.sendToPreloaded(ac.SendToPreloaded({type: "HELLO", data: 10}));
mm.sendToPreloaded(ac.AlsoToPreloaded({type: "HELLO", data: 10}));
assert.calledTwice(port.sendAsyncMessage);
});
it("should not send the message to the preloaded browser if there's no data and a preloaded browser does not exists", () => {
@ -322,7 +322,7 @@ describe("ActivityStreamMessageChannel", () => {
};
mm.createChannel();
mm.channel.messagePorts.push(port);
const action = ac.SendToPreloaded({type: "HELLO"});
const action = ac.AlsoToPreloaded({type: "HELLO"});
mm.sendToPreloaded(action);
assert.notCalled(port.sendAsyncMessage);
});
@ -331,7 +331,7 @@ describe("ActivityStreamMessageChannel", () => {
describe("Handling actions", () => {
describe("#onActionFromContent", () => {
beforeEach(() => mm.onActionFromContent({type: "FOO"}, "foo"));
it("should dispatch a SendToMain action", () => {
it("should dispatch a AlsoToMain action", () => {
assert.calledOnce(dispatch);
const [action] = dispatch.firstCall.args;
assert.equal(action.type, "FOO", "action.type");
@ -350,25 +350,25 @@ describe("ActivityStreamMessageChannel", () => {
store.dispatch({type: "ADD", data: 10});
assert.equal(store.getState(), 10);
});
it("should not call next if skipMain is true", () => {
store.dispatch({type: "ADD", data: 10, meta: {skipMain: true}});
assert.equal(store.getState(), 0);
it("should call .send but not affect the main store if an OnlyToOneContent action is dispatched", () => {
sinon.stub(mm, "send");
const action = ac.SendToContent({type: "ADD", data: 10, meta: {skipMain: true}}, "foo");
const action = ac.OnlyToOneContent({type: "ADD", data: 10}, "foo");
mm.createChannel();
store.dispatch(action);
assert.calledWith(mm.send, action);
assert.equal(store.getState(), 0);
});
it("should call .send if the action is SendToContent", () => {
it("should call .send and update the main store if an AlsoToOneContent action is dispatched", () => {
sinon.stub(mm, "send");
const action = ac.SendToContent({type: "FOO"}, "foo");
const action = ac.AlsoToOneContent({type: "ADD", data: 10}, "foo");
mm.createChannel();
store.dispatch(action);
assert.calledWith(mm.send, action);
assert.equal(store.getState(), 10);
});
it("should call .broadcast if the action is BroadcastToContent", () => {
sinon.stub(mm, "broadcast");
@ -379,9 +379,9 @@ describe("ActivityStreamMessageChannel", () => {
assert.calledWith(mm.broadcast, action);
});
it("should call .sendToPreloaded if the action is SendToPreloaded", () => {
it("should call .sendToPreloaded if the action is AlsoToPreloaded", () => {
sinon.stub(mm, "sendToPreloaded");
const action = ac.SendToPreloaded({type: "FOO"});
const action = ac.AlsoToPreloaded({type: "FOO"});
mm.createChannel();
store.dispatch(action);

View File

@ -5,7 +5,7 @@ describe("NewTabInit", () => {
let instance;
let store;
let STATE;
const requestFromTab = portID => instance.onAction(ac.SendToMain(
const requestFromTab = portID => instance.onAction(ac.AlsoToMain(
{type: at.NEW_TAB_STATE_REQUEST}, portID));
beforeEach(() => {
STATE = {};
@ -16,7 +16,7 @@ describe("NewTabInit", () => {
it("should reply with a copy of the state immediately", () => {
requestFromTab(123);
const resp = ac.SendToContent({type: at.NEW_TAB_INITIAL_STATE, data: STATE}, 123);
const resp = ac.AlsoToOneContent({type: at.NEW_TAB_INITIAL_STATE, data: STATE}, 123);
assert.calledWith(store.dispatch, resp);
});
describe("early / simulated new tabs", () => {
@ -30,7 +30,7 @@ describe("NewTabInit", () => {
it("should dispatch if not replied yet", () => {
requestFromTab("foo");
assert.calledWith(store.dispatch, ac.SendToContent({type: at.NEW_TAB_INITIAL_STATE, data: STATE}, "foo"));
assert.calledWith(store.dispatch, ac.AlsoToOneContent({type: at.NEW_TAB_INITIAL_STATE, data: STATE}, "foo"));
});
it("should dispatch once for multiple requests", () => {
requestFromTab("foo");
@ -54,11 +54,11 @@ describe("NewTabInit", () => {
});
it("should clean up when tabs close", () => {
assert.propertyVal(instance._repliedEarlyTabs, "size", 2);
instance.onAction(ac.SendToMain({type: at.NEW_TAB_UNLOAD}, "foo"));
instance.onAction(ac.AlsoToMain({type: at.NEW_TAB_UNLOAD}, "foo"));
assert.propertyVal(instance._repliedEarlyTabs, "size", 1);
instance.onAction(ac.SendToMain({type: at.NEW_TAB_UNLOAD}, "foo"));
instance.onAction(ac.AlsoToMain({type: at.NEW_TAB_UNLOAD}, "foo"));
assert.propertyVal(instance._repliedEarlyTabs, "size", 1);
instance.onAction(ac.SendToMain({type: at.NEW_TAB_UNLOAD}, "bar"));
instance.onAction(ac.AlsoToMain({type: at.NEW_TAB_UNLOAD}, "bar"));
assert.propertyVal(instance._repliedEarlyTabs, "size", 0);
});
});

View File

@ -166,6 +166,7 @@ describe("PlacesFeed", () => {
assert.equal(url, "foo.com");
assert.equal(where, "current");
assert.propertyVal(params, "private", false);
assert.propertyVal(params, "triggeringPrincipal", undefined);
});
it("should open link with referrer on OPEN_LINK", () => {
const openLinkIn = sinon.stub();

View File

@ -1,5 +1,5 @@
"use strict";
import {CONTENT_MESSAGE_TYPE, MAIN_MESSAGE_TYPE, PRELOAD_MESSAGE_TYPE} from "common/Actions.jsm";
import {actionCreators as ac, actionTypes as at, CONTENT_MESSAGE_TYPE, MAIN_MESSAGE_TYPE, PRELOAD_MESSAGE_TYPE} from "common/Actions.jsm";
import {EventEmitter, GlobalOverrider} from "test/unit/utils";
import {SectionsFeed, SectionsManager} from "lib/SectionsManager.jsm";
@ -209,6 +209,27 @@ describe("SectionsManager", () => {
assert.notCalled(spy);
});
});
describe("#removeSectionCard", () => {
it("should dispatch an SECTION_UPDATE action in which cards corresponding to the given url are removed", () => {
const rows = [{url: "foo.com"}, {url: "bar.com"}];
SectionsManager.addSection(FAKE_ID, Object.assign({}, FAKE_OPTIONS, {rows}));
const spy = sinon.spy();
SectionsManager.on(SectionsManager.UPDATE_SECTION, spy);
SectionsManager.removeSectionCard(FAKE_ID, "foo.com");
assert.calledOnce(spy);
assert.equal(spy.firstCall.args[1], FAKE_ID);
assert.deepEqual(spy.firstCall.args[2].rows, [{url: "bar.com"}]);
});
it("should do nothing if the section doesn't exist", () => {
SectionsManager.removeSection(FAKE_ID);
const spy = sinon.spy();
SectionsManager.on(SectionsManager.UPDATE_SECTION, spy);
SectionsManager.removeSectionCard(FAKE_ID, "bar.com");
assert.notCalled(spy);
});
});
describe("#updateBookmarkMetadata", () => {
beforeEach(() => {
let rows = [{
@ -464,5 +485,13 @@ describe("SectionsFeed", () => {
assert.calledOnce(stub);
});
it("should call SectionManager.removeSectionCard on WEBEXT_DISMISS", () => {
const stub = sinon.stub(SectionsManager, "removeSectionCard");
feed.onAction(ac.WebExtEvent(at.WEBEXT_DISMISS, {source: "Foo", url: "bar.com"}));
assert.calledOnce(stub);
assert.calledWith(stub, "Foo", "bar.com");
});
});
});

View File

@ -290,7 +290,7 @@ describe("TelemetryFeed", () => {
it("should create a valid event", async () => {
const portID = "foo";
const data = {source: "TOP_SITES", event: "CLICK"};
const action = ac.SendToMain(ac.UserEvent(data), portID);
const action = ac.AlsoToMain(ac.UserEvent(data), portID);
const session = instance.addSession(portID);
const ping = await instance.createUserEvent(action);
@ -315,7 +315,7 @@ describe("TelemetryFeed", () => {
it("should create a valid event with a session", async () => {
const portID = "foo";
const data = {source: "TOP_SITES", event: "MISSING_IMAGE", value: 10};
const action = ac.SendToMain(ac.UndesiredEvent(data), portID);
const action = ac.AlsoToMain(ac.UndesiredEvent(data), portID);
const session = instance.addSession(portID);
const ping = await instance.createUndesiredEvent(action);
@ -575,7 +575,7 @@ describe("TelemetryFeed", () => {
it("should call .handleNewTabInit on a NEW_TAB_INIT action", () => {
sandbox.spy(instance, "handleNewTabInit");
instance.onAction(ac.SendToMain({
instance.onAction(ac.AlsoToMain({
type: at.NEW_TAB_INIT,
data: {url: "about:newtab", browser}
}));
@ -586,7 +586,7 @@ describe("TelemetryFeed", () => {
const stub = sandbox.stub(instance, "addSession").returns({perf: {}});
sandbox.stub(instance, "setLoadTriggerInfo");
instance.onAction(ac.SendToMain({
instance.onAction(ac.AlsoToMain({
type: at.NEW_TAB_INIT,
data: {url: "about:monkeys", browser}
}, "port123"));
@ -597,7 +597,7 @@ describe("TelemetryFeed", () => {
it("should call .endSession() on a NEW_TAB_UNLOAD action", () => {
const stub = sandbox.stub(instance, "endSession");
instance.onAction(ac.SendToMain({type: at.NEW_TAB_UNLOAD}, "port123"));
instance.onAction(ac.AlsoToMain({type: at.NEW_TAB_UNLOAD}, "port123"));
assert.calledWith(stub, "port123");
});
@ -606,7 +606,7 @@ describe("TelemetryFeed", () => {
const data = {some_ts: 10};
const action = {type: at.SAVE_SESSION_PERF_DATA, data};
instance.onAction(ac.SendToMain(action, "port123"));
instance.onAction(ac.AlsoToMain(action, "port123"));
assert.calledWith(stub, "port123", data);
});
@ -655,7 +655,7 @@ describe("TelemetryFeed", () => {
sandbox.stub(instance.sessions, "get").returns(session);
sandbox.spy(instance, "handlePagePrerendered");
instance.onAction(ac.SendToMain({type: at.PAGE_PRERENDERED}));
instance.onAction(ac.AlsoToMain({type: at.PAGE_PRERENDERED}));
assert.calledOnce(instance.handlePagePrerendered);
assert.ok(session.perf.is_prerendered);
@ -669,7 +669,7 @@ describe("TelemetryFeed", () => {
const session = {perf: {}};
sandbox.stub(instance.sessions, "get").returns(session);
instance.onAction(ac.SendToMain({type: at.PAGE_PRERENDERED}));
instance.onAction(ac.AlsoToMain({type: at.PAGE_PRERENDERED}));
assert.ok(session.perf.is_prerendered);
});
@ -680,7 +680,7 @@ describe("TelemetryFeed", () => {
let preloadedBrowser = {getAttribute() { return "preloaded"; }};
sandbox.stub(instance, "addSession").returns(session);
instance.onAction(ac.SendToMain({
instance.onAction(ac.AlsoToMain({
type: at.NEW_TAB_INIT,
data: {url: "about:newtab", browser: preloadedBrowser}
}));
@ -692,7 +692,7 @@ describe("TelemetryFeed", () => {
let nonPreloadedBrowser = {getAttribute() { return ""; }};
sandbox.stub(instance, "addSession").returns(session);
instance.onAction(ac.SendToMain({
instance.onAction(ac.AlsoToMain({
type: at.NEW_TAB_INIT,
data: {url: "about:newtab", browser: nonPreloadedBrowser}
}));

View File

@ -2,14 +2,14 @@
import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
import {FakePrefs, GlobalOverrider} from "test/unit/utils";
import {insertPinned, TOP_SITES_SHOWMORE_LENGTH} from "common/Reducers.jsm";
import {insertPinned, TOP_SITES_DEFAULT_ROWS, TOP_SITES_MAX_SITES_PER_ROW} from "common/Reducers.jsm";
import injector from "inject!lib/TopSitesFeed.jsm";
import {Screenshots} from "lib/Screenshots.jsm";
const FAKE_FAVICON = "data987";
const FAKE_FAVICON_SIZE = 128;
const FAKE_FRECENCY = 200;
const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({
const FAKE_LINKS = new Array(TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW).fill(null).map((v, i) => ({
frecency: FAKE_FRECENCY,
url: `http://www.site${i}.com`
}));
@ -76,7 +76,7 @@ describe("Top Sites Feed", () => {
({TopSitesFeed, DEFAULT_TOP_SITES} = injector({
"lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs},
"common/Dedupe.jsm": {Dedupe: fakeDedupe},
"common/Reducers.jsm": {insertPinned, TOP_SITES_SHOWMORE_LENGTH},
"common/Reducers.jsm": {insertPinned, TOP_SITES_DEFAULT_ROWS, TOP_SITES_MAX_SITES_PER_ROW},
"lib/FilterAdult.jsm": {filterAdult: filterAdultStub},
"lib/Screenshots.jsm": {Screenshots: fakeScreenshot},
"lib/TippyTopProvider.jsm": {TippyTopProvider: FakeTippyTopProvider},
@ -87,7 +87,7 @@ describe("Top Sites Feed", () => {
dispatch: sinon.spy(),
getState() { return this.state; },
state: {
Prefs: {values: {filterAdult: false, topSitesCount: 6}},
Prefs: {values: {filterAdult: false, topSitesRows: 2}},
TopSites: {rows: Array(12).fill("site")}
}
};
@ -219,15 +219,16 @@ describe("Top Sites Feed", () => {
assert.deepEqual(result, reference);
});
it("should only add defaults up to TOP_SITES_SHOWMORE_LENGTH", async () => {
it("should only add defaults up to the number of visible slots", async () => {
links = [];
for (let i = 0; i < TOP_SITES_SHOWMORE_LENGTH - 1; i++) {
const numVisible = TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW;
for (let i = 0; i < numVisible - 1; i++) {
links.push({frecency: FAKE_FRECENCY, url: `foo${i}.com`});
}
const result = await feed.getLinksWithDefaults();
const reference = [...links, DEFAULT_TOP_SITES[0]].map(s => Object.assign({}, s, {hostname: shortURLStub(s)}));
assert.lengthOf(result, TOP_SITES_SHOWMORE_LENGTH);
assert.lengthOf(result, numVisible);
assert.deepEqual(result, reference);
});
it("should not throw if NewTabUtils returns null", () => {
@ -237,11 +238,15 @@ describe("Top Sites Feed", () => {
});
});
it("should get more if the user has asked for more", async () => {
feed.store.state.Prefs.values.topSitesCount = TOP_SITES_SHOWMORE_LENGTH + 1;
links = new Array(4 * TOP_SITES_MAX_SITES_PER_ROW).fill(null).map((v, i) => ({
frecency: FAKE_FRECENCY,
url: `http://www.site${i}.com`
}));
feed.store.state.Prefs.values.topSitesRows = 3;
const result = await feed.getLinksWithDefaults();
assert.propertyVal(result, "length", feed.store.state.Prefs.values.topSitesCount);
assert.propertyVal(result, "length", feed.store.state.Prefs.values.topSitesRows * TOP_SITES_MAX_SITES_PER_ROW);
});
});
describe("caching", () => {
@ -253,7 +258,7 @@ describe("Top Sites Feed", () => {
});
it("should ignore the cache when requesting more", async () => {
await feed.getLinksWithDefaults();
feed.store.state.Prefs.values.topSitesCount *= 3;
feed.store.state.Prefs.values.topSitesRows *= 3;
await feed.getLinksWithDefaults();
@ -325,7 +330,7 @@ describe("Top Sites Feed", () => {
beforeEach(() => {
({TopSitesFeed, DEFAULT_TOP_SITES} = injector({
"lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs},
"common/Reducers.jsm": {insertPinned, TOP_SITES_SHOWMORE_LENGTH},
"common/Reducers.jsm": {insertPinned, TOP_SITES_DEFAULT_ROWS, TOP_SITES_MAX_SITES_PER_ROW},
"lib/Screenshots.jsm": {Screenshots: fakeScreenshot}
}));
sandbox.stub(global.Services.eTLD, "getPublicSuffix").returns("com");
@ -339,7 +344,7 @@ describe("Top Sites Feed", () => {
const sites = await feed.getLinksWithDefaults();
assert.lengthOf(sites, TOP_SITES_SHOWMORE_LENGTH);
assert.lengthOf(sites, TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW);
assert.equal(sites[0].url, fakeNewTabUtils.pinnedLinks.links[0].url);
assert.equal(sites[1].url, fakeNewTabUtils.pinnedLinks.links[1].url);
assert.equal(sites[0].hostname, sites[1].hostname);
@ -425,12 +430,12 @@ describe("Top Sites Feed", () => {
await feed.refresh({broadcast: true});
assert.calledOnce(feed.store.dispatch);
});
it("should dispatch SendToPreloaded when broadcast is false", async () => {
it("should dispatch AlsoToPreloaded when broadcast is false", async () => {
sandbox.stub(feed, "getLinksWithDefaults").returns([]);
await feed.refresh({broadcast: false});
assert.calledOnce(feed.store.dispatch);
assert.calledWithExactly(feed.store.dispatch, ac.SendToPreloaded({
assert.calledWithExactly(feed.store.dispatch, ac.AlsoToPreloaded({
type: at.TOP_SITES_UPDATED,
data: []
}));
@ -645,7 +650,7 @@ describe("Top Sites Feed", () => {
assert.calledWith(fakeNewTabUtils.pinnedLinks.pin, site, 0);
assert.calledWith(fakeNewTabUtils.pinnedLinks.pin, site1, 1);
});
it("should unpin the site if all slots are already pinned", () => {
it("should unpin the last site if all slots are already pinned", () => {
const site1 = {url: "example.com"};
const site2 = {url: "example.org"};
const site3 = {url: "example.net"};
@ -653,6 +658,7 @@ describe("Top Sites Feed", () => {
const site5 = {url: "example.info"};
const site6 = {url: "example.news"};
fakeNewTabUtils.pinnedLinks.links = [site1, site2, site3, site4, site5, site6];
feed.store.state.Prefs.values.topSitesRows = 1;
const site = {url: "foo.bar", label: "foo"};
feed.insert({data: {site}});
assert.equal(fakeNewTabUtils.pinnedLinks.pin.callCount, 6);
@ -703,8 +709,41 @@ describe("Top Sites Feed", () => {
pinnedLinks = await feed.pinnedCache.request();
assert.propertyVal(pinnedLinks[0], "screenshot", "bar");
});
it("should call insert if index < 0", () => {
const site = {url: "foo.bar", label: "foo"};
const action = {data: {index: -1, site}};
sandbox.spy(feed, "insert");
feed.pin(action);
assert.calledOnce(feed.insert);
assert.calledWithExactly(feed.insert, action);
});
it("should not call insert if index == 0", () => {
const site = {url: "foo.bar", label: "foo"};
const action = {data: {index: 0, site}};
sandbox.spy(feed, "insert");
feed.pin(action);
assert.notCalled(feed.insert);
});
});
describe("#drop", () => {
it("should correctly handle different index values", () => {
let index = -1;
const site = {url: "foo.bar", label: "foo"};
const action = {data: {index, site}};
feed.insert(action);
assert.calledWith(fakeNewTabUtils.pinnedLinks.pin, site, 0);
index = undefined;
feed.insert(action);
assert.calledWith(fakeNewTabUtils.pinnedLinks.pin, site, 0);
});
it("should pin site in specified slot that is free", () => {
fakeNewTabUtils.pinnedLinks.links = [null, {url: "example.com"}];
const site = {url: "foo.bar", label: "foo"};
@ -734,7 +773,7 @@ describe("Top Sites Feed", () => {
assert.calledWith(fakeNewTabUtils.pinnedLinks.pin, site1, 2);
assert.calledWith(fakeNewTabUtils.pinnedLinks.pin, site2, 3);
});
it("should not insert past the topSitesCount", () => {
it("should not insert past the visible top sites", () => {
const site1 = {url: "foo.bar", label: "foo"};
feed.insert({data: {index: 42, site: site1, draggedFromIndex: 0}});
assert.notCalled(fakeNewTabUtils.pinnedLinks.pin);

View File

@ -59,10 +59,25 @@ describe("initStore", () => {
store.dispatch({type: MERGE_STORE_ACTION, data: {number: 42}});
assert.deepEqual(store.getState(), {number: 42});
});
it("should send out SendToMain actions", () => {
const action = ac.SendToMain({type: "FOO"});
it("should call .send and update the local store if an AlsoToMain action is dispatched", () => {
const subscriber = sinon.spy();
const action = ac.AlsoToMain({type: "FOO"});
store.subscribe(subscriber);
store.dispatch(action);
assert.calledWith(global.sendAsyncMessage, OUTGOING_MESSAGE_NAME, action);
assert.calledOnce(subscriber);
});
it("should call .send but not update the local store if an OnlyToMain action is dispatched", () => {
const subscriber = sinon.spy();
const action = ac.OnlyToMain({type: "FOO"});
store.subscribe(subscriber);
store.dispatch(action);
assert.calledWith(global.sendAsyncMessage, OUTGOING_MESSAGE_NAME, action);
assert.notCalled(subscriber);
});
it("should not send out other types of actions", () => {
store.dispatch({type: "FOO"});
@ -70,13 +85,13 @@ describe("initStore", () => {
});
describe("rehydrationMiddleware", () => {
it("should allow NEW_TAB_STATE_REQUEST to go through", () => {
const action = ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST});
const action = ac.AlsoToMain({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 requestAction = ac.AlsoToMain({type: at.NEW_TAB_STATE_REQUEST});
const next = sinon.spy();
rehydrationMiddleware(store)(next)(requestAction);
@ -95,7 +110,7 @@ describe("initStore", () => {
const next = sinon.spy();
rehydrationMiddleware(store)(next)(ac.BroadcastToContent({type: "FOO"}));
rehydrationMiddleware(store)(next)(ac.SendToContent({type: "FOO"}, 123));
rehydrationMiddleware(store)(next)(ac.AlsoToOneContent({type: "FOO"}, 123));
assert.notCalled(next);
});
@ -110,7 +125,7 @@ describe("initStore", () => {
rehydrationMiddleware(store)(next)({type: MERGE_STORE_ACTION});
next.reset();
const action = ac.SendToContent({type: "FOO"}, 123);
const action = ac.AlsoToOneContent({type: "FOO"}, 123);
rehydrationMiddleware(store)(next)(action);
assert.calledWith(next, action);
});
@ -125,7 +140,7 @@ describe("initStore", () => {
assert.calledWith(next, action);
});
it("should allow action to main that does not belong to EARLY_QUEUED_ACTIONS to go through", () => {
const action = ac.SendToMain({type: "FOO"});
const action = ac.AlsoToMain({type: "FOO"});
const next = sinon.spy();
queueEarlyMessageMiddleware(store)(next)(action);
@ -136,8 +151,8 @@ describe("initStore", () => {
EARLY_QUEUED_ACTIONS.forEach(actionType => {
const testStore = initStore({number: addNumberReducer});
const next = sinon.spy();
const action = ac.SendToMain({type: actionType});
const fromMainAction = ac.SendToContent({type: "FOO"}, 123);
const action = ac.AlsoToMain({type: actionType});
const fromMainAction = ac.AlsoToOneContent({type: "FOO"}, 123);
// Early actions should be added to the queue
queueEarlyMessageMiddleware(testStore)(next)(action);

View File

@ -39,6 +39,10 @@ overrider.set({
},
isSuccessCode: () => true
},
ChromeUtils: {
defineModuleGetter() {},
import() {}
},
// eslint-disable-next-line object-shorthand
ContentSearchUIController: function() {}, // NB: This is a function/constructor
dump() {},
@ -90,7 +94,10 @@ overrider.set({
getVisibleEngines: () => [{identifier: "google"}, {identifier: "bing"}],
defaultEngine: {identifier: "google"}
},
scriptSecurityManager: {getSystemPrincipal() {}}
scriptSecurityManager: {
createNullPrincipal() {},
getSystemPrincipal() {}
}
},
XPCOMUtils: {
defineLazyGetter(_1, _2, f) { f(); },