AdBanner.jsx (5695B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import React, { useState } from "react"; 6 import { SafeAnchor } from "../SafeAnchor/SafeAnchor"; 7 import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats"; 8 import { actionCreators as ac } from "common/Actions.mjs"; 9 import { AdBannerContextMenu } from "../AdBannerContextMenu/AdBannerContextMenu"; 10 import { PromoCard } from "../PromoCard/PromoCard.jsx"; 11 12 const PREF_SECTIONS_ENABLED = "discoverystream.sections.enabled"; 13 const PREF_OHTTP_UNIFIED_ADS = "unifiedAds.ohttp.enabled"; 14 const PREF_REPORT_ADS_ENABLED = "discoverystream.reportAds.enabled"; 15 const PREF_PROMOCARD_ENABLED = "discoverystream.promoCard.enabled"; 16 const PREF_PROMOCARD_VISIBLE = "discoverystream.promoCard.visible"; 17 18 /** 19 * A new banner ad that appears between rows of stories: leaderboard or billboard size. 20 * 21 * @param spoc 22 * @param dispatch 23 * @param firstVisibleTimestamp 24 * @param row 25 * @param type 26 * @param prefs 27 * @returns {Element} 28 * @class 29 */ 30 export const AdBanner = ({ 31 spoc, 32 dispatch, 33 firstVisibleTimestamp, 34 row, 35 type, 36 prefs, 37 }) => { 38 const getDimensions = format => { 39 switch (format) { 40 case "leaderboard": 41 return { 42 width: "728", 43 height: "90", 44 }; 45 case "billboard": 46 return { 47 width: "970", 48 height: "250", 49 }; 50 } 51 return { 52 // image will still render with default values 53 width: undefined, 54 height: undefined, 55 }; 56 }; 57 const promoCardEnabled = 58 spoc.format === "billboard" && 59 prefs[PREF_PROMOCARD_ENABLED] && 60 prefs[PREF_PROMOCARD_VISIBLE]; 61 62 const sectionsEnabled = prefs[PREF_SECTIONS_ENABLED]; 63 const ohttpEnabled = prefs[PREF_OHTTP_UNIFIED_ADS]; 64 const showAdReporting = prefs[PREF_REPORT_ADS_ENABLED]; 65 const ohttpImagesEnabled = prefs.ohttpImagesConfig?.enabled; 66 const [menuActive, setMenuActive] = useState(false); 67 const adBannerWrapperClassName = `ad-banner-wrapper ${menuActive ? "active" : ""} ${promoCardEnabled ? "promo-card" : ""}`; 68 69 const { width: imgWidth, height: imgHeight } = getDimensions(spoc.format); 70 71 const onLinkClick = () => { 72 dispatch( 73 ac.DiscoveryStreamUserEvent({ 74 event: "CLICK", 75 source: type.toUpperCase(), 76 // Banner ads don't have a position, but a row number 77 action_position: parseInt(row, 10), 78 value: { 79 card_type: "spoc", 80 tile_id: spoc.id, 81 ...(spoc.shim?.click ? { shim: spoc.shim.click } : {}), 82 fetchTimestamp: spoc.fetchTimestamp, 83 firstVisibleTimestamp, 84 format: spoc.format, 85 ...(sectionsEnabled 86 ? { 87 section: spoc.format, 88 section_position: parseInt(row, 10), 89 } 90 : {}), 91 }, 92 }) 93 ); 94 }; 95 96 const toggleActive = active => { 97 setMenuActive(active); 98 }; 99 100 // in the default card grid 1 would come before the 1st row of cards and 9 comes after the last row 101 // using clamp to make sure its between valid values (1-9) 102 const clampedRow = Math.max(1, Math.min(9, row)); 103 104 const secureImage = ohttpImagesEnabled && ohttpEnabled; 105 106 let rawImageSrc = spoc.raw_image_src; 107 108 // Wraps the image URL with the moz-cached-ohttp:// protocol. 109 // This enables Firefox to load resources over Oblivious HTTP (OHTTP), 110 // providing privacy-preserving resource loading. 111 // Applied only when inferred personalization is enabled. 112 // See: https://firefox-source-docs.mozilla.org/browser/components/mozcachedohttp/docs/index.html 113 if (secureImage) { 114 rawImageSrc = `moz-cached-ohttp://newtab-image/?url=${encodeURIComponent(spoc.raw_image_src)}`; 115 } 116 117 return ( 118 <aside className={adBannerWrapperClassName} style={{ gridRow: clampedRow }}> 119 <div className={`ad-banner-inner ${spoc.format}`}> 120 <SafeAnchor 121 className="ad-banner-link" 122 url={spoc.url} 123 title={spoc.title || spoc.sponsor || spoc.alt_text} 124 onLinkClick={onLinkClick} 125 dispatch={dispatch} 126 isSponsored={true} 127 > 128 <ImpressionStats 129 flightId={spoc.flight_id} 130 rows={[ 131 { 132 id: spoc.id, 133 card_type: "spoc", 134 pos: row, 135 recommended_at: spoc.recommended_at, 136 received_rank: spoc.received_rank, 137 format: spoc.format, 138 ...(spoc.shim?.impression 139 ? { shim: spoc.shim.impression } 140 : {}), 141 }, 142 ]} 143 dispatch={dispatch} 144 firstVisibleTimestamp={firstVisibleTimestamp} 145 /> 146 <div className="ad-banner-content"> 147 <img 148 src={rawImageSrc} 149 alt={spoc.alt_text} 150 loading="eager" 151 width={imgWidth} 152 height={imgHeight} 153 /> 154 </div> 155 <div className="ad-banner-sponsored"> 156 <span 157 className="ad-banner-sponsored-label" 158 data-l10n-id="newtab-label-sponsored-fixed" 159 /> 160 </div> 161 </SafeAnchor> 162 <div className="ad-banner-hover-background"> 163 <AdBannerContextMenu 164 dispatch={dispatch} 165 spoc={spoc} 166 position={row} 167 type={type} 168 showAdReporting={showAdReporting} 169 toggleActive={toggleActive} 170 /> 171 </div> 172 </div> 173 {promoCardEnabled && <PromoCard />} 174 </aside> 175 ); 176 };