/* 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/. */ import { CardGrid } from "content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid"; import { CollectionCardGrid } from "content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid"; import { CollapsibleSection } from "content-src/components/CollapsibleSection/CollapsibleSection"; import { connect } from "react-redux"; import { DSMessage } from "content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage"; import { DSPrivacyModal } from "content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal"; import { DSSignup } from "content-src/components/DiscoveryStreamComponents/DSSignup/DSSignup"; import { DSTextPromo } from "content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo"; import { Highlights } from "content-src/components/DiscoveryStreamComponents/Highlights/Highlights"; import { HorizontalRule } from "content-src/components/DiscoveryStreamComponents/HorizontalRule/HorizontalRule"; import { Navigation } from "content-src/components/DiscoveryStreamComponents/Navigation/Navigation"; import { PrivacyLink } from "content-src/components/DiscoveryStreamComponents/PrivacyLink/PrivacyLink"; import React from "react"; import { SectionTitle } from "content-src/components/DiscoveryStreamComponents/SectionTitle/SectionTitle"; import { selectLayoutRender } from "content-src/lib/selectLayoutRender"; import { TopSites } from "content-src/components/TopSites/TopSites"; const ALLOWED_CSS_URL_PREFIXES = [ "chrome://", "resource://", "https://img-getpocket.cdn.mozilla.net/", ]; const DUMMY_CSS_SELECTOR = "DUMMY#CSS.SELECTOR"; /** * Validate a CSS declaration. The values are assumed to be normalized by CSSOM. */ export function isAllowedCSS(property, value) { // Bug 1454823: INTERNAL properties, e.g., -moz-context-properties, are // exposed but their values aren't resulting in getting nothing. Fortunately, // we don't care about validating the values of the current set of properties. if (value === undefined) { return true; } // Make sure all urls are of the allowed protocols/prefixes const urls = value.match(/url\("[^"]+"\)/g); return ( !urls || urls.every(url => ALLOWED_CSS_URL_PREFIXES.some(prefix => url.slice(5).startsWith(prefix)) ) ); } export class _DiscoveryStreamBase extends React.PureComponent { constructor(props) { super(props); this.onStyleMount = this.onStyleMount.bind(this); } onStyleMount(style) { // Unmounting style gets rid of old styles, so nothing else to do if (!style) { return; } const { sheet } = style; const styles = JSON.parse(style.dataset.styles); styles.forEach((row, rowIndex) => { row.forEach((component, componentIndex) => { // Nothing to do without optional styles overrides if (!component) { return; } Object.entries(component).forEach(([selectors, declarations]) => { // Start with a dummy rule to validate declarations and selectors sheet.insertRule(`${DUMMY_CSS_SELECTOR} {}`); const [rule] = sheet.cssRules; // Validate declarations and remove any offenders. CSSOM silently // discards invalid entries, so here we apply extra restrictions. rule.style = declarations; [...rule.style].forEach(property => { const value = rule.style[property]; if (!isAllowedCSS(property, value)) { console.error(`Bad CSS declaration ${property}: ${value}`); rule.style.removeProperty(property); } }); // Set the actual desired selectors scoped to the component const prefix = `.ds-layout > .ds-column:nth-child(${ rowIndex + 1 }) .ds-column-grid > :nth-child(${componentIndex + 1})`; // NB: Splitting on "," doesn't work with strings with commas, but // we're okay with not supporting those selectors rule.selectorText = selectors .split(",") .map( selector => prefix + // Assume :pseudo-classes are for component instead of descendant (selector[0] === ":" ? "" : " ") + selector ) .join(","); // CSSOM silently ignores bad selectors, so we'll be noisy instead if (rule.selectorText === DUMMY_CSS_SELECTOR) { console.error(`Bad CSS selector ${selectors}`); } }); }); }); } renderComponent(component, embedWidth) { switch (component.type) { case "Highlights": return ; case "TopSites": return (
); case "TextPromo": return ( ); case "Signup": return ( ); case "Message": return ( ); case "SectionTitle": return ; case "Navigation": return ( ); case "CollectionCardGrid": const { DiscoveryStream } = this.props; return ( ); case "CardGrid": return ( ); case "HorizontalRule": return ; case "PrivacyLink": return ; default: return
{component.type}
; } } renderStyles(styles) { // Use json string as both the key and styles to render so React knows when // to unmount and mount a new instance for new styles. const json = JSON.stringify(styles); return