Bug 1384807 - Enable Pocket (en-US/en-CA), add hero element telemetry and bug fixes to Activity Stream. r=dmose

MozReview-Commit-ID: ATqGmgJf7l0

--HG--
extra : rebase_source : fa435bf10ab5f8a896523bc9d565b510daf1d1a0
This commit is contained in:
Ed Lee 2017-07-26 20:53:10 -07:00
Родитель 250eef68d6
Коммит c9400a94de
22 изменённых файлов: 941 добавлений и 386 удалений

Просмотреть файл

@ -36,10 +36,11 @@ for (const type of [
"LOCALE_UPDATED",
"MIGRATION_CANCEL",
"MIGRATION_START",
"NEW_TAB_INIT",
"NEW_TAB_INITIAL_STATE",
"NEW_TAB_LOAD",
"NEW_TAB_UNLOAD",
"NEW_TAB_VISIBLE",
"OPEN_LINK",
"OPEN_NEW_WINDOW",
"OPEN_PRIVATE_WINDOW",
"PINNED_SITES_UPDATED",
@ -51,6 +52,7 @@ for (const type of [
"PLACES_LINK_DELETED",
"PREFS_INITIAL_VALUES",
"PREF_CHANGED",
"SAVE_SESSION_PERF_DATA",
"SAVE_TO_POCKET",
"SCREENSHOT_UPDATED",
"SECTION_DEREGISTER",

Просмотреть файл

@ -70,7 +70,17 @@ _PerfService.prototype = {
},
/**
* This returns the startTime from the most recen!t performance.mark()
* Returns the "absolute" version of performance.now(), i.e. one that
* based on the timeOrigin of the XUL hiddenwindow.
*
* @return {Number}
*/
absNow: function absNow() {
return this.timeOrigin + this._perf.now();
},
/**
* This returns the absolute startTime from the most recent performance.mark()
* with the given name.
*
* @param {String} name the name to lookup the start time for
@ -78,6 +88,14 @@ _PerfService.prototype = {
* @return {Number} the returned start time, as a DOMHighResTimeStamp
*
* @throws {Error} "No Marks with the name ..." if none are available
*
* @note Always surround calls to this by try/catch. Otherwise your code
* may fail when the `privacy.resistFingerprinting` pref is true. When
* this pref is set, all attempts to get marks will likely fail, which will
* cause this method to throw.
*
* See [bug 1369303](https://bugzilla.mozilla.org/show_bug.cgi?id=1369303)
* for more info.
*/
getMostRecentAbsMarkStartByName(name) {
let entries = this.getEntriesByName(name, "mark");

Просмотреть файл

@ -103,7 +103,7 @@ const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS :
// UNINIT: "UNINIT"
// }
const actionTypes = {};
for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "FEED_INIT", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_START", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "NEW_TAB_VISIBLE", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PINNED_SITES_UPDATED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_REGISTER", "SECTION_ROWS_UPDATE", "SET_PREF", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "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", "FEED_INIT", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PINNED_SITES_UPDATED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_REGISTER", "SECTION_ROWS_UPDATE", "SET_PREF", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
actionTypes[type] = type;
}
@ -343,13 +343,13 @@ var _require = __webpack_require__(2);
const injectIntl = _require.injectIntl;
const ContextMenu = __webpack_require__(15);
const ContextMenu = __webpack_require__(16);
var _require2 = __webpack_require__(1);
const ac = _require2.actionCreators;
const linkMenuOptions = __webpack_require__(22);
const linkMenuOptions = __webpack_require__(23);
const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow"];
class LinkMenu extends React.Component {
@ -407,6 +407,129 @@ module.exports._unconnected = LinkMenu;
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* globals Services */
let usablePerfObj;
let Cu;
const isRunningInChrome = typeof Window === "undefined";
/* istanbul ignore if */
if (isRunningInChrome) {
Cu = Components.utils;
} else {
Cu = { import() {} };
}
Cu.import("resource://gre/modules/Services.jsm");
/* istanbul ignore if */
if (isRunningInChrome) {
// Borrow the high-resolution timer from the hidden window....
usablePerfObj = Services.appShell.hiddenDOMWindow.performance;
} else {
// we must be running in content space
usablePerfObj = performance;
}
var _PerfService = function _PerfService(options) {
// For testing, so that we can use a fake Window.performance object with
// known state.
if (options && options.performanceObj) {
this._perf = options.performanceObj;
} else {
this._perf = usablePerfObj;
}
};
_PerfService.prototype = {
/**
* Calls the underlying mark() method on the appropriate Window.performance
* object to add a mark with the given name to the appropriate performance
* timeline.
*
* @param {String} name the name to give the current mark
* @return {void}
*/
mark: function mark(str) {
this._perf.mark(str);
},
/**
* Calls the underlying getEntriesByName on the appropriate Window.performance
* object.
*
* @param {String} name
* @param {String} type eg "mark"
* @return {Array} Performance* objects
*/
getEntriesByName: function getEntriesByName(name, type) {
return this._perf.getEntriesByName(name, type);
},
/**
* The timeOrigin property from the appropriate performance object.
* Used to ensure that timestamps from the add-on code and the content code
* are comparable.
*
* @return {Number} A double of milliseconds with a precision of 0.5us.
*/
get timeOrigin() {
return this._perf.timeOrigin;
},
/**
* Returns the "absolute" version of performance.now(), i.e. one that
* based on the timeOrigin of the XUL hiddenwindow.
*
* @return {Number}
*/
absNow: function absNow() {
return this.timeOrigin + this._perf.now();
},
/**
* This returns the absolute startTime from the most recent performance.mark()
* with the given name.
*
* @param {String} name the name to lookup the start time for
*
* @return {Number} the returned start time, as a DOMHighResTimeStamp
*
* @throws {Error} "No Marks with the name ..." if none are available
*
* @note Always surround calls to this by try/catch. Otherwise your code
* may fail when the `privacy.resistFingerprinting` pref is true. When
* this pref is set, all attempts to get marks will likely fail, which will
* cause this method to throw.
*
* See [bug 1369303](https://bugzilla.mozilla.org/show_bug.cgi?id=1369303)
* for more info.
*/
getMostRecentAbsMarkStartByName(name) {
let entries = this.getEntriesByName(name, "mark");
if (!entries.length) {
throw new Error(`No marks with the name ${name}`);
}
let mostRecentEntry = entries[entries.length - 1];
return this._perf.timeOrigin + mostRecentEntry.startTime;
}
};
var perfService = new _PerfService();
module.exports = {
_PerfService,
perfService
};
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -421,12 +544,12 @@ var _require2 = __webpack_require__(2);
const addLocaleData = _require2.addLocaleData,
IntlProvider = _require2.IntlProvider;
const TopSites = __webpack_require__(20);
const Search = __webpack_require__(18);
const ConfirmDialog = __webpack_require__(14);
const ManualMigration = __webpack_require__(16);
const PreferencesPane = __webpack_require__(17);
const Sections = __webpack_require__(19);
const TopSites = __webpack_require__(21);
const Search = __webpack_require__(19);
const ConfirmDialog = __webpack_require__(15);
const ManualMigration = __webpack_require__(17);
const PreferencesPane = __webpack_require__(18);
const Sections = __webpack_require__(20);
// Locales that should be displayed RTL
const RTL_LIST = ["ar", "he", "fa", "ur"];
@ -498,7 +621,7 @@ class Base extends React.Component {
module.exports = connect(state => ({ App: state.App, Prefs: state.Prefs }))(Base);
/***/ }),
/* 7 */
/* 8 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -508,7 +631,7 @@ var _require = __webpack_require__(1);
const at = _require.actionTypes;
var _require2 = __webpack_require__(23);
var _require2 = __webpack_require__(6);
const perfSvc = _require2.perfService;
@ -547,17 +670,22 @@ module.exports = class DetectUserSessionStart {
/**
* _sendEvent - Sends a message to the main process to indicate the current
* tab is now visible to the user, includes the
* visibility-change-event time in ms from the UNIX epoch.
* visibility_event_rcvd_ts time in ms from the UNIX epoch.
*/
_sendEvent() {
this._perfService.mark("visibility-change-event");
this._perfService.mark("visibility_event_rcvd_ts");
let absVisChangeTime = this._perfService.getMostRecentAbsMarkStartByName("visibility-change-event");
try {
let visibility_event_rcvd_ts = this._perfService.getMostRecentAbsMarkStartByName("visibility_event_rcvd_ts");
this.sendAsyncMessage("ActivityStream:ContentToMain", {
type: at.NEW_TAB_VISIBLE,
data: { absVisibilityChangeTime: absVisChangeTime }
});
this.sendAsyncMessage("ActivityStream:ContentToMain", {
type: at.SAVE_SESSION_PERF_DATA,
data: { visibility_event_rcvd_ts }
});
} catch (ex) {
// If this failed, it's likely because the `privacy.resistFingerprinting`
// pref is true. We should at least not blow up.
}
}
/**
@ -573,7 +701,7 @@ module.exports = class DetectUserSessionStart {
};
/***/ }),
/* 8 */
/* 9 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -658,7 +786,7 @@ module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME;
module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
/***/ }),
/* 9 */
/* 10 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -820,7 +948,8 @@ class SnippetsProvider {
// Check if the cached version of of the snippets in snippetsMap. If it's too
// old, blow away the entire snippetsMap.
const cachedVersion = this.snippetsMap.get("snippets-cached-version");
if (cachedVersion !== this.version) {
if (cachedVersion !== this.appData.version) {
this.snippetsMap.clear();
}
@ -828,16 +957,16 @@ class SnippetsProvider {
const lastUpdate = this.snippetsMap.get("snippets-last-update");
const needsUpdate = !(lastUpdate >= 0) || Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
if (needsUpdate && this.snippetsURL) {
if (needsUpdate && this.appData.snippetsURL) {
this.snippetsMap.set("snippets-last-update", Date.now());
try {
// TODO: timeout?
const response = await fetch(this.snippetsURL);
const response = await fetch(this.appData.snippetsURL);
if (response.status === 200) {
const payload = await response.text();
this.snippetsMap.set("snippets", payload);
this.snippetsMap.set("snippets-cached-version", this.version);
this.snippetsMap.set("snippets-cached-version", this.appData.version);
}
} catch (e) {
console.error(e); // eslint-disable-line no-console
@ -884,14 +1013,15 @@ class SnippetsProvider {
* init - Fetch the snippet payload and show snippets
*
* @param {obj} options
* @param {str} options.snippetsURL The URL from which we fetch snippets
* @param {int} options.version The current snippets version
* @param {str} options.elementId The id of the element of the snippets container
* @param {str} options.appData.snippetsURL The URL from which we fetch snippets
* @param {int} options.appData.version The current snippets version
* @param {str} options.elementId The id of the element in which to inject snippets
* @param {str} options.containerElementId The id of the element of element containing the snippets element
* @param {bool} options.connect Should gSnippetsMap connect to indexedDB?
*/
async init(options) {
Object.assign(this, {
snippetsURL: "",
version: 0,
appData: {},
elementId: "snippets",
containerElementId: "snippets-container",
connect: true
@ -907,6 +1037,11 @@ class SnippetsProvider {
}
}
// Cache app data values so they can be accessible from gSnippetsMap
for (const key of Object.keys(this.appData)) {
this.snippetsMap.set(`appData.${key}`, this.appData[key]);
}
// Refresh snippets, if enough time has passed.
await this._refreshSnippets();
@ -925,7 +1060,7 @@ module.exports.SNIPPETS_UPDATE_INTERVAL_MS = SNIPPETS_UPDATE_INTERVAL_MS;
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24)))
/***/ }),
/* 10 */
/* 11 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1226,13 +1361,13 @@ module.exports = {
};
/***/ }),
/* 11 */
/* 12 */
/***/ (function(module, exports) {
module.exports = ReactDOM;
/***/ }),
/* 12 */
/* 13 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1246,7 +1381,12 @@ var _require = __webpack_require__(2);
const FormattedMessage = _require.FormattedMessage;
const cardContextTypes = __webpack_require__(13);
const cardContextTypes = __webpack_require__(14);
var _require2 = __webpack_require__(1);
const ac = _require2.actionCreators,
at = _require2.actionTypes;
/**
* Card component.
@ -1257,12 +1397,14 @@ const cardContextTypes = __webpack_require__(13);
* this class. Each card will then get a context menu which reflects the actions that
* can be done on this Card.
*/
class Card extends React.Component {
constructor(props) {
super(props);
this.state = { showContextMenu: false, activeCard: null };
this.onMenuButtonClick = this.onMenuButtonClick.bind(this);
this.onMenuUpdate = this.onMenuUpdate.bind(this);
this.onLinkClick = this.onLinkClick.bind(this);
}
onMenuButtonClick(event) {
event.preventDefault();
@ -1271,6 +1413,15 @@ class Card extends React.Component {
showContextMenu: true
});
}
onLinkClick(event) {
event.preventDefault();
this.props.dispatch(ac.SendToMain({ type: at.OPEN_LINK, data: this.props.link }));
this.props.dispatch(ac.UserEvent({
event: "CLICK",
source: this.props.eventSource,
action_position: this.props.index
}));
}
onMenuUpdate(showContextMenu) {
this.setState({ showContextMenu });
}
@ -1293,7 +1444,7 @@ class Card extends React.Component {
{ className: `card-outer${isContextMenuOpen ? " active" : ""}` },
React.createElement(
"a",
{ href: link.url },
{ href: link.url, onClick: this.onLinkClick },
React.createElement(
"div",
{ className: "card" },
@ -1362,7 +1513,7 @@ class Card extends React.Component {
module.exports = Card;
/***/ }),
/* 13 */
/* 14 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1388,7 +1539,7 @@ module.exports = {
};
/***/ }),
/* 14 */
/* 15 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1507,7 +1658,7 @@ module.exports._unconnected = ConfirmDialog;
module.exports.Dialog = ConfirmDialog;
/***/ }),
/* 15 */
/* 16 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1601,7 +1752,7 @@ module.exports.ContextMenu = ContextMenu;
module.exports.ContextMenuItem = ContextMenuItem;
/***/ }),
/* 16 */
/* 17 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1679,7 +1830,7 @@ module.exports = connect()(ManualMigration);
module.exports._unconnected = ManualMigration;
/***/ }),
/* 17 */
/* 18 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1708,7 +1859,7 @@ const PreferencesInput = props => React.createElement(
React.createElement(
"label",
{ htmlFor: props.prefName },
React.createElement(FormattedMessage, { id: props.titleStringId })
React.createElement(FormattedMessage, { id: props.titleStringId, values: props.titleStringValues })
),
props.descStringId && React.createElement(
"p",
@ -1724,6 +1875,13 @@ class PreferencesPane extends React.Component {
this.handleClickOutside = this.handleClickOutside.bind(this);
this.handleChange = this.handleChange.bind(this);
this.togglePane = this.togglePane.bind(this);
// TODO This is temporary until sections register their PreferenceInput component automatically
try {
this.topStoriesOptions = JSON.parse(props.Prefs.values["feeds.section.topstories.options"]);
} catch (e) {
console.error("Problem parsing feeds.section.topstories.options", e); // eslint-disable-line no-console
}
}
componentDidMount() {
document.addEventListener("click", this.handleClickOutside);
@ -1784,8 +1942,10 @@ class PreferencesPane extends React.Component {
titleStringId: "settings_pane_search_header", descStringId: "settings_pane_search_body" }),
React.createElement(PreferencesInput, { className: "showTopSites", prefName: "showTopSites", value: prefs.showTopSites, onChange: this.handleChange,
titleStringId: "settings_pane_topsites_header", descStringId: "settings_pane_topsites_body" }),
React.createElement(PreferencesInput, { className: "showTopStories", prefName: "feeds.section.topstories", value: prefs["feeds.section.topstories"], onChange: this.handleChange,
titleStringId: "settings_pane_pocketstories_header", descStringId: "settings_pane_pocketstories_body" })
this.topStoriesOptions && React.createElement(PreferencesInput, { className: "showTopStories", prefName: "feeds.section.topstories",
value: prefs["feeds.section.topstories"], onChange: this.handleChange,
titleStringId: "header_recommended_by", titleStringValues: { provider: this.topStoriesOptions.provider_name },
descStringId: this.topStoriesOptions.provider_description })
),
React.createElement(
"section",
@ -1807,7 +1967,7 @@ module.exports.PreferencesPane = PreferencesPane;
module.exports.PreferencesInput = PreferencesInput;
/***/ }),
/* 18 */
/* 19 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1906,7 +2066,7 @@ module.exports = connect()(injectIntl(Search));
module.exports._unconnected = Search;
/***/ }),
/* 19 */
/* 20 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1924,13 +2084,14 @@ var _require2 = __webpack_require__(2);
const FormattedMessage = _require2.FormattedMessage;
const Card = __webpack_require__(12);
const Topics = __webpack_require__(21);
const Card = __webpack_require__(13);
const Topics = __webpack_require__(22);
class Section extends React.Component {
render() {
var _props = this.props;
const id = _props.id,
eventSource = _props.eventSource,
title = _props.title,
icon = _props.icon,
rows = _props.rows,
@ -1989,7 +2150,7 @@ class Section extends React.Component {
React.createElement(
"ul",
{ className: "section-list", style: { padding: 0 } },
rows.slice(0, maxCards).map((link, index) => link && React.createElement(Card, { index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions }))
rows.slice(0, maxCards).map((link, index) => link && React.createElement(Card, { index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions, eventSource: eventSource }))
),
!initialized && React.createElement(
"div",
@ -2026,7 +2187,7 @@ module.exports._unconnected = Sections;
module.exports.Section = Section;
/***/ }),
/* 20 */
/* 21 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -2047,7 +2208,12 @@ const LinkMenu = __webpack_require__(5);
var _require3 = __webpack_require__(1);
const ac = _require3.actionCreators;
const ac = _require3.actionCreators,
at = _require3.actionTypes;
var _require4 = __webpack_require__(6);
const perfSvc = _require4.perfService;
const TOP_SITES_SOURCE = "TOP_SITES";
const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"];
@ -2139,6 +2305,97 @@ class TopSite extends React.Component {
}
}
/**
* A proxy class that uses double requestAnimationFrame from
* componentDidMount to dispatch a SAVE_SESSION_PERF_DATA to the main procsess
* after the paint.
*
* This uses two callbacks because, after one callback, this part of the tree
* may have rendered but not yet reflowed. This strategy is modeled after
* https://stackoverflow.com/a/34999925 but uses a double rFA because
* we want to get to the closest reliable paint for measuring, and
* setTimeout is often throttled or queued by browsers in ways that could
* make it lag too long.
*
* XXX Should be made more generic by using this.props.children, or potentially
* even split out into a higher-order component to wrap whatever.
*
* @class TopSitesPerfTimer
* @extends {React.Component}
*/
class TopSitesPerfTimer extends React.Component {
constructor(props) {
super(props);
// Just for test dependency injection:
this.perfSvc = this.props.perfSvc || perfSvc;
this._sendPaintedEvent = this._sendPaintedEvent.bind(this);
this._timestampSent = false;
}
componentDidMount() {
this._maybeSendPaintedEvent();
}
componentDidUpdate() {
this._maybeSendPaintedEvent();
}
/**
* Call the given callback when the subsequent animation frame
* (not the upcoming one) paints.
*
* @param {Function} callback
*
* @returns void
*/
_onNextFrame(callback) {
requestAnimationFrame(() => {
requestAnimationFrame(callback);
});
}
_maybeSendPaintedEvent() {
// If we've already saved a timestamp for this session, don't do so again.
if (this._timestampSent) {
return;
}
// We don't want this to ever happen, but sometimes it does. And when it
// does (typically on the first newtab at startup time calling
// componentDidMount), the paint(s) we care about will be later (eg
// in a subsequent componentDidUpdate).
if (!this.props.TopSites.initialized) {
// XXX should send bad event
return;
}
this._onNextFrame(this._sendPaintedEvent);
}
_sendPaintedEvent() {
this.perfSvc.mark("topsites_first_painted_ts");
try {
let topsites_first_painted_ts = this.perfSvc.getMostRecentAbsMarkStartByName("topsites_first_painted_ts");
this.props.dispatch(ac.SendToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: { topsites_first_painted_ts }
}));
} catch (ex) {
// If this failed, it's likely because the `privacy.resistFingerprinting`
// pref is true. We should at least not blow up, and should continue
// to set this._timestampSent to avoid going through this again.
}
this._timestampSent = true;
}
render() {
return React.createElement(TopSites, this.props);
}
}
const TopSites = props => React.createElement(
"section",
null,
@ -2159,12 +2416,13 @@ const TopSites = props => React.createElement(
)
);
module.exports = connect(state => ({ TopSites: state.TopSites }))(TopSites);
module.exports._unconnected = TopSites;
module.exports = connect(state => ({ TopSites: state.TopSites }))(TopSitesPerfTimer);
module.exports._unconnected = TopSitesPerfTimer;
module.exports.TopSite = TopSite;
module.exports.TopSites = TopSites;
/***/ }),
/* 21 */
/* 22 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -2229,7 +2487,7 @@ module.exports._unconnected = Topics;
module.exports.Topic = Topic;
/***/ }),
/* 22 */
/* 23 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -2272,7 +2530,7 @@ module.exports = {
icon: "new-window",
action: ac.SendToMain({
type: at.OPEN_NEW_WINDOW,
data: { url: site.url }
data: { url: site.url, referrer: site.referrer }
}),
userEvent: "OPEN_NEW_WINDOW"
}),
@ -2281,7 +2539,7 @@ module.exports = {
icon: "new-window-private",
action: ac.SendToMain({
type: at.OPEN_PRIVATE_WINDOW,
data: { url: site.url }
data: { url: site.url, referrer: site.referrer }
}),
userEvent: "OPEN_PRIVATE_WINDOW"
}),
@ -2339,111 +2597,6 @@ module.exports = {
module.exports.CheckBookmark = site => site.bookmarkGuid ? module.exports.RemoveBookmark(site) : module.exports.AddBookmark(site);
module.exports.CheckPinTopSite = (site, index) => site.isPinned ? module.exports.UnpinTopSite(site) : module.exports.PinTopSite(site, index);
/***/ }),
/* 23 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* globals Services */
let usablePerfObj;
let Cu;
const isRunningInChrome = typeof Window === "undefined";
/* istanbul ignore if */
if (isRunningInChrome) {
Cu = Components.utils;
} else {
Cu = { import() {} };
}
Cu.import("resource://gre/modules/Services.jsm");
/* istanbul ignore if */
if (isRunningInChrome) {
// Borrow the high-resolution timer from the hidden window....
usablePerfObj = Services.appShell.hiddenDOMWindow.performance;
} else {
// we must be running in content space
usablePerfObj = performance;
}
var _PerfService = function _PerfService(options) {
// For testing, so that we can use a fake Window.performance object with
// known state.
if (options && options.performanceObj) {
this._perf = options.performanceObj;
} else {
this._perf = usablePerfObj;
}
};
_PerfService.prototype = {
/**
* Calls the underlying mark() method on the appropriate Window.performance
* object to add a mark with the given name to the appropriate performance
* timeline.
*
* @param {String} name the name to give the current mark
* @return {void}
*/
mark: function mark(str) {
this._perf.mark(str);
},
/**
* Calls the underlying getEntriesByName on the appropriate Window.performance
* object.
*
* @param {String} name
* @param {String} type eg "mark"
* @return {Array} Performance* objects
*/
getEntriesByName: function getEntriesByName(name, type) {
return this._perf.getEntriesByName(name, type);
},
/**
* The timeOrigin property from the appropriate performance object.
* Used to ensure that timestamps from the add-on code and the content code
* are comparable.
*
* @return {Number} A double of milliseconds with a precision of 0.5us.
*/
get timeOrigin() {
return this._perf.timeOrigin;
},
/**
* This returns the startTime from the most recen!t performance.mark()
* with the given name.
*
* @param {String} name the name to lookup the start time for
*
* @return {Number} the returned start time, as a DOMHighResTimeStamp
*
* @throws {Error} "No Marks with the name ..." if none are available
*/
getMostRecentAbsMarkStartByName(name) {
let entries = this.getEntriesByName(name, "mark");
if (!entries.length) {
throw new Error(`No marks with the name ${name}`);
}
let mostRecentEntry = entries[entries.length - 1];
return this._perf.timeOrigin + mostRecentEntry.startTime;
}
};
var perfService = new _PerfService();
module.exports = {
_PerfService,
perfService
};
/***/ }),
/* 24 */
/***/ (function(module, exports) {
@ -2485,22 +2638,22 @@ module.exports = Redux;
const React = __webpack_require__(0);
const ReactDOM = __webpack_require__(11);
const Base = __webpack_require__(6);
const ReactDOM = __webpack_require__(12);
const Base = __webpack_require__(7);
var _require = __webpack_require__(3);
const Provider = _require.Provider;
const initStore = __webpack_require__(8);
const initStore = __webpack_require__(9);
var _require2 = __webpack_require__(10);
var _require2 = __webpack_require__(11);
const reducers = _require2.reducers;
const DetectUserSessionStart = __webpack_require__(7);
const DetectUserSessionStart = __webpack_require__(8);
var _require3 = __webpack_require__(9);
var _require3 = __webpack_require__(10);
const SnippetsProvider = _require3.SnippetsProvider;
@ -2520,10 +2673,7 @@ const snippets = new SnippetsProvider();
const unsubscribe = store.subscribe(() => {
const state = store.getState();
if (state.Snippets.initialized) {
snippets.init({
snippetsURL: state.Snippets.snippetsURL,
version: state.Snippets.version
});
snippets.init({ appData: state.Snippets });
unsubscribe();
}
});

Просмотреть файл

@ -78,7 +78,7 @@ body,
body {
background: #F6F6F8;
color: #383E49;
color: #0C0C0D;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Ubuntu', 'Helvetica Neue', sans-serif;
font-size: 16px; }
@ -137,7 +137,7 @@ a {
background: #FBFBFB;
border: solid 1px #BFBFBF;
border-radius: 5px;
color: #858585;
color: #0C0C0D;
cursor: pointer;
padding: 10px 30px; }
.actions button:hover {
@ -185,6 +185,8 @@ main {
font-weight: bold;
text-transform: uppercase;
margin: 0 0 18px; }
.section-title span {
vertical-align: middle; }
.top-sites-list {
list-style: none;
@ -373,7 +375,8 @@ main {
.topic {
font-size: 13px;
color: #BFC0C7;
min-width: 780px; }
min-width: 780px;
line-height: 16px; }
.topic ul {
display: inline;
padding-left: 12px; }
@ -396,7 +399,7 @@ main {
margin-left: 5px;
background-image: url("assets/topic-show-more-12.svg");
background-repeat: no-repeat;
background-position-y: 2px; }
vertical-align: middle; }
.search-wrapper {
cursor: default;
@ -499,7 +502,7 @@ main {
background: #2B99FF;
color: #FFF; }
.context-menu > ul > li > a:hover a, .context-menu > ul > li > a:focus a {
color: #383E49; }
color: #0C0C0D; }
.context-menu > ul > li > a:hover:hover, .context-menu > ul > li > a:hover:focus, .context-menu > ul > li > a:focus:hover, .context-menu > ul > li > a:focus:focus {
color: #FFF; }
@ -734,14 +737,14 @@ main {
.card-outer .card-title {
margin: 0 0 2px;
font-size: inherit;
word-wrap: break-word; }
word-wrap: break-word;
line-height: 19px; }
.card-outer .card-description {
font-size: 12px;
margin: 0;
word-wrap: break-word;
overflow: hidden;
line-height: 18px;
max-height: 34px; }
line-height: 19px; }
.card-outer .card-context {
padding: 16px 16px 14px 14px;
position: absolute;

Просмотреть файл

@ -154,6 +154,7 @@
"header_stories": "Qabaqcıl Hekayələr",
"header_visit_again": "Təkrar ziyarət et",
"header_bookmarks": "Son Əlfəcinlər",
"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:",
"type_label_visited": "Ziyarət edilib",
@ -162,6 +163,7 @@
"type_label_recommended": "Populyar",
"type_label_open": "Açıq",
"type_label_topic": "Mövzu",
"type_label_now": "İndi",
"menu_action_bookmark": "Əlfəcinlə",
"menu_action_remove_bookmark": "Əlfəcini sil",
"menu_action_copy_address": "Ünvanı köçür",
@ -180,6 +182,7 @@
"search_header": "{search_engine_name} Axtarış",
"search_web_placeholder": "İnternetdə Axtar",
"search_settings": "Axtarış Tənzimləmələrini Dəyiş",
"section_info_option": "Məlumat",
"welcome_title": "Yeni vərəqə xoş gəldiniz",
"welcome_body": "Firefox bu səhifədə ən uyğun əlfəcin, məqalə, video və son ziyarət etdiyiniz səhifələri göstərərək onları rahat tapmağınıza kömək edəcək.",
"welcome_label": "Seçilmişləriniz təyin edilir",
@ -548,6 +551,7 @@
"header_stories": "Nejlepší příběhy",
"header_visit_again": "Znovu navštívit",
"header_bookmarks": "Nedávno přidané záložky",
"header_recommended_by": "Doporučení ze služby {provider}",
"header_bookmarks_placeholder": "Zatím nemáte uložené žádné záložky.",
"header_stories_from": "ze šlužby",
"type_label_visited": "Navštívené",
@ -556,6 +560,7 @@
"type_label_recommended": "Populární",
"type_label_open": "Otevřené",
"type_label_topic": "Téma",
"type_label_now": "Teď",
"menu_action_bookmark": "Přidat do záložek",
"menu_action_remove_bookmark": "Odebrat záložku",
"menu_action_copy_address": "Zkopírovat adresu",
@ -574,6 +579,7 @@
"search_header": "Vyhledat pomocí {search_engine_name}",
"search_web_placeholder": "Hledat na webu",
"search_settings": "Změnit nastavení vyhledávání",
"section_info_option": "Informace",
"welcome_title": "Vítejte na stránce nového panelu",
"welcome_body": "Tady Firefox zobrazí nejrelevantnější záložky, články, videa a stránky, které jste nedávno navštívili. Návrat k nim je tak velmi jednoduchý.",
"welcome_label": "Rozpoznávání Vybraných stránek",
@ -618,7 +624,11 @@
"pocket_read_even_more": "Zobrazit více příběhů",
"pocket_feedback_header": "To nejlepší na webu podle hodnocení více než 25 milionů lidí.",
"pocket_feedback_body": "Pocket, služba od Mozilly, vám pomůže najít vysoce kvalitní obsah, který byste jinak neobjevili.",
"pocket_send_feedback": "Odeslat zpětnou vazbu"
"pocket_send_feedback": "Odeslat zpětnou vazbu",
"topstories_empty_state": "Už jste všechno přečetli. Další příběhy ze služby {provider} tu najdete zase později. Ale pokud se nemůžete dočkat, vyberte své oblíbené téma a podívejte se na další velké příběhy z celého webu.",
"manual_migration_explanation": "Vyzkoušejte Firefox se svými oblíbenými stránkami a záložkami z jiného prohlížeče.",
"manual_migration_cancel_button": "Ne, děkuji",
"manual_migration_import_button": "Importovat nyní"
},
"cy": {
"newtab_page_title": "Tab Newydd",
@ -627,6 +637,7 @@
"header_stories": "Hoff Straeon",
"header_visit_again": "Ymweld Eto",
"header_bookmarks": "Nodau Tudalen Diweddar",
"header_recommended_by": "Argymhellwyd gan {provider}",
"header_bookmarks_placeholder": "Nid oes gennych unrhyw nodau tudalen eto.",
"header_stories_from": "oddi wrth",
"type_label_visited": "Ymwelwyd",
@ -635,6 +646,7 @@
"type_label_recommended": "Trendio",
"type_label_open": "Ar Agor",
"type_label_topic": "Pwnc",
"type_label_now": "Nawr",
"menu_action_bookmark": "Nod Tudalen",
"menu_action_remove_bookmark": "Tynnu Nod Tudalen",
"menu_action_copy_address": "Copïo'r Cyfeiriad",
@ -653,6 +665,7 @@
"search_header": "{search_engine_name} Chwilio",
"search_web_placeholder": "Chwilio'r We",
"search_settings": "Newid y Gosodiadau Chwilio",
"section_info_option": "Gwybodaeth",
"welcome_title": "Croeso i dab newydd",
"welcome_body": "Bydd Firefox yn defnyddio'r gofod hwn i ddangos y nodau tudalen, erthyglau, fideos a thudalennau mwyaf perthnasol i chi, a thudalennau fuoch yn ymweld â nhw'n ddiweddar, fel bod modd i chi ddychwelydd atyn nhw'n hawdd.",
"welcome_label": "Adnabod eich Goreuon",
@ -697,7 +710,11 @@
"pocket_read_even_more": "Gweld Rhagor o Straeon",
"pocket_feedback_header": "Y gorau o'r we, wedi ei gasglu gan dros 25 miliwn o bobl.",
"pocket_feedback_body": "Gall Pocket, sy'n rhan o deulu Mozilla, eich helpu i ganfod cynnwys o ansawdd uchel na fyddech wedi eu canfod fel arall.",
"pocket_send_feedback": "Anfon Adborth"
"pocket_send_feedback": "Anfon Adborth",
"topstories_empty_state": "Rydych wedi dal i fynDewch nôl rhywbryd eto am fwy o'r straeon pwysicaf gan {provider}. Methu aros? Dewiswch bwnc poblogaidd i ganfod straeon da o ar draws y we. ",
"manual_migration_explanation": "Profwch Firefox gyda'ch hoff wefannau a nodau tudalen o borwr arall.",
"manual_migration_cancel_button": "Dim Diolch",
"manual_migration_import_button": "Mewnforio Nawr"
},
"da": {
"newtab_page_title": "Nyt faneblad",
@ -706,6 +723,7 @@
"header_stories": "Tophistorier",
"header_visit_again": "Besøg igen",
"header_bookmarks": "Seneste bogmærker",
"header_recommended_by": "Anbefalet af {provider}",
"header_bookmarks_placeholder": "Du har ingen bogmærker endnu.",
"header_stories_from": "fra",
"type_label_visited": "Besøgt",
@ -714,6 +732,7 @@
"type_label_recommended": "Populært",
"type_label_open": "Åben",
"type_label_topic": "Emne",
"type_label_now": "Nu",
"menu_action_bookmark": "Bogmærk",
"menu_action_remove_bookmark": "Fjern bogmærke",
"menu_action_copy_address": "Kopier adresse",
@ -732,6 +751,7 @@
"search_header": "{search_engine_name}-søgning",
"search_web_placeholder": "Søg på internettet",
"search_settings": "Skift søgeindstillinger",
"section_info_option": "Info",
"welcome_title": "Velkommen til nyt faneblad",
"welcome_body": "Firefox vil bruge denne plads til at vise dine mest relevante bogmærker, artikler, videoer og sider, du har besøgt for nylig - så kan du nemmere finde dem.",
"welcome_label": "Finder dine højdepunkter",
@ -776,7 +796,11 @@
"pocket_read_even_more": "Se flere historier",
"pocket_feedback_header": "Det bedste fra nettet, udvalgt af mere end 25 millioner mennesker.",
"pocket_feedback_body": "Pocket, en del af Mozilla-familien, hjælper dig med at opdage indhold af høj kvalitet, som du måske ellers ikke ville have fundet.",
"pocket_send_feedback": "Send feedback"
"pocket_send_feedback": "Send feedback",
"topstories_empty_state": "Der er ikke flere nye historier. Kom tilbage senere for at se flere tophistorier fra {provider}. Kan du ikke vente? Vælg et populært emne og find flere spændende historier fra hele verden.",
"manual_migration_explanation": "Prøv Firefox med dine favorit-websteder og bogmærker fra en anden browser.",
"manual_migration_cancel_button": "Nej tak",
"manual_migration_import_button": "Importer nu"
},
"de": {
"newtab_page_title": "Neuer Tab",
@ -785,6 +809,7 @@
"header_stories": "Meistgelesene Meldungen",
"header_visit_again": "Erneut besuchen",
"header_bookmarks": "Neue Lesezeichen",
"header_recommended_by": "Empfohlen von {provider}",
"header_bookmarks_placeholder": "Sie haben noch keine Lesezeichen.",
"header_stories_from": "von",
"type_label_visited": "Besucht",
@ -793,6 +818,7 @@
"type_label_recommended": "Populär",
"type_label_open": "Geöffnet",
"type_label_topic": "Thema",
"type_label_now": "Jetzt",
"menu_action_bookmark": "Lesezeichen",
"menu_action_remove_bookmark": "Lesezeichen entfernen",
"menu_action_copy_address": "Adresse kopieren",
@ -811,6 +837,7 @@
"search_header": "{search_engine_name}-Suche",
"search_web_placeholder": "Das Web durchsuchen",
"search_settings": "Sucheinstellungen ändern",
"section_info_option": "Info",
"welcome_title": "Willkommen im neuen Tab",
"welcome_body": "Firefox nutzt diesen Bereich, um Ihnen Ihre wichtigsten Lesezeichen, Artikel, Videos und kürzlich besuchten Seiten anzuzeigen, damit Sie diese einfach wiederfinden.",
"welcome_label": "Auswahl Ihrer wichtigsten Seiten",
@ -855,7 +882,11 @@
"pocket_read_even_more": "Weitere Nachrichten ansehen",
"pocket_feedback_header": "Das Beste aus dem Web, zusammengetragen von 25 Millionen Menschen.",
"pocket_feedback_body": "Pocket, ein Teil der Mozilla-Familie, hilft Ihnen beim Finden von qualitativ hochwertigen Inhalten, die Sie ansonsten vielleicht nicht gefunden hätten.",
"pocket_send_feedback": "Feedback senden"
"pocket_send_feedback": "Feedback senden",
"topstories_empty_state": "Jetzt kennen Sie die Neuigkeiten. Schauen Sie später wieder vorbei, um neue Informationen von {provider} zu erhalten. Können sie nicht warten? Wählen Sie ein beliebtes Thema und lesen Sie weitere interessante Geschichten aus dem Internet.",
"manual_migration_explanation": "Nutzen Sie Firefox mit Ihren Lieblings-Websites und Lesezeichen aus einem anderen Browser.",
"manual_migration_cancel_button": "Nein, danke",
"manual_migration_import_button": "Jetzt importieren"
},
"dsb": {
"newtab_page_title": "Nowy rejtark",
@ -864,6 +895,7 @@
"header_stories": "Nejcesćej pśecytane powěźenki",
"header_visit_again": "Hyšći raz se woglědaś",
"header_bookmarks": "Nejnowše cytańske znamjenja",
"header_recommended_by": "Wót {provider} dopórucony",
"header_bookmarks_placeholder": "Hyšći cytańske znamjenja njamaśo.",
"header_stories_from": "wót",
"type_label_visited": "Woglědany",
@ -872,6 +904,7 @@
"type_label_recommended": "Popularny",
"type_label_open": "Wócynjony",
"type_label_topic": "Tema",
"type_label_now": "Něnto",
"menu_action_bookmark": "Ako cytańske znamje składowaś",
"menu_action_remove_bookmark": "Cytańske znamje wótpóraś",
"menu_action_copy_address": "Adresu kopěrowaś",
@ -890,6 +923,7 @@
"search_header": "Z {search_engine_name} pytaś",
"search_web_placeholder": "Web pśepytaś",
"search_settings": "Pytańske nastajenja změniś",
"section_info_option": "Info",
"welcome_title": "Witajśo k nowemu rejtarkoju",
"welcome_body": "Firefox buźo toś ten rum wužywaś, aby waše nejwažnjejše cytańske znamjenja, nastawki, wideo a rowno woglědane boki pokazał, aby mógł se lažko k nim wrośiś.",
"welcome_label": "Wuběranje wašych nejwažnjejšych bokow",
@ -934,7 +968,11 @@
"pocket_read_even_more": "Dalšne powěźeńki se woglědaś",
"pocket_feedback_header": "Nejlěpše z weba, zezběrane wót wěcej ako 25 milionow luźi.",
"pocket_feedback_body": "Pocket, źěl familije Mozilla, buźo pomagaś, was z wopśimjeśim wusokeje kwality zwězowaś, kótaryž njeby wy snaź howac namakał.",
"pocket_send_feedback": "Komentar pósłaś"
"pocket_send_feedback": "Komentar pósłaś",
"topstories_empty_state": "To jo nachylu wšykno. Wrośćo se pózdźej wjelicnych tšojeńkow dla wót {provider}. Njamóžośo cakaś? Wubjeŕśo woblubowanu temu, aby dalšne wjelicne tšojeńka we webje namakał.",
"manual_migration_explanation": "Wopytajśo Firefox ze swójimi nejlubšymi websedłami a cytańskimi znamjenjami z drugego wobglědowaka.",
"manual_migration_cancel_button": "Ně, źěkujom se",
"manual_migration_import_button": "Něnto importěrowaś"
},
"el": {
"newtab_page_title": "Νέα καρτέλα",
@ -943,6 +981,7 @@
"header_stories": "Κορυφαίες ιστορίες",
"header_visit_again": "Επίσκεψη ξανά",
"header_bookmarks": "Πρόσφατοι σελιδοδείκτες",
"header_recommended_by": "Προτεινόμενο από τον πάροχο {provider}",
"header_bookmarks_placeholder": "Δεν έχετε κανένα σελιδοδείκτη ακόμα.",
"header_stories_from": "από",
"type_label_visited": "Από ιστορικό",
@ -951,6 +990,7 @@
"type_label_recommended": "Τάσεις",
"type_label_open": "Ανοικτό",
"type_label_topic": "Θέμα",
"type_label_now": "Τώρα",
"menu_action_bookmark": "Προσθήκη σελιδοδείκτη",
"menu_action_remove_bookmark": "Αφαίρεση σελιδοδείκτη",
"menu_action_copy_address": "Αντιγραφή διεύθυνσης",
@ -969,6 +1009,7 @@
"search_header": "Αναζήτηση {search_engine_name}",
"search_web_placeholder": "Αναζήτηση στον ιστό",
"search_settings": "Αλλαγή ρυθμίσεων αναζήτησης",
"section_info_option": "Πληροφορίες",
"welcome_title": "Καλώς ορίσατε στη νέα καρτέλα",
"welcome_body": "Το Firefox θα χρησιμοποιήσει αυτό το χώρο για να εμφανίσει τους πιο σχετικούς σελιδοδείκτες, άρθρα, βίντεο και σελίδες που επισκεφθήκατε πρόσφατα, ώστε να έχετε εύκολη πρόσβαση.",
"welcome_label": "Αναγνώριση κορυφαίων στιγμών",
@ -1013,7 +1054,11 @@
"pocket_read_even_more": "Προβολή περισσότερων ιστοριών",
"pocket_feedback_header": "Τα καλύτερα του διαδικτύου, παρέχονται από πάνω από 25 εκατομμύρια άτομα.",
"pocket_feedback_body": "Το Pocket, ένα μέλος της οικογένειας Mozilla, θα σάς βοηθήσει να ανακαλύψετε περιεχόμενο υψηλής ποιότητας που ίσως να μην βρίσκατε διαφορετικά.",
"pocket_send_feedback": "Αποστολή σχολίων"
"pocket_send_feedback": "Αποστολή σχολίων",
"topstories_empty_state": "Δεν υπάρχει κάτι νεότερο. Ελέγξτε αργότερα για περισσότερες ιστορίες από τον πάροχο {provider}. Δεν μπορείτε να περιμένετε; Διαλέξτε κάποιο από τα δημοφιλή θέματα και ανακαλύψτε ενδιαφέρουσες ιστορίες από όλο τον Ιστό.",
"manual_migration_explanation": "Δοκιμάστε τον Firefox για τις αγαπημένες σας σελίδες και σελιδοδείκτες από άλλους περιηγητές.",
"manual_migration_cancel_button": "Όχι ευχαριστώ",
"manual_migration_import_button": "Εισαγωγή τώρα"
},
"en-GB": {
"newtab_page_title": "New Tab",
@ -1267,6 +1312,7 @@
"header_stories": "Historias principales",
"header_visit_again": "Visitar de nuevo",
"header_bookmarks": "Marcadores recientes",
"header_recommended_by": "Recomendado por {provider}",
"header_bookmarks_placeholder": "Todavía no hay ningún marcador.",
"header_stories_from": "de",
"type_label_visited": "Visitados",
@ -1275,6 +1321,7 @@
"type_label_recommended": "Tendencias",
"type_label_open": "Abrir",
"type_label_topic": "Tópico",
"type_label_now": "Ahora",
"menu_action_bookmark": "Marcador",
"menu_action_remove_bookmark": "Eliminar marcador",
"menu_action_copy_address": "Copiar dirección",
@ -1293,6 +1340,7 @@
"search_header": "Buscar con {search_engine_name}",
"search_web_placeholder": "Buscar en la web",
"search_settings": "Cambiar opciones de búsqueda",
"section_info_option": "Información",
"welcome_title": "Bienvenido a una nueva pestaña",
"welcome_body": "Firefox usará este espacio para mostrar sus marcadores, artículos, videos y páginas más relevantes que se hayan visitado para poder volver más fácilmente.",
"welcome_label": "Identificar los destacados",
@ -1337,7 +1385,10 @@
"pocket_read_even_more": "Ver más historias",
"pocket_feedback_header": "Lo mejor de la web, seleccionado por más de 25 millones de personas.",
"pocket_feedback_body": "Pocket, parte de la familia Mozilla, ayudará a conectarte con contenido de alta calidad que no podrías haber encontrado de otra forma.",
"pocket_send_feedback": "Enviar opinión"
"pocket_send_feedback": "Enviar opinión",
"manual_migration_explanation": "Probá Firefox con tus sitios favoritos y marcadores de otro navegador.",
"manual_migration_cancel_button": "No gracias",
"manual_migration_import_button": "Importar ahora"
},
"es-CL": {
"newtab_page_title": "Nueva pestaña",
@ -1425,6 +1476,7 @@
"header_stories": "Historias populares",
"header_visit_again": "Visitar de nuevo",
"header_bookmarks": "Marcadores recientes",
"header_recommended_by": "Recomendado por {provider}",
"header_bookmarks_placeholder": "Todavía no tienes ningún marcador.",
"header_stories_from": "desde",
"type_label_visited": "Visitados",
@ -1433,6 +1485,7 @@
"type_label_recommended": "Tendencias",
"type_label_open": "Abrir",
"type_label_topic": "Tema",
"type_label_now": "Ahora",
"menu_action_bookmark": "Marcador",
"menu_action_remove_bookmark": "Eliminar marcador",
"menu_action_copy_address": "Copiar dirección",
@ -1451,6 +1504,7 @@
"search_header": "Búsqueda de {search_engine_name}",
"search_web_placeholder": "Buscar en la Web",
"search_settings": "Cambiar ajustes de búsqueda",
"section_info_option": "Info",
"welcome_title": "Bienvenido a la nueva pestaña",
"welcome_body": "Firefox utilizará este espacio para mostrarte los marcadores, artículos y vídeos más relevantes y las páginas que has visitado recientemente, para que puedas acceder más rápido.",
"welcome_label": "Identificar lo más destacado para ti",
@ -1495,7 +1549,11 @@
"pocket_read_even_more": "Ver más historias",
"pocket_feedback_header": "Lo mejor de la web, confirmado por más de 25 millones de personas.",
"pocket_feedback_body": "Pocket, que forma parte de la familia de Mozilla, te ayudará a encontrar contenido de alta calidad que puede que no encuentres de otra forma.",
"pocket_send_feedback": "Enviar comentario"
"pocket_send_feedback": "Enviar comentario",
"topstories_empty_state": "Ya estás al día. Vuelve luego y busca más historias de {provider}. ¿No puedes esperar? Selecciona un tema popular y encontrás más historias alucinantes por toda la web.",
"manual_migration_explanation": "Prueba Firefox con tusmarcadores y sitios favoritos importados desde otro navegador.",
"manual_migration_cancel_button": "No, gracias",
"manual_migration_import_button": "Importar ahora"
},
"es-MX": {
"newtab_page_title": "Nueva pestaña",
@ -1809,6 +1867,7 @@
"header_stories": "Articles populaires",
"header_visit_again": "Visiter à nouveau",
"header_bookmarks": "Marque-pages récents",
"header_recommended_by": "Recommandé par {provider}",
"header_bookmarks_placeholder": "Vous ne possédez aucun marque-page pour linstant.",
"header_stories_from": "par",
"type_label_visited": "Visité",
@ -1817,6 +1876,7 @@
"type_label_recommended": "Tendance",
"type_label_open": "Ouvert",
"type_label_topic": "Thème",
"type_label_now": "Maintenant",
"menu_action_bookmark": "Marquer cette page",
"menu_action_remove_bookmark": "Supprimer le marque-page",
"menu_action_copy_address": "Copier ladresse",
@ -1835,6 +1895,7 @@
"search_header": "Recherche {search_engine_name}",
"search_web_placeholder": "Rechercher sur le Web",
"search_settings": "Paramètres de recherche",
"section_info_option": "Informations",
"welcome_title": "Bienvenue sur la page Nouvel onglet",
"welcome_body": "Firefox utilisera cet espace pour afficher des éléments pertinents, comme des marque-pages, des articles, des vidéos, et des pages que vous avez visitées, afin que vous les retrouviez facilement.",
"welcome_label": "Identification des éléments-clés",
@ -1846,7 +1907,7 @@
"settings_pane_header": "Préférences Nouvel onglet",
"settings_pane_body": "Choisissez ce qui saffiche à louverture dun nouvel onglet.",
"settings_pane_search_header": "Recherche",
"settings_pane_search_body": "Effectuez une recherche sur le Web à partir du nouvel onglet.",
"settings_pane_search_body": "Effectuez une recherche sur le Web depuis le nouvel onglet.",
"settings_pane_topsites_header": "Sites les plus visités",
"settings_pane_topsites_body": "Accédez aux sites que vous consultez le plus.",
"settings_pane_topsites_options_showmore": "Afficher deux lignes",
@ -1879,7 +1940,11 @@
"pocket_read_even_more": "Afficher plus darticles",
"pocket_feedback_header": "Le meilleur du Web, sélectionné par plus de 25 millions de personnes.",
"pocket_feedback_body": "Pocket, un membre de la famille Mozilla, vous aide à découvrir du contenu de grande qualité que vous auriez pu manquer dans le cas contraire.",
"pocket_send_feedback": "Donner mon avis"
"pocket_send_feedback": "Donner mon avis",
"topstories_empty_state": "Il ny en a pas dautres. Revenez plus tard pour plus darticles de {provider}. Vous ne voulez pas attendre ? Choisissez un sujet parmi les plus populaires pour découvrir dautres articles intéressants sur le Web.",
"manual_migration_explanation": "Essayez Firefox avec vos sites et marque-pages préférés, importés depuis un autre navigateur.",
"manual_migration_cancel_button": "Non merci",
"manual_migration_import_button": "Importer maintenant"
},
"fy-NL": {
"newtab_page_title": "Nij ljepblêd",
@ -1888,6 +1953,7 @@
"header_stories": "Topferhalen",
"header_visit_again": "Nochris besykje",
"header_bookmarks": "Resinte blêdwizers",
"header_recommended_by": "Oanrekommandearre troch {provider}",
"header_bookmarks_placeholder": "Jo hawwe noch gjin inkelde blêdwizer.",
"header_stories_from": "fan",
"type_label_visited": "Besocht",
@ -1896,6 +1962,7 @@
"type_label_recommended": "Trending",
"type_label_open": "Iepene",
"type_label_topic": "Underwerp",
"type_label_now": "No",
"menu_action_bookmark": "Blêdwizer",
"menu_action_remove_bookmark": "Blêdwizer fuortsmite",
"menu_action_copy_address": "Adres kopiearje",
@ -1914,6 +1981,7 @@
"search_header": "{search_engine_name} trochsykje",
"search_web_placeholder": "Sykje op it web",
"search_settings": "Sykynstellingen wizigje",
"section_info_option": "Ynfo",
"welcome_title": "Wolkom by it nije ljepblêd",
"welcome_body": "Firefox brûkt dizze romte om jo meast relevante blêdwizers, artikelen, fideos en siden dy't jo koartlyn besocht hawwe wer te jaan, sadat jo dizze ienfâldichwei weromfine kinne.",
"welcome_label": "Jo hichtepunten oantsjutte",
@ -1958,7 +2026,11 @@
"pocket_read_even_more": "Mear ferhalen besjen",
"pocket_feedback_header": "It bêste fan it web, sammele troch mear as 25 miljoen minsken.",
"pocket_feedback_body": "Pocket, part fan de Mozilla-famylje, sil jo helpe te ferbinen mei hege kwaliteit ynhâld dy't jo oars miskien net fûn hienen.",
"pocket_send_feedback": "Kommentaar ferstjoere"
"pocket_send_feedback": "Kommentaar ferstjoere",
"topstories_empty_state": "Jo binne by. Kom letter werom foar mear ferhalen fan {provider}. Kin jo net wachtsje? Selektearje in populêr ûnderwerp om mear ferhalen fan it ynternet te finen.",
"manual_migration_explanation": "Probearje Firefox mei jo favorite websites en blêdwizers fan in oare browser.",
"manual_migration_cancel_button": "Nee tankewol",
"manual_migration_import_button": "No ymportearje"
},
"ga-IE": {
"newtab_page_title": "Cluaisín Nua",
@ -2339,6 +2411,7 @@
"header_stories": "Najhusćišo přečitane zdźělenki",
"header_visit_again": "Hišće raz wopytać",
"header_bookmarks": "Najnowše zapołožki",
"header_recommended_by": "Wot {provider} doporučeny",
"header_bookmarks_placeholder": "Hišće zapołožki nimaće.",
"header_stories_from": "wot",
"type_label_visited": "Wopytany",
@ -2347,6 +2420,7 @@
"type_label_recommended": "Popularny",
"type_label_open": "Wočinjeny",
"type_label_topic": "Tema",
"type_label_now": "Nětko",
"menu_action_bookmark": "Zapołožki składować",
"menu_action_remove_bookmark": "Zapołožku wotstronić",
"menu_action_copy_address": "Adresu kopěrować",
@ -2365,6 +2439,7 @@
"search_header": "Z {search_engine_name} pytać",
"search_web_placeholder": "Web přepytać",
"search_settings": "Pytanske nastajenja změnić",
"section_info_option": "Info",
"welcome_title": "Witajće k nowemu rajtarkej",
"welcome_body": "Firefox budźe tutón rum wužiwać, zo by waše najwažniše zapołožki, nastawki, wideja a runje wopytane strony pokazał, zo byšće móhł so lochko k nim wróćić.",
"welcome_label": "Wuběranje wašich najwažnišich stronow",
@ -2409,7 +2484,11 @@
"pocket_read_even_more": "Dalše zdźělenki sej wobhladać",
"pocket_feedback_header": "Najlěpše z weba, zhromadźene wot wjace hač 25 milionow ludźi.",
"pocket_feedback_body": "Pocket, dźěl swójby Mozilla, budźe pomhać, was z wobsahom wysokeje kwality zwjazować, kotryž njebyšće snano hewak namakał.",
"pocket_send_feedback": "Komentar pósłać"
"pocket_send_feedback": "Komentar pósłać",
"topstories_empty_state": "To je nachwilu wšitko. Wróćće so pozdźišo dalšich wulkotnych stawiznow dla wot {provider}. Njemóžeće čakać? Wubjerće woblubowanu temu, zo byšće dalše wulkotne stawizny z weba namakał.",
"manual_migration_explanation": "Wupruwujće Firefox ze swojimi najlubšimi websydłami a zapołožkami z druheho wobhladowaka.",
"manual_migration_cancel_button": "Ně, dźakuju so",
"manual_migration_import_button": "Nětko importować"
},
"hu": {
"newtab_page_title": "Új lap",
@ -2418,6 +2497,7 @@
"header_stories": "Népszerű történetek",
"header_visit_again": "Látogasson el ismét",
"header_bookmarks": "Friss könyvjelzők",
"header_recommended_by": "A(z) {provider} ajánlásával",
"header_bookmarks_placeholder": "Még nincs könyvjelzője.",
"header_stories_from": "innen:",
"type_label_visited": "Látogatott",
@ -2426,6 +2506,7 @@
"type_label_recommended": "Népszerű",
"type_label_open": "Megnyitás",
"type_label_topic": "Téma",
"type_label_now": "Most",
"menu_action_bookmark": "Könyvjelzőzés",
"menu_action_remove_bookmark": "Könyvjelző eltávolítása",
"menu_action_copy_address": "Cím másolása",
@ -2444,6 +2525,7 @@
"search_header": "{search_engine_name} keresés",
"search_web_placeholder": "Keresés a weben",
"search_settings": "Keresési beállítások módosítása",
"section_info_option": "Információ",
"welcome_title": "Üdvözöljük az új lapon",
"welcome_body": "A Firefox ezt a területet a leginkább releváns könyvjelzők, cikkek, videók és nemrég látogatott oldalak megjelenítésére fogja használni, így könnyedén visszatalálhat hozzájuk.",
"welcome_label": "A kiemeléseinek azonosítása",
@ -2488,7 +2570,11 @@
"pocket_read_even_more": "További történetek",
"pocket_feedback_header": "A web legjava, több mint 25 millió ember válogatásában.",
"pocket_feedback_body": "A Pocket a Mozilla család tagja, segít az olyan jó minőségű tartalmak fellelésében, melyekkel egyébként nem is találkozott volna.",
"pocket_send_feedback": "Visszajelzés küldése"
"pocket_send_feedback": "Visszajelzés küldése",
"topstories_empty_state": "Már felzárkózott. Nézzen vissza később a legújabb {provider} hírekért. Nem tud várni? Válasszon egy népszerű témát, hogy még több sztorit találjon a weben.",
"manual_migration_explanation": "Próbálja ki a Firefoxot egy másik böngészőben lévő kedvenc oldalaival és könyvjelzőivel.",
"manual_migration_cancel_button": "Köszönöm, nem",
"manual_migration_import_button": "Importálás most"
},
"hy-AM": {
"newtab_page_title": "Նոր ներդիր",
@ -2599,6 +2685,7 @@
"header_stories": "Storie principali",
"header_visit_again": "Visita di nuovo",
"header_bookmarks": "Segnalibri recenti",
"header_recommended_by": "Consigliato da {provider}",
"header_bookmarks_placeholder": "Non è ancora disponibile alcun segnalibro.",
"header_stories_from": "da",
"type_label_visited": "Visitato",
@ -2607,6 +2694,7 @@
"type_label_recommended": "Di tendenza",
"type_label_open": "Apri",
"type_label_topic": "Argomento",
"type_label_now": "Adesso",
"menu_action_bookmark": "Aggiungi ai segnalibri",
"menu_action_remove_bookmark": "Elimina segnalibro",
"menu_action_copy_address": "Copia indirizzo",
@ -2625,6 +2713,7 @@
"search_header": "Ricerca {search_engine_name}",
"search_web_placeholder": "Cerca sul Web",
"search_settings": "Cambia impostazioni di ricerca",
"section_info_option": "Info",
"welcome_title": "Benvenuto nella nuova scheda",
"welcome_body": "Firefox utilizzerà questo spazio per visualizzare gli elementi più significativi, come segnalibri, articoli, video e pagine visitate di recente, in modo che siano sempre facili da raggiungere.",
"welcome_label": "Identificazione elementi in evidenza…",
@ -2669,7 +2758,11 @@
"pocket_read_even_more": "Visualizza altre storie",
"pocket_feedback_header": "Il meglio del web, selezionato da 25 milioni di persone.",
"pocket_feedback_body": "Grazie a Pocket, un componente della famiglia Mozilla, puoi raggiungere contenuti di alta qualità che altrimenti potrebbero sfuggirti.",
"pocket_send_feedback": "Invia feedback"
"pocket_send_feedback": "Invia feedback",
"topstories_empty_state": "Non c'è altro. Controlla più tardi per altre storie da {provider}. Non vuoi aspettare? Seleziona un argomento tra quelli più popolari per scoprire altre notizie interessanti dal Web.",
"manual_migration_explanation": "Prova Firefox con i siti preferiti e i segnalibri importati da un altro browser.",
"manual_migration_cancel_button": "No grazie",
"manual_migration_import_button": "Importa adesso"
},
"ja": {
"newtab_page_title": "新しいタブ",
@ -2678,14 +2771,16 @@
"header_stories": "トップ記事",
"header_visit_again": "再度訪れる",
"header_bookmarks": "最近のブックマーク",
"header_recommended_by": "{provider} のおすすめ",
"header_bookmarks_placeholder": "まだブックマークがありません。",
"header_stories_from": "配信元",
"type_label_visited": "訪問済み",
"type_label_bookmarked": "ブックマーク",
"type_label_bookmarked": "ブックマーク済み",
"type_label_synced": "他の端末から同期",
"type_label_recommended": "話題の記事",
"type_label_open": "開く",
"type_label_topic": "トピック",
"type_label_now": "今",
"menu_action_bookmark": "ブックマーク",
"menu_action_remove_bookmark": "ブックマークを削除",
"menu_action_copy_address": "URL をコピー",
@ -2704,6 +2799,7 @@
"search_header": "{search_engine_name} 検索",
"search_web_placeholder": "ウェブを検索",
"search_settings": "検索設定を変更",
"section_info_option": "情報",
"welcome_title": "新しいタブへようこそ",
"welcome_body": "Firefox はこのスペースを使って、関連性の高いブックマーク、記事、動画、最近訪れたページを表示し、それらのコンテンツへ簡単に戻れるようにします。",
"welcome_label": "あなたのハイライトを確認しています",
@ -2748,7 +2844,11 @@
"pocket_read_even_more": "他の記事を見る",
"pocket_feedback_header": "2,500 万人以上の人々によって収集されている、ウェブ上で最も優れたコンテンツ。",
"pocket_feedback_body": "Mozilla ファミリーの一員となった Pocket は、他では見つからなかったかもしれない高品質なコンテンツとあなたを結び付ける手助けをします。",
"pocket_send_feedback": "フィードバックを送る"
"pocket_send_feedback": "フィードバックを送る",
"topstories_empty_state": "すべて既読です。また後で戻って {provider} からのおすすめ記事をチェックしてください。もし待ちきれないなら、人気のトピックを選択すれば、他にもウェブ上の優れた記事を見つけられます。",
"manual_migration_explanation": "他のブラウザーから Firefox へあなたのお気に入りのサイトやブックマークを取り込んでみましょう。",
"manual_migration_cancel_button": "今はしない",
"manual_migration_import_button": "今すぐインポート"
},
"ka": {
"newtab_page_title": "ახალი ჩანართი",
@ -2757,6 +2857,7 @@
"header_stories": "მთავარი სიახლეები",
"header_visit_again": "ხელახლა ნახვა",
"header_bookmarks": "ბოლოს ჩანიშნულები",
"header_recommended_by": "რეკომენდებულია {provider}-ის მიერ",
"header_bookmarks_placeholder": "სანიშნეები ჯერ არაა დამატებული.",
"header_stories_from": "-იდან",
"type_label_visited": "მონახულებული",
@ -2765,6 +2866,7 @@
"type_label_recommended": "პოპულარული",
"type_label_open": "გახსნა",
"type_label_topic": "თემა",
"type_label_now": "ახლა",
"menu_action_bookmark": "ჩანიშვნა",
"menu_action_remove_bookmark": "სანიშნეებიდან ამოღება",
"menu_action_copy_address": "მისამართის დაკოპირება",
@ -2783,6 +2885,7 @@
"search_header": "{search_engine_name} -ში ძიება",
"search_web_placeholder": "ინტერნეტში ძიება",
"search_settings": "ძიების პარამეტრების შეცვლა",
"section_info_option": "ინფორმაცია",
"welcome_title": "მოგესალმებით ახალ ჩანართზე",
"welcome_body": "Firefox ამ სივრცეს გამოიყენებს თქვენთვის ყველაზე საჭირო სანიშნეების, სტატიების, ვიდეოებისა და ბოლოს მონახულებული გვერდებისთვის, რომ ადვილად შეძლოთ მათზე დაბრუნება.",
"welcome_label": "რჩეული ვებ-გვერდების დადგენა",
@ -2827,7 +2930,11 @@
"pocket_read_even_more": "მეტი სიახლის ნახვა",
"pocket_feedback_header": "საუკეთესოები ინტერნეტიდან, 25 მილიონზე მეტი ადამიანის მიერ არჩეული.",
"pocket_feedback_body": "Pocket არის Mozilla-ს ოჯახის ნაწილი, რომელიც დაგეხმარებათ ისეთი მაღალი ხარისხის კონტენტის მოძიებაში, რომელიც სხვა გზებით, შეიძლება ვერ მოგენახათ.",
"pocket_send_feedback": "უკუკავშირი"
"pocket_send_feedback": "უკუკავშირი",
"topstories_empty_state": "უკვე ყველაფერი წაკითხული გაქვთ. {provider}-იდან ახალი რჩეული სტატიების მისაღებად, მოგვიანებით შემოიარეთ. თუ ვერ ითმენთ, აირჩიეთ რომელიმე მოთხოვნადი თემა, ახალი საინტერესო სტატიების მოსაძიებლად.",
"manual_migration_explanation": "სცადეთ Firefox, გადმოიტანეთ თქვენი რჩეული საიტები და სანიშნეები სხვა ბრაუზერიდან.",
"manual_migration_cancel_button": "არა, გმადლობთ",
"manual_migration_import_button": "ახლავე გადმოტანა"
},
"kab": {
"newtab_page_title": "Iccer amaynut",
@ -2836,6 +2943,7 @@
"header_stories": "Tiqsiɣin ifazen",
"header_visit_again": "Rzu tikelt-nniḍen",
"header_bookmarks": "Ticraḍ n melmi kan",
"header_recommended_by": "Iwelleh-it-id {provider}",
"header_bookmarks_placeholder": "Ur ɣur-k ara ticraḍ yakan.",
"header_stories_from": "seg",
"type_label_visited": "Yettwarza",
@ -2844,6 +2952,7 @@
"type_label_recommended": "Tiddin",
"type_label_open": "Yeldi",
"type_label_topic": "Asentel",
"type_label_now": "Tura",
"menu_action_bookmark": "Creḍ asebter-agi",
"menu_action_remove_bookmark": "Kkes tacreṭ-agi",
"menu_action_copy_address": "Nγel tansa",
@ -2862,6 +2971,7 @@
"search_header": "Anadi {search_engine_name}",
"search_web_placeholder": "Nadi di Web",
"search_settings": "Snifel iγewwaṛen n unadi",
"section_info_option": "Talɣut",
"welcome_title": "Ansuf ar yiccer amaynut",
"welcome_body": "Firefox ad iseqdec tallunt akken ad d-yesken akk ticraḍ n isebtar iwulmen, imagraden, tividyutin, akked isebtar aniɣer terziḍ melmi kan, ihi tzemreḍ ad d-uɣaleḍ ɣer-sen s wudem fessusen.",
"welcome_label": "Asulu n iferdisen tisura",
@ -2906,7 +3016,11 @@
"pocket_read_even_more": "Wali ugar n teqsiḍin",
"pocket_feedback_header": "D amezwaru n Web, ittwafren sγur ugar 25 imelyan n imdanen.",
"pocket_feedback_body": "Pocket, aɛeggal n twaxult n Mozilla, ak-d-yefk afus ad twaliḍ agbur n tɣara meqqren i tzemred ad tzegleḍ.",
"pocket_send_feedback": "Azen tikti"
"pocket_send_feedback": "Azen tikti",
"topstories_empty_state": "Ulac wiyaḍ. Uɣal-d ticki s wugar n imagraden seg {provider}. Ur tebɣiḍ ara ad terǧuḍ? Fren asentel seg wid yettwasnen akken ad twaliḍ imagraden yelhan di Web.",
"manual_migration_explanation": "Ɛreḍ Firefox s ismal-ik inurifen akked ticraḍ seg iminig-nniḍen.",
"manual_migration_cancel_button": "Ala, tanemmirt",
"manual_migration_import_button": "Kter tura"
},
"kk": {
"newtab_page_title": "Жаңа бет",
@ -3391,6 +3505,7 @@
"header_stories": "Berita Hangat",
"header_visit_again": "Lawat Semula",
"header_bookmarks": "Tandabuku Terkini",
"header_recommended_by": "Disyorkan oleh {provider}",
"header_bookmarks_placeholder": "Anda masih belum ada tandabuku lagi.",
"header_stories_from": "dari",
"type_label_visited": "Dilawati",
@ -3399,6 +3514,7 @@
"type_label_recommended": "Trending",
"type_label_open": "Buka",
"type_label_topic": "Topik",
"type_label_now": "Sekarang",
"menu_action_bookmark": "Tandabuku",
"menu_action_remove_bookmark": "Alihkeluar Tandabuku",
"menu_action_copy_address": "Salin Alamat",
@ -3417,6 +3533,7 @@
"search_header": "{search_engine_name} Cari",
"search_web_placeholder": "Cari dalam Web",
"search_settings": "Ubah Tetapan Carian",
"section_info_option": "Info",
"welcome_title": "Selamat Datang ke tab baru",
"welcome_body": "Firefox akan menggunakan ruang ini untuk mempamerkan tandabuku, artikel, video dan halaman yang paling berkaitan dan terkini anda lawati supaya anda boleh mendapatkannya semula dengan mudah.",
"welcome_label": "Mengenalpasti Serlahan anda",
@ -3461,7 +3578,11 @@
"pocket_read_even_more": "Papar Kisah Selanjutnya",
"pocket_feedback_header": "Terbaik daripada web, disokong oleh lebih 25 juta pengguna.",
"pocket_feedback_body": "Pocket, sebahagian daripada ciri Mozilla, akan membantu anda sentiasa berhubung dengan kandungan berkualiti tinggi yang mungkin tidak akan anda jumpa tanpanya.",
"pocket_send_feedback": "Hantar Maklum balas"
"pocket_send_feedback": "Hantar Maklum balas",
"topstories_empty_state": "Anda sudah di sini. Tapi sila datang lagi untuk mendapatkan lebih banyak berita hangat daripada {provider}. Tidak boleh tunggu? Pilih topik untuk mendapatkannya dari serata dunia.",
"manual_migration_explanation": "Cuba Firefox dengan laman kegemaran dan tandabuku anda daripada pelayar lain.",
"manual_migration_cancel_button": "Tidak, Terima kasih",
"manual_migration_import_button": "Import Sekarang"
},
"my": {
"newtab_page_title": "တပ်ဗ်အသစ်ဖွင့်",
@ -3520,6 +3641,7 @@
"header_stories": "Hovedsakene",
"header_visit_again": "Besøk igjen",
"header_bookmarks": "Nylige bokmerker",
"header_recommended_by": "Anbefalt av {provider}",
"header_bookmarks_placeholder": "Du har ingen bokmerker enda.",
"header_stories_from": "fra",
"type_label_visited": "Besøkt",
@ -3528,6 +3650,7 @@
"type_label_recommended": "Trender",
"type_label_open": "Åpne",
"type_label_topic": "Emne",
"type_label_now": "Nå",
"menu_action_bookmark": "Bokmerke",
"menu_action_remove_bookmark": "Fjern bokmerke",
"menu_action_copy_address": "Kopier adresse",
@ -3546,6 +3669,7 @@
"search_header": "{search_engine_name}-søk",
"search_web_placeholder": "Søk på nettet",
"search_settings": "Endre søkeinnstillinger",
"section_info_option": "Informasjon",
"welcome_title": "Velkommen til ny fane",
"welcome_body": "Firefox vil bruke denne plassen til å vise deg de mest relevante bokmerkene, artiklene, videoene og sidene du nettopp har besøkt, slik at du enkelt kan finne tilbake til de.",
"welcome_label": "Identifiserer dine høydepunkter",
@ -3590,7 +3714,11 @@
"pocket_read_even_more": "Vis flere saker",
"pocket_feedback_header": "Det beste av nettet, kurert av over 25 millioner mennesker.",
"pocket_feedback_body": "Pocket, en del av Mozilla-familien, vil hjelpe deg med å finne innhold av høy kvalitet, som du kanskje ikke ville ha funnet ellers.",
"pocket_send_feedback": "Send tilbakemelding"
"pocket_send_feedback": "Send tilbakemelding",
"topstories_empty_state": "Du har tatt igjen. Kom tilbake senere for flere topphistorier fra {provider}. Kan du ikke vente? Velg et populært emne for å finne flere gode artikler fra hele Internett.",
"manual_migration_explanation": "Prøv Firefox med dine favorittnettsteder og bokmerker fra en annen nettleser.",
"manual_migration_cancel_button": "Nei takk",
"manual_migration_import_button": "Importer nå"
},
"ne-NP": {
"newtab_page_title": "नयाँ ट्याब",
@ -3628,6 +3756,7 @@
"header_stories": "Topverhalen",
"header_visit_again": "Nogmaals bezoeken",
"header_bookmarks": "Recente bladwijzers",
"header_recommended_by": "Aanbevolen door {provider}",
"header_bookmarks_placeholder": "U hebt nog geen bladwijzers.",
"header_stories_from": "van",
"type_label_visited": "Bezocht",
@ -3636,6 +3765,7 @@
"type_label_recommended": "Trending",
"type_label_open": "Open",
"type_label_topic": "Onderwerp",
"type_label_now": "Nu",
"menu_action_bookmark": "Bladwijzer maken",
"menu_action_remove_bookmark": "Bladwijzer verwijderen",
"menu_action_copy_address": "Adres kopiëren",
@ -3654,6 +3784,7 @@
"search_header": "{search_engine_name} doorzoeken",
"search_web_placeholder": "Zoeken op het web",
"search_settings": "Zoekinstellingen wijzigen",
"section_info_option": "Info",
"welcome_title": "Welkom bij het nieuwe tabblad",
"welcome_body": "Firefox gebruikt deze ruimte om uw meest relevante bladwijzers, artikelen, videos en paginas die u onlangs hebt bezocht weer te geven, zodat u deze eenvoudig kunt terugvinden.",
"welcome_label": "Uw highlights aanduiden",
@ -3663,7 +3794,7 @@
"time_label_day": "{number} d",
"settings_pane_button_label": "Uw Nieuw-tabbladpagina aanpassen",
"settings_pane_header": "Nieuw-tabbladvoorkeuren",
"settings_pane_body": "Kiezen wat u ziet bij het openen van een nieuw tabblad.",
"settings_pane_body": "Kies wat u ziet bij het openen van een nieuw tabblad.",
"settings_pane_search_header": "Zoeken",
"settings_pane_search_body": "Het web doorzoeken vanaf uw nieuwe tabblad.",
"settings_pane_topsites_header": "Topwebsites",
@ -3698,7 +3829,11 @@
"pocket_read_even_more": "Meer verhalen bekijken",
"pocket_feedback_header": "Het beste van het web, geselecteerd door meer dan 25 miljoen mensen.",
"pocket_feedback_body": "Pocket, een onderdeel van de Mozilla-familie, helpt u bij het vinden van inhoud met hoge kwaliteit die u anders misschien niet had kunnen vinden.",
"pocket_send_feedback": "Feedback verzenden"
"pocket_send_feedback": "Feedback verzenden",
"topstories_empty_state": "U bent weer bij. Kijk later nog eens voor meer topverhalen van {provider}. Kunt u niet wachten? Selecteer een populair onderwerp voor meer geweldige verhalen van het hele web.",
"manual_migration_explanation": "Probeer Firefox met uw favoriete websites en bladwijzers uit een andere browser.",
"manual_migration_cancel_button": "Nee bedankt",
"manual_migration_import_button": "Nu importeren"
},
"nn-NO": {
"newtab_page_title": "Ny flik",
@ -3837,6 +3972,7 @@
"header_stories": "Popularne artykuły",
"header_visit_again": "Odwiedź ponownie",
"header_bookmarks": "Najnowsze zakładki",
"header_recommended_by": "Poleca: {provider}",
"header_bookmarks_placeholder": "Nie ma jeszcze żadnych zakładek.",
"header_stories_from": "od:",
"type_label_visited": "Odwiedzone",
@ -3845,6 +3981,7 @@
"type_label_recommended": "Polecane",
"type_label_open": "Otwarte",
"type_label_topic": "Temat",
"type_label_now": "Teraz",
"menu_action_bookmark": "Dodaj zakładkę",
"menu_action_remove_bookmark": "Usuń zakładkę",
"menu_action_copy_address": "Kopiuj adres",
@ -3863,6 +4000,7 @@
"search_header": "Wyszukiwanie w {search_engine_name}",
"search_web_placeholder": "Szukaj",
"search_settings": "Zmień ustawienia wyszukiwania",
"section_info_option": "Informacja",
"welcome_title": "Witamy w nowej karcie",
"welcome_body": "W tym miejscu Firefox będzie wyświetlał najciekawsze zakładki, artykuły, filmy i niedawno odwiedzone strony, aby można było do nich łatwo wrócić.",
"welcome_label": "Wykrywanie ulubionych treści użytkownika",
@ -3907,7 +4045,11 @@
"pocket_read_even_more": "Więcej artykułów",
"pocket_feedback_header": "Najlepsze, co oferuje Internet, wybrane przez ponad 25 milionów osób.",
"pocket_feedback_body": "Pocket, będący częścią Mozilli, pomoże w szukaniu artykułów wysokiej jakości, aby nic Cię nie ominęło.",
"pocket_send_feedback": "Wyślij opinię"
"pocket_send_feedback": "Wyślij opinię",
"topstories_empty_state": "To na razie wszystko. {provider} później będzie mieć więcej popularnych artykułów. Nie możesz się doczekać? Wybierz popularny temat, aby znaleźć więcej artykułów z całego Internetu.",
"manual_migration_explanation": "Wypróbuj Firefoksa ze swoimi ulubionymi stronami i zakładkami z innej przeglądarki.",
"manual_migration_cancel_button": "Nie, dziękuję",
"manual_migration_import_button": "Importuj teraz"
},
"pt-BR": {
"newtab_page_title": "Nova aba",
@ -3916,6 +4058,7 @@
"header_stories": "Histórias populares",
"header_visit_again": "Visitar novamente",
"header_bookmarks": "Favoritos recentes",
"header_recommended_by": "Recomendado por {provider}",
"header_bookmarks_placeholder": "Você ainda não tem nenhum favorito.",
"header_stories_from": "de",
"type_label_visited": "Visitado",
@ -3924,6 +4067,7 @@
"type_label_recommended": "Tendência",
"type_label_open": "Abertas",
"type_label_topic": "Tópico",
"type_label_now": "Agora",
"menu_action_bookmark": "Adicionar aos favoritos",
"menu_action_remove_bookmark": "Remover favorito",
"menu_action_copy_address": "Copiar endereço",
@ -3942,6 +4086,7 @@
"search_header": "Pesquisa {search_engine_name}",
"search_web_placeholder": "Pesquisar na Web",
"search_settings": "Alterar configurações de pesquisa",
"section_info_option": "Info",
"welcome_title": "Bem-vindo a nova aba",
"welcome_body": "O Firefox usará este espaço para mostrar seus favoritos, artigos, vídeos e páginas mais relevantes visitados recentemente, assim você pode voltar mais facilmente.",
"welcome_label": "Identificando seus destaques",
@ -3986,7 +4131,11 @@
"pocket_read_even_more": "Ver mais histórias",
"pocket_feedback_header": "O melhor da web, com curadoria de mais de 25 milhões de pessoas.",
"pocket_feedback_body": "O Pocket, parte da família Mozilla, irá ajudar a conecta-se a conteúdo de alta qualidade que talvez não tenha encontrado de outra forma.",
"pocket_send_feedback": "Enviar feedback"
"pocket_send_feedback": "Enviar feedback",
"topstories_empty_state": "Você já viu tudo. Volte mais tarde para mais histórias do {provider}. Não consegue esperar? Escolha um assunto popular para encontrar mais grandes histórias através da web.",
"manual_migration_explanation": "Use o Firefox com seus sites favoritos de outro navegador.",
"manual_migration_cancel_button": "Não, obrigado",
"manual_migration_import_button": "Importar agora"
},
"pt-PT": {
"newtab_page_title": "Novo separador",
@ -3995,6 +4144,7 @@
"header_stories": "Histórias principais",
"header_visit_again": "Visitar novamente",
"header_bookmarks": "Marcadores recentes",
"header_recommended_by": "Recomendado por {provider}",
"header_bookmarks_placeholder": "Ainda não tem quaisquer marcadores.",
"header_stories_from": "de",
"type_label_visited": "Visitados",
@ -4003,6 +4153,7 @@
"type_label_recommended": "Tendência",
"type_label_open": "Abertos",
"type_label_topic": "Tópico",
"type_label_now": "Agora",
"menu_action_bookmark": "Adicionar aos marcadores",
"menu_action_remove_bookmark": "Remover marcador",
"menu_action_copy_address": "Copiar endereço",
@ -4021,6 +4172,7 @@
"search_header": "Pesquisa {search_engine_name}",
"search_web_placeholder": "Pesquisar na Web",
"search_settings": "Alterar definições de pesquisa",
"section_info_option": "Informação",
"welcome_title": "Bem-vindo ao novo separador",
"welcome_body": "O Firefox irá utilizar este espaço para lhe mostrar os seus marcadores, artigos, vídeos, e páginas mais relevantes que visitou recentemente, para que possa regressar a estes mais facilmente.",
"welcome_label": "A identificar os seus destaques",
@ -4065,7 +4217,11 @@
"pocket_read_even_more": "Ver mais histórias",
"pocket_feedback_header": "O melhor da web, com curadoria de mais de 25 milhões de pessoas.",
"pocket_feedback_body": "O Pocket, parte da família Mozilla, irá ajudar a ligar-se a conteúdo de alta qualidade que você poderia não ter encontrado de outra forma.",
"pocket_send_feedback": "Enviar feedback"
"pocket_send_feedback": "Enviar feedback",
"topstories_empty_state": "Já apanhou tudo. Verifique mais tarde para mais histórias principais de {provider}. Não pode esperar? Selecione um tópico popular para encontrar mais boas histórias de toda a web.",
"manual_migration_explanation": "Experimente o Firefox com os seus sites favoritos e marcadores de outro navegador.",
"manual_migration_cancel_button": "Não obrigado",
"manual_migration_import_button": "Importar agora"
},
"rm": {
"newtab_page_title": "Nov tab",
@ -4287,6 +4443,7 @@
"header_stories": "Top príbehy",
"header_visit_again": "Navštívte znova",
"header_bookmarks": "Nedávno pridané záložky",
"header_recommended_by": "Odporúča {provider}",
"header_bookmarks_placeholder": "Zatiaľ nemáte žiadne záložky.",
"header_stories_from": "zo služby",
"type_label_visited": "Navštívené",
@ -4295,6 +4452,7 @@
"type_label_recommended": "Trendy",
"type_label_open": "Otvorené",
"type_label_topic": "Téma",
"type_label_now": "Teraz",
"menu_action_bookmark": "Pridať medzi záložky",
"menu_action_remove_bookmark": "Odstrániť záložku",
"menu_action_copy_address": "Kopírovať adresu",
@ -4313,6 +4471,7 @@
"search_header": "Vyhľadávanie pomocou {search_engine_name}",
"search_web_placeholder": "Vyhľadávanie na webe",
"search_settings": "Zmeniť nastavenia vyhľadávania",
"section_info_option": "Informácie",
"welcome_title": "Vitajte na stránke novej karty",
"welcome_body": "Firefox bude na tomto mieste zobrazovať často zobrazované záložky, články, videá a stránky, ktoré ste nedávno navštívili. Váš prístup k nim je tak omnoho ľahší.",
"welcome_label": "Identifikácia vybraných stránok",
@ -4357,7 +4516,11 @@
"pocket_read_even_more": "Zobraziť ďalšie príbehy",
"pocket_feedback_header": "To najlepšie z webu - podľa názoru 25 miliónov ľudí.",
"pocket_feedback_body": "Pocket, súčasť Mozilla rodiny, vám pomôže nájsť vysokokvalitný obsah, ktorý by ste inde zrejme nenašli.",
"pocket_send_feedback": "Odoslať spätnú väzbu"
"pocket_send_feedback": "Odoslať spätnú väzbu",
"topstories_empty_state": "Už ste prečítali všetko. Ďalšie príbehy zo služby {provider} tu nájdete opäť neskôr. Nemôžete sa dočkať? Vyberte si populárnu tému a pozrite sa na ďalšie skvelé príbehy z celého webu.",
"manual_migration_explanation": "Vyskúšajte Firefox so svojimi obľúbenými stránkami a záložkami z iného prehliadača.",
"manual_migration_cancel_button": "Nie, ďakujem",
"manual_migration_import_button": "Importovať teraz"
},
"sl": {
"newtab_page_title": "Nov zavihek",
@ -4374,6 +4537,7 @@
"type_label_recommended": "Najbolj priljubljeno",
"type_label_open": "Odpri",
"type_label_topic": "Tema",
"type_label_now": "Zdaj",
"menu_action_bookmark": "Dodaj med zaznamke",
"menu_action_remove_bookmark": "Odstrani zaznamek",
"menu_action_copy_address": "Kopiraj naslov",
@ -4436,7 +4600,10 @@
"pocket_read_even_more": "Prikaži več vesti",
"pocket_feedback_header": "Najboljše s spleta, kar je izbralo več kot 25 milijonov ljudi.",
"pocket_feedback_body": "Pocket, del Mozilline družine, vam bo pomagal pridobiti visokokakovostne vsebine, ki jih sicer ne bi našli.",
"pocket_send_feedback": "Pošlji povratne informacije"
"pocket_send_feedback": "Pošlji povratne informacije",
"manual_migration_explanation": "Preskusite Firefox s svojimi priljubljenimi stranmi in zaznamki iz drugih brskalnikov.",
"manual_migration_cancel_button": "Ne, hvala",
"manual_migration_import_button": "Uvozi zdaj"
},
"son": {},
"sq": {
@ -4575,6 +4742,7 @@
"header_stories": "Huvudnyheter",
"header_visit_again": "Besökt igen",
"header_bookmarks": "Senaste bokmärken",
"header_recommended_by": "Rekommenderas av {provider}",
"header_bookmarks_placeholder": "Du har inga bokmärken ännu.",
"header_stories_from": "från",
"type_label_visited": "Besökta",
@ -4583,6 +4751,7 @@
"type_label_recommended": "Trend",
"type_label_open": "Öppna",
"type_label_topic": "Ämne",
"type_label_now": "Nu",
"menu_action_bookmark": "Bokmärke",
"menu_action_remove_bookmark": "Ta bort bokmärke",
"menu_action_copy_address": "Kopiera adress",
@ -4601,6 +4770,7 @@
"search_header": "{search_engine_name}",
"search_web_placeholder": "Sök på webben",
"search_settings": "Ändra sökinställningar",
"section_info_option": "Info",
"welcome_title": "Välkommen till ny flik",
"welcome_body": "Firefox kommer att använda detta utrymme för att visa dina mest relevanta bokmärken, artiklar, videor och sidor du nyligen besökt, så du kan hitta dem lätt.",
"welcome_label": "Identifierar dina höjdpunkter",
@ -4645,7 +4815,11 @@
"pocket_read_even_more": "Visa fler nyheter",
"pocket_feedback_header": "Det bästa av webben, sammanställt av över 25 miljoner människor.",
"pocket_feedback_body": "Pocket, en del av Mozilla-familjen, hjälper dig att hitta högkvalitativt innehåll som du kanske annars inte hade hittat.",
"pocket_send_feedback": "Skicka återkoppling"
"pocket_send_feedback": "Skicka återkoppling",
"topstories_empty_state": "Det finns inte fler. Kom tillbaka senare för fler huvudnyheter från {provider}. Kan du inte vänta? Välj ett populärt ämne för att hitta fler bra nyheter från hela världen.",
"manual_migration_explanation": "Prova Firefox med dina favoritwebbplatser och bokmärken från en annan webbläsare.",
"manual_migration_cancel_button": "Nej tack",
"manual_migration_import_button": "Importera nu"
},
"ta": {
"newtab_page_title": "புதிய கீற்று",
@ -4813,6 +4987,7 @@
"header_stories": "เรื่องราวเด่น",
"header_visit_again": "เยี่ยมชมอีกครั้ง",
"header_bookmarks": "ที่คั่นหน้าเมื่อเร็ว ๆ นี้",
"header_recommended_by": "แนะนำโดย {provider}",
"header_bookmarks_placeholder": "คุณยังไม่มีที่คั่นหน้าใด ๆ",
"header_stories_from": "จาก",
"type_label_visited": "เยี่ยมชมแล้ว",
@ -4821,6 +4996,7 @@
"type_label_recommended": "กำลังนิยม",
"type_label_open": "เปิด",
"type_label_topic": "หัวข้อ",
"type_label_now": "ตอนนี้",
"menu_action_bookmark": "เพิ่มที่คั่นหน้า",
"menu_action_remove_bookmark": "เอาที่คั่นหน้าออก",
"menu_action_copy_address": "คัดลอกที่อยู่",
@ -4838,6 +5014,7 @@
"search_header": "ค้นหา {search_engine_name}",
"search_web_placeholder": "ค้นหาเว็บ",
"search_settings": "เปลี่ยนการตั้งค่าการค้นหา",
"section_info_option": "ข้อมูล",
"welcome_title": "ยินดีต้อนรับสู่แท็บใหม่",
"welcome_body": "Firefox จะใช้พื้นที่นี้เพื่อแสดงที่คั่นหน้า, บทความ, วิดีโอ และหน้าที่คุณเพิ่งเยี่ยมชมที่เกี่ยวข้องกับคุณมากที่สุด เพื่อให้คุณสามารถกลับมาชมได้อย่างง่ายดาย",
"welcome_label": "กำลังระบุรายการเด่นของคุณ",
@ -4880,7 +5057,10 @@
"pocket_read_even_more": "ดูเรื่องราวเพิ่มเติม",
"pocket_feedback_header": "ที่สุดของเว็บ จัดรายการโดยผู้คนกว่า 25 ล้านคน",
"pocket_feedback_body": "Pocket ส่วนหนึ่งของครอบครัว Mozilla จะช่วยเชื่อมต่อคุณกับเนื้อหาคุณภาพสูงที่คุณอาจไม่พบที่อื่น",
"pocket_send_feedback": "ส่งข้อคิดเห็น"
"pocket_send_feedback": "ส่งข้อคิดเห็น",
"manual_migration_explanation": "ลอง Firefox ด้วยไซต์โปรดและที่คั่นหน้าของคุณจากเบราว์เซอร์อื่น",
"manual_migration_cancel_button": "ไม่ ขอบคุณ",
"manual_migration_import_button": "นำเข้าตอนนี้"
},
"tl": {
"newtab_page_title": "Bagong Tab",
@ -4939,6 +5119,7 @@
"header_stories": "İlginç Yazılar",
"header_visit_again": "Yeniden Ziyaret Edin",
"header_bookmarks": "Son Yer imleri",
"header_recommended_by": "{provider} öneriyor",
"header_bookmarks_placeholder": "Henüz hiç yer iminiz yok.",
"header_stories_from": "kaynak:",
"type_label_visited": "Ziyaret edildi",
@ -4947,6 +5128,7 @@
"type_label_recommended": "Popüler",
"type_label_open": "Açık",
"type_label_topic": "Konu",
"type_label_now": "Şimdi",
"menu_action_bookmark": "Yer imlerine ekle",
"menu_action_remove_bookmark": "Yer imini sil",
"menu_action_copy_address": "Adresi kopyala",
@ -4965,6 +5147,7 @@
"search_header": "{search_engine_name} Araması",
"search_web_placeholder": "Web'de ara",
"search_settings": "Arama ayarlarını değiştir",
"section_info_option": "Bilgi",
"welcome_title": "Yeni sekmeye hoş geldiniz",
"welcome_body": "Firefox son zamanlarda ziyaret ettiğiniz ve sık kullandığınız yer imlerini, makaleleri, videoları ve sayfaları onlara tekrar kolayca geri dönebilmeniz için bu alanda gösterecektir.",
"welcome_label": "Öne Çıkanlar'ınızı tanıyın",
@ -5009,7 +5192,11 @@
"pocket_read_even_more": "Daha fazla yazı göster",
"pocket_feedback_header": "25 milyon kişinin katkılarıyla, webin en iyileri.",
"pocket_feedback_body": "Mozilla ailesinin yeni üyesi Pocket, kolayca bulamayacağınız, kaliteli içerikleri karşınıza getiriyor.",
"pocket_send_feedback": "Görüş gönder"
"pocket_send_feedback": "Görüş gönder",
"topstories_empty_state": "Hepsini bitirdiniz. Yeni {provider} haberleri için daha fazla yine gelin. Beklemek istemiyor musunuz? İlginç yazılara ulaşmak için popüler konulardan birini seçebilirsiniz.",
"manual_migration_explanation": "Başka tarayıcılardaki sevdiğiniz siteleri ve yer imlerinizi Firefoxa aktarabilirsiniz.",
"manual_migration_cancel_button": "Gerek yok",
"manual_migration_import_button": "Olur, aktaralım"
},
"uk": {
"newtab_page_title": "Нова вкладка",
@ -5207,6 +5394,7 @@
"header_stories": "热门报道",
"header_visit_again": "再次造访",
"header_bookmarks": "最近的书签",
"header_recommended_by": "{provider} 推荐",
"header_bookmarks_placeholder": "您还没有最近的书签。",
"header_stories_from": "出自",
"type_label_visited": "曾经访问",
@ -5215,6 +5403,7 @@
"type_label_recommended": "趋势",
"type_label_open": "打开",
"type_label_topic": "主题",
"type_label_now": "现在",
"menu_action_bookmark": "添加书签",
"menu_action_remove_bookmark": "移除书签",
"menu_action_copy_address": "复制地址",
@ -5233,6 +5422,7 @@
"search_header": "{search_engine_name} 搜索",
"search_web_placeholder": "在网络上搜索",
"search_settings": "更改搜索设置",
"section_info_option": "信息",
"welcome_title": "欢迎使用新标签页",
"welcome_body": "Firefox 会在这里显示对您最有用的书签、文章、视频和访问过的页面,便于您回到这些网站。",
"welcome_label": "正在为您准备集锦",
@ -5277,7 +5467,11 @@
"pocket_read_even_more": "查看更多报道",
"pocket_feedback_header": "超过2500万人构成的互联网。",
"pocket_feedback_body": "PocketMozilla 家族的一员,它可以帮助您找到更多不易发现的高品质内容。",
"pocket_send_feedback": "发送反馈"
"pocket_send_feedback": "发送反馈",
"topstories_empty_state": "已经都看过了。请过会再来查看 {provider} 提供的热门故事。不想等待?选择一个热门话题,找到网络上的更多好故事。",
"manual_migration_explanation": "将您在其他浏览器中常用的网站和书签导入 Firefox体验别具一格的浏览器。",
"manual_migration_cancel_button": "不用了",
"manual_migration_import_button": "立即导入"
},
"zh-TW": {
"newtab_page_title": "新分頁",
@ -5286,6 +5480,7 @@
"header_stories": "熱門文章",
"header_visit_again": "再次造訪",
"header_bookmarks": "近期新增的書籤",
"header_recommended_by": "{provider} 推薦",
"header_bookmarks_placeholder": "您還沒有任何書籤。",
"header_stories_from": "來自",
"type_label_visited": "造訪過的網站",
@ -5294,6 +5489,7 @@
"type_label_recommended": "熱門",
"type_label_open": "開啟",
"type_label_topic": "主題",
"type_label_now": "現在",
"menu_action_bookmark": "書籤",
"menu_action_remove_bookmark": "移除書籤",
"menu_action_copy_address": "複製網址",
@ -5312,6 +5508,7 @@
"search_header": "{search_engine_name} 搜尋",
"search_web_placeholder": "搜尋 Web",
"search_settings": "變更搜尋選項",
"section_info_option": "更多資訊",
"welcome_title": "歡迎來到新分頁",
"welcome_body": "Firefox 會使用此空間來顯示與您最相關的書籤、文章、影片以及您最近造訪的頁面,這樣您就可以快速回到這些網站。",
"welcome_label": "找出您的精選網站",
@ -5332,7 +5529,7 @@
"settings_pane_visit_again_header": "再次造訪",
"settings_pane_visit_again_body": "Firefox 將顯示您一部份可能想要再次開啟的瀏覽紀錄。",
"settings_pane_pocketstories_header": "熱門文章",
"settings_pane_pocketstories_body": "Pocket 是 Mozilla 大家庭的一份子,可讓您與您還沒發現的高品質內容連結起來。",
"settings_pane_pocketstories_body": "Pocket 是 Mozilla 家族的一份子,將您可能沒發現的高品質內容帶到眼前。",
"settings_pane_done_button": "完成",
"edit_topsites_button_text": "編輯",
"edit_topsites_button_label": "自訂您的「熱門網站」區塊",
@ -5354,9 +5551,13 @@
"topsites_form_url_validation": "請輸入有效的網址",
"pocket_read_more": "熱門主題:",
"pocket_read_even_more": "檢視更多文章",
"pocket_feedback_header": "由超過兩千五百萬使用者一同策展出最棒的 Web。",
"pocket_feedback_body": "Pocket 是 Mozilla 大家庭的一份子,可讓您與您還沒發現的高品質內容連結起來。",
"pocket_send_feedback": "傳送意見回饋"
"pocket_feedback_header": "由超過兩千五百萬人找出來的 Web 最佳內容。",
"pocket_feedback_body": "Pocket 是 Mozilla 家族的一份子,將您可能沒發現的高品質內容帶到眼前。",
"pocket_send_feedback": "傳送意見回饋",
"topstories_empty_state": "所有文章都讀完啦!晚點再來,{provider} 將提供更多推薦故事。等不及了?選擇熱門主題,看看 Web 上各式精采資訊。",
"manual_migration_explanation": "匯入其他瀏覽器裡的書籤和網站,體驗 Firefox 的方便之處。",
"manual_migration_cancel_button": "不必了",
"manual_migration_import_button": "立即匯入"
},
"zu": {}
}

Просмотреть файл

@ -4,6 +4,7 @@
"use strict";
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
// NB: Eagerly load modules that will be loaded/constructed/initialized in the
// common case to avoid the overhead of wrapping and detecting lazy loading.
@ -28,7 +29,8 @@ const SECTIONS = new Map([
["topstories", {
feed: TopStoriesFeed,
prefTitle: "Fetches content recommendations from a configurable content provider",
showByDefault: false
// for now, we only want to show top stories by default to the following locales
showByDefault: ["en-US", "en-CA"].includes(Services.locale.getRequestedLocale())
}]
]);
@ -74,13 +76,15 @@ const PREFS_CONFIG = new Map([
title: "Configuration options for top stories feed",
value: `{
"stories_endpoint": "https://getpocket.com/v3/firefox/global-recs?consumer_key=$apiKey",
"stories_referrer": "https://getpocket.com/recommendations",
"topics_endpoint": "https://getpocket.com/v3/firefox/trending-topics?consumer_key=$apiKey",
"read_more_endpoint": "https://getpocket.com/explore/trending?src=ff_new_tab",
"learn_more_endpoint": "https://getpocket.com/firefox_learnmore?src=ff_newtab",
"survey_link": "https://www.surveymonkey.com/r/newtabffx",
"api_key_pref": "extensions.pocket.oAuthConsumerKey",
"provider_name": "Pocket",
"provider_icon": "pocket"
"provider_icon": "pocket",
"provider_description": "pocket_feedback_body"
}`
}],
["migrationExpired", {

Просмотреть файл

@ -47,6 +47,7 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
this.onMessage = this.onMessage.bind(this);
this.onNewTabLoad = this.onNewTabLoad.bind(this);
this.onNewTabUnload = this.onNewTabUnload.bind(this);
this.onNewTabInit = this.onNewTabInit.bind(this);
}
/**
@ -130,6 +131,7 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
AboutNewTab.override();
}
this.channel = new RemotePages(this.pageURL);
this.channel.addMessageListener("RemotePage:Init", this.onNewTabInit);
this.channel.addMessageListener("RemotePage:Load", this.onNewTabLoad);
this.channel.addMessageListener("RemotePage:Unload", this.onNewTabUnload);
this.channel.addMessageListener(this.incomingMessageName, this.onMessage);
@ -146,6 +148,16 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
}
}
/**
* onNewTabInit - Handler for special RemotePage:Init message fired
* by RemotePages
*
* @param {obj} msg The messsage from a page that was just initialized
*/
onNewTabInit(msg) {
this.onActionFromContent({type: at.NEW_TAB_INIT}, msg.target.portID);
}
/**
* onNewTabLoad - Handler for special RemotePage:Load message fired by RemotePages
*

Просмотреть файл

@ -186,6 +186,14 @@ class PlacesFeed {
}
}
openNewWindow(action, isPrivate = false) {
const win = action._target.browser.ownerGlobal;
const privateParam = {private: isPrivate};
const params = (action.data.referrer) ?
Object.assign(privateParam, {referrerURI: Services.io.newURI(action.data.referrer)}) : privateParam;
win.openLinkIn(action.data.url, "window", params);
}
onAction(action) {
switch (action.type) {
case at.INIT:
@ -207,9 +215,23 @@ class PlacesFeed {
case at.DELETE_HISTORY_URL:
NewTabUtils.activityStreamLinks.deleteHistoryEntry(action.data);
break;
case at.OPEN_NEW_WINDOW:
this.openNewWindow(action);
break;
case at.OPEN_PRIVATE_WINDOW:
this.openNewWindow(action, true);
break;
case at.SAVE_TO_POCKET:
Pocket.savePage(action._target.browser, action.data.site.url, action.data.site.title);
break;
case at.OPEN_LINK: {
if (action.data.referrer) {
action._target.browser.loadURI(action.data.url, Services.io.newURI(action.data.referrer));
} else {
action._target.browser.loadURI(action.data.url);
}
break;
}
}
}
}

Просмотреть файл

@ -5,19 +5,25 @@
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Console.jsm");
const {actionTypes: at, actionCreators: ac} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
"resource://gre/modules/ProfileAge.jsm");
// Url to fetch snippets, in the urlFormatter service format.
const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl";
const TELEMETRY_PREF = "datareporting.healthreport.uploadEnabled";
// Should be bumped up if the snippets content format changes.
const STARTPAGE_VERSION = 4;
const STARTPAGE_VERSION = 5;
const ONE_WEEK = 7 * 24 * 60 * 60 * 1000;
this.SnippetsFeed = class SnippetsFeed {
constructor() {
this._onUrlChange = this._onUrlChange.bind(this);
this._refresh = this._refresh.bind(this);
}
get snippetsURL() {
const updateURL = Services
@ -25,23 +31,36 @@ this.SnippetsFeed = class SnippetsFeed {
.replace("%STARTPAGE_VERSION%", STARTPAGE_VERSION);
return Services.urlFormatter.formatURL(updateURL);
}
init() {
async getProfileInfo() {
const profileAge = new ProfileAge(null, null);
const createdDate = await profileAge.created;
const resetDate = await profileAge.reset;
return {
createdWeeksAgo: Math.floor((Date.now() - createdDate) / ONE_WEEK),
resetWeeksAgo: resetDate ? Math.floor((Date.now() - resetDate) / ONE_WEEK) : null
};
}
async _refresh() {
const profileInfo = await this.getProfileInfo();
const data = {
snippetsURL: this.snippetsURL,
version: STARTPAGE_VERSION
version: STARTPAGE_VERSION,
profileCreatedWeeksAgo: profileInfo.createdWeeksAgo,
profileResetWeeksAgo: profileInfo.resetWeeksAgo,
telemetryEnabled: Services.prefs.getBoolPref(TELEMETRY_PREF)
};
this.store.dispatch(ac.BroadcastToContent({type: at.SNIPPETS_DATA, data}));
Services.prefs.addObserver(SNIPPETS_URL_PREF, this._onUrlChange);
}
async init() {
await this._refresh();
Services.prefs.addObserver(SNIPPETS_URL_PREF, this._refresh);
Services.prefs.addObserver(TELEMETRY_PREF, this._refresh);
}
uninit() {
this.store.dispatch({type: at.SNIPPETS_RESET});
Services.prefs.removeObserver(SNIPPETS_URL_PREF, this._onUrlChange);
}
_onUrlChange() {
this.store.dispatch(ac.BroadcastToContent({
type: at.SNIPPETS_DATA,
data: {snippetsURL: this.snippetsURL}
}));
Services.prefs.removeObserver(SNIPPETS_URL_PREF, this._refresh);
Services.prefs.removeObserver(TELEMETRY_PREF, this._refresh);
}
onAction(action) {
switch (action.type) {

Просмотреть файл

@ -35,6 +35,41 @@ this.TelemetryFeed = class TelemetryFeed {
perfService.mark("browser-open-newtab-start");
}
setLoadTriggerInfo(port) {
// XXX note that there is a race condition here; we're assuming that no
// other tab will be interleaving calls to browserOpenNewtabStart and
// when at.NEW_TAB_INIT gets triggered by RemotePages and calls this
// method. For manually created windows, it's hard to imagine us hitting
// this race condition.
//
// However, for session restore, where multiple windows with multiple tabs
// might be restored much closer together in time, it's somewhat less hard,
// though it should still be pretty rare.
//
// The fix to this would be making all of the load-trigger notifications
// return some data with their notifications, and somehow propagate that
// data through closures into the tab itself so that we could match them
//
// As of this writing (very early days of system add-on perf telemetry),
// the hypothesis is that hitting this race should be so rare that makes
// more sense to live with the slight data inaccuracy that it would
// introduce, rather than doing the correct but complicated thing. It may
// well be worth reexamining this hypothesis after we have more experience
// with the data.
let data_to_save;
try {
data_to_save = {
load_trigger_ts: perfService.getMostRecentAbsMarkStartByName("browser-open-newtab-start"),
load_trigger_type: "menu_plus_or_keyboard"
};
} catch (e) {
// if no mark was returned, we have nothing to save
return;
}
this.saveSessionPerfData(port, data_to_save);
}
/**
* Lazily get the Telemetry id promise
*/
@ -55,52 +90,17 @@ this.TelemetryFeed = class TelemetryFeed {
* addSession - Start tracking a new session
*
* @param {string} id the portID of the open session
* @param {number} absVisChangeTime Optional. Absolute timestamp of
* document.visibilityState becoming visible
*
* @return {obj} Session object
*/
addSession(id, absVisChangeTime) {
// XXX note that there is a race condition here; we're assuming that no
// other tab will be interleaving calls to browserOpenNewtabStart and
// addSession on this object. For manually created windows, it's hard to
// imagine us hitting this race condition.
//
// However, for session restore, where multiple windows with multiple tabs
// might be restored much closer together in time, it's somewhat less hard,
// though it should still be pretty rare.
//
// The fix to this would be making all of the load-trigger notifications
// return some data with their notifications, and somehow propagate that
// data through closures into the tab itself so that we could match them
//
// As of this writing (very early days of system add-on perf telemetry),
// the hypothesis is that hitting this race should be so rare that makes
// more sense to live with the slight data inaccuracy that it would
// introduce, rather than doing the correct by complicated thing. It may
// well be worth reexamining this hypothesis after we have more experience
// with the data.
let absBrowserOpenTabStart;
try {
absBrowserOpenTabStart = perfService.getMostRecentAbsMarkStartByName("browser-open-newtab-start");
} catch (e) {
// Just use undefined so it doesn't get sent to the server
}
// If we're missing either starting timestamps, treat it as an unexpected
// session; otherwise, assume it's the usual behavior.
const triggerType = absBrowserOpenTabStart === undefined ||
absVisChangeTime === undefined ? "unexpected" : "menu_plus_or_keyboard";
addSession(id) {
const session = {
start_time: Components.utils.now(),
session_id: String(gUUIDGenerator.generateUUID()),
page: "about:newtab", // TODO: Handle about:home here and in perf below
perf: {
load_trigger_ts: absBrowserOpenTabStart,
load_trigger_type: triggerType,
visibility_event_rcvd_ts: absVisChangeTime
}
page: "about:newtab", // TODO: Handle about:home here
// "unexpected" will be overwritten when appropriate
perf: {load_trigger_type: "unexpected"}
};
this.sessions.set(id, session);
return session;
}
@ -118,7 +118,10 @@ this.TelemetryFeed = class TelemetryFeed {
return;
}
session.session_duration = Math.round(Components.utils.now() - session.start_time);
if (session.perf.visibility_event_rcvd_ts) {
session.session_duration = Math.round(perfService.absNow() - session.perf.visibility_event_rcvd_ts);
}
this.sendEvent(this.createSessionEndEvent(session));
this.sessions.delete(portID);
}
@ -167,7 +170,7 @@ this.TelemetryFeed = class TelemetryFeed {
async createPerformanceEvent(action) {
return Object.assign(
await this.createPing(au.getPortIdOfSender(action)),
await this.createPing(),
action.data,
{action: "activity_stream_performance_event"}
);
@ -195,13 +198,16 @@ this.TelemetryFeed = class TelemetryFeed {
case at.INIT:
this.init();
break;
case at.NEW_TAB_VISIBLE:
this.addSession(au.getPortIdOfSender(action),
action.data.absVisibilityChangeTime);
case at.NEW_TAB_INIT:
this.addSession(au.getPortIdOfSender(action));
this.setLoadTriggerInfo(au.getPortIdOfSender(action));
break;
case at.NEW_TAB_UNLOAD:
this.endSession(au.getPortIdOfSender(action));
break;
case at.SAVE_SESSION_PERF_DATA:
this.saveSessionPerfData(au.getPortIdOfSender(action), action.data);
break;
case at.TELEMETRY_UNDESIRED_EVENT:
this.sendEvent(this.createUndesiredEvent(action));
break;
@ -214,6 +220,27 @@ this.TelemetryFeed = class TelemetryFeed {
}
}
/**
* Take all enumerable members of the data object and merge them into
* the session.perf object for the given port, so that it is sent to the
* server when the session ends. All members of the data object should
* be valid values of the perf object, as defined in pings.js and the
* data*.md documentation.
*
* @note Any existing keys with the same names already in the
* session perf object will be overwritten by values passed in here.
*
* @param {String} port The session with which this is associated
* @param {Object} data The perf data to be
*/
saveSessionPerfData(port, data) {
// XXX should use try/catch and send a bad state indicator if this
// get blows up.
let session = this.sessions.get(port);
Object.assign(session.perf, data);
}
uninit() {
Services.obs.removeObserver(this.browserOpenNewtabStart,
"browser-open-newtab-start");

Просмотреть файл

@ -88,10 +88,6 @@ this.TopSitesFeed = class TopSitesFeed {
}
this.lastUpdated = Date.now();
}
openNewWindow(action, isPrivate = false) {
const win = action._target.browser.ownerGlobal;
win.openLinkIn(action.data.url, "window", {private: isPrivate});
}
_getPinnedWithData() {
// Augment the pinned links with any other extra data we have for them already in the store
const links = this.store.getState().TopSites.rows;
@ -134,12 +130,6 @@ this.TopSitesFeed = class TopSitesFeed {
this.refresh(action.meta.fromTarget);
}
break;
case at.OPEN_NEW_WINDOW:
this.openNewWindow(action);
break;
case at.OPEN_PRIVATE_WINDOW:
this.openNewWindow(action, true);
break;
case at.PLACES_HISTORY_CLEARED:
this.refresh();
break;

Просмотреть файл

@ -31,10 +31,12 @@ this.TopStoriesFeed = class TopStoriesFeed {
this.stories_endpoint = this._produceUrlWithApiKey(options.stories_endpoint, apiKey);
this.topics_endpoint = this._produceUrlWithApiKey(options.topics_endpoint, apiKey);
this.read_more_endpoint = options.read_more_endpoint;
this.stories_referrer = options.stories_referrer;
// TODO https://github.com/mozilla/activity-stream/issues/2902
const sectionOptions = {
id: SECTION_ID,
eventSource: "TOP_STORIES",
icon: options.provider_icon,
title: {id: "header_recommended_by", values: {provider: options.provider_name}},
rows: [],
@ -42,7 +44,7 @@ this.TopStoriesFeed = class TopStoriesFeed {
contextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
infoOption: {
header: {id: "pocket_feedback_header"},
body: {id: "pocket_feedback_body"},
body: {id: options.provider_description},
link: {
href: options.survey_link,
id: "pocket_send_feedback"
@ -85,6 +87,7 @@ this.TopStoriesFeed = class TopStoriesFeed {
"title": s.title,
"description": s.excerpt,
"image": this._normalizeUrl(s.image_src),
"referrer": this.stories_referrer,
"url": s.dedupe_url
}));
return items;

Просмотреть файл

@ -70,7 +70,7 @@ const PerfPing = Joi.object().keys(Object.assign({}, baseKeys, {
const SessionPing = Joi.object().keys(Object.assign({}, baseKeys, {
session_id: baseKeys.session_id.required(),
page: baseKeys.page.required(),
session_duration: Joi.number().integer().required(),
session_duration: Joi.number().integer(),
action: Joi.valid("activity_stream_session").required(),
perf: Joi.object().keys({
// Timestamp of the action perceived by the user to trigger the load
@ -88,6 +88,13 @@ const SessionPing = Joi.object().keys(Object.assign({}, baseKeys, {
load_trigger_type: Joi.valid(["menu_plus_or_keyboard", "unexpected"])
.notes(["server counter", "server counter alert"]).required(),
// When did the topsites element finish painting? Note that, at least for
// the first tab to be loaded, and maybe some others, this will be before
// topsites has yet to receive screenshots updates from the add-on code,
// and is therefore just showing placeholder screenshots.
topsites_first_painted_ts: Joi.number().positive()
.notes(["server counter", "server counter alert"]),
// When the page itself receives an event that document.visibilityState
// == visible.
//

Просмотреть файл

@ -18,6 +18,13 @@ describe("_PerfService", () => {
sandbox.restore();
});
describe("#absNow", () => {
it("should return a number > the time origin", () => {
const absNow = perfService.absNow();
assert.isAbove(absNow, perfService.timeOrigin);
});
});
describe("#getEntriesByName", () => {
it("should call getEntriesByName on the appropriate Window.performance",
() => {
@ -78,7 +85,7 @@ describe("_PerfService", () => {
describe("#timeOrigin", () => {
it("should get the origin of the wrapped performance object", () => {
assert.equal(perfService.timeOrigin, 10000); // fake origin from utils.js
assert.equal(perfService.timeOrigin, fakePerfObj.timeOrigin);
});
});
});

Просмотреть файл

@ -52,9 +52,9 @@ describe("ActivityStreamMessageChannel", () => {
assert.ok(mm.channel);
assert.equal(mm.channel.url, mm.pageURL);
});
it("should add 3 message listeners", () => {
it("should add 4 message listeners", () => {
mm.createChannel();
assert.callCount(mm.channel.addMessageListener, 3);
assert.callCount(mm.channel.addMessageListener, 4);
});
it("should add the custom message listener to the channel", () => {
mm.createChannel();
@ -111,6 +111,14 @@ describe("ActivityStreamMessageChannel", () => {
assert.equal(mm.getTargetById("bar"), null);
});
});
describe("#onNewTabInit", () => {
it("should dispatch a NEW_TAB_INIT action", () => {
const t = {portID: "foo"};
sinon.stub(mm, "onActionFromContent");
mm.onNewTabInit({target: t});
assert.calledWith(mm.onActionFromContent, {type: at.NEW_TAB_INIT}, "foo");
});
});
describe("#onNewTabLoad", () => {
it("should dispatch a NEW_TAB_LOAD action", () => {
const t = {portID: "foo"};

Просмотреть файл

@ -99,6 +99,49 @@ describe("PlacesFeed", () => {
feed.onAction({type: at.DELETE_HISTORY_URL, data: "guava.com"});
assert.calledWith(global.NewTabUtils.activityStreamLinks.deleteHistoryEntry, "guava.com");
});
it("should call openNewWindow with the correct url on OPEN_NEW_WINDOW", () => {
sinon.stub(feed, "openNewWindow");
const openWindowAction = {type: at.OPEN_NEW_WINDOW, data: {url: "foo.com"}};
feed.onAction(openWindowAction);
assert.calledWith(feed.openNewWindow, openWindowAction);
});
it("should call openNewWindow with the correct url and privacy args on OPEN_PRIVATE_WINDOW", () => {
sinon.stub(feed, "openNewWindow");
const openWindowAction = {type: at.OPEN_PRIVATE_WINDOW, data: {url: "foo.com"}};
feed.onAction(openWindowAction);
assert.calledWith(feed.openNewWindow, openWindowAction, true);
});
it("should call openNewWindow with the correct url on OPEN_NEW_WINDOW", () => {
const openWindowAction = {
type: at.OPEN_NEW_WINDOW,
data: {url: "foo.com"},
_target: {browser: {ownerGlobal: {openLinkIn: () => {}}}}
};
sinon.stub(openWindowAction._target.browser.ownerGlobal, "openLinkIn");
feed.onAction(openWindowAction);
assert.calledOnce(openWindowAction._target.browser.ownerGlobal.openLinkIn);
});
it("should open link on OPEN_LINK", () => {
sinon.stub(feed, "openNewWindow");
const openLinkAction = {
type: at.OPEN_LINK,
data: {url: "foo.com"},
_target: {browser: {loadURI: sinon.spy()}}
};
feed.onAction(openLinkAction);
assert.calledWith(openLinkAction._target.browser.loadURI, openLinkAction.data.url);
});
it("should open link with referrer on OPEN_LINK", () => {
globals.set("Services", {io: {newURI: url => `URI:${url}`}});
sinon.stub(feed, "openNewWindow");
const openLinkAction = {
type: at.OPEN_LINK,
data: {url: "foo.com", referrer: "foo.com/ref"},
_target: {browser: {loadURI: sinon.spy()}}
};
feed.onAction(openLinkAction);
assert.calledWith(openLinkAction._target.browser.loadURI, openLinkAction.data.url, `URI:${openLinkAction.data.referrer}`);
});
it("should save to Pocket on SAVE_TO_POCKET", () => {
feed.onAction({type: at.SAVE_TO_POCKET, data: {site: {url: "raspberry.com", title: "raspberry"}}, _target: {browser: {}}});
assert.calledWith(global.Pocket.savePage, {}, "raspberry.com", "raspberry");

Просмотреть файл

@ -1,29 +1,60 @@
const {SnippetsFeed} = require("lib/SnippetsFeed.jsm");
const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
const {actionTypes: at} = require("common/Actions.jsm");
const {GlobalOverrider} = require("test/unit/utils");
const WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000;
let overrider = new GlobalOverrider();
describe("SnippetsFeed", () => {
let sandbox;
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
overrider.set({
ProfileAge: class ProfileAge {
constructor() {
this.created = Promise.resolve(0);
this.reset = Promise.resolve(WEEK_IN_MS);
}
}
});
sandbox = sinon.sandbox.create();
});
afterEach(() => {
clock.restore();
overrider.restore();
sandbox.restore();
});
it("should dispatch the right version and snippetsURL on INIT", () => {
it("should dispatch a SNIPPETS_DATA action with the right data on INIT", async () => {
const url = "foo.com/%STARTPAGE_VERSION%";
sandbox.stub(global.Services.prefs, "getStringPref").returns(url);
sandbox.stub(global.Services.prefs, "getBoolPref").returns(true);
const feed = new SnippetsFeed();
feed.store = {dispatch: sandbox.stub()};
feed.onAction({type: at.INIT});
clock.tick(WEEK_IN_MS * 2);
assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({
type: at.SNIPPETS_DATA,
data: {
snippetsURL: "foo.com/4",
version: 4
}
}));
await feed.init();
assert.calledOnce(feed.store.dispatch);
const action = feed.store.dispatch.firstCall.args[0];
assert.propertyVal(action, "type", at.SNIPPETS_DATA);
assert.isObject(action.data);
assert.propertyVal(action.data, "snippetsURL", "foo.com/5");
assert.propertyVal(action.data, "version", 5);
assert.propertyVal(action.data, "profileCreatedWeeksAgo", 2);
assert.propertyVal(action.data, "profileResetWeeksAgo", 1);
assert.propertyVal(action.data, "telemetryEnabled", true);
});
it("should call .init on an INIT aciton", () => {
const feed = new SnippetsFeed();
sandbox.stub(feed, "init");
feed.store = {dispatch: sandbox.stub()};
feed.onAction({type: at.INIT});
assert.calledOnce(feed.init);
});
it("should call .init when a FEED_INIT happens for feeds.snippets", () => {
const feed = new SnippetsFeed();
@ -42,19 +73,4 @@ describe("SnippetsFeed", () => {
assert.calledWith(feed.store.dispatch, {type: at.SNIPPETS_RESET});
});
describe("_onUrlChange", () => {
it("should dispatch a new snippetsURL", () => {
const url = "boo.com/%STARTPAGE_VERSION%";
sandbox.stub(global.Services.prefs, "getStringPref").returns(url);
const feed = new SnippetsFeed();
feed.store = {dispatch: sandbox.stub()};
feed._onUrlChange();
assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({
type: at.SNIPPETS_DATA,
data: {snippetsURL: "boo.com/4"}
}));
});
});
});

Просмотреть файл

@ -23,7 +23,11 @@ describe("TelemetryFeed", () => {
};
let instance;
class TelemetrySender {sendPing() {} uninit() {}}
class PerfService {getMostRecentAbsMarkStartByName() { return 1234; } mark() {}}
class PerfService {
getMostRecentAbsMarkStartByName() { return 1234; }
mark() {}
absNow() { return 123; }
}
const perfService = new PerfService();
const {TelemetryFeed} = injector({
"lib/TelemetrySender.jsm": {TelemetrySender},
@ -64,14 +68,6 @@ describe("TelemetryFeed", () => {
assert.equal(instance.sessions.get("foo"), session);
});
it("should set the start_time", () => {
sandbox.spy(Components.utils, "now");
const session = instance.addSession("foo");
assert.calledOnce(Components.utils.now);
assert.equal(session.start_time, Components.utils.now.firstCall.returnValue);
});
it("should set the session_id", () => {
sandbox.spy(global.gUUIDGenerator, "generateUUID");
@ -90,21 +86,6 @@ describe("TelemetryFeed", () => {
assert.propertyVal(session.perf, "load_trigger_type", "unexpected");
});
it("should set the perf type with timestamp", () => {
const session = instance.addSession("foo", 123);
assert.propertyVal(session.perf, "load_trigger_type", "menu_plus_or_keyboard"); // This is hardcoded for now.
});
it("should save visibility time", () => {
const session = instance.addSession("foo", 123);
assert.propertyVal(session.perf, "visibility_event_rcvd_ts", 123);
});
it("should not save visibility time when lacking timestamp", () => {
const session = instance.addSession("foo");
assert.propertyVal(session.perf, "visibility_event_rcvd_ts", undefined);
});
});
describe("#browserOpenNewtabStart", () => {
it("should call perfService.mark with browser-open-newtab-start", () => {
@ -121,13 +102,24 @@ describe("TelemetryFeed", () => {
it("should not throw if there is no session for the given port ID", () => {
assert.doesNotThrow(() => instance.endSession("doesn't exist"));
});
it("should add a session_duration", () => {
it("should add a session_duration integer if there is a visibility_event_rcvd_ts", () => {
sandbox.stub(instance, "sendEvent");
const session = instance.addSession("foo");
session.perf.visibility_event_rcvd_ts = 444.4732;
instance.endSession("foo");
assert.isNumber(session.session_duration);
assert.ok(Number.isInteger(session.session_duration),
"session_duration should be an integer");
});
it("shouldn't add session_duration if there's no visibility_event_rcvd_ts", () => {
sandbox.stub(instance, "sendEvent");
const session = instance.addSession("foo");
instance.endSession("foo");
assert.property(session, "session_duration");
assert.notProperty(session, "session_duration");
});
it("should remove the session from .sessions", () => {
sandbox.stub(instance, "sendEvent");
@ -231,21 +223,6 @@ describe("TelemetryFeed", () => {
// Does it have the right value?
assert.propertyVal(ping, "value", 100);
});
it("should create a valid event with a session", async () => {
const portID = "foo";
const data = {event: "PAGE_LOADED", value: 100};
const action = ac.SendToMain(ac.PerfEvent(data), portID);
const session = instance.addSession(portID);
const ping = await instance.createPerformanceEvent(action);
// Is it valid?
assert.validate(ping, PerfPing);
// Does it have the right session_id?
assert.propertyVal(ping, "session_id", session.session_id);
// Does it have the right value?
assert.propertyVal(ping, "value", 100);
});
});
describe("#createSessionEndEvent", () => {
it("should create a valid event", async () => {
@ -290,6 +267,43 @@ describe("TelemetryFeed", () => {
assert.calledWith(instance.telemetrySender.sendPing, event);
});
});
describe("#setLoadTriggerInfo", () => {
it("should call saveSessionPerfData w/load_trigger_{ts,type} data", () => {
const stub = sandbox.stub(instance, "saveSessionPerfData");
sandbox.stub(perfService, "getMostRecentAbsMarkStartByName").returns(777);
instance.addSession("port123");
instance.setLoadTriggerInfo("port123");
assert.calledWith(stub, "port123", {
load_trigger_ts: 777,
load_trigger_type: "menu_plus_or_keyboard"
});
});
it("should not call saveSessionPerfData when getting mark throws", () => {
const stub = sandbox.stub(instance, "saveSessionPerfData");
sandbox.stub(perfService, "getMostRecentAbsMarkStartByName").throws();
instance.addSession("port123");
instance.setLoadTriggerInfo("port123");
assert.notCalled(stub);
});
});
describe("#saveSessionPerfData", () => {
it("should update the given session with the given data", () => {
instance.addSession("port123");
assert.notProperty(instance.sessions.get("port123"), "fake_ts");
const data = {fake_ts: 456, other_fake_ts: 789};
instance.saveSessionPerfData("port123", data);
assert.include(instance.sessions.get("port123").perf, data);
});
});
describe("#uninit", () => {
it("should call .telemetrySender.uninit", () => {
const stub = sandbox.stub(instance.telemetrySender, "uninit");
@ -314,12 +328,27 @@ describe("TelemetryFeed", () => {
instance.onAction({type: at.INIT});
assert.calledOnce(stub);
});
it("should call .addSession() on a NEW_TAB_VISIBLE action", () => {
it("should call .addSession() on a NEW_TAB_INIT action", () => {
const stub = sandbox.stub(instance, "addSession");
sandbox.stub(instance, "setLoadTriggerInfo");
instance.onAction(ac.SendToMain({
type: at.NEW_TAB_VISIBLE,
data: {absVisibilityChangeTime: 789}
type: at.NEW_TAB_INIT,
data: {}
}, "port123"));
assert.calledOnce(stub);
assert.calledWith(stub, "port123");
});
it("should call .setLoadTriggerInfo() on NEW_TAB_INIT action", () => {
const stub = sandbox.stub(instance, "setLoadTriggerInfo");
instance.onAction(ac.SendToMain({
type: at.NEW_TAB_INIT,
data: {}
}, "port123"));
assert.calledOnce(stub);
assert.calledWith(stub, "port123");
});
it("should call .endSession() on a NEW_TAB_UNLOAD action", () => {
@ -327,6 +356,15 @@ describe("TelemetryFeed", () => {
instance.onAction(ac.SendToMain({type: at.NEW_TAB_UNLOAD}, "port123"));
assert.calledWith(stub, "port123");
});
it("should call .saveSessionPerfData on SAVE_SESSION_PERF_DATA", () => {
const stub = sandbox.stub(instance, "saveSessionPerfData");
const data = {some_ts: 10};
const action = {type: at.SAVE_SESSION_PERF_DATA, data};
instance.onAction(ac.SendToMain(action, "port123"));
assert.calledWith(stub, "port123", data);
});
it("should send an event on an TELEMETRY_UNDESIRED_EVENT action", () => {
const sendEvent = sandbox.stub(instance, "sendEvent");
const eventCreator = sandbox.stub(instance, "createUndesiredEvent");

Просмотреть файл

@ -165,28 +165,6 @@ describe("Top Sites Feed", () => {
feed.onAction(newTabAction);
assert.notCalled(feed.refresh);
});
it("should call openNewWindow with the correct url on OPEN_NEW_WINDOW", () => {
sinon.stub(feed, "openNewWindow");
const openWindowAction = {type: at.OPEN_NEW_WINDOW, data: {url: "foo.com"}};
feed.onAction(openWindowAction);
assert.calledWith(feed.openNewWindow, openWindowAction);
});
it("should call openNewWindow with the correct url and privacy args on OPEN_PRIVATE_WINDOW", () => {
sinon.stub(feed, "openNewWindow");
const openWindowAction = {type: at.OPEN_PRIVATE_WINDOW, data: {url: "foo.com"}};
feed.onAction(openWindowAction);
assert.calledWith(feed.openNewWindow, openWindowAction, true);
});
it("should call openNewWindow with the correct url on OPEN_NEW_WINDOW", () => {
const openWindowAction = {
type: at.OPEN_NEW_WINDOW,
data: {url: "foo.com"},
_target: {browser: {ownerGlobal: {openLinkIn: () => {}}}}
};
sinon.stub(openWindowAction._target.browser.ownerGlobal, "openLinkIn");
feed.onAction(openWindowAction);
assert.calledOnce(openWindowAction._target.browser.ownerGlobal.openLinkIn);
});
it("should call with correct parameters on TOP_SITES_PIN", () => {
const pinAction = {
type: at.TOP_SITES_PIN,

Просмотреть файл

@ -16,11 +16,13 @@ describe("Top Stories Feed", () => {
beforeEach(() => {
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = `{
"stories_endpoint": "https://somedomain.org/stories?key=$apiKey",
"stories_referrer": "https://somedomain.org/referrer",
"topics_endpoint": "https://somedomain.org/topics?key=$apiKey",
"survey_link": "https://www.surveymonkey.com/r/newtabffx",
"api_key_pref": "apiKeyPref",
"provider_name": "test-provider",
"provider_icon": "provider-icon"
"provider_icon": "provider-icon",
"provider_description": "provider_desc"
}`;
FakePrefs.prototype.prefs.apiKeyPref = "test-api-key";
@ -42,11 +44,13 @@ describe("Top Stories Feed", () => {
it("should initialize endpoints based on prefs", () => {
instance.onAction({type: at.INIT});
assert.equal("https://somedomain.org/stories?key=test-api-key", instance.stories_endpoint);
assert.equal("https://somedomain.org/referrer", instance.stories_referrer);
assert.equal("https://somedomain.org/topics?key=test-api-key", instance.topics_endpoint);
});
it("should register section", () => {
const expectedSectionOptions = {
id: SECTION_ID,
eventSource: "TOP_STORIES",
icon: "provider-icon",
title: {id: "header_recommended_by", values: {provider: "test-provider"}},
rows: [],
@ -54,7 +58,7 @@ describe("Top Stories Feed", () => {
contextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
infoOption: {
header: {id: "pocket_feedback_header"},
body: {id: "pocket_feedback_body"},
body: {id: "provider_desc"},
link: {
href: "https://www.surveymonkey.com/r/newtabffx",
id: "pocket_send_feedback"
@ -145,10 +149,12 @@ describe("Top Stories Feed", () => {
"title": "title",
"description": "description",
"image": "image-url",
"referrer": "referrer",
"url": "rec-url"
}];
instance.stories_endpoint = "stories-endpoint";
instance.stories_referrer = "referrer";
fetchStub.resolves({ok: true, status: 200, text: () => response});
await instance.fetchStories();

Просмотреть файл

@ -43,6 +43,7 @@ overrider.set({
addObserver() {},
removeObserver() {},
getStringPref() {},
getBoolPref() {},
getDefaultBranch() {
return {
setBoolPref() {},

Просмотреть файл

@ -125,9 +125,9 @@ FakePerformance.prototype = {
now() {
return window.performance.now();
},
timing: {navigationStart: 222222},
timing: {navigationStart: 222222.123},
get timeOrigin() {
return 10000;
return 10000.234;
},
// XXX assumes type == "mark"
getEntriesByName(name, type) {