commit 2e6d58145ff2de2ac705f2feebe3d594eab382a8 Author: Arthur Edelstein arthuredelstein@gmail.com Date: Mon Sep 18 16:42:07 2017 -0700
Bug 23483: Donation banner on about:tor for 2017
(Also removes a dot from aboutTor.donationBanner.slogan) --- src/chrome/content/aboutTor/aboutTor-content.js | 5 ++ src/chrome/content/aboutTor/aboutTor.xhtml | 19 +++- src/chrome/content/aboutTor/donation_banner.js | 105 ++++++++++++++++++++++ src/chrome/content/aboutTor/onion-hand.png | Bin 0 -> 69055 bytes src/chrome/content/torbutton.js | 4 +- src/chrome/locale/en/aboutTor.properties | 2 +- src/chrome/skin/donation_banner.css | 113 ++++++++++++++++++++++++ src/modules/donation-banner.js | 112 +++++++++++++++++++++++ 8 files changed, 357 insertions(+), 3 deletions(-)
diff --git a/src/chrome/content/aboutTor/aboutTor-content.js b/src/chrome/content/aboutTor/aboutTor-content.js index ec515bb..95e8abd 100644 --- a/src/chrome/content/aboutTor/aboutTor-content.js +++ b/src/chrome/content/aboutTor/aboutTor-content.js @@ -105,6 +105,11 @@ var AboutTorListener = { else body.removeAttribute("showmanual");
+ if (aData.bannerData) + body.setAttribute("banner-data", aData.bannerData); + else + body.removeAttribute("banner-data"); + // Setting body.initialized="yes" displays the body, which must be done // at this point because our remaining initialization depends on elements // being visible so that their size and position are accurate. diff --git a/src/chrome/content/aboutTor/aboutTor.xhtml b/src/chrome/content/aboutTor/aboutTor.xhtml index 7ae4b8b..367f9a6 100644 --- a/src/chrome/content/aboutTor/aboutTor.xhtml +++ b/src/chrome/content/aboutTor/aboutTor.xhtml @@ -21,6 +21,8 @@ <title>&aboutTor.title;</title> <link rel="stylesheet" type="text/css" media="all" href="resource://torbutton/chrome/skin/aboutTor.css"/> + <link rel="stylesheet" type="text/css" media="all" + href="resource://torbutton/chrome/skin/donation_banner.css"/> <script type="text/javascript;version=1.7"> <![CDATA[ window.addEventListener("pageshow", function() { @@ -31,6 +33,21 @@ window.addEventListener("pageshow", function() { </script> </head> <body dir="&locale.dir;"> + <div id="banner"> + <div id="banner-contents-container"> + <div id="banner-tagline"><span></span></div> + <div id="banner-slogan"><span></span></div> + <a id="banner-donate-button-link" + href="https://www.torproject.org/donate/donate-tbb%22%3E + <div id="banner-donate-button"> + <div id="banner-donate-button-inner"> + <span></span> + </div> + </div> + </a> + </div> + </div> + <div id="banner-spacer"></div> <div id="torstatus" class="top"> <div id="torstatus-version"/> <div id="torstatus-image"/> @@ -112,6 +129,6 @@ window.addEventListener("pageshow", function() { <p>&aboutTor.footer.label; <a href="&aboutTor.learnMore.link;">&aboutTor.learnMore.label;</a></p> </div> - + <script src="resource://torbutton/chrome/content/aboutTor/donation_banner.js"></script> </body> </html> diff --git a/src/chrome/content/aboutTor/donation_banner.js b/src/chrome/content/aboutTor/donation_banner.js new file mode 100644 index 0000000..1c95822 --- /dev/null +++ b/src/chrome/content/aboutTor/donation_banner.js @@ -0,0 +1,105 @@ +/* jshint esnext:true */ + +let sel = selector => document.querySelector(selector); + +// Shrink the font size if the text in the given element is overflowing. +let fitTextInElement = function (element) { + element.style.fontSize = "8px"; + let defaultWidth = element.scrollWidth, + defaultHeight = element.scrollHeight; + let bestSize; + for (let testSize = 8; testSize <= 40; testSize += 0.5) { + element.style.fontSize = `${testSize}px`; + if (element.scrollWidth <= defaultWidth && + element.scrollHeight <= defaultHeight) { + bestSize = testSize; + } else { + break; + } + } + element.style.fontSize = `${bestSize}px`; +}; + +// Increase padding at end to "squeeze" text, until just before +// it gets squeezed so much that it gets longer vertically. +let avoidWidows = function (element) { + element.style.paddingRight = "0px"; + let originalWidth = element.scrollWidth; + let originalHeight = element.scrollHeight; + let bestPadding; + for (let testPadding = 0; testPadding < originalWidth; testPadding += 0.5) { + element.style.paddingRight = `${testPadding}px`; + if (element.scrollHeight <= originalHeight) { + bestPadding = testPadding; + } else { + break; + } + } + element.style.paddingRight = `${bestPadding}px`; + if (window.getComputedStyle(element).direction === "rtl") { + element.style.paddingLeft = element.style.paddingRight; + element.style.paddingRight = "0px"; + } +}; + +// Resize the text inside banner to fit. +let updateTextSizes = function () { + fitTextInElement(sel("#banner-tagline")); + fitTextInElement(sel("#banner-slogan")); + fitTextInElement(sel("#banner-donate-button-inner")); + avoidWidows(sel("#banner-tagline span")); +}; + +// Returns a random integer x, such that 0 <= x < max +let randomInteger = max => Math.floor(max * Math.random()); + +// The main donation banner function. +let runDonationBanner = function ({ taglines, slogan, donate, shortLocale }) { + try { + sel("#banner-tagline span").innerText = taglines[randomInteger(taglines.length)]; + sel("#banner-slogan span").innerText = slogan; + let donateButtonText = sel("#banner-donate-button-inner span"); + let rtl = window.getComputedStyle(donateButtonText).direction === "rtl"; + donateButtonText.innerHTML = donate + " " + (rtl ? "◀" : "▶"); + sel("#banner").style.display = "flex"; + sel("#banner-spacer").style.display = "block"; + addEventListener("resize", updateTextSizes); + updateTextSizes(); + // Add a suffix corresponding to locale so we can send user + // to a correctly-localized donation page via redirect. + sel("#banner-donate-button-link").href += "-" + shortLocale; + sel("#torstatus-image").style.display = "none"; + } catch (e) { + // Something went wrong. + console.error(e); + sel("#banner").style.display = "none"; + sel("#bannerSpacer").style.display = "none"; + sel("#torstatus-image").style.display = "block"; + } +}; + +// Calls callback(attributeValue) when the specified attribute changes on +// target. Returns a zero-arg function that stops observing. +let observeAttribute = function (target, attributeName, callback) { + let observer = new MutationObserver(mutations => { + mutations.forEach(mutation => { + if (mutation.type === "attributes" && + mutation.attributeName === attributeName) { + callback(target.getAttribute(attributeName)); + } + }); + }); + observer.observe(target, { attributes: true }); + return () => observer.disconnect(); +}; + +// Start the donation banner if "toron" has been set to "yes". +let stopObserving = observeAttribute(document.body, "toron", value => { + stopObserving(); + if (value === "yes") { + let bannerDataJSON = document.body.getAttribute("banner-data"); + if (bannerDataJSON && bannerDataJSON.length > 0) { + runDonationBanner(JSON.parse(bannerDataJSON)); + } + } +}); diff --git a/src/chrome/content/aboutTor/onion-hand.png b/src/chrome/content/aboutTor/onion-hand.png new file mode 100644 index 0000000..00a5a41 Binary files /dev/null and b/src/chrome/content/aboutTor/onion-hand.png differ diff --git a/src/chrome/content/torbutton.js b/src/chrome/content/torbutton.js index db6b694..955f8eb 100644 --- a/src/chrome/content/torbutton.js +++ b/src/chrome/content/torbutton.js @@ -13,6 +13,7 @@ let { showDialog } = Cu.import("resource://torbutton/modules/utils.js", {}); let { unescapeTorString } = Cu.import("resource://torbutton/modules/utils.js", {}); let SecurityPrefs = Cu.import("resource://torbutton/modules/security-prefs.js", {}); let { bindPrefAndInit, observe } = Cu.import("resource://torbutton/modules/utils.js", {}); +let { bannerData } = Cu.import("resource://torbutton/modules/donation-banner.js", {});
const k_tb_last_browser_version_pref = "extensions.torbutton.lastBrowserVersion"; const k_tb_browser_update_needed_pref = "extensions.torbutton.updateNeeded"; @@ -450,7 +451,8 @@ var torbutton_abouttor_message_handler = { torOn: torbutton_tor_check_ok(), updateNeeded: torbutton_update_is_needed(), showManual: torbutton_show_torbrowser_manual(), - toolbarButtonXPos: torbutton_get_toolbarbutton_xpos() + toolbarButtonXPos: torbutton_get_toolbarbutton_xpos(), + bannerData: bannerData(), }; },
diff --git a/src/chrome/locale/en/aboutTor.properties b/src/chrome/locale/en/aboutTor.properties index 4436e21..d0d3a64 100644 --- a/src/chrome/locale/en/aboutTor.properties +++ b/src/chrome/locale/en/aboutTor.properties @@ -10,7 +10,7 @@ aboutTor.searchDDG.search.link=https://duckduckgo.com/
aboutTor.donationBanner.donate=Donate Now!
-aboutTor.donationBanner.slogan=Tor: Powering Digital Resistance. +aboutTor.donationBanner.slogan=Tor: Powering Digital Resistance aboutTor.donationBanner.mozilla=Give today and Mozilla will match your gift!
aboutTor.donationBanner.tagline1=Protecting Journalists, Whistleblowers, & Activists Since 2006 diff --git a/src/chrome/skin/donation_banner.css b/src/chrome/skin/donation_banner.css new file mode 100644 index 0000000..8580066 --- /dev/null +++ b/src/chrome/skin/donation_banner.css @@ -0,0 +1,113 @@ +#banner { + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + display: none; + height: 150px; + justify-content: center; + left: 0px; + margin-top: 0px; + min-width: 900px; + opacity: 1; + position: absolute; + user-select: none; + width: 100%; + z-index: 1; +} +#banner:before { + background-color: #406; + background-image: url('resource://torbutton/chrome/content/aboutTor/onion-hand.png'); + background-position: center; + background-size: cover; + content: ""; + height: 150px; + left: 0px; + position: absolute; + top: 0px; + width: 100%; +} +#banner:-moz-dir(rtl):before { + transform: scaleX(-1); +} +#banner-contents-container { + align-items: center; + height: 100%; + max-width: 700px; + position: relative; + width: 700px; +} +#banner-tagline { + align-items: center; + bottom: 60px; + color: white; + display: flex; + font-family: monospace; + font-size: 8px; + font-weight: bold; + left: 85px; + position: absolute; + right: 0px; + text-align: start; + text-transform: uppercase; + top: 10px; +} +#banner-tagline:-moz-dir(rtl) { + left: 0px; + right: 85px; +} +#banner-slogan { + align-items: start; + bottom: 0px; + color: #f8f8a0; + display: flex; + font-family: monospace; + font-weight: bold; + left: 85px; + position: absolute; + right: 285px; + text-align: start; + top: 100px; + white-space: nowrap; +} +#banner-slogan:-moz-dir(rtl) { + left: 285px; + right: 85px; +} +#banner-donate-button { + background-color: #13a513; + border: 0px; + bottom: 10px; + color: #fbf7ef; + font-family: sans-serif; + font-size: 12px; + font-weight: bold; + left: 430px; + letter-spacing: -0.00em; + position: absolute; + right: 0px; + top: 100px; +} +#banner-donate-button:-moz-dir(rtl) { + left: 0px; + right: 430px; +} +#banner-donate-button:hover { + background-color: #38bc38; +} +#banner-donate-button-inner { + bottom: 6px; + display: flex; + justify-content: center; + left: 8px; + position: absolute; + right: 8px; + top: 6px; +} +#banner-spacer { + display: none; + height: 150px; + position: relative; + top: 0; +} diff --git a/src/modules/donation-banner.js b/src/modules/donation-banner.js new file mode 100644 index 0000000..bb35e86 --- /dev/null +++ b/src/modules/donation-banner.js @@ -0,0 +1,112 @@ +/* jshint esversion:6 */ + +const Cu = Components.utils; + +// ### Import Mozilla Services +Cu.import("resource://gre/modules/Services.jsm"); + +// A list of locales for which the banner has been translated. +const kBannerLocales = [ + "bg", + "da", + "el", + "en", + "es", + "fr", + "fr_CA", + "is", + "it", + "nb", + "tr", +]; + +// A list of donation page locales (at least redirects should exist). +const kDonationPageLocales = [ + "ar", + "de", + "en", + "es", + "fa", + "fr", + "it", + "ja", + "ko", + "nl", + "pl", + "pt", + "ru", + "tr", + "vi", + "zh", +]; + +const kPropertiesURL = "chrome://torbutton/locale/aboutTor.properties"; +const gStringBundle = Services.strings.createBundle(kPropertiesURL); + +// Check if we should show the banner, depends on +// browser locale, current date, and how many times +// we have already shown the banner. +const shouldShowBanner = function ({ locale, shortLocale }) { + try { + // If our override test pref is true, then just show the banner regardless. + if (Services.prefs.getBoolPref("extensions.torbutton.testBanner", false)) { + return true; + } + // Don't show a banner if update is needed. + let updateNeeded = Services.prefs.getBoolPref("extensions.torbutton.updateNeeded"); + if (updateNeeded) { + return false; + } + // Only show banner when we have that locale and if a donation redirect exists. + if (kBannerLocales.indexOf(locale) === -1 || + kDonationPageLocales.indexOf(shortLocale) === -1) { + return false; + } + // Only show banner between 2017 Oct 23 and 2018 Jan 25. + let now = new Date(); + let start = new Date(2017, 9, 23); + let end = new Date(2018, 0, 26); + let shownCountPref = "extensions.torbutton.donation_banner2017.shown_count"; + if (now < start || now > end) { + // Clean up pref if not in use. + Services.prefs.clearUserPref(shownCountPref); + return false; + } + // Only show banner 50 times. + let count = 0; + if (Services.prefs.prefHasUserValue(shownCountPref)) { + count = Services.prefs.getIntPref(shownCountPref); + } + if (count >= 50) { + return false; + } + Services.prefs.setIntPref(shownCountPref, count+1); + return true; + } catch (e) { + return false; + } +}; + +// Read data needed for displaying banner on page. +var bannerData = function () { + // Read short locale. + let locale = Services.prefs.getCharPref("general.useragent.locale"); + let shortLocale = locale.match(/[a-zA-Z]+/)[0].toLowerCase(); + if (!shouldShowBanner({ locale, shortLocale })) { + return null; + } + // Load tag lines. + let taglines = []; + for (let index = 0; index < 5; ++index) { + let tagline = gStringBundle.GetStringFromName( + "aboutTor.donationBanner.tagline" + (index + 1)); + taglines.push(tagline); + } + // Read slogan and donate button text. + let slogan = gStringBundle.GetStringFromName("aboutTor.donationBanner.slogan"); + let donate = gStringBundle.GetStringFromName("aboutTor.donationBanner.donate"); + return JSON.stringify({ taglines, slogan, donate, shortLocale }); +}; + +// Export utility functions for external use. +var EXPORTED_SYMBOLS = ["bannerData"];