--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -55,17 +55,17 @@
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
-/******/ return __webpack_require__(__webpack_require__.s = 12);
+/******/ return __webpack_require__(__webpack_require__.s = 10);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -330,39 +330,26 @@ try {
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}
module.exports = g;
/***/ }),
/* 5 */
-/***/ (function(module, exports) {
-
-module.exports = {
- TOP_SITES_SOURCE: "TOP_SITES",
- TOP_SITES_CONTEXT_MENU_OPTIONS: ["CheckPinTopSite", "EditTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"],
- // minimum size necessary to show a rich icon instead of a screenshot
- MIN_RICH_FAVICON_SIZE: 96,
- // minimum size necessary to show any icon in the top left corner with a screenshot
- MIN_CORNER_FAVICON_SIZE: 16
-};
-
-/***/ }),
-/* 6 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { actionTypes: at } = __webpack_require__(0);
-const { Dedupe } = __webpack_require__(20);
+const { Dedupe } = __webpack_require__(13);
const TOP_SITES_DEFAULT_LENGTH = 6;
const TOP_SITES_SHOWMORE_LENGTH = 12;
const dedupe = new Dedupe(site => site && site.url);
const INITIAL_STATE = {
App: {
@@ -687,271 +674,257 @@ module.exports = {
reducers,
INITIAL_STATE,
insertPinned,
TOP_SITES_DEFAULT_LENGTH,
TOP_SITES_SHOWMORE_LENGTH
};
/***/ }),
-/* 7 */
-/***/ (function(module, exports, __webpack_require__) {
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-const React = __webpack_require__(1);
-const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
-
-const LinkMenu = __webpack_require__(8);
-
-const { TOP_SITES_SOURCE, TOP_SITES_CONTEXT_MENU_OPTIONS, MIN_RICH_FAVICON_SIZE, MIN_CORNER_FAVICON_SIZE } = __webpack_require__(5);
-
-const TopSiteLink = props => {
- const { link, title } = props;
- const topSiteOuterClassName = `top-site-outer${props.className ? ` ${props.className}` : ""}`;
- const { tippyTopIcon, faviconSize } = link;
- const letterFallback = title[0];
- let imageClassName;
- let imageStyle;
- let showSmallFavicon = false;
- let smallFaviconStyle;
- let smallFaviconFallback;
- if (tippyTopIcon || faviconSize >= MIN_RICH_FAVICON_SIZE) {
- // styles and class names for top sites with rich icons
- imageClassName = "top-site-icon rich-icon";
- imageStyle = {
- backgroundColor: link.backgroundColor,
- backgroundImage: `url(${tippyTopIcon || link.favicon})`
- };
- } else {
- // styles and class names for top sites with screenshot + small icon in top left corner
- imageClassName = `screenshot${link.screenshot ? " active" : ""}`;
- imageStyle = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" };
-
- // only show a favicon in top left if it's greater than 16x16
- if (faviconSize >= MIN_CORNER_FAVICON_SIZE) {
- showSmallFavicon = true;
- smallFaviconStyle = { backgroundImage: `url(${link.favicon})` };
- } else if (link.screenshot) {
- // Don't show a small favicon if there is no screenshot, because that
- // would result in two fallback icons
- showSmallFavicon = true;
- smallFaviconFallback = true;
- }
- }
- return React.createElement(
- "li",
- { className: topSiteOuterClassName, key: link.guid || link.url },
- React.createElement(
- "a",
- { href: link.url, onClick: props.onClick },
- React.createElement(
- "div",
- { className: "tile", "aria-hidden": true, "data-fallback": letterFallback },
- React.createElement("div", { className: imageClassName, style: imageStyle }),
- showSmallFavicon && React.createElement("div", {
- className: "top-site-icon default-icon",
- "data-fallback": smallFaviconFallback && letterFallback,
- style: smallFaviconStyle })
- ),
- React.createElement(
- "div",
- { className: `title ${link.isPinned ? "pinned" : ""}` },
- link.isPinned && React.createElement("div", { className: "icon icon-pin-small" }),
- React.createElement(
- "span",
- { dir: "auto" },
- title
- )
- )
- ),
- props.children
- );
-};
-TopSiteLink.defaultProps = {
- title: "",
- link: {}
-};
-
-class TopSite extends React.PureComponent {
+/* 6 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+
+// EXTERNAL MODULE: ./system-addon/common/Actions.jsm
+var Actions = __webpack_require__(0);
+var Actions_default = /*#__PURE__*/__webpack_require__.n(Actions);
+
+// EXTERNAL MODULE: external "React"
+var external__React_ = __webpack_require__(1);
+var external__React__default = /*#__PURE__*/__webpack_require__.n(external__React_);
+
+// CONCATENATED MODULE: ./system-addon/content-src/components/ContextMenu/ContextMenu.jsx
+
+
+class ContextMenu_ContextMenu extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
- this.state = { showContextMenu: false, activeTile: null };
- this.onLinkClick = this.onLinkClick.bind(this);
- this.onMenuButtonClick = this.onMenuButtonClick.bind(this);
- this.onMenuUpdate = this.onMenuUpdate.bind(this);
- this.onDismissButtonClick = this.onDismissButtonClick.bind(this);
- this.onPinButtonClick = this.onPinButtonClick.bind(this);
- this.onEditButtonClick = this.onEditButtonClick.bind(this);
- }
- toggleContextMenu(event, index) {
- this.setState({
- activeTile: index,
- showContextMenu: true
- });
+ this.hideContext = this.hideContext.bind(this);
}
- userEvent(event) {
- this.props.dispatch(ac.UserEvent({
- event,
- source: TOP_SITES_SOURCE,
- action_position: this.props.index
- }));
+ hideContext() {
+ this.props.onUpdate(false);
}
- onLinkClick(ev) {
- if (this.props.onEdit) {
- // Ignore clicks if we are in the edit modal.
- ev.preventDefault();
- return;
- }
- this.userEvent("CLICK");
+ componentWillMount() {
+ this.hideContext();
}
- onMenuButtonClick(event) {
- event.preventDefault();
- this.toggleContextMenu(event, this.props.index);
- }
- onMenuUpdate(showContextMenu) {
- this.setState({ showContextMenu });
- }
- onDismissButtonClick() {
- const { link } = this.props;
- if (link.isPinned) {
- this.props.dispatch(ac.SendToMain({
- type: at.TOP_SITES_UNPIN,
- data: { site: { url: link.url } }
- }));
+ componentDidUpdate(prevProps) {
+ if (this.props.visible && !prevProps.visible) {
+ setTimeout(() => {
+ window.addEventListener("click", this.hideContext);
+ }, 0);
}
- this.props.dispatch(ac.SendToMain({
- type: at.BLOCK_URL,
- data: link.url
- }));
- this.userEvent("BLOCK");
- }
- onPinButtonClick() {
- const { link, index } = this.props;
- if (link.isPinned) {
- this.props.dispatch(ac.SendToMain({
- type: at.TOP_SITES_UNPIN,
- data: { site: { url: link.url } }
- }));
- this.userEvent("UNPIN");
- } else {
- this.props.dispatch(ac.SendToMain({
- type: at.TOP_SITES_PIN,
- data: { site: { url: link.url }, index }
- }));
- this.userEvent("PIN");
+ if (!this.props.visible && prevProps.visible) {
+ window.removeEventListener("click", this.hideContext);
}
}
- onEditButtonClick() {
- this.props.onEdit(this.props.index);
+ componentWillUnmount() {
+ window.removeEventListener("click", this.hideContext);
}
render() {
- const { props } = this;
- const { link } = props;
- const isContextMenuOpen = this.state.showContextMenu && this.state.activeTile === props.index;
- const title = link.label || link.hostname;
- return React.createElement(
- TopSiteLink,
- _extends({}, props, { onClick: this.onLinkClick, className: isContextMenuOpen ? "active" : "", title: title }),
- !props.onEdit && React.createElement(
- "div",
- null,
- React.createElement(
- "button",
- { className: "context-menu-button icon", onClick: this.onMenuButtonClick },
- React.createElement(
- "span",
- { className: "sr-only" },
- `Open context menu for ${title}`
- )
- ),
- React.createElement(LinkMenu, {
- dispatch: props.dispatch,
- index: props.index,
- onUpdate: this.onMenuUpdate,
- options: TOP_SITES_CONTEXT_MENU_OPTIONS,
- site: link,
- source: TOP_SITES_SOURCE,
- visible: isContextMenuOpen })
- ),
- props.onEdit && React.createElement(
- "div",
- { className: "edit-menu" },
- React.createElement("button", {
- className: `icon icon-${link.isPinned ? "unpin" : "pin"}`,
- title: this.props.intl.formatMessage({ id: `edit_topsites_${link.isPinned ? "unpin" : "pin"}_button` }),
- onClick: this.onPinButtonClick }),
- React.createElement("button", {
- className: "icon icon-edit",
- title: this.props.intl.formatMessage({ id: "edit_topsites_edit_button" }),
- onClick: this.onEditButtonClick }),
- React.createElement("button", {
- className: "icon icon-dismiss",
- title: this.props.intl.formatMessage({ id: "edit_topsites_dismiss_button" }),
- onClick: this.onDismissButtonClick })
+ return external__React__default.a.createElement(
+ "span",
+ { hidden: !this.props.visible, className: "context-menu" },
+ external__React__default.a.createElement(
+ "ul",
+ { role: "menu", className: "context-menu-list" },
+ this.props.options.map((option, i) => option.type === "separator" ? external__React__default.a.createElement("li", { key: i, className: "separator" }) : external__React__default.a.createElement(ContextMenu_ContextMenuItem, { key: i, option: option, hideContext: this.hideContext }))
+ )
+ );
+ }
+}
+
+class ContextMenu_ContextMenuItem extends external__React__default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.onClick = this.onClick.bind(this);
+ this.onKeyDown = this.onKeyDown.bind(this);
+ }
+ onClick() {
+ this.props.hideContext();
+ this.props.option.onClick();
+ }
+ onKeyDown(event) {
+ const { option } = this.props;
+ switch (event.key) {
+ case "Tab":
+ // tab goes down in context menu, shift + tab goes up in context menu
+ // if we're on the last item, one more tab will close the context menu
+ // similarly, if we're on the first item, one more shift + tab will close it
+ if (event.shiftKey && option.first || !event.shiftKey && option.last) {
+ this.props.hideContext();
+ }
+ break;
+ case "Enter":
+ this.props.hideContext();
+ option.onClick();
+ break;
+ }
+ }
+ render() {
+ const { option } = this.props;
+ return external__React__default.a.createElement(
+ "li",
+ { role: "menuitem", className: "context-menu-item" },
+ external__React__default.a.createElement(
+ "a",
+ { onClick: this.onClick, onKeyDown: this.onKeyDown, tabIndex: "0" },
+ option.icon && external__React__default.a.createElement("span", { className: `icon icon-spacer icon-${option.icon}` }),
+ option.label
)
);
}
}
-TopSite.defaultProps = { link: {} };
-
-const TopSitePlaceholder = () => React.createElement(TopSiteLink, { className: "placeholder" });
-
-const TopSiteList = props => {
- const topSites = props.TopSites.rows.slice(0, props.TopSitesCount);
- const topSitesUI = [];
- for (let i = 0, l = props.TopSitesCount; i < l; i++) {
- const link = topSites[i];
- topSitesUI.push(!link ? React.createElement(TopSitePlaceholder, { key: i }) : React.createElement(TopSite, {
- key: link.guid || link.url,
- dispatch: props.dispatch,
- link: link,
- index: i,
- intl: props.intl,
- onEdit: props.onEdit }));
- }
- return React.createElement(
- "ul",
- { className: "top-sites-list" },
- topSitesUI
- );
+// EXTERNAL MODULE: external "ReactIntl"
+var external__ReactIntl_ = __webpack_require__(2);
+var external__ReactIntl__default = /*#__PURE__*/__webpack_require__.n(external__ReactIntl_);
+
+// CONCATENATED MODULE: ./system-addon/content-src/lib/link-menu-options.js
+
+
+/**
+ * List of functions that return items that can be included as menu options in a
+ * LinkMenu. All functions take the site as the first parameter, and optionally
+ * the index of the site.
+ */
+const LinkMenuOptions = {
+ Separator: () => ({ type: "separator" }),
+ RemoveBookmark: site => ({
+ id: "menu_action_remove_bookmark",
+ icon: "bookmark-added",
+ action: Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].DELETE_BOOKMARK_BY_ID,
+ data: site.bookmarkGuid
+ }),
+ userEvent: "BOOKMARK_DELETE"
+ }),
+ AddBookmark: site => ({
+ id: "menu_action_bookmark",
+ icon: "bookmark-hollow",
+ action: Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].BOOKMARK_URL,
+ data: { url: site.url, title: site.title, type: site.type }
+ }),
+ userEvent: "BOOKMARK_ADD"
+ }),
+ OpenInNewWindow: site => ({
+ id: "menu_action_open_new_window",
+ icon: "new-window",
+ action: Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].OPEN_NEW_WINDOW,
+ data: { url: site.url, referrer: site.referrer }
+ }),
+ userEvent: "OPEN_NEW_WINDOW"
+ }),
+ OpenInPrivateWindow: site => ({
+ id: "menu_action_open_private_window",
+ icon: "new-window-private",
+ action: Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].OPEN_PRIVATE_WINDOW,
+ data: { url: site.url, referrer: site.referrer }
+ }),
+ userEvent: "OPEN_PRIVATE_WINDOW"
+ }),
+ BlockUrl: (site, index, eventSource) => ({
+ id: "menu_action_dismiss",
+ icon: "dismiss",
+ action: Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].BLOCK_URL,
+ data: site.url
+ }),
+ impression: Actions["actionCreators"].ImpressionStats({
+ source: eventSource,
+ block: 0,
+ tiles: [{ id: site.guid, pos: index }]
+ }),
+ userEvent: "BLOCK"
+ }),
+ DeleteUrl: site => ({
+ id: "menu_action_delete",
+ icon: "delete",
+ action: {
+ type: Actions["actionTypes"].DIALOG_OPEN,
+ data: {
+ onConfirm: [Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].DELETE_HISTORY_URL, data: { url: site.url, forceBlock: site.bookmarkGuid } }), Actions["actionCreators"].UserEvent({ event: "DELETE" })],
+ body_string_id: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
+ confirm_button_string_id: "menu_action_delete",
+ cancel_button_string_id: "topsites_form_cancel_button",
+ icon: "modal-delete"
+ }
+ },
+ userEvent: "DIALOG_OPEN"
+ }),
+ PinTopSite: (site, index) => ({
+ id: "menu_action_pin",
+ icon: "pin",
+ action: Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].TOP_SITES_PIN,
+ data: { site: { url: site.url }, index }
+ }),
+ userEvent: "PIN"
+ }),
+ UnpinTopSite: site => ({
+ id: "menu_action_unpin",
+ icon: "unpin",
+ action: Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].TOP_SITES_UNPIN,
+ data: { site: { url: site.url } }
+ }),
+ userEvent: "UNPIN"
+ }),
+ SaveToPocket: (site, index, eventSource) => ({
+ id: "menu_action_save_to_pocket",
+ icon: "pocket",
+ action: Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].SAVE_TO_POCKET,
+ data: { site: { url: site.url, title: site.title } }
+ }),
+ impression: Actions["actionCreators"].ImpressionStats({
+ source: eventSource,
+ pocket: 0,
+ tiles: [{ id: site.guid, pos: index }]
+ }),
+ userEvent: "SAVE_TO_POCKET"
+ }),
+ EditTopSite: site => ({
+ id: "edit_topsites_button_text",
+ icon: "edit",
+ action: {
+ type: Actions["actionTypes"].TOP_SITES_EDIT,
+ data: { url: site.url, label: site.label }
+ }
+ }),
+ CheckBookmark: site => site.bookmarkGuid ? LinkMenuOptions.RemoveBookmark(site) : LinkMenuOptions.AddBookmark(site),
+ CheckPinTopSite: (site, index) => site.isPinned ? LinkMenuOptions.UnpinTopSite(site) : LinkMenuOptions.PinTopSite(site, index)
};
-
-module.exports.TopSite = TopSite;
-module.exports.TopSiteLink = TopSiteLink;
-module.exports.TopSitePlaceholder = TopSitePlaceholder;
-module.exports.TopSiteList = TopSiteList;
-
-/***/ }),
-/* 8 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { injectIntl } = __webpack_require__(2);
-const ContextMenu = __webpack_require__(18);
-const { actionCreators: ac } = __webpack_require__(0);
-const linkMenuOptions = __webpack_require__(19);
+// CONCATENATED MODULE: ./system-addon/content-src/components/LinkMenu/LinkMenu.jsx
+
+
+
+
+
+
const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
-class LinkMenu extends React.PureComponent {
+class LinkMenu__LinkMenu extends external__React__default.a.PureComponent {
getOptions() {
const props = this.props;
const { site, index, source } = props;
// Handle special case of default site
const propOptions = !site.isDefault ? props.options : DEFAULT_SITE_MENU_OPTIONS;
- const options = propOptions.map(o => linkMenuOptions[o](site, index, source)).map(option => {
+ const options = propOptions.map(o => LinkMenuOptions[o](site, index, source)).map(option => {
const { action, impression, id, type, userEvent } = option;
if (!type && id) {
option.label = props.intl.formatMessage(option);
option.onClick = () => {
props.dispatch(action);
if (userEvent) {
- props.dispatch(ac.UserEvent({
+ props.dispatch(Actions["actionCreators"].UserEvent({
event: userEvent,
source,
action_position: index
}));
}
if (impression && props.shouldSendImpressionStats) {
props.dispatch(impression);
}
@@ -963,51 +936,61 @@ class LinkMenu extends React.PureCompone
// This is for accessibility to support making each item tabbable.
// We want to know which item is the first and which item
// is the last, so we can close the context menu accordingly.
options[0].first = true;
options[options.length - 1].last = true;
return options;
}
render() {
- return React.createElement(ContextMenu, {
+ return external__React__default.a.createElement(ContextMenu_ContextMenu, {
visible: this.props.visible,
onUpdate: this.props.onUpdate,
options: this.getOptions() });
}
}
-
-module.exports = injectIntl(LinkMenu);
-module.exports._unconnected = LinkMenu;
+/* unused harmony export _LinkMenu */
+
+
+const LinkMenu = Object(external__ReactIntl_["injectIntl"])(LinkMenu__LinkMenu);
+/* harmony export (immutable) */ __webpack_exports__["a"] = LinkMenu;
+
/***/ }),
-/* 9 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(global) {var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-const React = __webpack_require__(1);
-const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
-const { injectIntl, FormattedMessage } = __webpack_require__(2);
+/* 7 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react_intl__ = __webpack_require__(2);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react_intl___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_react_intl__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_react__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_react__);
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+
+
+
const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange";
function getFormattedMessage(message) {
- return typeof message === "string" ? React.createElement(
+ return typeof message === "string" ? __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"span",
null,
message
- ) : React.createElement(FormattedMessage, message);
+ ) : __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_1_react_intl__["FormattedMessage"], message);
}
function getCollapsed(props) {
return props.Prefs.values[props.prefName];
}
-class Info extends React.PureComponent {
+class Info extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.PureComponent {
constructor(props) {
super(props);
this.onInfoEnter = this.onInfoEnter.bind(this);
this.onInfoLeave = this.onInfoLeave.bind(this);
this.onManageClick = this.onManageClick.bind(this);
this.state = { infoActive: false };
}
@@ -1026,111 +1009,119 @@ class Info extends React.PureComponent {
}
onInfoLeave(event) {
// We currently have an active (true) info state, so keep it true only if we
// have a related event target that is contained "within" the current target
// (section-info-option) as itself or a descendant. Set to false otherwise.
this._setInfoState(event && event.relatedTarget && (event.relatedTarget === event.currentTarget || event.relatedTarget.compareDocumentPosition(event.currentTarget) & Node.DOCUMENT_POSITION_CONTAINS));
}
onManageClick() {
- this.props.dispatch({ type: at.SETTINGS_OPEN });
- this.props.dispatch(ac.UserEvent({ event: "OPEN_NEWTAB_PREFS" }));
+ this.props.dispatch({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SETTINGS_OPEN });
+ this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].UserEvent({ event: "OPEN_NEWTAB_PREFS" }));
}
render() {
const { infoOption, intl } = this.props;
const infoOptionIconA11yAttrs = {
"aria-haspopup": "true",
"aria-controls": "info-option",
"aria-expanded": this.state.infoActive ? "true" : "false",
"role": "note",
"tabIndex": 0
};
const sectionInfoTitle = intl.formatMessage({ id: "section_info_option" });
- return React.createElement(
+ return __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"span",
{ className: "section-info-option",
onBlur: this.onInfoLeave,
onFocus: this.onInfoEnter,
onMouseOut: this.onInfoLeave,
onMouseOver: this.onInfoEnter },
- React.createElement("img", _extends({ className: "info-option-icon", title: sectionInfoTitle
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement("img", _extends({ className: "info-option-icon", title: sectionInfoTitle
}, infoOptionIconA11yAttrs)),
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"div",
{ className: "info-option" },
- infoOption.header && React.createElement(
+ infoOption.header && __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"div",
{ className: "info-option-header", role: "heading" },
getFormattedMessage(infoOption.header)
),
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"p",
{ className: "info-option-body" },
infoOption.body && getFormattedMessage(infoOption.body),
- infoOption.link && React.createElement(
+ infoOption.link && __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"a",
{ href: infoOption.link.href, target: "_blank", rel: "noopener noreferrer", className: "info-option-link" },
getFormattedMessage(infoOption.link.title || infoOption.link)
)
),
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"div",
{ className: "info-option-manage" },
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"button",
{ onClick: this.onManageClick },
- React.createElement(FormattedMessage, { id: "settings_pane_header" })
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_1_react_intl__["FormattedMessage"], { id: "settings_pane_header" })
)
)
)
);
}
}
-
-const InfoIntl = injectIntl(Info);
-
-class Disclaimer extends React.PureComponent {
+/* unused harmony export Info */
+
+
+const InfoIntl = Object(__WEBPACK_IMPORTED_MODULE_1_react_intl__["injectIntl"])(Info);
+/* unused harmony export InfoIntl */
+
+
+class Disclaimer extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.PureComponent {
constructor(props) {
super(props);
this.onAcknowledge = this.onAcknowledge.bind(this);
}
onAcknowledge() {
- this.props.dispatch(ac.SetPref(this.props.disclaimerPref, false));
- this.props.dispatch(ac.UserEvent({ event: "SECTION_DISCLAIMER_ACKNOWLEDGED", source: this.props.eventSource }));
+ this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SetPref(this.props.disclaimerPref, false));
+ this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].UserEvent({ event: "SECTION_DISCLAIMER_ACKNOWLEDGED", source: this.props.eventSource }));
}
render() {
const disclaimer = this.props.disclaimer;
- return React.createElement(
+ return __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"div",
{ className: "section-disclaimer" },
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"div",
{ className: "section-disclaimer-text" },
getFormattedMessage(disclaimer.text),
- disclaimer.link && React.createElement(
+ disclaimer.link && __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"a",
{ href: disclaimer.link.href, target: "_blank", rel: "noopener noreferrer" },
getFormattedMessage(disclaimer.link.title || disclaimer.link)
)
),
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"button",
{ onClick: this.onAcknowledge },
getFormattedMessage(disclaimer.button)
)
);
}
}
-
-const DisclaimerIntl = injectIntl(Disclaimer);
-
-class CollapsibleSection extends React.PureComponent {
+/* unused harmony export Disclaimer */
+
+
+const DisclaimerIntl = Object(__WEBPACK_IMPORTED_MODULE_1_react_intl__["injectIntl"])(Disclaimer);
+/* unused harmony export DisclaimerIntl */
+
+
+class _CollapsibleSection extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.PureComponent {
constructor(props) {
super(props);
this.onBodyMount = this.onBodyMount.bind(this);
this.onInfoEnter = this.onInfoEnter.bind(this);
this.onInfoLeave = this.onInfoLeave.bind(this);
this.onHeaderClick = this.onHeaderClick.bind(this);
this.onTransitionEnd = this.onTransitionEnd.bind(this);
this.enableOrDisableAnimation = this.enableOrDisableAnimation.bind(this);
@@ -1181,105 +1172,111 @@ class CollapsibleSection extends React.P
this._setInfoState(event && event.relatedTarget && (event.relatedTarget === event.currentTarget || event.relatedTarget.compareDocumentPosition(event.currentTarget) & Node.DOCUMENT_POSITION_CONTAINS));
}
onHeaderClick() {
// Get the current height of the body so max-height transitions can work
this.setState({
isAnimating: true,
maxHeight: `${this.sectionBody.scrollHeight}px`
});
- this.props.dispatch(ac.SetPref(this.props.prefName, !getCollapsed(this.props)));
+ this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SetPref(this.props.prefName, !getCollapsed(this.props)));
}
onTransitionEnd(event) {
// Only update the animating state for our own transition (not a child's)
if (event.target === event.currentTarget) {
this.setState({ isAnimating: false });
}
}
renderIcon() {
const icon = this.props.icon;
if (icon && icon.startsWith("moz-extension://")) {
- return React.createElement("span", { className: "icon icon-small-spacer", style: { backgroundImage: `url('${icon}')` } });
+ return __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement("span", { className: "icon icon-small-spacer", style: { backgroundImage: `url('${icon}')` } });
}
- return React.createElement("span", { className: `icon icon-small-spacer icon-${icon || "webextension"}` });
+ return __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement("span", { className: `icon icon-small-spacer icon-${icon || "webextension"}` });
}
render() {
const isCollapsed = getCollapsed(this.props);
const { enableAnimation, isAnimating, maxHeight } = this.state;
const { id, infoOption, eventSource, disclaimer } = this.props;
const disclaimerPref = `section.${id}.showDisclaimer`;
const needsDisclaimer = disclaimer && this.props.Prefs.values[disclaimerPref];
- return React.createElement(
+ return __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"section",
{ className: `collapsible-section ${this.props.className}${enableAnimation ? " animation-enabled" : ""}${isCollapsed ? " collapsed" : ""}` },
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"div",
{ className: "section-top-bar" },
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"h3",
{ className: "section-title" },
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"span",
{ className: "click-target", onClick: this.onHeaderClick },
this.renderIcon(),
this.props.title,
- React.createElement("span", { className: `icon ${isCollapsed ? "icon-arrowhead-forward" : "icon-arrowhead-down"}` })
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement("span", { className: `icon ${isCollapsed ? "icon-arrowhead-forward" : "icon-arrowhead-down"}` })
)
),
- infoOption && React.createElement(InfoIntl, { infoOption: infoOption, dispatch: this.props.dispatch })
+ infoOption && __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(InfoIntl, { infoOption: infoOption, dispatch: this.props.dispatch })
),
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
"div",
{
className: `section-body${isAnimating ? " animating" : ""}`,
onTransitionEnd: this.onTransitionEnd,
ref: this.onBodyMount,
style: isAnimating && !isCollapsed ? { maxHeight } : null },
- needsDisclaimer && React.createElement(DisclaimerIntl, { disclaimerPref: disclaimerPref, disclaimer: disclaimer, eventSource: eventSource, dispatch: this.props.dispatch }),
+ needsDisclaimer && __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(DisclaimerIntl, { disclaimerPref: disclaimerPref, disclaimer: disclaimer, eventSource: eventSource, dispatch: this.props.dispatch }),
this.props.children
)
);
}
}
-
-CollapsibleSection.defaultProps = {
+/* unused harmony export _CollapsibleSection */
+
+
+_CollapsibleSection.defaultProps = {
document: global.document || {
addEventListener: () => {},
removeEventListener: () => {},
visibilityState: "hidden"
},
Prefs: { values: {} }
};
-module.exports = injectIntl(CollapsibleSection);
-module.exports._unconnected = CollapsibleSection;
-module.exports.Info = Info;
-module.exports.InfoIntl = InfoIntl;
-module.exports.Disclaimer = Disclaimer;
-module.exports.DisclaimerIntl = DisclaimerIntl;
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
+const CollapsibleSection = Object(__WEBPACK_IMPORTED_MODULE_1_react_intl__["injectIntl"])(_CollapsibleSection);
+/* harmony export (immutable) */ __webpack_exports__["a"] = CollapsibleSection;
+
+/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
/***/ }),
-/* 10 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
-const { perfService: perfSvc } = __webpack_require__(11);
+/* 8 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__ = __webpack_require__(9);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_react__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_react__);
+
+
+
// Currently record only a fixed set of sections. This will prevent data
// from custom sections from showing up or from topstories.
const RECORDED_SECTIONS = ["highlights", "topsites"];
-class ComponentPerfTimer extends React.Component {
+class ComponentPerfTimer extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.Component {
constructor(props) {
super(props);
// Just for test dependency injection:
- this.perfSvc = this.props.perfSvc || perfSvc;
+ this.perfSvc = this.props.perfSvc || __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__["perfService"];
this._sendBadStateEvent = this._sendBadStateEvent.bind(this);
this._sendPaintedEvent = this._sendPaintedEvent.bind(this);
this._reportMissingData = false;
this._timestampHandled = false;
this._recordedFirstRender = false;
}
@@ -1375,18 +1372,18 @@ class ComponentPerfTimer extends React.C
// highlights_data_ready_ts, topsites_data_ready_ts.
const dataReadyKey = `${this.props.id}_data_ready_ts`;
this.perfSvc.mark(dataReadyKey);
try {
const firstRenderKey = `${this.props.id}_first_render_ts`;
// value has to be Int32.
const value = parseInt(this.perfSvc.getMostRecentAbsMarkStartByName(dataReadyKey) - this.perfSvc.getMostRecentAbsMarkStartByName(firstRenderKey), 10);
- this.props.dispatch(ac.SendToMain({
- type: at.SAVE_SESSION_PERF_DATA,
+ this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({
+ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SAVE_SESSION_PERF_DATA,
// highlights_data_late_by_ms, topsites_data_late_by_ms.
data: { [`${this.props.id}_data_late_by_ms`]: value }
}));
} catch (ex) {
// If this failed, it's likely because the `privacy.resistFingerprinting`
// pref is true.
}
}
@@ -1400,18 +1397,18 @@ class ComponentPerfTimer extends React.C
// topsites_first_painted_ts.
const key = `${this.props.id}_first_painted_ts`;
this.perfSvc.mark(key);
try {
const data = {};
data[key] = this.perfSvc.getMostRecentAbsMarkStartByName(key);
- this.props.dispatch(ac.SendToMain({
- type: at.SAVE_SESSION_PERF_DATA,
+ this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({
+ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SAVE_SESSION_PERF_DATA,
data
}));
} 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._timestampHandled to avoid going through this again.
}
}
@@ -1419,21 +1416,21 @@ class ComponentPerfTimer extends React.C
render() {
if (RECORDED_SECTIONS.includes(this.props.id)) {
this._ensureFirstRenderTsRecorded();
this._maybeSendBadStateEvent();
}
return this.props.children;
}
}
-
-module.exports = ComponentPerfTimer;
+/* harmony export (immutable) */ __webpack_exports__["a"] = ComponentPerfTimer;
+
/***/ }),
-/* 11 */
+/* 9 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* globals Services */
/* istanbul ignore if */
// Note: normally we would just feature detect Components.utils here, but
@@ -1447,16 +1444,17 @@ let usablePerfObj;
/* istanbul ignore if */
/* istanbul ignore else */
if (typeof Services !== "undefined") {
// Borrow the high-resolution timer from the hidden window....
usablePerfObj = Services.appShell.hiddenDOMWindow.performance;
} else if (typeof performance !== "undefined") {
// we must be running in content space
+ // eslint-disable-next-line no-undef
usablePerfObj = performance;
} else {
// This is a dummy object so this file doesn't crash in the node prerendering
// task.
usablePerfObj = {
now() {},
mark() {}
};
@@ -1559,435 +1557,1173 @@ var _PerfService = function _PerfService
var perfService = new _PerfService();
module.exports = {
_PerfService,
perfService
};
/***/ }),
-/* 12 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(global) {const React = __webpack_require__(1);
-const ReactDOM = __webpack_require__(13);
-const Base = __webpack_require__(14);
-const { Provider } = __webpack_require__(3);
-const initStore = __webpack_require__(31);
-const { reducers } = __webpack_require__(6);
-const DetectUserSessionStart = __webpack_require__(33);
-const { addSnippetsSubscriber } = __webpack_require__(34);
-const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
-
-const store = initStore(reducers, global.gActivityStreamPrerenderedState);
-
-new DetectUserSessionStart(store).sendEventOrAddListener();
+/* 10 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_content_src_lib_snippets__ = __webpack_require__(11);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_content_src_components_Base_Base__ = __webpack_require__(12);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_content_src_lib_detect_user_session_start__ = __webpack_require__(19);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_content_src_lib_init_store__ = __webpack_require__(20);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5_react_redux__ = __webpack_require__(3);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5_react_redux___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5_react_redux__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_react__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_6_react__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_react_dom__ = __webpack_require__(22);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_react_dom___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_7_react_dom__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__ = __webpack_require__(5);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__);
+
+
+
+
+
+
+
+
+
+
+const store = Object(__WEBPACK_IMPORTED_MODULE_4_content_src_lib_init_store__["a" /* initStore */])(__WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__["reducers"], global.gActivityStreamPrerenderedState);
+
+new __WEBPACK_IMPORTED_MODULE_3_content_src_lib_detect_user_session_start__["a" /* DetectUserSessionStart */](store).sendEventOrAddListener();
// If we are starting in a prerendered state, we must wait until the first render
// to request state rehydration (see Base.jsx). If we are NOT in a prerendered state,
// we can request it immedately.
if (!global.gActivityStreamPrerenderedState) {
- store.dispatch(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+ store.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].NEW_TAB_STATE_REQUEST }));
}
-ReactDOM.render(React.createElement(
- Provider,
+__WEBPACK_IMPORTED_MODULE_7_react_dom___default.a.render(__WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
+ __WEBPACK_IMPORTED_MODULE_5_react_redux__["Provider"],
{ store: store },
- React.createElement(Base, {
+ __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_2_content_src_components_Base_Base__["a" /* Base */], {
isPrerendered: !!global.gActivityStreamPrerenderedState,
locale: global.document.documentElement.lang,
strings: global.gActivityStreamStrings })
), document.getElementById("root"));
-addSnippetsSubscriber(store);
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
-
-/***/ }),
-/* 13 */
-/***/ (function(module, exports) {
-
-module.exports = ReactDOM;
+Object(__WEBPACK_IMPORTED_MODULE_1_content_src_lib_snippets__["a" /* addSnippetsSubscriber */])(store);
+/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
/***/ }),
-/* 14 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { connect } = __webpack_require__(3);
-const { addLocaleData, IntlProvider } = __webpack_require__(2);
-const TopSites = __webpack_require__(15);
-const Search = __webpack_require__(21);
-const ConfirmDialog = __webpack_require__(23);
-const ManualMigration = __webpack_require__(24);
-const PreferencesPane = __webpack_require__(25);
-const Sections = __webpack_require__(26);
-const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
-const { PrerenderData } = __webpack_require__(30);
-
-// Add the locale data for pluralization and relative-time formatting for now,
-// this just uses english locale data. We can make this more sophisticated if
-// more features are needed.
-function addLocaleDataForReactIntl(locale) {
- addLocaleData([{ locale, parentLocale: "en" }]);
-}
-
-class Base extends React.PureComponent {
- componentWillMount() {
- const { App, locale } = this.props;
- this.sendNewTabRehydrated(App);
- addLocaleDataForReactIntl(locale);
+/* 11 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (immutable) */ __webpack_exports__["a"] = addSnippetsSubscriber;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
+const DATABASE_NAME = "snippets_db";
+const DATABASE_VERSION = 1;
+const SNIPPETS_OBJECTSTORE_NAME = "snippets";
+const SNIPPETS_UPDATE_INTERVAL_MS = 14400000;
+/* unused harmony export SNIPPETS_UPDATE_INTERVAL_MS */
+ // 4 hours.
+
+const SNIPPETS_ENABLED_EVENT = "Snippets:Enabled";
+const SNIPPETS_DISABLED_EVENT = "Snippets:Disabled";
+
+
+
+/**
+ * SnippetsMap - A utility for cacheing values related to the snippet. It has
+ * the same interface as a Map, but is optionally backed by
+ * indexedDB for persistent storage.
+ * Call .connect() to open a database connection and restore any
+ * previously cached data, if necessary.
+ *
+ */
+class SnippetsMap extends Map {
+ constructor(dispatch) {
+ super();
+ this._db = null;
+ this._dispatch = dispatch;
}
- componentDidMount() {
- // Request state AFTER the first render to ensure we don't cause the
- // prerendered DOM to be unmounted. Otherwise, NEW_TAB_STATE_REQUEST is
- // dispatched right after the store is ready.
- if (this.props.isPrerendered) {
- this.props.dispatch(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
- this.props.dispatch(ac.SendToMain({ type: at.PAGE_PRERENDERED }));
- }
+ set(key, value) {
+ super.set(key, value);
+ return this._dbTransaction(db => db.put(value, key));
+ }
+
+ delete(key) {
+ super.delete(key);
+ return this._dbTransaction(db => db.delete(key));
+ }
+
+ clear() {
+ super.clear();
+ return this._dbTransaction(db => db.clear());
+ }
+
+ get blockList() {
+ return this.get("blockList") || [];
}
- componentWillUpdate({ App }) {
- this.sendNewTabRehydrated(App);
- }
-
- // The NEW_TAB_REHYDRATED event is used to inform feeds that their
- // data has been consumed e.g. for counting the number of tabs that
- // have rendered that data.
- sendNewTabRehydrated(App) {
- if (App && App.initialized && !this.renderNotified) {
- this.props.dispatch(ac.SendToMain({ type: at.NEW_TAB_REHYDRATED, data: {} }));
- this.renderNotified = true;
+ /**
+ * blockSnippetById - Blocks a snippet given an id
+ *
+ * @param {str|int} id The id of the snippet
+ * @return {Promise} Resolves when the id has been written to indexedDB,
+ * or immediately if the snippetMap is not connected
+ */
+ async blockSnippetById(id) {
+ if (!id) {
+ return;
+ }
+ let blockList = this.blockList;
+ if (!blockList.includes(id)) {
+ blockList.push(id);
+ this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SNIPPETS_BLOCKLIST_UPDATED, data: blockList }));
+ await this.set("blockList", blockList);
}
}
- render() {
- const props = this.props;
- const { App, locale, strings } = props;
- const { initialized } = App;
- const prefs = props.Prefs.values;
-
- const shouldBeFixedToTop = PrerenderData.arePrefsValid(name => prefs[name]);
-
- const outerClassName = `outer-wrapper${shouldBeFixedToTop ? " fixed-to-top" : ""}`;
-
- if (!props.isPrerendered && !initialized) {
- return null;
- }
-
- return React.createElement(
- IntlProvider,
- { locale: locale, messages: strings },
- React.createElement(
- "div",
- { className: outerClassName },
- React.createElement(
- "main",
- null,
- prefs.showSearch && React.createElement(Search, null),
- React.createElement(
- "div",
- { className: `body-wrapper${initialized ? " on" : ""}` },
- !prefs.migrationExpired && React.createElement(ManualMigration, null),
- prefs.showTopSites && React.createElement(TopSites, null),
- React.createElement(Sections, null)
- ),
- React.createElement(ConfirmDialog, null)
- ),
- initialized && React.createElement(PreferencesPane, null)
- )
- );
+ disableOnboarding() {
+ this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].DISABLE_ONBOARDING }));
+ }
+
+ showFirefoxAccounts() {
+ this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SHOW_FIREFOX_ACCOUNTS }));
}
-}
-
-module.exports = connect(state => ({ App: state.App, Prefs: state.Prefs }))(Base);
-module.exports._unconnected = Base;
-
-/***/ }),
-/* 15 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { connect } = __webpack_require__(3);
-const { FormattedMessage } = __webpack_require__(2);
-
-const TopSitesEdit = __webpack_require__(16);
-const { TopSiteList } = __webpack_require__(7);
-const CollapsibleSection = __webpack_require__(9);
-const ComponentPerfTimer = __webpack_require__(10);
-const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
-const { MIN_RICH_FAVICON_SIZE, MIN_CORNER_FAVICON_SIZE } = __webpack_require__(5);
-
-/**
- * Iterates through TopSites and counts types of images.
- * @param acc Accumulator for reducer.
- * @param topsite Entry in TopSites.
- */
-function countTopSitesIconsTypes(topSites) {
- const countTopSitesTypes = (acc, link) => {
- if (link.tippyTopIcon || link.faviconRef === "tippytop") {
- acc.tippytop++;
- } else if (link.faviconSize >= MIN_RICH_FAVICON_SIZE) {
- acc.rich_icon++;
- } else if (link.screenshot && link.faviconSize >= MIN_CORNER_FAVICON_SIZE) {
- acc.screenshot_with_icon++;
- } else if (link.screenshot) {
- acc.screenshot++;
- } else {
- acc.no_image++;
- }
-
- return acc;
- };
-
- return topSites.reduce(countTopSitesTypes, {
- "screenshot_with_icon": 0,
- "screenshot": 0,
- "tippytop": 0,
- "rich_icon": 0,
- "no_image": 0
- });
-}
-
-class TopSites extends React.PureComponent {
+
/**
- * Dispatch session statistics about the quality of TopSites icons.
+ * connect - Attaches an indexedDB back-end to the Map so that any set values
+ * are also cached in a store. It also restores any existing values
+ * that are already stored in the indexedDB store.
+ *
+ * @return {type} description
*/
- _dispatchTopSitesIconStats() {
- const topSitesIconsStats = countTopSitesIconsTypes(this._getTopSites());
- // Dispatch telemetry event with the count of TopSites images types.
- this.props.dispatch(ac.SendToMain({
- type: at.SAVE_SESSION_PERF_DATA,
- data: { topsites_icon_stats: topSitesIconsStats }
- }));
+ async connect() {
+ // Open the connection
+ const db = await this._openDB();
+
+ // Restore any existing values
+ await this._restoreFromDb(db);
+
+ // Attach a reference to the db
+ this._db = db;
}
/**
- * Return the TopSites to display based on prefs.
+ * _dbTransaction - Returns a db transaction wrapped with the given modifier
+ * function as a Promise. If the db has not been connected,
+ * it resolves immediately.
+ *
+ * @param {func} modifier A function to call with the transaction
+ * @return {obj} A Promise that resolves when the transaction has
+ * completed or errored
*/
- _getTopSites() {
- return this.props.TopSites.rows.slice(0, this.props.TopSitesCount);
+ _dbTransaction(modifier) {
+ if (!this._db) {
+ return Promise.resolve();
+ }
+ return new Promise((resolve, reject) => {
+ const transaction = modifier(this._db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite").objectStore(SNIPPETS_OBJECTSTORE_NAME));
+ transaction.onsuccess = event => resolve();
+
+ /* istanbul ignore next */
+ transaction.onerror = event => reject(transaction.error);
+ });
+ }
+
+ _openDB() {
+ return new Promise((resolve, reject) => {
+ const openRequest = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
+
+ /* istanbul ignore next */
+ openRequest.onerror = event => {
+ // Try to delete the old database so that we can start this process over
+ // next time.
+ indexedDB.deleteDatabase(DATABASE_NAME);
+ reject(event);
+ };
+
+ openRequest.onupgradeneeded = event => {
+ const db = event.target.result;
+ if (!db.objectStoreNames.contains(SNIPPETS_OBJECTSTORE_NAME)) {
+ db.createObjectStore(SNIPPETS_OBJECTSTORE_NAME);
+ }
+ };
+
+ openRequest.onsuccess = event => {
+ let db = event.target.result;
+
+ /* istanbul ignore next */
+ db.onerror = err => console.error(err); // eslint-disable-line no-console
+ /* istanbul ignore next */
+ db.onversionchange = versionChangeEvent => versionChangeEvent.target.close();
+
+ resolve(db);
+ };
+ });
+ }
+
+ _restoreFromDb(db) {
+ return new Promise((resolve, reject) => {
+ let cursorRequest;
+ try {
+ cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME).objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor();
+ } catch (err) {
+ // istanbul ignore next
+ reject(err);
+ // istanbul ignore next
+ return;
+ }
+
+ /* istanbul ignore next */
+ cursorRequest.onerror = event => reject(event);
+
+ cursorRequest.onsuccess = event => {
+ let cursor = event.target.result;
+ // Populate the cache from the persistent storage.
+ if (cursor) {
+ this.set(cursor.key, cursor.value);
+ cursor.continue();
+ } else {
+ // We are done.
+ resolve();
+ }
+ };
+ });
+ }
+}
+/* unused harmony export SnippetsMap */
+
+
+/**
+ * SnippetsProvider - Initializes a SnippetsMap and loads snippets from a
+ * remote location, or else default snippets if the remote
+ * snippets cannot be retrieved.
+ */
+class SnippetsProvider {
+ constructor(dispatch) {
+ // Initialize the Snippets Map and attaches it to a global so that
+ // the snippet payload can interact with it.
+ global.gSnippetsMap = new SnippetsMap(dispatch);
+ this._onAction = this._onAction.bind(this);
+ }
+
+ get snippetsMap() {
+ return global.gSnippetsMap;
+ }
+
+ async _refreshSnippets() {
+ // 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.appData.version) {
+ this.snippetsMap.clear();
+ }
+
+ // Has enough time passed for us to require an update?
+ const lastUpdate = this.snippetsMap.get("snippets-last-update");
+ const needsUpdate = !(lastUpdate >= 0) || Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
+
+ if (needsUpdate && this.appData.snippetsURL) {
+ this.snippetsMap.set("snippets-last-update", Date.now());
+ try {
+ 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.appData.version);
+ }
+ } catch (e) {
+ console.error(e); // eslint-disable-line no-console
+ }
+ }
+ }
+
+ _noSnippetFallback() {
+ // TODO
+ }
+
+ _forceOnboardingVisibility(shouldBeVisible) {
+ const onboardingEl = document.getElementById("onboarding-notification-bar");
+
+ if (onboardingEl) {
+ onboardingEl.style.display = shouldBeVisible ? "" : "none";
+ }
+ }
+
+ _showRemoteSnippets() {
+ const snippetsEl = document.getElementById(this.elementId);
+ const payload = this.snippetsMap.get("snippets");
+
+ if (!snippetsEl) {
+ throw new Error(`No element was found with id '${this.elementId}'.`);
+ }
+
+ // This could happen if fetching failed
+ if (!payload) {
+ throw new Error("No remote snippets were found in gSnippetsMap.");
+ }
+
+ if (typeof payload !== "string") {
+ throw new Error("Snippet payload was incorrectly formatted");
+ }
+
+ // Note that injecting snippets can throw if they're invalid XML.
+ // eslint-disable-next-line no-unsanitized/property
+ snippetsEl.innerHTML = payload;
+
+ // Scripts injected by innerHTML are inactive, so we have to relocate them
+ // through DOM manipulation to activate their contents.
+ for (const scriptEl of snippetsEl.getElementsByTagName("script")) {
+ const relocatedScript = document.createElement("script");
+ relocatedScript.text = scriptEl.text;
+ scriptEl.parentNode.replaceChild(relocatedScript, scriptEl);
+ }
+ }
+
+ _onAction(msg) {
+ if (msg.data.type === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SNIPPET_BLOCKED) {
+ this.snippetsMap.set("blockList", msg.data.data);
+ document.getElementById("snippets-container").style.display = "none";
+ }
}
- componentDidUpdate() {
- this._dispatchTopSitesIconStats();
+ /**
+ * init - Fetch the snippet payload and show snippets
+ *
+ * @param {obj} options
+ * @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 {bool} options.connect Should gSnippetsMap connect to indexedDB?
+ */
+ async init(options) {
+ Object.assign(this, {
+ appData: {},
+ elementId: "snippets",
+ connect: true
+ }, options);
+
+ // Add listener so we know when snippets are blocked on other pages
+ if (global.addMessageListener) {
+ global.addMessageListener("ActivityStream:MainToContent", this._onAction);
+ }
+
+ // TODO: Requires enabling indexedDB on newtab
+ // Restore the snippets map from indexedDB
+ if (this.connect) {
+ try {
+ await this.snippetsMap.connect();
+ } catch (e) {
+ console.error(e); // eslint-disable-line no-console
+ }
+ }
+
+ // 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();
+
+ // Try showing remote snippets, falling back to defaults if necessary.
+ try {
+ this._showRemoteSnippets();
+ } catch (e) {
+ this._noSnippetFallback(e);
+ }
+
+ window.dispatchEvent(new Event(SNIPPETS_ENABLED_EVENT));
+
+ this._forceOnboardingVisibility(true);
+ this.initialized = true;
+ }
+
+ uninit() {
+ window.dispatchEvent(new Event(SNIPPETS_DISABLED_EVENT));
+ this._forceOnboardingVisibility(false);
+ if (global.removeMessageListener) {
+ global.removeMessageListener("ActivityStream:MainToContent", this._onAction);
+ }
+ this.initialized = false;
}
-
- componentDidMount() {
- this._dispatchTopSitesIconStats();
+}
+/* unused harmony export SnippetsProvider */
+
+
+/**
+ * addSnippetsSubscriber - Creates a SnippetsProvider that Initializes
+ * when the store has received the appropriate
+ * Snippet data.
+ *
+ * @param {obj} store The redux store
+ * @return {obj} Returns the snippets instance and unsubscribe function
+ */
+function addSnippetsSubscriber(store) {
+ const snippets = new SnippetsProvider(store.dispatch);
+
+ let initializing = false;
+
+ store.subscribe(async () => {
+ const state = store.getState();
+ // state.Prefs.values["feeds.snippets"]: Should snippets be shown?
+ // state.Snippets.initialized Is the snippets data initialized?
+ // snippets.initialized: Is SnippetsProvider currently initialised?
+ if (state.Prefs.values["feeds.snippets"] && !state.Prefs.values.disableSnippets && state.Snippets.initialized && !snippets.initialized &&
+ // Don't call init multiple times
+ !initializing) {
+ initializing = true;
+ await snippets.init({ appData: state.Snippets });
+ initializing = false;
+ } else if ((state.Prefs.values["feeds.snippets"] === false || state.Prefs.values.disableSnippets === true) && snippets.initialized) {
+ snippets.uninit();
+ }
+ });
+
+ // These values are returned for testing purposes
+ return snippets;
+}
+/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
+
+/***/ }),
+/* 12 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+
+// EXTERNAL MODULE: ./system-addon/common/Actions.jsm
+var Actions = __webpack_require__(0);
+var Actions_default = /*#__PURE__*/__webpack_require__.n(Actions);
+
+// EXTERNAL MODULE: external "ReactIntl"
+var external__ReactIntl_ = __webpack_require__(2);
+var external__ReactIntl__default = /*#__PURE__*/__webpack_require__.n(external__ReactIntl_);
+
+// EXTERNAL MODULE: external "ReactRedux"
+var external__ReactRedux_ = __webpack_require__(3);
+var external__ReactRedux__default = /*#__PURE__*/__webpack_require__.n(external__ReactRedux_);
+
+// EXTERNAL MODULE: external "React"
+var external__React_ = __webpack_require__(1);
+var external__React__default = /*#__PURE__*/__webpack_require__.n(external__React_);
+
+// CONCATENATED MODULE: ./system-addon/content-src/components/ConfirmDialog/ConfirmDialog.jsx
+
+
+
+
+
+/**
+ * ConfirmDialog component.
+ * One primary action button, one cancel button.
+ *
+ * Content displayed is controlled by `data` prop the component receives.
+ * Example:
+ * data: {
+ * // Any sort of data needed to be passed around by actions.
+ * payload: site.url,
+ * // Primary button SendToMain action.
+ * action: "DELETE_HISTORY_URL",
+ * // Primary button USerEvent action.
+ * userEvent: "DELETE",
+ * // Array of locale ids to display.
+ * message_body: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
+ * // Text for primary button.
+ * confirm_button_string_id: "menu_action_delete"
+ * },
+ */
+class ConfirmDialog__ConfirmDialog extends external__React__default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this._handleCancelBtn = this._handleCancelBtn.bind(this);
+ this._handleConfirmBtn = this._handleConfirmBtn.bind(this);
+ }
+
+ _handleCancelBtn() {
+ this.props.dispatch({ type: Actions["actionTypes"].DIALOG_CANCEL });
+ this.props.dispatch(Actions["actionCreators"].UserEvent({ event: Actions["actionTypes"].DIALOG_CANCEL }));
+ }
+
+ _handleConfirmBtn() {
+ this.props.data.onConfirm.forEach(this.props.dispatch);
+ }
+
+ _renderModalMessage() {
+ const message_body = this.props.data.body_string_id;
+
+ if (!message_body) {
+ return null;
+ }
+
+ return external__React__default.a.createElement(
+ "span",
+ null,
+ message_body.map(msg => external__React__default.a.createElement(
+ "p",
+ { key: msg },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: msg })
+ ))
+ );
}
render() {
- const props = this.props;
- const infoOption = {
- header: { id: "settings_pane_topsites_header" },
- body: { id: "settings_pane_topsites_body" }
- };
- return React.createElement(
- ComponentPerfTimer,
- { id: "topsites", initialized: props.TopSites.initialized, dispatch: props.dispatch },
- React.createElement(
- CollapsibleSection,
- { className: "top-sites", icon: "topsites", title: React.createElement(FormattedMessage, { id: "header_top_sites" }), infoOption: infoOption, prefName: "collapseTopSites", Prefs: props.Prefs, dispatch: props.dispatch },
- React.createElement(TopSiteList, { TopSites: props.TopSites, TopSitesCount: props.TopSitesCount, dispatch: props.dispatch, intl: props.intl }),
- React.createElement(TopSitesEdit, props)
- )
- );
- }
-}
-
-module.exports = connect(state => ({ TopSites: state.TopSites, Prefs: state.Prefs, TopSitesCount: state.Prefs.values.topSitesCount }))(TopSites);
-module.exports._unconnected = TopSites;
-
-/***/ }),
-/* 16 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { FormattedMessage, injectIntl } = __webpack_require__(2);
-const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
-
-const TopSiteForm = __webpack_require__(17);
-const { TopSiteList } = __webpack_require__(7);
-
-const { TOP_SITES_DEFAULT_LENGTH, TOP_SITES_SHOWMORE_LENGTH } = __webpack_require__(6);
-const { TOP_SITES_SOURCE } = __webpack_require__(5);
-
-class TopSitesEdit extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- showEditModal: false,
- showAddForm: false,
- showEditForm: false,
- editIndex: -1 // Index of top site being edited
- };
- this.onEditButtonClick = this.onEditButtonClick.bind(this);
- this.onShowMoreLessClick = this.onShowMoreLessClick.bind(this);
- this.onModalOverlayClick = this.onModalOverlayClick.bind(this);
- this.onAddButtonClick = this.onAddButtonClick.bind(this);
- this.onFormClose = this.onFormClose.bind(this);
- this.onEdit = this.onEdit.bind(this);
- }
- onEditButtonClick() {
- this.setState({ showEditModal: !this.state.showEditModal });
- const event = this.state.showEditModal ? "TOP_SITES_EDIT_OPEN" : "TOP_SITES_EDIT_CLOSE";
- this.props.dispatch(ac.UserEvent({
- source: TOP_SITES_SOURCE,
- event
- }));
- }
- onModalOverlayClick() {
- this.setState({ showEditModal: false, showAddForm: false, showEditForm: false });
- this.props.dispatch(ac.UserEvent({
- source: TOP_SITES_SOURCE,
- event: "TOP_SITES_EDIT_CLOSE"
- }));
- this.props.dispatch({ type: at.TOP_SITES_CANCEL_EDIT });
- }
- onShowMoreLessClick() {
- const prefIsSetToDefault = this.props.TopSitesCount === TOP_SITES_DEFAULT_LENGTH;
- this.props.dispatch(ac.SendToMain({
- type: at.SET_PREF,
- data: { name: "topSitesCount", value: prefIsSetToDefault ? TOP_SITES_SHOWMORE_LENGTH : TOP_SITES_DEFAULT_LENGTH }
- }));
- this.props.dispatch(ac.UserEvent({
- source: TOP_SITES_SOURCE,
- event: prefIsSetToDefault ? "TOP_SITES_EDIT_SHOW_MORE" : "TOP_SITES_EDIT_SHOW_LESS"
- }));
- }
- onAddButtonClick() {
- this.setState({ showAddForm: true });
- this.props.dispatch(ac.UserEvent({
- source: TOP_SITES_SOURCE,
- event: "TOP_SITES_ADD_FORM_OPEN"
- }));
- }
- onFormClose() {
- this.setState({ showAddForm: false, showEditForm: false });
- this.props.dispatch({ type: at.TOP_SITES_CANCEL_EDIT });
- }
- onEdit(index) {
- this.setState({ showEditForm: true, editIndex: index });
- this.props.dispatch(ac.UserEvent({
- source: TOP_SITES_SOURCE,
- event: "TOP_SITES_EDIT_FORM_OPEN"
- }));
- }
- render() {
- const showEditForm = this.props.TopSites.editForm && this.props.TopSites.editForm.visible || this.state.showEditModal && this.state.showEditForm;
- let editIndex = this.state.editIndex;
- if (showEditForm && this.props.TopSites.editForm.visible) {
- const targetURL = this.props.TopSites.editForm.site.url;
- editIndex = this.props.TopSites.rows.findIndex(s => s.url === targetURL);
+ if (!this.props.visible) {
+ return null;
}
- return React.createElement(
+
+ return external__React__default.a.createElement(
"div",
- { className: "edit-topsites-wrapper" },
- React.createElement(
- "div",
- { className: "edit-topsites-button" },
- React.createElement(
- "button",
- {
- className: "edit",
- title: this.props.intl.formatMessage({ id: "edit_topsites_button_label" }),
- onClick: this.onEditButtonClick },
- React.createElement(FormattedMessage, { id: "edit_topsites_button_text" })
- )
- ),
- this.state.showEditModal && !this.state.showAddForm && !this.state.showEditForm && React.createElement(
+ { className: "confirmation-dialog" },
+ external__React__default.a.createElement("div", { className: "modal-overlay", onClick: this._handleCancelBtn }),
+ external__React__default.a.createElement(
"div",
- { className: "edit-topsites" },
- React.createElement("div", { className: "modal-overlay", onClick: this.onModalOverlayClick }),
- React.createElement(
- "div",
- { className: "modal" },
- React.createElement(
- "section",
- { className: "edit-topsites-inner-wrapper" },
- React.createElement(
- "div",
- { className: "section-top-bar" },
- React.createElement(
- "h3",
- { className: "section-title" },
- React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }),
- React.createElement(FormattedMessage, { id: "header_top_sites" })
- )
- ),
- React.createElement(TopSiteList, { TopSites: this.props.TopSites, TopSitesCount: this.props.TopSitesCount, onEdit: this.onEdit, dispatch: this.props.dispatch, intl: this.props.intl })
+ { className: "modal" },
+ external__React__default.a.createElement(
+ "section",
+ { className: "modal-message" },
+ this.props.data.icon && external__React__default.a.createElement("span", { className: `icon icon-spacer icon-${this.props.data.icon}` }),
+ this._renderModalMessage()
+ ),
+ external__React__default.a.createElement(
+ "section",
+ { className: "actions" },
+ external__React__default.a.createElement(
+ "button",
+ { onClick: this._handleCancelBtn },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: this.props.data.cancel_button_string_id })
),
- React.createElement(
- "section",
- { className: "actions" },
- React.createElement(
- "button",
- { className: "add", onClick: this.onAddButtonClick },
- React.createElement(FormattedMessage, { id: "edit_topsites_add_button" })
- ),
- React.createElement(
- "button",
- { className: `icon icon-topsites show-${this.props.TopSitesCount === TOP_SITES_DEFAULT_LENGTH ? "more" : "less"}`, onClick: this.onShowMoreLessClick },
- React.createElement(FormattedMessage, { id: `edit_topsites_show${this.props.TopSitesCount === TOP_SITES_DEFAULT_LENGTH ? "more" : "less"}_button` })
- ),
- React.createElement(
- "button",
- { className: "done", onClick: this.onEditButtonClick },
- React.createElement(FormattedMessage, { id: "edit_topsites_done_button" })
- )
+ external__React__default.a.createElement(
+ "button",
+ { className: "done", onClick: this._handleConfirmBtn },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: this.props.data.confirm_button_string_id })
)
)
- ),
- this.state.showEditModal && this.state.showAddForm && React.createElement(
- "div",
- { className: "edit-topsites" },
- React.createElement("div", { className: "modal-overlay", onClick: this.onModalOverlayClick }),
- React.createElement(
- "div",
- { className: "modal" },
- React.createElement(TopSiteForm, { onClose: this.onFormClose, dispatch: this.props.dispatch, intl: this.props.intl })
- )
- ),
- showEditForm && React.createElement(
- "div",
- { className: "edit-topsites" },
- React.createElement("div", { className: "modal-overlay", onClick: this.onModalOverlayClick }),
- React.createElement(
- "div",
- { className: "modal" },
- React.createElement(TopSiteForm, {
- label: this.props.TopSites.rows[editIndex].label || this.props.TopSites.rows[editIndex].hostname,
- url: this.props.TopSites.rows[editIndex].url,
- index: editIndex,
- editMode: true,
- onClose: this.onFormClose,
- dispatch: this.props.dispatch,
- intl: this.props.intl })
- )
)
);
}
}
-module.exports = injectIntl(TopSitesEdit);
-module.exports._unconnected = TopSitesEdit;
-
-/***/ }),
-/* 17 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
-const { FormattedMessage } = __webpack_require__(2);
-
-const { TOP_SITES_SOURCE } = __webpack_require__(5);
-
-class TopSiteForm extends React.PureComponent {
+const ConfirmDialog = Object(external__ReactRedux_["connect"])(state => state.Dialog)(ConfirmDialog__ConfirmDialog);
+// CONCATENATED MODULE: ./system-addon/content-src/components/ManualMigration/ManualMigration.jsx
+
+
+
+
+
+/**
+ * Manual migration component used to start the profile import wizard.
+ * Message is presented temporarily and will go away if:
+ * 1. User clicks "No Thanks"
+ * 2. User completed the data import
+ * 3. After 3 active days
+ * 4. User clicks "Cancel" on the import wizard (currently not implemented).
+ */
+class ManualMigration__ManualMigration extends external__React__default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.onLaunchTour = this.onLaunchTour.bind(this);
+ this.onCancelTour = this.onCancelTour.bind(this);
+ }
+ onLaunchTour() {
+ this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].MIGRATION_START }));
+ this.props.dispatch(Actions["actionCreators"].UserEvent({ event: Actions["actionTypes"].MIGRATION_START }));
+ }
+
+ onCancelTour() {
+ this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].MIGRATION_CANCEL }));
+ this.props.dispatch(Actions["actionCreators"].UserEvent({ event: Actions["actionTypes"].MIGRATION_CANCEL }));
+ }
+
+ render() {
+ return external__React__default.a.createElement(
+ "div",
+ { className: "manual-migration-container" },
+ external__React__default.a.createElement(
+ "p",
+ null,
+ external__React__default.a.createElement("span", { className: "icon icon-import" }),
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "manual_migration_explanation2" })
+ ),
+ external__React__default.a.createElement(
+ "div",
+ { className: "manual-migration-actions actions" },
+ external__React__default.a.createElement(
+ "button",
+ { className: "dismiss", onClick: this.onCancelTour },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "manual_migration_cancel_button" })
+ ),
+ external__React__default.a.createElement(
+ "button",
+ { onClick: this.onLaunchTour },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "manual_migration_import_button" })
+ )
+ )
+ );
+ }
+}
+
+const ManualMigration = Object(external__ReactRedux_["connect"])()(ManualMigration__ManualMigration);
+// EXTERNAL MODULE: ./system-addon/common/Reducers.jsm
+var Reducers = __webpack_require__(5);
+var Reducers_default = /*#__PURE__*/__webpack_require__.n(Reducers);
+
+// CONCATENATED MODULE: ./system-addon/content-src/components/PreferencesPane/PreferencesPane.jsx
+
+
+
+
+
+
+const getFormattedMessage = message => typeof message === "string" ? external__React__default.a.createElement(
+ "span",
+ null,
+ message
+) : external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], message);
+
+const PreferencesInput = props => external__React__default.a.createElement(
+ "section",
+ null,
+ external__React__default.a.createElement("input", { type: "checkbox", id: props.prefName, name: props.prefName, checked: props.value, disabled: props.disabled, onChange: props.onChange, className: props.className }),
+ external__React__default.a.createElement(
+ "label",
+ { htmlFor: props.prefName, className: props.labelClassName },
+ getFormattedMessage(props.titleString)
+ ),
+ props.descString && external__React__default.a.createElement(
+ "p",
+ { className: "prefs-input-description" },
+ getFormattedMessage(props.descString)
+ ),
+ external__React__default.a.Children.map(props.children, child => external__React__default.a.createElement(
+ "div",
+ { className: `options${child.props.disabled ? " disabled" : ""}` },
+ child
+ ))
+);
+
+class PreferencesPane__PreferencesPane extends external__React__default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.handleClickOutside = this.handleClickOutside.bind(this);
+ this.handlePrefChange = this.handlePrefChange.bind(this);
+ this.handleSectionChange = this.handleSectionChange.bind(this);
+ this.togglePane = this.togglePane.bind(this);
+ this.onWrapperMount = this.onWrapperMount.bind(this);
+ }
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.PreferencesPane.visible !== this.props.PreferencesPane.visible) {
+ // While the sidebar is open, listen for all document clicks.
+ if (this.isSidebarOpen()) {
+ document.addEventListener("click", this.handleClickOutside);
+ } else {
+ document.removeEventListener("click", this.handleClickOutside);
+ }
+ }
+ }
+ isSidebarOpen() {
+ return this.props.PreferencesPane.visible;
+ }
+ handleClickOutside(event) {
+ // if we are showing the sidebar and there is a click outside, close it.
+ if (this.isSidebarOpen() && !this.wrapper.contains(event.target)) {
+ this.togglePane();
+ }
+ }
+ handlePrefChange(event) {
+ const target = event.target;
+ const { name, checked } = target;
+ let value = checked;
+ if (name === "topSitesCount") {
+ value = checked ? Reducers["TOP_SITES_SHOWMORE_LENGTH"] : Reducers["TOP_SITES_DEFAULT_LENGTH"];
+ }
+ this.props.dispatch(Actions["actionCreators"].SetPref(name, value));
+ }
+ handleSectionChange(event) {
+ const target = event.target;
+ const id = target.name;
+ const type = target.checked ? Actions["actionTypes"].SECTION_ENABLE : Actions["actionTypes"].SECTION_DISABLE;
+ this.props.dispatch(Actions["actionCreators"].SendToMain({ type, data: id }));
+ }
+ togglePane() {
+ if (this.isSidebarOpen()) {
+ this.props.dispatch({ type: Actions["actionTypes"].SETTINGS_CLOSE });
+ this.props.dispatch(Actions["actionCreators"].UserEvent({ event: "CLOSE_NEWTAB_PREFS" }));
+ } else {
+ this.props.dispatch({ type: Actions["actionTypes"].SETTINGS_OPEN });
+ this.props.dispatch(Actions["actionCreators"].UserEvent({ event: "OPEN_NEWTAB_PREFS" }));
+ }
+ }
+ onWrapperMount(wrapper) {
+ this.wrapper = wrapper;
+ }
+ render() {
+ const props = this.props;
+ const prefs = props.Prefs.values;
+ const sections = props.Sections;
+ const isVisible = this.isSidebarOpen();
+ return external__React__default.a.createElement(
+ "div",
+ { className: "prefs-pane-wrapper", ref: this.onWrapperMount },
+ external__React__default.a.createElement(
+ "div",
+ { className: "prefs-pane-button" },
+ external__React__default.a.createElement("button", {
+ className: `prefs-button icon ${isVisible ? "icon-dismiss" : "icon-settings"}`,
+ title: props.intl.formatMessage({ id: isVisible ? "settings_pane_done_button" : "settings_pane_button_label" }),
+ onClick: this.togglePane })
+ ),
+ external__React__default.a.createElement(
+ "div",
+ { className: "prefs-pane" },
+ external__React__default.a.createElement(
+ "div",
+ { className: `sidebar ${isVisible ? "" : "hidden"}` },
+ external__React__default.a.createElement(
+ "div",
+ { className: "prefs-modal-inner-wrapper" },
+ external__React__default.a.createElement(
+ "h1",
+ null,
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "settings_pane_header" })
+ ),
+ external__React__default.a.createElement(
+ "p",
+ null,
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "settings_pane_body2" })
+ ),
+ external__React__default.a.createElement(PreferencesInput, {
+ className: "showSearch",
+ prefName: "showSearch",
+ value: prefs.showSearch,
+ onChange: this.handlePrefChange,
+ titleString: { id: "settings_pane_search_header" },
+ descString: { id: "settings_pane_search_body" } }),
+ external__React__default.a.createElement("hr", null),
+ external__React__default.a.createElement(
+ PreferencesInput,
+ {
+ className: "showTopSites",
+ prefName: "showTopSites",
+ value: prefs.showTopSites,
+ onChange: this.handlePrefChange,
+ titleString: { id: "settings_pane_topsites_header" },
+ descString: { id: "settings_pane_topsites_body" } },
+ external__React__default.a.createElement(PreferencesInput, {
+ className: "showMoreTopSites",
+ prefName: "topSitesCount",
+ disabled: !prefs.showTopSites,
+ value: prefs.topSitesCount !== Reducers["TOP_SITES_DEFAULT_LENGTH"],
+ onChange: this.handlePrefChange,
+ titleString: { id: "settings_pane_topsites_options_showmore" },
+ labelClassName: "icon icon-topsites" })
+ ),
+ sections.filter(section => !section.shouldHidePref).map(({ id, title, enabled, pref }) => external__React__default.a.createElement(
+ PreferencesInput,
+ {
+ key: id,
+ className: "showSection",
+ prefName: pref && pref.feed || id,
+ value: enabled,
+ onChange: pref && pref.feed ? this.handlePrefChange : this.handleSectionChange,
+ titleString: pref && pref.titleString || title,
+ descString: pref && pref.descString },
+ pref.nestedPrefs && pref.nestedPrefs.map(nestedPref => external__React__default.a.createElement(PreferencesInput, {
+ key: nestedPref.name,
+ prefName: nestedPref.name,
+ disabled: !enabled,
+ value: prefs[nestedPref.name],
+ onChange: this.handlePrefChange,
+ titleString: nestedPref.titleString,
+ labelClassName: `icon ${nestedPref.icon}` }))
+ )),
+ !prefs.disableSnippets && external__React__default.a.createElement("hr", null),
+ !prefs.disableSnippets && external__React__default.a.createElement(PreferencesInput, { className: "showSnippets", prefName: "feeds.snippets",
+ value: prefs["feeds.snippets"], onChange: this.handlePrefChange,
+ titleString: { id: "settings_pane_snippets_header" },
+ descString: { id: "settings_pane_snippets_body" } })
+ ),
+ external__React__default.a.createElement(
+ "section",
+ { className: "actions" },
+ external__React__default.a.createElement(
+ "button",
+ { className: "done", onClick: this.togglePane },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "settings_pane_done_button" })
+ )
+ )
+ )
+ )
+ );
+ }
+}
+
+const PreferencesPane = Object(external__ReactRedux_["connect"])(state => ({
+ Prefs: state.Prefs,
+ PreferencesPane: state.PreferencesPane,
+ Sections: state.Sections
+}))(Object(external__ReactIntl_["injectIntl"])(PreferencesPane__PreferencesPane));
+// EXTERNAL MODULE: ./system-addon/common/PrerenderData.jsm
+var PrerenderData = __webpack_require__(14);
+var PrerenderData_default = /*#__PURE__*/__webpack_require__.n(PrerenderData);
+
+// EXTERNAL MODULE: ./system-addon/content-src/lib/constants.js
+var constants = __webpack_require__(15);
+
+// CONCATENATED MODULE: ./system-addon/content-src/components/Search/Search.jsx
+/* globals ContentSearchUIController */
+
+
+
+
+
+
+
+
+class Search__Search extends external__React__default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.onClick = this.onClick.bind(this);
+ this.onInputMount = this.onInputMount.bind(this);
+ }
+
+ handleEvent(event) {
+ // Also track search events with our own telemetry
+ if (event.detail.type === "Search") {
+ this.props.dispatch(Actions["actionCreators"].UserEvent({ event: "SEARCH" }));
+ }
+ }
+ onClick(event) {
+ window.gContentSearchController.search(event);
+ }
+ componentWillUnmount() {
+ delete window.gContentSearchController;
+ }
+ onInputMount(input) {
+ if (input) {
+ // The "healthReportKey" and needs to be "newtab" or "abouthome" so that
+ // BrowserUsageTelemetry.jsm knows to handle events with this name, and
+ // can add the appropriate telemetry probes for search. Without the correct
+ // name, certain tests like browser_UsageTelemetry_content.js will fail
+ // (See github ticket #2348 for more details)
+ const healthReportKey = constants["a" /* IS_NEWTAB */] ? "newtab" : "abouthome";
+
+ // The "searchSource" needs to be "newtab" or "homepage" and is sent with
+ // the search data and acts as context for the search request (See
+ // nsISearchEngine.getSubmission). It is necessary so that search engine
+ // plugins can correctly atribute referrals. (See github ticket #3321 for
+ // more details)
+ const searchSource = constants["a" /* IS_NEWTAB */] ? "newtab" : "homepage";
+
+ // gContentSearchController needs to exist as a global so that tests for
+ // the existing about:home can find it; and so it allows these tests to pass.
+ // In the future, when activity stream is default about:home, this can be renamed
+ window.gContentSearchController = new ContentSearchUIController(input, input.parentNode, healthReportKey, searchSource);
+ addEventListener("ContentSearchClient", this);
+ } else {
+ window.gContentSearchController = null;
+ removeEventListener("ContentSearchClient", this);
+ }
+ }
+
+ /*
+ * Do not change the ID on the input field, as legacy newtab code
+ * specifically looks for the id 'newtab-search-text' on input fields
+ * in order to execute searches in various tests
+ */
+ render() {
+ return external__React__default.a.createElement(
+ "div",
+ { className: "search-wrapper" },
+ external__React__default.a.createElement(
+ "label",
+ { htmlFor: "newtab-search-text", className: "search-label" },
+ external__React__default.a.createElement(
+ "span",
+ { className: "sr-only" },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "search_web_placeholder" })
+ )
+ ),
+ external__React__default.a.createElement("input", {
+ id: "newtab-search-text",
+ maxLength: "256",
+ placeholder: this.props.intl.formatMessage({ id: "search_web_placeholder" }),
+ ref: this.onInputMount,
+ title: this.props.intl.formatMessage({ id: "search_web_placeholder" }),
+ type: "search" }),
+ external__React__default.a.createElement(
+ "button",
+ {
+ id: "searchSubmit",
+ className: "search-button",
+ onClick: this.onClick,
+ title: this.props.intl.formatMessage({ id: "search_button" }) },
+ external__React__default.a.createElement(
+ "span",
+ { className: "sr-only" },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "search_button" })
+ )
+ )
+ );
+ }
+}
+
+const Search = Object(external__ReactRedux_["connect"])()(Object(external__ReactIntl_["injectIntl"])(Search__Search));
+// EXTERNAL MODULE: ./system-addon/content-src/components/Sections/Sections.jsx
+var Sections = __webpack_require__(16);
+
+// CONCATENATED MODULE: ./system-addon/content-src/components/TopSites/TopSitesConstants.js
+const TOP_SITES_SOURCE = "TOP_SITES";
+const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "EditTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"];
+// minimum size necessary to show a rich icon instead of a screenshot
+const MIN_RICH_FAVICON_SIZE = 96;
+// minimum size necessary to show any icon in the top left corner with a screenshot
+const MIN_CORNER_FAVICON_SIZE = 16;
+// EXTERNAL MODULE: ./system-addon/content-src/components/CollapsibleSection/CollapsibleSection.jsx
+var CollapsibleSection = __webpack_require__(7);
+
+// EXTERNAL MODULE: ./system-addon/content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx
+var ComponentPerfTimer = __webpack_require__(8);
+
+// EXTERNAL MODULE: ./system-addon/content-src/components/LinkMenu/LinkMenu.jsx + 2 modules
+var LinkMenu = __webpack_require__(6);
+
+// CONCATENATED MODULE: ./system-addon/content-src/components/TopSites/TopSite.jsx
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+
+
+
+
+
+const TopSiteLink = props => {
+ const { link, title } = props;
+ const topSiteOuterClassName = `top-site-outer${props.className ? ` ${props.className}` : ""}`;
+ const { tippyTopIcon, faviconSize } = link;
+ const letterFallback = title[0];
+ let imageClassName;
+ let imageStyle;
+ let showSmallFavicon = false;
+ let smallFaviconStyle;
+ let smallFaviconFallback;
+ if (tippyTopIcon || faviconSize >= MIN_RICH_FAVICON_SIZE) {
+ // styles and class names for top sites with rich icons
+ imageClassName = "top-site-icon rich-icon";
+ imageStyle = {
+ backgroundColor: link.backgroundColor,
+ backgroundImage: `url(${tippyTopIcon || link.favicon})`
+ };
+ } else {
+ // styles and class names for top sites with screenshot + small icon in top left corner
+ imageClassName = `screenshot${link.screenshot ? " active" : ""}`;
+ imageStyle = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" };
+
+ // only show a favicon in top left if it's greater than 16x16
+ if (faviconSize >= MIN_CORNER_FAVICON_SIZE) {
+ showSmallFavicon = true;
+ smallFaviconStyle = { backgroundImage: `url(${link.favicon})` };
+ } else if (link.screenshot) {
+ // Don't show a small favicon if there is no screenshot, because that
+ // would result in two fallback icons
+ showSmallFavicon = true;
+ smallFaviconFallback = true;
+ }
+ }
+ return external__React__default.a.createElement(
+ "li",
+ { className: topSiteOuterClassName, key: link.guid || link.url },
+ external__React__default.a.createElement(
+ "a",
+ { href: link.url, onClick: props.onClick },
+ external__React__default.a.createElement(
+ "div",
+ { className: "tile", "aria-hidden": true, "data-fallback": letterFallback },
+ external__React__default.a.createElement("div", { className: imageClassName, style: imageStyle }),
+ showSmallFavicon && external__React__default.a.createElement("div", {
+ className: "top-site-icon default-icon",
+ "data-fallback": smallFaviconFallback && letterFallback,
+ style: smallFaviconStyle })
+ ),
+ external__React__default.a.createElement(
+ "div",
+ { className: `title ${link.isPinned ? "pinned" : ""}` },
+ link.isPinned && external__React__default.a.createElement("div", { className: "icon icon-pin-small" }),
+ external__React__default.a.createElement(
+ "span",
+ { dir: "auto" },
+ title
+ )
+ )
+ ),
+ props.children
+ );
+};
+TopSiteLink.defaultProps = {
+ title: "",
+ link: {}
+};
+
+class TopSite_TopSite extends external__React__default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = { showContextMenu: false, activeTile: null };
+ this.onLinkClick = this.onLinkClick.bind(this);
+ this.onMenuButtonClick = this.onMenuButtonClick.bind(this);
+ this.onMenuUpdate = this.onMenuUpdate.bind(this);
+ this.onDismissButtonClick = this.onDismissButtonClick.bind(this);
+ this.onPinButtonClick = this.onPinButtonClick.bind(this);
+ this.onEditButtonClick = this.onEditButtonClick.bind(this);
+ }
+ toggleContextMenu(event, index) {
+ this.setState({
+ activeTile: index,
+ showContextMenu: true
+ });
+ }
+ userEvent(event) {
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
+ event,
+ source: TOP_SITES_SOURCE,
+ action_position: this.props.index
+ }));
+ }
+ onLinkClick(ev) {
+ if (this.props.onEdit) {
+ // Ignore clicks if we are in the edit modal.
+ ev.preventDefault();
+ return;
+ }
+ this.userEvent("CLICK");
+ }
+ onMenuButtonClick(event) {
+ event.preventDefault();
+ this.toggleContextMenu(event, this.props.index);
+ }
+ onMenuUpdate(showContextMenu) {
+ this.setState({ showContextMenu });
+ }
+ onDismissButtonClick() {
+ const { link } = this.props;
+ if (link.isPinned) {
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].TOP_SITES_UNPIN,
+ data: { site: { url: link.url } }
+ }));
+ }
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].BLOCK_URL,
+ data: link.url
+ }));
+ this.userEvent("BLOCK");
+ }
+ onPinButtonClick() {
+ const { link, index } = this.props;
+ if (link.isPinned) {
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].TOP_SITES_UNPIN,
+ data: { site: { url: link.url } }
+ }));
+ this.userEvent("UNPIN");
+ } else {
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].TOP_SITES_PIN,
+ data: { site: { url: link.url }, index }
+ }));
+ this.userEvent("PIN");
+ }
+ }
+ onEditButtonClick() {
+ this.props.onEdit(this.props.index);
+ }
+ render() {
+ const { props } = this;
+ const { link } = props;
+ const isContextMenuOpen = this.state.showContextMenu && this.state.activeTile === props.index;
+ const title = link.label || link.hostname;
+ return external__React__default.a.createElement(
+ TopSiteLink,
+ _extends({}, props, { onClick: this.onLinkClick, className: isContextMenuOpen ? "active" : "", title: title }),
+ !props.onEdit && external__React__default.a.createElement(
+ "div",
+ null,
+ external__React__default.a.createElement(
+ "button",
+ { className: "context-menu-button icon", onClick: this.onMenuButtonClick },
+ external__React__default.a.createElement(
+ "span",
+ { className: "sr-only" },
+ `Open context menu for ${title}`
+ )
+ ),
+ external__React__default.a.createElement(LinkMenu["a" /* LinkMenu */], {
+ dispatch: props.dispatch,
+ index: props.index,
+ onUpdate: this.onMenuUpdate,
+ options: TOP_SITES_CONTEXT_MENU_OPTIONS,
+ site: link,
+ source: TOP_SITES_SOURCE,
+ visible: isContextMenuOpen })
+ ),
+ props.onEdit && external__React__default.a.createElement(
+ "div",
+ { className: "edit-menu" },
+ external__React__default.a.createElement("button", {
+ className: `icon icon-${link.isPinned ? "unpin" : "pin"}`,
+ title: this.props.intl.formatMessage({ id: `edit_topsites_${link.isPinned ? "unpin" : "pin"}_button` }),
+ onClick: this.onPinButtonClick }),
+ external__React__default.a.createElement("button", {
+ className: "icon icon-edit",
+ title: this.props.intl.formatMessage({ id: "edit_topsites_edit_button" }),
+ onClick: this.onEditButtonClick }),
+ external__React__default.a.createElement("button", {
+ className: "icon icon-dismiss",
+ title: this.props.intl.formatMessage({ id: "edit_topsites_dismiss_button" }),
+ onClick: this.onDismissButtonClick })
+ )
+ );
+ }
+}
+TopSite_TopSite.defaultProps = { link: {} };
+
+const TopSitePlaceholder = () => external__React__default.a.createElement(TopSiteLink, { className: "placeholder" });
+
+const TopSiteList = props => {
+ const topSites = props.TopSites.rows.slice(0, props.TopSitesCount);
+ const topSitesUI = [];
+ for (let i = 0, l = props.TopSitesCount; i < l; i++) {
+ const link = topSites[i];
+ topSitesUI.push(!link ? external__React__default.a.createElement(TopSitePlaceholder, { key: i }) : external__React__default.a.createElement(TopSite_TopSite, {
+ key: link.guid || link.url,
+ dispatch: props.dispatch,
+ link: link,
+ index: i,
+ intl: props.intl,
+ onEdit: props.onEdit }));
+ }
+ return external__React__default.a.createElement(
+ "ul",
+ { className: "top-sites-list" },
+ topSitesUI
+ );
+};
+// CONCATENATED MODULE: ./system-addon/content-src/components/TopSites/TopSiteForm.jsx
+
+
+
+
+
+class TopSiteForm_TopSiteForm extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
this.state = {
label: props.label || "",
url: props.url || "",
validationError: false
};
this.onLabelChange = this.onLabelChange.bind(this);
@@ -2011,39 +2747,39 @@ class TopSiteForm extends React.PureComp
}
onAddButtonClick(ev) {
ev.preventDefault();
if (this.validateForm()) {
let site = { url: this.cleanUrl() };
if (this.state.label !== "") {
site.label = this.state.label;
}
- this.props.dispatch(ac.SendToMain({
- type: at.TOP_SITES_ADD,
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].TOP_SITES_ADD,
data: { site }
}));
- this.props.dispatch(ac.UserEvent({
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
source: TOP_SITES_SOURCE,
event: "TOP_SITES_ADD"
}));
this.props.onClose();
}
}
onSaveButtonClick(ev) {
ev.preventDefault();
if (this.validateForm()) {
let site = { url: this.cleanUrl() };
if (this.state.label !== "") {
site.label = this.state.label;
}
- this.props.dispatch(ac.SendToMain({
- type: at.TOP_SITES_PIN,
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].TOP_SITES_PIN,
data: { site, index: this.props.index }
}));
- this.props.dispatch(ac.UserEvent({
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
source: TOP_SITES_SOURCE,
event: "TOP_SITES_EDIT",
action_position: this.props.index
}));
this.props.onClose();
}
}
cleanUrl() {
@@ -2075,302 +2811,446 @@ class TopSiteForm extends React.PureComp
return false;
}
return true;
}
onUrlInputMount(input) {
this.inputUrl = input;
}
render() {
- return React.createElement(
+ return external__React__default.a.createElement(
"form",
{ className: "topsite-form" },
- React.createElement(
+ external__React__default.a.createElement(
"section",
{ className: "edit-topsites-inner-wrapper" },
- React.createElement(
+ external__React__default.a.createElement(
"div",
{ className: "form-wrapper" },
- React.createElement(
+ external__React__default.a.createElement(
"h3",
{ className: "section-title" },
- React.createElement(FormattedMessage, { id: this.props.editMode ? "topsites_form_edit_header" : "topsites_form_add_header" })
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: this.props.editMode ? "topsites_form_edit_header" : "topsites_form_add_header" })
),
- React.createElement(
+ external__React__default.a.createElement(
"div",
{ className: "field title" },
- React.createElement("input", {
+ external__React__default.a.createElement("input", {
type: "text",
value: this.state.label,
onChange: this.onLabelChange,
placeholder: this.props.intl.formatMessage({ id: "topsites_form_title_placeholder" }) })
),
- React.createElement(
+ external__React__default.a.createElement(
"div",
{ className: `field url${this.state.validationError ? " invalid" : ""}` },
- React.createElement("input", {
+ external__React__default.a.createElement("input", {
type: "text",
ref: this.onUrlInputMount,
value: this.state.url,
onChange: this.onUrlChange,
placeholder: this.props.intl.formatMessage({ id: "topsites_form_url_placeholder" }) }),
- this.state.validationError && React.createElement(
+ this.state.validationError && external__React__default.a.createElement(
"aside",
{ className: "error-tooltip" },
- React.createElement(FormattedMessage, { id: "topsites_form_url_validation" })
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "topsites_form_url_validation" })
)
)
)
),
- React.createElement(
+ external__React__default.a.createElement(
"section",
{ className: "actions" },
- React.createElement(
+ external__React__default.a.createElement(
"button",
{ className: "cancel", type: "button", onClick: this.onCancelButtonClick },
- React.createElement(FormattedMessage, { id: "topsites_form_cancel_button" })
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "topsites_form_cancel_button" })
),
- this.props.editMode && React.createElement(
+ this.props.editMode && external__React__default.a.createElement(
"button",
{ className: "done save", type: "submit", onClick: this.onSaveButtonClick },
- React.createElement(FormattedMessage, { id: "topsites_form_save_button" })
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "topsites_form_save_button" })
),
- !this.props.editMode && React.createElement(
+ !this.props.editMode && external__React__default.a.createElement(
"button",
{ className: "done add", type: "submit", onClick: this.onAddButtonClick },
- React.createElement(FormattedMessage, { id: "topsites_form_add_button" })
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "topsites_form_add_button" })
)
)
);
}
}
-TopSiteForm.defaultProps = {
+TopSiteForm_TopSiteForm.defaultProps = {
label: "",
url: "",
index: 0,
editMode: false // by default we are in "Add New Top Site" mode
};
-
-module.exports = TopSiteForm;
-
-/***/ }),
-/* 18 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-
-class ContextMenu extends React.PureComponent {
+// CONCATENATED MODULE: ./system-addon/content-src/components/TopSites/TopSitesEdit.jsx
+
+
+
+
+
+
+
+
+class TopSitesEdit__TopSitesEdit extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
- this.hideContext = this.hideContext.bind(this);
+ this.state = {
+ showEditModal: false,
+ showAddForm: false,
+ showEditForm: false,
+ editIndex: -1 // Index of top site being edited
+ };
+ this.onEditButtonClick = this.onEditButtonClick.bind(this);
+ this.onShowMoreLessClick = this.onShowMoreLessClick.bind(this);
+ this.onModalOverlayClick = this.onModalOverlayClick.bind(this);
+ this.onAddButtonClick = this.onAddButtonClick.bind(this);
+ this.onFormClose = this.onFormClose.bind(this);
+ this.onEdit = this.onEdit.bind(this);
}
- hideContext() {
- this.props.onUpdate(false);
+ onEditButtonClick() {
+ this.setState({ showEditModal: !this.state.showEditModal });
+ const event = this.state.showEditModal ? "TOP_SITES_EDIT_OPEN" : "TOP_SITES_EDIT_CLOSE";
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
+ source: TOP_SITES_SOURCE,
+ event
+ }));
}
- componentWillMount() {
- this.hideContext();
+ onModalOverlayClick() {
+ this.setState({ showEditModal: false, showAddForm: false, showEditForm: false });
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
+ source: TOP_SITES_SOURCE,
+ event: "TOP_SITES_EDIT_CLOSE"
+ }));
+ this.props.dispatch({ type: Actions["actionTypes"].TOP_SITES_CANCEL_EDIT });
}
- componentDidUpdate(prevProps) {
- if (this.props.visible && !prevProps.visible) {
- setTimeout(() => {
- window.addEventListener("click", this.hideContext);
- }, 0);
- }
- if (!this.props.visible && prevProps.visible) {
- window.removeEventListener("click", this.hideContext);
- }
+ onShowMoreLessClick() {
+ const prefIsSetToDefault = this.props.TopSitesCount === Reducers["TOP_SITES_DEFAULT_LENGTH"];
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].SET_PREF,
+ data: { name: "topSitesCount", value: prefIsSetToDefault ? Reducers["TOP_SITES_SHOWMORE_LENGTH"] : Reducers["TOP_SITES_DEFAULT_LENGTH"] }
+ }));
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
+ source: TOP_SITES_SOURCE,
+ event: prefIsSetToDefault ? "TOP_SITES_EDIT_SHOW_MORE" : "TOP_SITES_EDIT_SHOW_LESS"
+ }));
}
- componentWillUnmount() {
- window.removeEventListener("click", this.hideContext);
+ onAddButtonClick() {
+ this.setState({ showAddForm: true });
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
+ source: TOP_SITES_SOURCE,
+ event: "TOP_SITES_ADD_FORM_OPEN"
+ }));
+ }
+ onFormClose() {
+ this.setState({ showAddForm: false, showEditForm: false });
+ this.props.dispatch({ type: Actions["actionTypes"].TOP_SITES_CANCEL_EDIT });
+ }
+ onEdit(index) {
+ this.setState({ showEditForm: true, editIndex: index });
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
+ source: TOP_SITES_SOURCE,
+ event: "TOP_SITES_EDIT_FORM_OPEN"
+ }));
}
render() {
- return React.createElement(
- "span",
- { hidden: !this.props.visible, className: "context-menu" },
- React.createElement(
- "ul",
- { role: "menu", className: "context-menu-list" },
- this.props.options.map((option, i) => option.type === "separator" ? React.createElement("li", { key: i, className: "separator" }) : React.createElement(ContextMenuItem, { key: i, option: option, hideContext: this.hideContext }))
- )
- );
- }
-}
-
-class ContextMenuItem extends React.PureComponent {
- constructor(props) {
- super(props);
- this.onClick = this.onClick.bind(this);
- this.onKeyDown = this.onKeyDown.bind(this);
- }
- onClick() {
- this.props.hideContext();
- this.props.option.onClick();
- }
- onKeyDown(event) {
- const { option } = this.props;
- switch (event.key) {
- case "Tab":
- // tab goes down in context menu, shift + tab goes up in context menu
- // if we're on the last item, one more tab will close the context menu
- // similarly, if we're on the first item, one more shift + tab will close it
- if (event.shiftKey && option.first || !event.shiftKey && option.last) {
- this.props.hideContext();
- }
- break;
- case "Enter":
- this.props.hideContext();
- option.onClick();
- break;
+ const showEditForm = this.props.TopSites.editForm && this.props.TopSites.editForm.visible || this.state.showEditModal && this.state.showEditForm;
+ let editIndex = this.state.editIndex;
+ if (showEditForm && this.props.TopSites.editForm.visible) {
+ const targetURL = this.props.TopSites.editForm.site.url;
+ editIndex = this.props.TopSites.rows.findIndex(s => s.url === targetURL);
}
- }
- render() {
- const { option } = this.props;
- return React.createElement(
- "li",
- { role: "menuitem", className: "context-menu-item" },
- React.createElement(
- "a",
- { onClick: this.onClick, onKeyDown: this.onKeyDown, tabIndex: "0" },
- option.icon && React.createElement("span", { className: `icon icon-spacer icon-${option.icon}` }),
- option.label
+ return external__React__default.a.createElement(
+ "div",
+ { className: "edit-topsites-wrapper" },
+ external__React__default.a.createElement(
+ "div",
+ { className: "edit-topsites-button" },
+ external__React__default.a.createElement(
+ "button",
+ {
+ className: "edit",
+ title: this.props.intl.formatMessage({ id: "edit_topsites_button_label" }),
+ onClick: this.onEditButtonClick },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "edit_topsites_button_text" })
+ )
+ ),
+ this.state.showEditModal && !this.state.showAddForm && !this.state.showEditForm && external__React__default.a.createElement(
+ "div",
+ { className: "edit-topsites" },
+ external__React__default.a.createElement("div", { className: "modal-overlay", onClick: this.onModalOverlayClick }),
+ external__React__default.a.createElement(
+ "div",
+ { className: "modal" },
+ external__React__default.a.createElement(
+ "section",
+ { className: "edit-topsites-inner-wrapper" },
+ external__React__default.a.createElement(
+ "div",
+ { className: "section-top-bar" },
+ external__React__default.a.createElement(
+ "h3",
+ { className: "section-title" },
+ external__React__default.a.createElement("span", { className: `icon icon-small-spacer icon-topsites` }),
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "header_top_sites" })
+ )
+ ),
+ external__React__default.a.createElement(TopSiteList, { TopSites: this.props.TopSites, TopSitesCount: this.props.TopSitesCount, onEdit: this.onEdit, dispatch: this.props.dispatch, intl: this.props.intl })
+ ),
+ external__React__default.a.createElement(
+ "section",
+ { className: "actions" },
+ external__React__default.a.createElement(
+ "button",
+ { className: "add", onClick: this.onAddButtonClick },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "edit_topsites_add_button" })
+ ),
+ external__React__default.a.createElement(
+ "button",
+ { className: `icon icon-topsites show-${this.props.TopSitesCount === Reducers["TOP_SITES_DEFAULT_LENGTH"] ? "more" : "less"}`, onClick: this.onShowMoreLessClick },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: `edit_topsites_show${this.props.TopSitesCount === Reducers["TOP_SITES_DEFAULT_LENGTH"] ? "more" : "less"}_button` })
+ ),
+ external__React__default.a.createElement(
+ "button",
+ { className: "done", onClick: this.onEditButtonClick },
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "edit_topsites_done_button" })
+ )
+ )
+ )
+ ),
+ this.state.showEditModal && this.state.showAddForm && external__React__default.a.createElement(
+ "div",
+ { className: "edit-topsites" },
+ external__React__default.a.createElement("div", { className: "modal-overlay", onClick: this.onModalOverlayClick }),
+ external__React__default.a.createElement(
+ "div",
+ { className: "modal" },
+ external__React__default.a.createElement(TopSiteForm_TopSiteForm, { onClose: this.onFormClose, dispatch: this.props.dispatch, intl: this.props.intl })
+ )
+ ),
+ showEditForm && external__React__default.a.createElement(
+ "div",
+ { className: "edit-topsites" },
+ external__React__default.a.createElement("div", { className: "modal-overlay", onClick: this.onModalOverlayClick }),
+ external__React__default.a.createElement(
+ "div",
+ { className: "modal" },
+ external__React__default.a.createElement(TopSiteForm_TopSiteForm, {
+ label: this.props.TopSites.rows[editIndex].label || this.props.TopSites.rows[editIndex].hostname,
+ url: this.props.TopSites.rows[editIndex].url,
+ index: editIndex,
+ editMode: true,
+ onClose: this.onFormClose,
+ dispatch: this.props.dispatch,
+ intl: this.props.intl })
+ )
)
);
}
}
-module.exports = ContextMenu;
-module.exports.ContextMenu = ContextMenu;
-module.exports.ContextMenuItem = ContextMenuItem;
-
-/***/ }),
-/* 19 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
+const TopSitesEdit = Object(external__ReactIntl_["injectIntl"])(TopSitesEdit__TopSitesEdit);
+// CONCATENATED MODULE: ./system-addon/content-src/components/TopSites/TopSites.jsx
+
+
+
+
+
+
+
+
+
/**
- * List of functions that return items that can be included as menu options in a
- * LinkMenu. All functions take the site as the first parameter, and optionally
- * the index of the site.
+ * Iterates through TopSites and counts types of images.
+ * @param acc Accumulator for reducer.
+ * @param topsite Entry in TopSites.
*/
-module.exports = {
- Separator: () => ({ type: "separator" }),
- RemoveBookmark: site => ({
- id: "menu_action_remove_bookmark",
- icon: "bookmark-added",
- action: ac.SendToMain({
- type: at.DELETE_BOOKMARK_BY_ID,
- data: site.bookmarkGuid
- }),
- userEvent: "BOOKMARK_DELETE"
- }),
- AddBookmark: site => ({
- id: "menu_action_bookmark",
- icon: "bookmark-hollow",
- action: ac.SendToMain({
- type: at.BOOKMARK_URL,
- data: { url: site.url, title: site.title, type: site.type }
- }),
- userEvent: "BOOKMARK_ADD"
- }),
- OpenInNewWindow: site => ({
- id: "menu_action_open_new_window",
- icon: "new-window",
- action: ac.SendToMain({
- type: at.OPEN_NEW_WINDOW,
- data: { url: site.url, referrer: site.referrer }
- }),
- userEvent: "OPEN_NEW_WINDOW"
- }),
- OpenInPrivateWindow: site => ({
- id: "menu_action_open_private_window",
- icon: "new-window-private",
- action: ac.SendToMain({
- type: at.OPEN_PRIVATE_WINDOW,
- data: { url: site.url, referrer: site.referrer }
- }),
- userEvent: "OPEN_PRIVATE_WINDOW"
- }),
- BlockUrl: (site, index, eventSource) => ({
- id: "menu_action_dismiss",
- icon: "dismiss",
- action: ac.SendToMain({
- type: at.BLOCK_URL,
- data: site.url
- }),
- impression: ac.ImpressionStats({
- source: eventSource,
- block: 0,
- tiles: [{ id: site.guid, pos: index }]
- }),
- userEvent: "BLOCK"
- }),
- DeleteUrl: site => ({
- id: "menu_action_delete",
- icon: "delete",
- action: {
- type: at.DIALOG_OPEN,
- data: {
- onConfirm: [ac.SendToMain({ type: at.DELETE_HISTORY_URL, data: { url: site.url, forceBlock: site.bookmarkGuid } }), ac.UserEvent({ event: "DELETE" })],
- body_string_id: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
- confirm_button_string_id: "menu_action_delete",
- cancel_button_string_id: "topsites_form_cancel_button",
- icon: "modal-delete"
- }
- },
- userEvent: "DIALOG_OPEN"
- }),
- PinTopSite: (site, index) => ({
- id: "menu_action_pin",
- icon: "pin",
- action: ac.SendToMain({
- type: at.TOP_SITES_PIN,
- data: { site: { url: site.url }, index }
- }),
- userEvent: "PIN"
- }),
- UnpinTopSite: site => ({
- id: "menu_action_unpin",
- icon: "unpin",
- action: ac.SendToMain({
- type: at.TOP_SITES_UNPIN,
- data: { site: { url: site.url } }
- }),
- userEvent: "UNPIN"
- }),
- SaveToPocket: (site, index, eventSource) => ({
- id: "menu_action_save_to_pocket",
- icon: "pocket",
- action: ac.SendToMain({
- type: at.SAVE_TO_POCKET,
- data: { site: { url: site.url, title: site.title } }
- }),
- impression: ac.ImpressionStats({
- source: eventSource,
- pocket: 0,
- tiles: [{ id: site.guid, pos: index }]
- }),
- userEvent: "SAVE_TO_POCKET"
- }),
- EditTopSite: site => ({
- id: "edit_topsites_button_text",
- icon: "edit",
- action: {
- type: at.TOP_SITES_EDIT,
- data: { url: site.url, label: site.label }
+function countTopSitesIconsTypes(topSites) {
+ const countTopSitesTypes = (acc, link) => {
+ if (link.tippyTopIcon || link.faviconRef === "tippytop") {
+ acc.tippytop++;
+ } else if (link.faviconSize >= MIN_RICH_FAVICON_SIZE) {
+ acc.rich_icon++;
+ } else if (link.screenshot && link.faviconSize >= MIN_CORNER_FAVICON_SIZE) {
+ acc.screenshot_with_icon++;
+ } else if (link.screenshot) {
+ acc.screenshot++;
+ } else {
+ acc.no_image++;
}
- })
-};
-
-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);
+
+ return acc;
+ };
+
+ return topSites.reduce(countTopSitesTypes, {
+ "screenshot_with_icon": 0,
+ "screenshot": 0,
+ "tippytop": 0,
+ "rich_icon": 0,
+ "no_image": 0
+ });
+}
+
+class TopSites__TopSites extends external__React__default.a.PureComponent {
+ /**
+ * Dispatch session statistics about the quality of TopSites icons and pinned count.
+ */
+ _dispatchTopSitesStats() {
+ const topSites = this._getTopSites();
+ const topSitesIconsStats = countTopSitesIconsTypes(topSites);
+ const topSitesPinned = topSites.filter(site => !!site.isPinned).length;
+ // Dispatch telemetry event with the count of TopSites images types.
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].SAVE_SESSION_PERF_DATA,
+ data: { topsites_icon_stats: topSitesIconsStats, topsites_pinned: topSitesPinned }
+ }));
+ }
+
+ /**
+ * Return the TopSites to display based on prefs.
+ */
+ _getTopSites() {
+ return this.props.TopSites.rows.slice(0, this.props.TopSitesCount);
+ }
+
+ componentDidUpdate() {
+ this._dispatchTopSitesStats();
+ }
+
+ componentDidMount() {
+ this._dispatchTopSitesStats();
+ }
+
+ render() {
+ const props = this.props;
+ const infoOption = {
+ header: { id: "settings_pane_topsites_header" },
+ body: { id: "settings_pane_topsites_body" }
+ };
+ return external__React__default.a.createElement(
+ ComponentPerfTimer["a" /* ComponentPerfTimer */],
+ { id: "topsites", initialized: props.TopSites.initialized, dispatch: props.dispatch },
+ external__React__default.a.createElement(
+ CollapsibleSection["a" /* CollapsibleSection */],
+ { className: "top-sites", icon: "topsites", title: external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "header_top_sites" }), infoOption: infoOption, prefName: "collapseTopSites", Prefs: props.Prefs, dispatch: props.dispatch },
+ external__React__default.a.createElement(TopSiteList, { TopSites: props.TopSites, TopSitesCount: props.TopSitesCount, dispatch: props.dispatch, intl: props.intl }),
+ external__React__default.a.createElement(TopSitesEdit, props)
+ )
+ );
+ }
+}
+
+const TopSites = Object(external__ReactRedux_["connect"])(state => ({
+ TopSites: state.TopSites,
+ Prefs: state.Prefs,
+ TopSitesCount: state.Prefs.values.topSitesCount
+}))(TopSites__TopSites);
+// CONCATENATED MODULE: ./system-addon/content-src/components/Base/Base.jsx
+
+
+
+
+
+
+
+
+
+
+
+
+// Add the locale data for pluralization and relative-time formatting for now,
+// this just uses english locale data. We can make this more sophisticated if
+// more features are needed.
+function addLocaleDataForReactIntl(locale) {
+ Object(external__ReactIntl_["addLocaleData"])([{ locale, parentLocale: "en" }]);
+}
+
+class Base__Base extends external__React__default.a.PureComponent {
+ componentWillMount() {
+ const { App, locale } = this.props;
+ this.sendNewTabRehydrated(App);
+ addLocaleDataForReactIntl(locale);
+ }
+
+ componentDidMount() {
+ // Request state AFTER the first render to ensure we don't cause the
+ // prerendered DOM to be unmounted. Otherwise, NEW_TAB_STATE_REQUEST is
+ // dispatched right after the store is ready.
+ if (this.props.isPrerendered) {
+ this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].NEW_TAB_STATE_REQUEST }));
+ this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].PAGE_PRERENDERED }));
+ }
+ }
+
+ componentWillUpdate({ App }) {
+ this.sendNewTabRehydrated(App);
+ }
+
+ // The NEW_TAB_REHYDRATED event is used to inform feeds that their
+ // data has been consumed e.g. for counting the number of tabs that
+ // have rendered that data.
+ sendNewTabRehydrated(App) {
+ if (App && App.initialized && !this.renderNotified) {
+ this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].NEW_TAB_REHYDRATED, data: {} }));
+ this.renderNotified = true;
+ }
+ }
+
+ render() {
+ const props = this.props;
+ const { App, locale, strings } = props;
+ const { initialized } = App;
+ const prefs = props.Prefs.values;
+
+ const shouldBeFixedToTop = PrerenderData["PrerenderData"].arePrefsValid(name => prefs[name]);
+
+ const outerClassName = `outer-wrapper${shouldBeFixedToTop ? " fixed-to-top" : ""}`;
+
+ if (!props.isPrerendered && !initialized) {
+ return null;
+ }
+
+ return external__React__default.a.createElement(
+ external__ReactIntl_["IntlProvider"],
+ { locale: locale, messages: strings },
+ external__React__default.a.createElement(
+ "div",
+ { className: outerClassName },
+ external__React__default.a.createElement(
+ "main",
+ null,
+ prefs.showSearch && external__React__default.a.createElement(Search, null),
+ external__React__default.a.createElement(
+ "div",
+ { className: `body-wrapper${initialized ? " on" : ""}` },
+ !prefs.migrationExpired && external__React__default.a.createElement(ManualMigration, null),
+ prefs.showTopSites && external__React__default.a.createElement(TopSites, null),
+ external__React__default.a.createElement(Sections["a" /* Sections */], null)
+ ),
+ external__React__default.a.createElement(ConfirmDialog, null)
+ ),
+ initialized && external__React__default.a.createElement(PreferencesPane, null)
+ )
+ );
+ }
+}
+/* unused harmony export _Base */
+
+
+const Base = Object(external__ReactRedux_["connect"])(state => ({ App: state.App, Prefs: state.Prefs }))(Base__Base);
+/* harmony export (immutable) */ __webpack_exports__["a"] = Base;
+
/***/ }),
-/* 20 */
+/* 13 */
/***/ (function(module, exports) {
var Dedupe = class Dedupe {
constructor(createKey) {
this.createKey = createKey || this.defaultCreateKey;
}
defaultCreateKey(item) {
@@ -2400,530 +3280,166 @@ var Dedupe = class Dedupe {
return result.map(m => Array.from(m.values()));
}
};
module.exports = {
Dedupe
};
/***/ }),
-/* 21 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-/* globals ContentSearchUIController */
-
-
-const React = __webpack_require__(1);
-const { connect } = __webpack_require__(3);
-const { FormattedMessage, injectIntl } = __webpack_require__(2);
-const { actionCreators: ac } = __webpack_require__(0);
-const { IS_NEWTAB } = __webpack_require__(22);
-
-class Search extends React.PureComponent {
- constructor(props) {
- super(props);
- this.onClick = this.onClick.bind(this);
- this.onInputMount = this.onInputMount.bind(this);
+/* 14 */
+/***/ (function(module, exports) {
+
+class _PrerenderData {
+ constructor(options) {
+ this.initialPrefs = options.initialPrefs;
+ this.initialSections = options.initialSections;
+ this._setValidation(options.validation);
}
- handleEvent(event) {
- // Also track search events with our own telemetry
- if (event.detail.type === "Search") {
- this.props.dispatch(ac.UserEvent({ event: "SEARCH" }));
- }
+ get validation() {
+ return this._validation;
}
- onClick(event) {
- window.gContentSearchController.search(event);
+
+ set validation(value) {
+ this._setValidation(value);
}
- componentWillUnmount() {
- delete window.gContentSearchController;
+
+ get invalidatingPrefs() {
+ return this._invalidatingPrefs;
}
- onInputMount(input) {
- if (input) {
- // The "healthReportKey" and needs to be "newtab" or "abouthome" so that
- // BrowserUsageTelemetry.jsm knows to handle events with this name, and
- // can add the appropriate telemetry probes for search. Without the correct
- // name, certain tests like browser_UsageTelemetry_content.js will fail
- // (See github ticket #2348 for more details)
- const healthReportKey = IS_NEWTAB ? "newtab" : "abouthome";
-
- // The "searchSource" needs to be "newtab" or "homepage" and is sent with
- // the search data and acts as context for the search request (See
- // nsISearchEngine.getSubmission). It is necessary so that search engine
- // plugins can correctly atribute referrals. (See github ticket #3321 for
- // more details)
- const searchSource = IS_NEWTAB ? "newtab" : "homepage";
-
- // gContentSearchController needs to exist as a global so that tests for
- // the existing about:home can find it; and so it allows these tests to pass.
- // In the future, when activity stream is default about:home, this can be renamed
- window.gContentSearchController = new ContentSearchUIController(input, input.parentNode, healthReportKey, searchSource);
- addEventListener("ContentSearchClient", this);
- } else {
- window.gContentSearchController = null;
- removeEventListener("ContentSearchClient", this);
- }
+
+ // This is needed so we can use it in the constructor
+ _setValidation(value = []) {
+ this._validation = value;
+ this._invalidatingPrefs = value.reduce((result, next) => {
+ if (typeof next === "string") {
+ result.push(next);
+ return result;
+ } else if (next && next.oneOf) {
+ return result.concat(next.oneOf);
+ }
+ throw new Error("Your validation configuration is not properly configured");
+ }, []);
}
- /*
- * Do not change the ID on the input field, as legacy newtab code
- * specifically looks for the id 'newtab-search-text' on input fields
- * in order to execute searches in various tests
- */
- render() {
- return React.createElement(
- "div",
- { className: "search-wrapper" },
- React.createElement(
- "label",
- { htmlFor: "newtab-search-text", className: "search-label" },
- React.createElement(
- "span",
- { className: "sr-only" },
- React.createElement(FormattedMessage, { id: "search_web_placeholder" })
- )
- ),
- React.createElement("input", {
- id: "newtab-search-text",
- maxLength: "256",
- placeholder: this.props.intl.formatMessage({ id: "search_web_placeholder" }),
- ref: this.onInputMount,
- title: this.props.intl.formatMessage({ id: "search_web_placeholder" }),
- type: "search" }),
- React.createElement(
- "button",
- {
- id: "searchSubmit",
- className: "search-button",
- onClick: this.onClick,
- title: this.props.intl.formatMessage({ id: "search_button" }) },
- React.createElement(
- "span",
- { className: "sr-only" },
- React.createElement(FormattedMessage, { id: "search_button" })
- )
- )
- );
+ arePrefsValid(getPref) {
+ for (const prefs of this.validation) {
+ // {oneOf: ["foo", "bar"]}
+ if (prefs && prefs.oneOf && !prefs.oneOf.some(name => getPref(name) === this.initialPrefs[name])) {
+ return false;
+
+ // "foo"
+ } else if (getPref(prefs) !== this.initialPrefs[prefs]) {
+ return false;
+ }
+ }
+ return true;
}
}
-module.exports = connect()(injectIntl(Search));
-module.exports._unconnected = Search;
-
-/***/ }),
-/* 22 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(global) {module.exports = {
- // constant to know if the page is about:newtab or about:home
- IS_NEWTAB: global.document && global.document.documentURI === "about:newtab"
+var PrerenderData = new _PrerenderData({
+ initialPrefs: {
+ "migrationExpired": true,
+ "showTopSites": true,
+ "showSearch": true,
+ "topSitesCount": 6,
+ "collapseTopSites": false,
+ "section.highlights.collapsed": false,
+ "section.topstories.collapsed": false,
+ "feeds.section.topstories": true,
+ "feeds.section.highlights": true
+ },
+ // Prefs listed as invalidating will prevent the prerendered version
+ // of AS from being used if their value is something other than what is listed
+ // here. This is required because some preferences cause the page layout to be
+ // too different for the prerendered version to be used. Unfortunately, this
+ // will result in users who have modified some of their preferences not being
+ // able to get the benefits of prerendering.
+ validation: ["showTopSites", "showSearch", "collapseTopSites", "section.highlights.collapsed", "section.topstories.collapsed",
+ // This means if either of these are set to their default values,
+ // prerendering can be used.
+ { oneOf: ["feeds.section.topstories", "feeds.section.highlights"] }],
+ initialSections: [{
+ enabled: true,
+ icon: "pocket",
+ id: "topstories",
+ order: 1,
+ title: { id: "header_recommended_by", values: { provider: "Pocket" } }
+ }, {
+ enabled: true,
+ id: "highlights",
+ icon: "highlights",
+ order: 2,
+ title: { id: "header_highlights" }
+ }]
+});
+module.exports = {
+ PrerenderData,
+ _PrerenderData
};
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
-
-/***/ }),
-/* 23 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { connect } = __webpack_require__(3);
-const { FormattedMessage } = __webpack_require__(2);
-const { actionTypes, actionCreators: ac } = __webpack_require__(0);
-
-/**
- * ConfirmDialog component.
- * One primary action button, one cancel button.
- *
- * Content displayed is controlled by `data` prop the component receives.
- * Example:
- * data: {
- * // Any sort of data needed to be passed around by actions.
- * payload: site.url,
- * // Primary button SendToMain action.
- * action: "DELETE_HISTORY_URL",
- * // Primary button USerEvent action.
- * userEvent: "DELETE",
- * // Array of locale ids to display.
- * message_body: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
- * // Text for primary button.
- * confirm_button_string_id: "menu_action_delete"
- * },
- */
-class ConfirmDialog extends React.PureComponent {
- constructor(props) {
- super(props);
- this._handleCancelBtn = this._handleCancelBtn.bind(this);
- this._handleConfirmBtn = this._handleConfirmBtn.bind(this);
- }
-
- _handleCancelBtn() {
- this.props.dispatch({ type: actionTypes.DIALOG_CANCEL });
- this.props.dispatch(ac.UserEvent({ event: actionTypes.DIALOG_CANCEL }));
- }
-
- _handleConfirmBtn() {
- this.props.data.onConfirm.forEach(this.props.dispatch);
- }
-
- _renderModalMessage() {
- const message_body = this.props.data.body_string_id;
-
- if (!message_body) {
- return null;
- }
-
- return React.createElement(
- "span",
- null,
- message_body.map(msg => React.createElement(
- "p",
- { key: msg },
- React.createElement(FormattedMessage, { id: msg })
- ))
- );
- }
-
- render() {
- if (!this.props.visible) {
- return null;
- }
-
- return React.createElement(
- "div",
- { className: "confirmation-dialog" },
- React.createElement("div", { className: "modal-overlay", onClick: this._handleCancelBtn }),
- React.createElement(
- "div",
- { className: "modal" },
- React.createElement(
- "section",
- { className: "modal-message" },
- this.props.data.icon && React.createElement("span", { className: `icon icon-spacer icon-${this.props.data.icon}` }),
- this._renderModalMessage()
- ),
- React.createElement(
- "section",
- { className: "actions" },
- React.createElement(
- "button",
- { onClick: this._handleCancelBtn },
- React.createElement(FormattedMessage, { id: this.props.data.cancel_button_string_id })
- ),
- React.createElement(
- "button",
- { className: "done", onClick: this._handleConfirmBtn },
- React.createElement(FormattedMessage, { id: this.props.data.confirm_button_string_id })
- )
- )
- )
- );
- }
-}
-
-module.exports = connect(state => state.Dialog)(ConfirmDialog);
-module.exports._unconnected = ConfirmDialog;
-module.exports.Dialog = ConfirmDialog;
/***/ }),
-/* 24 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { connect } = __webpack_require__(3);
-const { FormattedMessage } = __webpack_require__(2);
-const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
-
-/**
- * Manual migration component used to start the profile import wizard.
- * Message is presented temporarily and will go away if:
- * 1. User clicks "No Thanks"
- * 2. User completed the data import
- * 3. After 3 active days
- * 4. User clicks "Cancel" on the import wizard (currently not implemented).
- */
-class ManualMigration extends React.PureComponent {
- constructor(props) {
- super(props);
- this.onLaunchTour = this.onLaunchTour.bind(this);
- this.onCancelTour = this.onCancelTour.bind(this);
- }
- onLaunchTour() {
- this.props.dispatch(ac.SendToMain({ type: at.MIGRATION_START }));
- this.props.dispatch(ac.UserEvent({ event: at.MIGRATION_START }));
- }
-
- onCancelTour() {
- this.props.dispatch(ac.SendToMain({ type: at.MIGRATION_CANCEL }));
- this.props.dispatch(ac.UserEvent({ event: at.MIGRATION_CANCEL }));
- }
-
- render() {
- return React.createElement(
- "div",
- { className: "manual-migration-container" },
- React.createElement(
- "p",
- null,
- React.createElement("span", { className: "icon icon-import" }),
- React.createElement(FormattedMessage, { id: "manual_migration_explanation2" })
- ),
- React.createElement(
- "div",
- { className: "manual-migration-actions actions" },
- React.createElement(
- "button",
- { className: "dismiss", onClick: this.onCancelTour },
- React.createElement(FormattedMessage, { id: "manual_migration_cancel_button" })
- ),
- React.createElement(
- "button",
- { onClick: this.onLaunchTour },
- React.createElement(FormattedMessage, { id: "manual_migration_import_button" })
- )
- )
- );
- }
-}
-
-module.exports = connect()(ManualMigration);
-module.exports._unconnected = ManualMigration;
+/* 15 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {const IS_NEWTAB = global.document && global.document.documentURI === "about:newtab";
+/* harmony export (immutable) */ __webpack_exports__["a"] = IS_NEWTAB;
+
+/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
/***/ }),
-/* 25 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { connect } = __webpack_require__(3);
-const { injectIntl, FormattedMessage } = __webpack_require__(2);
-const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
-const { TOP_SITES_DEFAULT_LENGTH, TOP_SITES_SHOWMORE_LENGTH } = __webpack_require__(6);
-
-const getFormattedMessage = message => typeof message === "string" ? React.createElement(
- "span",
- null,
- message
-) : React.createElement(FormattedMessage, message);
-
-const PreferencesInput = props => React.createElement(
- "section",
- null,
- React.createElement("input", { type: "checkbox", id: props.prefName, name: props.prefName, checked: props.value, disabled: props.disabled, onChange: props.onChange, className: props.className }),
- React.createElement(
- "label",
- { htmlFor: props.prefName, className: props.labelClassName },
- getFormattedMessage(props.titleString)
- ),
- props.descString && React.createElement(
- "p",
- { className: "prefs-input-description" },
- getFormattedMessage(props.descString)
- ),
- React.Children.map(props.children, child => React.createElement(
- "div",
- { className: `options${child.props.disabled ? " disabled" : ""}` },
- child
- ))
-);
-
-class PreferencesPane extends React.PureComponent {
- constructor(props) {
- super(props);
- this.handleClickOutside = this.handleClickOutside.bind(this);
- this.handlePrefChange = this.handlePrefChange.bind(this);
- this.handleSectionChange = this.handleSectionChange.bind(this);
- this.togglePane = this.togglePane.bind(this);
- this.onWrapperMount = this.onWrapperMount.bind(this);
- }
- componentDidUpdate(prevProps, prevState) {
- if (prevProps.PreferencesPane.visible !== this.props.PreferencesPane.visible) {
- // While the sidebar is open, listen for all document clicks.
- if (this.isSidebarOpen()) {
- document.addEventListener("click", this.handleClickOutside);
- } else {
- document.removeEventListener("click", this.handleClickOutside);
- }
- }
- }
- isSidebarOpen() {
- return this.props.PreferencesPane.visible;
- }
- handleClickOutside(event) {
- // if we are showing the sidebar and there is a click outside, close it.
- if (this.isSidebarOpen() && !this.wrapper.contains(event.target)) {
- this.togglePane();
- }
- }
- handlePrefChange(event) {
- const target = event.target;
- const { name, checked } = target;
- let value = checked;
- if (name === "topSitesCount") {
- value = checked ? TOP_SITES_SHOWMORE_LENGTH : TOP_SITES_DEFAULT_LENGTH;
- }
- this.props.dispatch(ac.SetPref(name, value));
- }
- handleSectionChange(event) {
- const target = event.target;
- const id = target.name;
- const type = target.checked ? at.SECTION_ENABLE : at.SECTION_DISABLE;
- this.props.dispatch(ac.SendToMain({ type, data: id }));
- }
- togglePane() {
- if (this.isSidebarOpen()) {
- this.props.dispatch({ type: at.SETTINGS_CLOSE });
- this.props.dispatch(ac.UserEvent({ event: "CLOSE_NEWTAB_PREFS" }));
- } else {
- this.props.dispatch({ type: at.SETTINGS_OPEN });
- this.props.dispatch(ac.UserEvent({ event: "OPEN_NEWTAB_PREFS" }));
- }
- }
- onWrapperMount(wrapper) {
- this.wrapper = wrapper;
- }
- render() {
- const props = this.props;
- const prefs = props.Prefs.values;
- const sections = props.Sections;
- const isVisible = this.isSidebarOpen();
- return React.createElement(
- "div",
- { className: "prefs-pane-wrapper", ref: this.onWrapperMount },
- React.createElement(
- "div",
- { className: "prefs-pane-button" },
- React.createElement("button", {
- className: `prefs-button icon ${isVisible ? "icon-dismiss" : "icon-settings"}`,
- title: props.intl.formatMessage({ id: isVisible ? "settings_pane_done_button" : "settings_pane_button_label" }),
- onClick: this.togglePane })
- ),
- React.createElement(
- "div",
- { className: "prefs-pane" },
- React.createElement(
- "div",
- { className: `sidebar ${isVisible ? "" : "hidden"}` },
- React.createElement(
- "div",
- { className: "prefs-modal-inner-wrapper" },
- React.createElement(
- "h1",
- null,
- React.createElement(FormattedMessage, { id: "settings_pane_header" })
- ),
- React.createElement(
- "p",
- null,
- React.createElement(FormattedMessage, { id: "settings_pane_body2" })
- ),
- React.createElement(PreferencesInput, {
- className: "showSearch",
- prefName: "showSearch",
- value: prefs.showSearch,
- onChange: this.handlePrefChange,
- titleString: { id: "settings_pane_search_header" },
- descString: { id: "settings_pane_search_body" } }),
- React.createElement("hr", null),
- React.createElement(
- PreferencesInput,
- {
- className: "showTopSites",
- prefName: "showTopSites",
- value: prefs.showTopSites,
- onChange: this.handlePrefChange,
- titleString: { id: "settings_pane_topsites_header" },
- descString: { id: "settings_pane_topsites_body" } },
- React.createElement(PreferencesInput, {
- className: "showMoreTopSites",
- prefName: "topSitesCount",
- disabled: !prefs.showTopSites,
- value: prefs.topSitesCount !== TOP_SITES_DEFAULT_LENGTH,
- onChange: this.handlePrefChange,
- titleString: { id: "settings_pane_topsites_options_showmore" },
- labelClassName: "icon icon-topsites" })
- ),
- sections.filter(section => !section.shouldHidePref).map(({ id, title, enabled, pref }) => React.createElement(
- PreferencesInput,
- {
- key: id,
- className: "showSection",
- prefName: pref && pref.feed || id,
- value: enabled,
- onChange: pref && pref.feed ? this.handlePrefChange : this.handleSectionChange,
- titleString: pref && pref.titleString || title,
- descString: pref && pref.descString },
- pref.nestedPrefs && pref.nestedPrefs.map(nestedPref => React.createElement(PreferencesInput, {
- key: nestedPref.name,
- prefName: nestedPref.name,
- disabled: !enabled,
- value: prefs[nestedPref.name],
- onChange: this.handlePrefChange,
- titleString: nestedPref.titleString,
- labelClassName: `icon ${nestedPref.icon}` }))
- )),
- !prefs.disableSnippets && React.createElement("hr", null),
- !prefs.disableSnippets && React.createElement(PreferencesInput, { className: "showSnippets", prefName: "feeds.snippets",
- value: prefs["feeds.snippets"], onChange: this.handlePrefChange,
- titleString: { id: "settings_pane_snippets_header" },
- descString: { id: "settings_pane_snippets_body" } })
- ),
- React.createElement(
- "section",
- { className: "actions" },
- React.createElement(
- "button",
- { className: "done", onClick: this.togglePane },
- React.createElement(FormattedMessage, { id: "settings_pane_done_button" })
- )
- )
- )
- )
- );
- }
-}
-
-module.exports = connect(state => ({ Prefs: state.Prefs, PreferencesPane: state.PreferencesPane, Sections: state.Sections }))(injectIntl(PreferencesPane));
-module.exports.PreferencesPane = PreferencesPane;
-module.exports.PreferencesInput = PreferencesInput;
-
-/***/ }),
-/* 26 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(global) {var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-const React = __webpack_require__(1);
-const { connect } = __webpack_require__(3);
-const { injectIntl, FormattedMessage } = __webpack_require__(2);
-const Card = __webpack_require__(27);
-const { PlaceholderCard } = Card;
-const Topics = __webpack_require__(29);
-const { actionCreators: ac } = __webpack_require__(0);
-const CollapsibleSection = __webpack_require__(9);
-const ComponentPerfTimer = __webpack_require__(10);
+/* 16 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_content_src_components_Card_Card__ = __webpack_require__(17);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react_intl__ = __webpack_require__(2);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react_intl___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_react_intl__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_content_src_components_CollapsibleSection_CollapsibleSection__ = __webpack_require__(7);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_content_src_components_ComponentPerfTimer_ComponentPerfTimer__ = __webpack_require__(8);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5_react_redux__ = __webpack_require__(3);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5_react_redux___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5_react_redux__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_react__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_6_react__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_content_src_components_Topics_Topics__ = __webpack_require__(18);
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+
+
+
+
+
+
+
+
const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange";
const CARDS_PER_ROW = 3;
function getFormattedMessage(message) {
- return typeof message === "string" ? React.createElement(
+ return typeof message === "string" ? __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
"span",
null,
message
- ) : React.createElement(FormattedMessage, message);
+ ) : __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_1_react_intl__["FormattedMessage"], message);
}
-class Section extends React.PureComponent {
+class Section extends __WEBPACK_IMPORTED_MODULE_6_react___default.a.PureComponent {
_dispatchImpressionStats() {
const { props } = this;
const maxCards = 3 * props.maxRows;
const cards = props.rows.slice(0, maxCards);
if (this.needsImpressionStats(cards)) {
- props.dispatch(ac.ImpressionStats({
+ props.dispatch(__WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm__["actionCreators"].ImpressionStats({
source: props.eventSource,
tiles: cards.map(link => ({ id: link.guid }))
}));
this.impressionCardGuids = cards.map(link => link.guid);
}
}
// This sends an event when a user sees a set of new content. If content
@@ -3027,105 +3543,147 @@ class Section extends React.PureComponen
const placeholders = this.numberOfPlaceholders(realRows.length);
// The empty state should only be shown after we have initialized and there is no content.
// Otherwise, we should show placeholders.
const shouldShowEmptyState = initialized && !rows.length;
// <Section> <-- React component
// <section> <-- HTML5 element
- return React.createElement(
- ComponentPerfTimer,
+ return __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
+ __WEBPACK_IMPORTED_MODULE_4_content_src_components_ComponentPerfTimer_ComponentPerfTimer__["a" /* ComponentPerfTimer */],
this.props,
- React.createElement(
- CollapsibleSection,
+ __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
+ __WEBPACK_IMPORTED_MODULE_3_content_src_components_CollapsibleSection_CollapsibleSection__["a" /* CollapsibleSection */],
{ className: "section", icon: icon, title: getFormattedMessage(title),
infoOption: infoOption,
id: id,
eventSource: eventSource,
disclaimer: disclaimer,
prefName: `section.${id}.collapsed`,
Prefs: this.props.Prefs,
dispatch: this.props.dispatch },
- !shouldShowEmptyState && React.createElement(
+ !shouldShowEmptyState && __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
"ul",
{ className: "section-list", style: { padding: 0 } },
- realRows.map((link, index) => link && React.createElement(Card, { key: index, index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions,
+ realRows.map((link, index) => link && __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_0_content_src_components_Card_Card__["a" /* Card */], { key: index, index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions,
eventSource: eventSource, shouldSendImpressionStats: this.props.shouldSendImpressionStats })),
- placeholders > 0 && [...new Array(placeholders)].map((_, i) => React.createElement(PlaceholderCard, { key: i }))
+ placeholders > 0 && [...new Array(placeholders)].map((_, i) => __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_0_content_src_components_Card_Card__["b" /* PlaceholderCard */], { key: i }))
),
- shouldShowEmptyState && React.createElement(
+ shouldShowEmptyState && __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
"div",
{ className: "section-empty-state" },
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
"div",
{ className: "empty-state" },
- emptyState.icon && emptyState.icon.startsWith("moz-extension://") ? React.createElement("img", { className: "empty-state-icon icon", style: { "background-image": `url('${emptyState.icon}')` } }) : React.createElement("img", { className: `empty-state-icon icon icon-${emptyState.icon}` }),
- React.createElement(
+ emptyState.icon && emptyState.icon.startsWith("moz-extension://") ? __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement("img", { className: "empty-state-icon icon", style: { "background-image": `url('${emptyState.icon}')` } }) : __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement("img", { className: `empty-state-icon icon icon-${emptyState.icon}` }),
+ __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
"p",
{ className: "empty-state-message" },
getFormattedMessage(emptyState.message)
)
)
),
- shouldShowTopics && React.createElement(Topics, { topics: this.props.topics, read_more_endpoint: this.props.read_more_endpoint })
+ shouldShowTopics && __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_7_content_src_components_Topics_Topics__["a" /* Topics */], { topics: this.props.topics, read_more_endpoint: this.props.read_more_endpoint })
)
);
}
}
+/* unused harmony export Section */
+
Section.defaultProps = {
document: global.document,
rows: [],
emptyState: {},
title: ""
};
-const SectionIntl = injectIntl(Section);
-
-class Sections extends React.PureComponent {
+const SectionIntl = Object(__WEBPACK_IMPORTED_MODULE_1_react_intl__["injectIntl"])(Section);
+/* unused harmony export SectionIntl */
+
+
+class _Sections extends __WEBPACK_IMPORTED_MODULE_6_react___default.a.PureComponent {
render() {
const sections = this.props.Sections;
- return React.createElement(
+ return __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
"div",
{ className: "sections-list" },
- sections.filter(section => section.enabled).map(section => React.createElement(SectionIntl, _extends({ key: section.id }, section, { Prefs: this.props.Prefs, dispatch: this.props.dispatch })))
+ sections.filter(section => section.enabled).map(section => __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(SectionIntl, _extends({ key: section.id }, section, { Prefs: this.props.Prefs, dispatch: this.props.dispatch })))
);
}
}
-
-module.exports = connect(state => ({ Sections: state.Sections, Prefs: state.Prefs }))(Sections);
-module.exports._unconnected = Sections;
-module.exports.SectionIntl = SectionIntl;
-module.exports._unconnectedSection = Section;
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
+/* unused harmony export _Sections */
+
+
+const Sections = Object(__WEBPACK_IMPORTED_MODULE_5_react_redux__["connect"])(state => ({ Sections: state.Sections, Prefs: state.Prefs }))(_Sections);
+/* harmony export (immutable) */ __webpack_exports__["a"] = Sections;
+
+/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
/***/ }),
-/* 27 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const LinkMenu = __webpack_require__(8);
-const { FormattedMessage } = __webpack_require__(2);
-const cardContextTypes = __webpack_require__(28);
-const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
+/* 17 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+
+// EXTERNAL MODULE: ./system-addon/common/Actions.jsm
+var Actions = __webpack_require__(0);
+var Actions_default = /*#__PURE__*/__webpack_require__.n(Actions);
+
+// CONCATENATED MODULE: ./system-addon/content-src/components/Card/types.js
+const cardContextTypes = {
+ history: {
+ intlID: "type_label_visited",
+ icon: "historyItem"
+ },
+ bookmark: {
+ intlID: "type_label_bookmarked",
+ icon: "bookmark-added"
+ },
+ trending: {
+ intlID: "type_label_recommended",
+ icon: "trending"
+ },
+ now: {
+ intlID: "type_label_now",
+ icon: "now"
+ }
+};
+// EXTERNAL MODULE: external "ReactIntl"
+var external__ReactIntl_ = __webpack_require__(2);
+var external__ReactIntl__default = /*#__PURE__*/__webpack_require__.n(external__ReactIntl_);
+
+// EXTERNAL MODULE: ./system-addon/content-src/components/LinkMenu/LinkMenu.jsx + 2 modules
+var LinkMenu = __webpack_require__(6);
+
+// EXTERNAL MODULE: external "React"
+var external__React_ = __webpack_require__(1);
+var external__React__default = /*#__PURE__*/__webpack_require__.n(external__React_);
+
+// CONCATENATED MODULE: ./system-addon/content-src/components/Card/Card.jsx
+
+
+
+
+
// Keep track of pending image loads to only request once
const gImageLoading = new Map();
/**
* Card component.
* Cards are found within a Section component and contain information about a link such
* as preview image, page title, page description, and some context about if the page
* was visited, bookmarked, trending etc...
* Each Section can make an unordered list of Cards which will create one instane of
* 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.PureComponent {
+class Card_Card extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
this.state = {
activeCard: null,
imageLoaded: false,
showContextMenu: false
};
this.onMenuButtonClick = this.onMenuButtonClick.bind(this);
@@ -3169,27 +3727,27 @@ class Card extends React.PureComponent {
this.setState({
activeCard: this.props.index,
showContextMenu: true
});
}
onLinkClick(event) {
event.preventDefault();
const { altKey, button, ctrlKey, metaKey, shiftKey } = event;
- this.props.dispatch(ac.SendToMain({
- type: at.OPEN_LINK,
+ this.props.dispatch(Actions["actionCreators"].SendToMain({
+ type: Actions["actionTypes"].OPEN_LINK,
data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } })
}));
- this.props.dispatch(ac.UserEvent({
+ this.props.dispatch(Actions["actionCreators"].UserEvent({
event: "CLICK",
source: this.props.eventSource,
action_position: this.props.index
}));
if (this.props.shouldSendImpressionStats) {
- this.props.dispatch(ac.ImpressionStats({
+ this.props.dispatch(Actions["actionCreators"].ImpressionStats({
source: this.props.eventSource,
click: 0,
tiles: [{ id: this.props.link.guid, pos: this.props.index }]
}));
}
}
onMenuUpdate(showContextMenu) {
this.setState({ showContextMenu });
@@ -3210,280 +3768,261 @@ class Card extends React.PureComponent {
const { index, link, dispatch, contextMenuOptions, eventSource, shouldSendImpressionStats } = this.props;
const { props } = this;
const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
// Display "now" as "trending" until we have new strings #3402
const { icon, intlID } = cardContextTypes[link.type === "now" ? "trending" : link.type] || {};
const hasImage = link.image || link.hasImage;
const imageStyle = { backgroundImage: link.image ? `url(${link.image})` : "none" };
- return React.createElement(
+ return external__React__default.a.createElement(
"li",
{ className: `card-outer${isContextMenuOpen ? " active" : ""}${props.placeholder ? " placeholder" : ""}` },
- React.createElement(
+ external__React__default.a.createElement(
"a",
{ href: link.url, onClick: !props.placeholder && this.onLinkClick },
- React.createElement(
+ external__React__default.a.createElement(
"div",
{ className: "card" },
- hasImage && React.createElement(
+ hasImage && external__React__default.a.createElement(
"div",
{ className: "card-preview-image-outer" },
- React.createElement("div", { className: `card-preview-image${this.state.imageLoaded ? " loaded" : ""}`, style: imageStyle })
+ external__React__default.a.createElement("div", { className: `card-preview-image${this.state.imageLoaded ? " loaded" : ""}`, style: imageStyle })
),
- React.createElement(
+ external__React__default.a.createElement(
"div",
{ className: `card-details${hasImage ? "" : " no-image"}` },
- link.hostname && React.createElement(
+ link.hostname && external__React__default.a.createElement(
"div",
{ className: "card-host-name" },
link.hostname
),
- React.createElement(
+ external__React__default.a.createElement(
"div",
{ className: ["card-text", icon ? "" : "no-context", link.description ? "" : "no-description", link.hostname ? "" : "no-host-name", hasImage ? "" : "no-image"].join(" ") },
- React.createElement(
+ external__React__default.a.createElement(
"h4",
{ className: "card-title", dir: "auto" },
link.title
),
- React.createElement(
+ external__React__default.a.createElement(
"p",
{ className: "card-description", dir: "auto" },
link.description
)
),
- React.createElement(
+ external__React__default.a.createElement(
"div",
{ className: "card-context" },
- icon && !link.context && React.createElement("span", { className: `card-context-icon icon icon-${icon}` }),
- link.icon && link.context && React.createElement("span", { className: "card-context-icon icon", style: { backgroundImage: `url('${link.icon}')` } }),
- intlID && !link.context && React.createElement(
+ icon && !link.context && external__React__default.a.createElement("span", { className: `card-context-icon icon icon-${icon}` }),
+ link.icon && link.context && external__React__default.a.createElement("span", { className: "card-context-icon icon", style: { backgroundImage: `url('${link.icon}')` } }),
+ intlID && !link.context && external__React__default.a.createElement(
"div",
{ className: "card-context-label" },
- React.createElement(FormattedMessage, { id: intlID, defaultMessage: "Visited" })
+ external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: intlID, defaultMessage: "Visited" })
),
- link.context && React.createElement(
+ link.context && external__React__default.a.createElement(
"div",
{ className: "card-context-label" },
link.context
)
)
)
)
),
- !props.placeholder && React.createElement(
+ !props.placeholder && external__React__default.a.createElement(
"button",
{ className: "context-menu-button icon",
onClick: this.onMenuButtonClick },
- React.createElement(
+ external__React__default.a.createElement(
"span",
{ className: "sr-only" },
`Open context menu for ${link.title}`
)
),
- !props.placeholder && React.createElement(LinkMenu, {
+ !props.placeholder && external__React__default.a.createElement(LinkMenu["a" /* LinkMenu */], {
dispatch: dispatch,
index: index,
source: eventSource,
onUpdate: this.onMenuUpdate,
options: link.contextMenuOptions || contextMenuOptions,
site: link,
visible: isContextMenuOpen,
shouldSendImpressionStats: shouldSendImpressionStats })
);
}
}
-Card.defaultProps = { link: {} };
-
-const PlaceholderCard = () => React.createElement(Card, { placeholder: true });
-
-module.exports = Card;
-module.exports.PlaceholderCard = PlaceholderCard;
+/* harmony export (immutable) */ __webpack_exports__["a"] = Card_Card;
+
+Card_Card.defaultProps = { link: {} };
+
+const PlaceholderCard = () => external__React__default.a.createElement(Card_Card, { placeholder: true });
+/* harmony export (immutable) */ __webpack_exports__["b"] = PlaceholderCard;
+
/***/ }),
-/* 28 */
-/***/ (function(module, exports) {
-
-module.exports = {
- history: {
- intlID: "type_label_visited",
- icon: "historyItem"
- },
- bookmark: {
- intlID: "type_label_bookmarked",
- icon: "bookmark-added"
- },
- trending: {
- intlID: "type_label_recommended",
- icon: "trending"
- },
- now: {
- intlID: "type_label_now",
- icon: "now"
- }
-};
-
-/***/ }),
-/* 29 */
-/***/ (function(module, exports, __webpack_require__) {
-
-const React = __webpack_require__(1);
-const { FormattedMessage } = __webpack_require__(2);
-
-class Topic extends React.PureComponent {
+/* 18 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react_intl__ = __webpack_require__(2);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react_intl___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_react_intl__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_react__);
+
+
+
+class Topic extends __WEBPACK_IMPORTED_MODULE_1_react___default.a.PureComponent {
render() {
const { url, name } = this.props;
- return React.createElement(
+ return __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(
"li",
null,
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(
"a",
{ key: name, className: "topic-link", href: url },
name
)
);
}
}
-
-class Topics extends React.PureComponent {
+/* unused harmony export Topic */
+
+
+class Topics extends __WEBPACK_IMPORTED_MODULE_1_react___default.a.PureComponent {
render() {
const { topics, read_more_endpoint } = this.props;
- return React.createElement(
+ return __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(
"div",
{ className: "topic" },
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(
"span",
null,
- React.createElement(FormattedMessage, { id: "pocket_read_more" })
+ __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_0_react_intl__["FormattedMessage"], { id: "pocket_read_more" })
),
- React.createElement(
+ __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(
"ul",
null,
- topics && topics.map(t => React.createElement(Topic, { key: t.name, url: t.url, name: t.name }))
+ topics && topics.map(t => __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(Topic, { key: t.name, url: t.url, name: t.name }))
),
- read_more_endpoint && React.createElement(
+ read_more_endpoint && __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(
"a",
{ className: "topic-read-more", href: read_more_endpoint },
- React.createElement(FormattedMessage, { id: "pocket_read_even_more" })
+ __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_0_react_intl__["FormattedMessage"], { id: "pocket_read_even_more" })
)
);
}
}
-
-module.exports = Topics;
-module.exports._unconnected = Topics;
-module.exports.Topic = Topic;
+/* harmony export (immutable) */ __webpack_exports__["a"] = Topics;
+
/***/ }),
-/* 30 */
-/***/ (function(module, exports) {
-
-class _PrerenderData {
- constructor(options) {
- this.initialPrefs = options.initialPrefs;
- this.initialSections = options.initialSections;
- this._setValidation(options.validation);
- }
-
- get validation() {
- return this._validation;
- }
-
- set validation(value) {
- this._setValidation(value);
- }
-
- get invalidatingPrefs() {
- return this._invalidatingPrefs;
+/* 19 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__ = __webpack_require__(9);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__);
+
+
+
+const VISIBLE = "visible";
+const VISIBILITY_CHANGE_EVENT = "visibilitychange";
+
+class DetectUserSessionStart {
+ constructor(store, options = {}) {
+ this._store = store;
+ // Overrides for testing
+ this.document = options.document || global.document;
+ this._perfService = options.perfService || __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__["perfService"];
+ this._onVisibilityChange = this._onVisibilityChange.bind(this);
}
- // This is needed so we can use it in the constructor
- _setValidation(value = []) {
- this._validation = value;
- this._invalidatingPrefs = value.reduce((result, next) => {
- if (typeof next === "string") {
- result.push(next);
- return result;
- } else if (next && next.oneOf) {
- return result.concat(next.oneOf);
- }
- throw new Error("Your validation configuration is not properly configured");
- }, []);
+ /**
+ * sendEventOrAddListener - Notify immediately if the page is already visible,
+ * or else set up a listener for when visibility changes.
+ * This is needed for accurate session tracking for telemetry,
+ * because tabs are pre-loaded.
+ */
+ sendEventOrAddListener() {
+ if (this.document.visibilityState === VISIBLE) {
+ // If the document is already visible, to the user, send a notification
+ // immediately that a session has started.
+ this._sendEvent();
+ } else {
+ // If the document is not visible, listen for when it does become visible.
+ this.document.addEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
+ }
}
- arePrefsValid(getPref) {
- for (const prefs of this.validation) {
- // {oneOf: ["foo", "bar"]}
- if (prefs && prefs.oneOf && !prefs.oneOf.some(name => getPref(name) === this.initialPrefs[name])) {
- return false;
-
- // "foo"
- } else if (getPref(prefs) !== this.initialPrefs[prefs]) {
- return false;
- }
+ /**
+ * _sendEvent - Sends a message to the main process to indicate the current
+ * tab is now visible to the user, includes the
+ * visibility_event_rcvd_ts time in ms from the UNIX epoch.
+ */
+ _sendEvent() {
+ this._perfService.mark("visibility_event_rcvd_ts");
+
+ try {
+ let visibility_event_rcvd_ts = this._perfService.getMostRecentAbsMarkStartByName("visibility_event_rcvd_ts");
+
+ this._store.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({
+ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].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.
}
- return true;
+ }
+
+ /**
+ * _onVisibilityChange - If the visibility has changed to visible, sends a notification
+ * and removes the event listener. This should only be called once per tab.
+ */
+ _onVisibilityChange() {
+ if (this.document.visibilityState === VISIBLE) {
+ this._sendEvent();
+ this.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
+ }
}
}
-
-var PrerenderData = new _PrerenderData({
- initialPrefs: {
- "migrationExpired": true,
- "showTopSites": true,
- "showSearch": true,
- "topSitesCount": 6,
- "collapseTopSites": false,
- "section.highlights.collapsed": false,
- "section.topstories.collapsed": false,
- "feeds.section.topstories": true,
- "feeds.section.highlights": true
- },
- // Prefs listed as invalidating will prevent the prerendered version
- // of AS from being used if their value is something other than what is listed
- // here. This is required because some preferences cause the page layout to be
- // too different for the prerendered version to be used. Unfortunately, this
- // will result in users who have modified some of their preferences not being
- // able to get the benefits of prerendering.
- validation: ["showTopSites", "showSearch", "collapseTopSites", "section.highlights.collapsed", "section.topstories.collapsed",
- // This means if either of these are set to their default values,
- // prerendering can be used.
- { oneOf: ["feeds.section.topstories", "feeds.section.highlights"] }],
- initialSections: [{
- enabled: true,
- icon: "pocket",
- id: "topstories",
- order: 1,
- title: { id: "header_recommended_by", values: { provider: "Pocket" } }
- }, {
- enabled: true,
- id: "highlights",
- icon: "highlights",
- order: 2,
- title: { id: "header_highlights" }
- }]
-});
-module.exports = {
- PrerenderData,
- _PrerenderData
-};
+/* harmony export (immutable) */ __webpack_exports__["a"] = DetectUserSessionStart;
+
+/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
/***/ }),
-/* 31 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(global) {/* eslint-env mozilla/frame-script */
-
-const { createStore, combineReducers, applyMiddleware } = __webpack_require__(32);
-const { actionTypes: at, actionCreators: ac, actionUtils: au } = __webpack_require__(0);
+/* 20 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (immutable) */ __webpack_exports__["a"] = initStore;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_redux__ = __webpack_require__(21);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_redux___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_redux__);
+/* eslint-env mozilla/frame-script */
+
+
+
const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
+/* unused harmony export MERGE_STORE_ACTION */
+
const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
+/* unused harmony export OUTGOING_MESSAGE_NAME */
+
const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
-const EARLY_QUEUED_ACTIONS = [at.SAVE_SESSION_PERF_DATA, at.PAGE_PRERENDERED];
+/* unused harmony export INCOMING_MESSAGE_NAME */
+
+const EARLY_QUEUED_ACTIONS = [__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SAVE_SESSION_PERF_DATA, __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].PAGE_PRERENDERED];
+/* unused harmony export EARLY_QUEUED_ACTIONS */
+
/**
* A higher-order function which returns a reducer that, on MERGE_STORE action,
* will return the action.data object merged into the previous state.
*
* For all other actions, it merely calls mainReducer.
*
* Because we want this to merge the entire state object, it's written as a
@@ -3505,562 +4044,120 @@ function mergeStateReducer(mainReducer)
return mainReducer(prevState, action);
};
}
/**
* messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
*/
const messageMiddleware = store => next => action => {
- if (au.isSendToMain(action)) {
+ if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionUtils"].isSendToMain(action)) {
sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
}
next(action);
};
const rehydrationMiddleware = store => next => action => {
if (store._didRehydrate) {
return next(action);
}
const isMergeStoreAction = action.type === MERGE_STORE_ACTION;
- const isRehydrationRequest = action.type === at.NEW_TAB_STATE_REQUEST;
+ const isRehydrationRequest = action.type === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].NEW_TAB_STATE_REQUEST;
if (isRehydrationRequest) {
store._didRequestInitialState = true;
return next(action);
}
if (isMergeStoreAction) {
store._didRehydrate = true;
return next(action);
}
// If init happened after our request was made, we need to re-request
- if (store._didRequestInitialState && action.type === at.INIT) {
- return next(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+ if (store._didRequestInitialState && action.type === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].INIT) {
+ return next(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].NEW_TAB_STATE_REQUEST }));
}
- if (au.isBroadcastToContent(action) || au.isSendToContent(action)) {
+ if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionUtils"].isBroadcastToContent(action) || __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionUtils"].isSendToContent(action)) {
// Note that actions received before didRehydrate will not be dispatched
// because this could negatively affect preloading and the the state
// will be replaced by rehydration anyway.
return null;
}
return next(action);
};
+/* unused harmony export rehydrationMiddleware */
+
/**
* This middleware queues up all the EARLY_QUEUED_ACTIONS until it receives
* the first action from main. This is useful for those actions for main which
* require higher reliability, i.e. the action will not be lost in the case
* that it gets sent before the main is ready to receive it. Conversely, any
* actions allowed early are accepted to be ignorable or re-sendable.
*/
const queueEarlyMessageMiddleware = store => next => action => {
if (store._receivedFromMain) {
next(action);
- } else if (au.isFromMain(action)) {
+ } else if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionUtils"].isFromMain(action)) {
next(action);
store._receivedFromMain = true;
// Sending out all the early actions as main is ready now
if (store._earlyActionQueue) {
store._earlyActionQueue.forEach(next);
store._earlyActionQueue = [];
}
} else if (EARLY_QUEUED_ACTIONS.includes(action.type)) {
store._earlyActionQueue = store._earlyActionQueue || [];
store._earlyActionQueue.push(action);
} else {
// Let any other type of action go through
next(action);
}
};
+/* unused harmony export queueEarlyMessageMiddleware */
+
/**
* initStore - Create a store and listen for incoming actions
*
* @param {object} reducers An object containing Redux reducers
* @param {object} intialState (optional) The initial state of the store, if desired
* @return {object} A redux store
*/
-module.exports = function initStore(reducers, initialState) {
- const store = createStore(mergeStateReducer(combineReducers(reducers)), initialState, global.addMessageListener && applyMiddleware(rehydrationMiddleware, queueEarlyMessageMiddleware, messageMiddleware));
+function initStore(reducers, initialState) {
+ const store = Object(__WEBPACK_IMPORTED_MODULE_1_redux__["createStore"])(mergeStateReducer(Object(__WEBPACK_IMPORTED_MODULE_1_redux__["combineReducers"])(reducers)), initialState, global.addMessageListener && Object(__WEBPACK_IMPORTED_MODULE_1_redux__["applyMiddleware"])(rehydrationMiddleware, queueEarlyMessageMiddleware, messageMiddleware));
store._didRehydrate = false;
store._didRequestInitialState = false;
if (global.addMessageListener) {
global.addMessageListener(INCOMING_MESSAGE_NAME, msg => {
try {
store.dispatch(msg.data);
} catch (ex) {
console.error("Content msg:", msg, "Dispatch error: ", ex); // eslint-disable-line no-console
dump(`Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${ex.stack}`);
}
});
}
return store;
-};
-
-module.exports.rehydrationMiddleware = rehydrationMiddleware;
-module.exports.queueEarlyMessageMiddleware = queueEarlyMessageMiddleware;
-module.exports.MERGE_STORE_ACTION = MERGE_STORE_ACTION;
-module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME;
-module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
-module.exports.EARLY_QUEUED_ACTIONS = EARLY_QUEUED_ACTIONS;
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
+}
+/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
/***/ }),
-/* 32 */
+/* 21 */
/***/ (function(module, exports) {
module.exports = Redux;
/***/ }),
-/* 33 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(global) {const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
-const { perfService: perfSvc } = __webpack_require__(11);
-
-const VISIBLE = "visible";
-const VISIBILITY_CHANGE_EVENT = "visibilitychange";
-
-module.exports = class DetectUserSessionStart {
- constructor(store, options = {}) {
- this._store = store;
- // Overrides for testing
- this.document = options.document || global.document;
- this._perfService = options.perfService || perfSvc;
- this._onVisibilityChange = this._onVisibilityChange.bind(this);
- }
-
- /**
- * sendEventOrAddListener - Notify immediately if the page is already visible,
- * or else set up a listener for when visibility changes.
- * This is needed for accurate session tracking for telemetry,
- * because tabs are pre-loaded.
- */
- sendEventOrAddListener() {
- if (this.document.visibilityState === VISIBLE) {
- // If the document is already visible, to the user, send a notification
- // immediately that a session has started.
- this._sendEvent();
- } else {
- // If the document is not visible, listen for when it does become visible.
- this.document.addEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
- }
- }
-
- /**
- * _sendEvent - Sends a message to the main process to indicate the current
- * tab is now visible to the user, includes the
- * visibility_event_rcvd_ts time in ms from the UNIX epoch.
- */
- _sendEvent() {
- this._perfService.mark("visibility_event_rcvd_ts");
-
- try {
- let visibility_event_rcvd_ts = this._perfService.getMostRecentAbsMarkStartByName("visibility_event_rcvd_ts");
-
- this._store.dispatch(ac.SendToMain({
- 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.
- }
- }
-
- /**
- * _onVisibilityChange - If the visibility has changed to visible, sends a notification
- * and removes the event listener. This should only be called once per tab.
- */
- _onVisibilityChange() {
- if (this.document.visibilityState === VISIBLE) {
- this._sendEvent();
- this.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
- }
- }
-};
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
-
-/***/ }),
-/* 34 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(global) {const DATABASE_NAME = "snippets_db";
-const DATABASE_VERSION = 1;
-const SNIPPETS_OBJECTSTORE_NAME = "snippets";
-const SNIPPETS_UPDATE_INTERVAL_MS = 14400000; // 4 hours.
-
-const SNIPPETS_ENABLED_EVENT = "Snippets:Enabled";
-const SNIPPETS_DISABLED_EVENT = "Snippets:Disabled";
-
-const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
-
-/**
- * SnippetsMap - A utility for cacheing values related to the snippet. It has
- * the same interface as a Map, but is optionally backed by
- * indexedDB for persistent storage.
- * Call .connect() to open a database connection and restore any
- * previously cached data, if necessary.
- *
- */
-class SnippetsMap extends Map {
- constructor(dispatch) {
- super();
- this._db = null;
- this._dispatch = dispatch;
- }
-
- set(key, value) {
- super.set(key, value);
- return this._dbTransaction(db => db.put(value, key));
- }
-
- delete(key) {
- super.delete(key);
- return this._dbTransaction(db => db.delete(key));
- }
-
- clear() {
- super.clear();
- return this._dbTransaction(db => db.clear());
- }
-
- get blockList() {
- return this.get("blockList") || [];
- }
-
- /**
- * blockSnippetById - Blocks a snippet given an id
- *
- * @param {str|int} id The id of the snippet
- * @return {Promise} Resolves when the id has been written to indexedDB,
- * or immediately if the snippetMap is not connected
- */
- async blockSnippetById(id) {
- if (!id) {
- return;
- }
- let blockList = this.blockList;
- if (!blockList.includes(id)) {
- blockList.push(id);
- this._dispatch(ac.SendToMain({ type: at.SNIPPETS_BLOCKLIST_UPDATED, data: blockList }));
- await this.set("blockList", blockList);
- }
- }
-
- disableOnboarding() {
- this._dispatch(ac.SendToMain({ type: at.DISABLE_ONBOARDING }));
- }
-
- showFirefoxAccounts() {
- this._dispatch(ac.SendToMain({ type: at.SHOW_FIREFOX_ACCOUNTS }));
- }
-
- /**
- * connect - Attaches an indexedDB back-end to the Map so that any set values
- * are also cached in a store. It also restores any existing values
- * that are already stored in the indexedDB store.
- *
- * @return {type} description
- */
- async connect() {
- // Open the connection
- const db = await this._openDB();
-
- // Restore any existing values
- await this._restoreFromDb(db);
-
- // Attach a reference to the db
- this._db = db;
- }
-
- /**
- * _dbTransaction - Returns a db transaction wrapped with the given modifier
- * function as a Promise. If the db has not been connected,
- * it resolves immediately.
- *
- * @param {func} modifier A function to call with the transaction
- * @return {obj} A Promise that resolves when the transaction has
- * completed or errored
- */
- _dbTransaction(modifier) {
- if (!this._db) {
- return Promise.resolve();
- }
- return new Promise((resolve, reject) => {
- const transaction = modifier(this._db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite").objectStore(SNIPPETS_OBJECTSTORE_NAME));
- transaction.onsuccess = event => resolve();
-
- /* istanbul ignore next */
- transaction.onerror = event => reject(transaction.error);
- });
- }
-
- _openDB() {
- return new Promise((resolve, reject) => {
- const openRequest = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
-
- /* istanbul ignore next */
- openRequest.onerror = event => {
- // Try to delete the old database so that we can start this process over
- // next time.
- indexedDB.deleteDatabase(DATABASE_NAME);
- reject(event);
- };
-
- openRequest.onupgradeneeded = event => {
- const db = event.target.result;
- if (!db.objectStoreNames.contains(SNIPPETS_OBJECTSTORE_NAME)) {
- db.createObjectStore(SNIPPETS_OBJECTSTORE_NAME);
- }
- };
-
- openRequest.onsuccess = event => {
- let db = event.target.result;
-
- /* istanbul ignore next */
- db.onerror = err => console.error(err); // eslint-disable-line no-console
- /* istanbul ignore next */
- db.onversionchange = versionChangeEvent => versionChangeEvent.target.close();
-
- resolve(db);
- };
- });
- }
-
- _restoreFromDb(db) {
- return new Promise((resolve, reject) => {
- let cursorRequest;
- try {
- cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME).objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor();
- } catch (err) {
- // istanbul ignore next
- reject(err);
- // istanbul ignore next
- return;
- }
-
- /* istanbul ignore next */
- cursorRequest.onerror = event => reject(event);
-
- cursorRequest.onsuccess = event => {
- let cursor = event.target.result;
- // Populate the cache from the persistent storage.
- if (cursor) {
- this.set(cursor.key, cursor.value);
- cursor.continue();
- } else {
- // We are done.
- resolve();
- }
- };
- });
- }
-}
-
-/**
- * SnippetsProvider - Initializes a SnippetsMap and loads snippets from a
- * remote location, or else default snippets if the remote
- * snippets cannot be retrieved.
- */
-class SnippetsProvider {
- constructor(dispatch) {
- // Initialize the Snippets Map and attaches it to a global so that
- // the snippet payload can interact with it.
- global.gSnippetsMap = new SnippetsMap(dispatch);
- this._onAction = this._onAction.bind(this);
- }
-
- get snippetsMap() {
- return global.gSnippetsMap;
- }
-
- async _refreshSnippets() {
- // 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.appData.version) {
- this.snippetsMap.clear();
- }
-
- // Has enough time passed for us to require an update?
- const lastUpdate = this.snippetsMap.get("snippets-last-update");
- const needsUpdate = !(lastUpdate >= 0) || Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
-
- if (needsUpdate && this.appData.snippetsURL) {
- this.snippetsMap.set("snippets-last-update", Date.now());
- try {
- 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.appData.version);
- }
- } catch (e) {
- console.error(e); // eslint-disable-line no-console
- }
- }
- }
-
- _noSnippetFallback() {
- // TODO
- }
-
- _forceOnboardingVisibility(shouldBeVisible) {
- const onboardingEl = document.getElementById("onboarding-notification-bar");
-
- if (onboardingEl) {
- onboardingEl.style.display = shouldBeVisible ? "" : "none";
- }
- }
-
- _showRemoteSnippets() {
- const snippetsEl = document.getElementById(this.elementId);
- const payload = this.snippetsMap.get("snippets");
-
- if (!snippetsEl) {
- throw new Error(`No element was found with id '${this.elementId}'.`);
- }
-
- // This could happen if fetching failed
- if (!payload) {
- throw new Error("No remote snippets were found in gSnippetsMap.");
- }
-
- if (typeof payload !== "string") {
- throw new Error("Snippet payload was incorrectly formatted");
- }
-
- // Note that injecting snippets can throw if they're invalid XML.
- // eslint-disable-next-line no-unsanitized/property
- snippetsEl.innerHTML = payload;
-
- // Scripts injected by innerHTML are inactive, so we have to relocate them
- // through DOM manipulation to activate their contents.
- for (const scriptEl of snippetsEl.getElementsByTagName("script")) {
- const relocatedScript = document.createElement("script");
- relocatedScript.text = scriptEl.text;
- scriptEl.parentNode.replaceChild(relocatedScript, scriptEl);
- }
- }
-
- _onAction(msg) {
- if (msg.data.type === at.SNIPPET_BLOCKED) {
- this.snippetsMap.set("blockList", msg.data.data);
- document.getElementById("snippets-container").style.display = "none";
- }
- }
-
- /**
- * init - Fetch the snippet payload and show snippets
- *
- * @param {obj} options
- * @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 {bool} options.connect Should gSnippetsMap connect to indexedDB?
- */
- async init(options) {
- Object.assign(this, {
- appData: {},
- elementId: "snippets",
- connect: true
- }, options);
-
- // Add listener so we know when snippets are blocked on other pages
- if (global.addMessageListener) {
- global.addMessageListener("ActivityStream:MainToContent", this._onAction);
- }
-
- // TODO: Requires enabling indexedDB on newtab
- // Restore the snippets map from indexedDB
- if (this.connect) {
- try {
- await this.snippetsMap.connect();
- } catch (e) {
- console.error(e); // eslint-disable-line no-console
- }
- }
-
- // 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();
-
- // Try showing remote snippets, falling back to defaults if necessary.
- try {
- this._showRemoteSnippets();
- } catch (e) {
- this._noSnippetFallback(e);
- }
-
- window.dispatchEvent(new Event(SNIPPETS_ENABLED_EVENT));
-
- this._forceOnboardingVisibility(true);
- this.initialized = true;
- }
-
- uninit() {
- window.dispatchEvent(new Event(SNIPPETS_DISABLED_EVENT));
- this._forceOnboardingVisibility(false);
- if (global.removeMessageListener) {
- global.removeMessageListener("ActivityStream:MainToContent", this._onAction);
- }
- this.initialized = false;
- }
-}
-
-/**
- * addSnippetsSubscriber - Creates a SnippetsProvider that Initializes
- * when the store has received the appropriate
- * Snippet data.
- *
- * @param {obj} store The redux store
- * @return {obj} Returns the snippets instance and unsubscribe function
- */
-function addSnippetsSubscriber(store) {
- const snippets = new SnippetsProvider(store.dispatch);
-
- let initializing = false;
-
- store.subscribe(async () => {
- const state = store.getState();
- // state.Prefs.values["feeds.snippets"]: Should snippets be shown?
- // state.Snippets.initialized Is the snippets data initialized?
- // snippets.initialized: Is SnippetsProvider currently initialised?
- if (state.Prefs.values["feeds.snippets"] && !state.Prefs.values.disableSnippets && state.Snippets.initialized && !snippets.initialized &&
- // Don't call init multiple times
- !initializing) {
- initializing = true;
- await snippets.init({ appData: state.Snippets });
- initializing = false;
- } else if ((state.Prefs.values["feeds.snippets"] === false || state.Prefs.values.disableSnippets === true) && snippets.initialized) {
- snippets.uninit();
- }
- });
-
- // These values are returned for testing purposes
- return snippets;
-}
-
-module.exports = {
- addSnippetsSubscriber,
- SnippetsMap,
- SnippetsProvider,
- SNIPPETS_UPDATE_INTERVAL_MS
-};
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
+/* 22 */
+/***/ (function(module, exports) {
+
+module.exports = ReactDOM;
/***/ })
/******/ ]);
\ No newline at end of file