diff --git a/devtools/shim/aboutdevtools/aboutdevtools.css b/devtools/shim/aboutdevtools/aboutdevtools.css
index ece9874d489d..60a0eb8919b1 100644
--- a/devtools/shim/aboutdevtools/aboutdevtools.css
+++ b/devtools/shim/aboutdevtools/aboutdevtools.css
@@ -1,3 +1,7 @@
+/* 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/. */
+
:root {
/* Photon color variables used on the aboutdevtools page */
--blue-60: #0060df;
@@ -8,7 +12,14 @@
--grey-90-alpha-10: rgba(12, 12, 13, 0.1);
--grey-90-alpha-20: rgba(12, 12, 13, 0.2);
--grey-90-alpha-30: rgba(12, 12, 13, 0.3);
+ --grey-90-alpha-40: rgba(12, 12, 13, 0.4);
+ --grey-90-alpha-50: rgba(12, 12, 13, 0.5);
+ --teal-60: #00c8d7;
+ --red-50: #ff0039;
--white: #ffffff;
+
+ /* Shared variables */
+ --line-height: 1.5em;
}
html, body {
@@ -17,16 +28,15 @@ html, body {
}
p {
- line-height: 1.5em;
+ line-height: var(--line-height);
}
.box {
width: 100%;
max-width: 850px;
display: flex;
- align-items: center;
- height: 400px;
flex-shrink: 0;
+ padding: 34px 0 50px 0;
}
.wrapper {
@@ -38,19 +48,16 @@ p {
}
.left-pane {
- width: 360px;
- height: 100%;
+ width: 300px;
+ height: 300px;
+ margin-inline-end: 20px;
background-image: url(images/otter.svg);
- background-size: 80%;
+ background-size: 100%;
background-position: 50%;
background-repeat: no-repeat;
flex-shrink: 0;
}
-.right-pane {
- height: 250px;
-}
-
.features {
max-width: 980px;
border-top: 1px solid var(--grey-30);
@@ -129,7 +136,7 @@ p {
}
button {
- margin: 2em 0 0 0;
+ margin: 20px 0 0 0;
padding: 10px 20px;
border: none;
diff --git a/devtools/shim/aboutdevtools/aboutdevtools.xhtml b/devtools/shim/aboutdevtools/aboutdevtools.xhtml
index e49c1f866bd0..f3982f114d86 100644
--- a/devtools/shim/aboutdevtools/aboutdevtools.xhtml
+++ b/devtools/shim/aboutdevtools/aboutdevtools.xhtml
@@ -14,7 +14,9 @@
a
+
+
@@ -48,6 +50,33 @@
&aboutDevtools.welcome.title;
&aboutDevtools.welcome.message;
+
+
+
+
&aboutDevtools.newsletter.title;
+
&aboutDevtools.newsletter.message;
+
+
+
+
&aboutDevtools.newsletter.thanks.title;
+
&aboutDevtools.newsletter.thanks.message;
+
+
diff --git a/devtools/shim/aboutdevtools/subscribe.css b/devtools/shim/aboutdevtools/subscribe.css
new file mode 100644
index 000000000000..3fba8f39668c
--- /dev/null
+++ b/devtools/shim/aboutdevtools/subscribe.css
@@ -0,0 +1,100 @@
+/* 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/. */
+
+/**
+ * This file contains the styles for the newsletter subscription form on about:devtools.
+ * It is largely inspired from https://mozilla.github.io/basket-example/
+ */
+
+#newsletter-errors {
+ /* Hidden by default */
+ display: none;
+
+ margin-bottom: 20px;
+ padding: 10px;
+ border-radius: 2px;
+
+ background-color: var(--red-50);
+ color: var(--white);
+}
+
+#newsletter-errors.show {
+ display: block;
+}
+
+#newsletter-errors .error {
+ margin: 0;
+ margin-bottom: 10px;
+}
+
+#newsletter-errors .error:last-child {
+ margin-bottom: 0;
+}
+
+#newsletter-thanks {
+ /* Hidden by default */
+ display: none;
+}
+
+#newsletter-thanks.show {
+ display: block;
+}
+
+.newsletter-form-section {
+ display: block;
+ margin-bottom: 20px;
+ width: 320px;
+}
+
+#newsletter-privacy {
+ display: flex;
+
+ /* The privacy section is hidden by default and only displayed on focus */
+ height: 0;
+ margin-bottom: -20px;
+ overflow: hidden;
+}
+
+#newsletter-privacy.animate {
+ transition: all 0.25s cubic-bezier(.15,.75,.35,.9);
+}
+
+#newsletter-privacy label {
+ line-height: var(--line-height);
+}
+
+#privacy {
+ width: 20px;
+ height: 20px;
+ margin: 2px;
+ margin-inline-end: 10px;
+ flex-shrink: 0;
+}
+
+#email {
+ width: 100%;
+ box-sizing: border-box;
+ padding: 12px 15px;
+}
+
+#newsletter-form input {
+ border-color: var(--grey-90-alpha-30);
+}
+
+#newsletter-form input:hover {
+ border-color: var(--grey-90-alpha-50);
+}
+
+#newsletter-form input:focus {
+ border-color: var(--teal-60);
+ box-shadow: 0 0 2px 0 var(--teal-60);
+}
+
+#newsletter-form::placeholder {
+ color: var(--grey-90-alpha-40);
+}
+
+#newsletter-submit {
+ display: block;
+}
diff --git a/devtools/shim/aboutdevtools/subscribe.js b/devtools/shim/aboutdevtools/subscribe.js
new file mode 100644
index 000000000000..53928eeffc1f
--- /dev/null
+++ b/devtools/shim/aboutdevtools/subscribe.js
@@ -0,0 +1,147 @@
+/* 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/. */
+
+"use strict";
+
+/**
+ * This file handles the newsletter subscription form on about:devtools.
+ * It is largely inspired from https://mozilla.github.io/basket-example/
+ */
+
+window.addEventListener("load", function () {
+ const { utils: Cu } = Components;
+ const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+ // Timeout for the subscribe XHR.
+ const REQUEST_TIMEOUT = 5000;
+
+ const ABOUTDEVTOOLS_STRINGS = "chrome://devtools-shim/locale/aboutdevtools.properties";
+ const aboutDevtoolsBundle = Services.strings.createBundle(ABOUTDEVTOOLS_STRINGS);
+
+ let emailInput = document.getElementById("email");
+ let newsletterErrors = document.getElementById("newsletter-errors");
+ let newsletterForm = document.getElementById("newsletter-form");
+ let newsletterPrivacySection = document.getElementById("newsletter-privacy");
+ let newsletterThanks = document.getElementById("newsletter-thanks");
+
+ /**
+ * Update the error panel to display the provided errors. If the argument is null or
+ * empty, a default error message will be displayed.
+ *
+ * @param {Array} errors
+ * Array of strings, each item being an error message to display.
+ */
+ function updateErrorPanel(errors) {
+ clearErrorPanel();
+
+ if (!errors || errors.length == 0) {
+ errors = [aboutDevtoolsBundle.GetStringFromName("newsletter.error.unknown")];
+ }
+
+ // Create errors markup.
+ let fragment = document.createDocumentFragment();
+ for (let error of errors) {
+ let item = document.createElement("p");
+ item.classList.add("error");
+ item.appendChild(document.createTextNode(error));
+ fragment.appendChild(item);
+ }
+
+ newsletterErrors.appendChild(fragment);
+ newsletterErrors.classList.add("show");
+ }
+
+ /**
+ * Hide the error panel and remove all errors.
+ */
+ function clearErrorPanel() {
+ newsletterErrors.classList.remove("show");
+ newsletterErrors.innerHTML = "";
+ }
+
+ // Show the additional form fields on focus of the email input.
+ function onEmailInputFocus() {
+ // Create a hidden measuring container, append it to the parent of the privacy section
+ let container = document.createElement("div");
+ container.style.cssText = "visibility: hidden; overflow: hidden; position: absolute";
+ newsletterPrivacySection.parentNode.appendChild(container);
+
+ // Clone the privacy section, append the clone to the measuring container.
+ let clone = newsletterPrivacySection.cloneNode(true);
+ container.appendChild(clone);
+
+ // Measure the target height of the privacy section.
+ clone.style.height = "auto";
+ let height = clone.offsetHeight;
+
+ // Cleanup the measuring container.
+ container.remove();
+
+ // Set the animate class and set the height to the measured height.
+ newsletterPrivacySection.classList.add("animate");
+ newsletterPrivacySection.style.cssText = `height: ${height}px; margin-bottom: 0;`;
+ }
+
+ // XHR subscribe; handle errors; display thanks message on success.
+ function onFormSubmit(evt) {
+ evt.preventDefault();
+ evt.stopPropagation();
+
+ // New submission, clear old errors
+ clearErrorPanel();
+
+ let xhr = new XMLHttpRequest();
+
+ xhr.onload = function (r) {
+ if (r.target.status >= 200 && r.target.status < 300) {
+ let {response} = r.target;
+
+ if (response.success === true) {
+ // Hide form and show success message.
+ newsletterForm.style.display = "none";
+ newsletterThanks.classList.add("show");
+ } else {
+ // We trust the error messages from the service to be meaningful for the user.
+ updateErrorPanel(response.errors);
+ }
+ } else {
+ let {status, statusText} = r.target;
+ let statusInfo = `${status} - ${statusText}`;
+ let error = aboutDevtoolsBundle
+ .formatStringFromName("newsletter.error.common", [statusInfo], 1);
+ updateErrorPanel([error]);
+ }
+ };
+
+ xhr.onerror = () => {
+ updateErrorPanel();
+ };
+
+ xhr.ontimeout = () => {
+ let error = aboutDevtoolsBundle.GetStringFromName("newsletter.error.timeout");
+ updateErrorPanel([error]);
+ };
+
+ let url = newsletterForm.getAttribute("action");
+
+ xhr.open("POST", url, true);
+ xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.timeout = REQUEST_TIMEOUT;
+ xhr.responseType = "json";
+
+ // Create form data.
+ let formData = new FormData(newsletterForm);
+ formData.append("source_url", document.location.href);
+
+ let params = new URLSearchParams(formData);
+
+ // Send the request.
+ xhr.send(params.toString());
+ }
+
+ // Attach event listeners.
+ newsletterForm.addEventListener("submit", onFormSubmit);
+ emailInput.addEventListener("focus", onEmailInputFocus);
+}, { once: true });
diff --git a/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd b/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd
index c0d1a144e3e0..8daf0b03e299 100644
--- a/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd
+++ b/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd
@@ -22,4 +22,14 @@
-
\ No newline at end of file
+
+
+
+
+
+Privacy Policy.">
+
+
+
+
+
diff --git a/devtools/shim/jar.mn b/devtools/shim/jar.mn
index 77da69e68de1..3affffcd940b 100644
--- a/devtools/shim/jar.mn
+++ b/devtools/shim/jar.mn
@@ -7,6 +7,8 @@ devtools-shim.jar:
content/aboutdevtools/aboutdevtools.xhtml (aboutdevtools/aboutdevtools.xhtml)
content/aboutdevtools/aboutdevtools.css (aboutdevtools/aboutdevtools.css)
content/aboutdevtools/aboutdevtools.js (aboutdevtools/aboutdevtools.js)
+ content/aboutdevtools/subscribe.css (aboutdevtools/subscribe.css)
+ content/aboutdevtools/subscribe.js (aboutdevtools/subscribe.js)
content/aboutdevtools/images/otter.svg (aboutdevtools/images/otter.svg)
diff --git a/devtools/shim/locales/en-US/aboutdevtools.properties b/devtools/shim/locales/en-US/aboutdevtools.properties
index 8e33e25cc1fb..cb0cc505df84 100644
--- a/devtools/shim/locales/en-US/aboutdevtools.properties
+++ b/devtools/shim/locales/en-US/aboutdevtools.properties
@@ -35,3 +35,16 @@ features.performance.desc=Unblock bottlenecks, streamline processes, optimize as
features.memory.title=Memory
features.memory.desc=Find memory leaks and make your application zippy.
+
+# LOCALIZATION NOTE (newsletter.error.common): error text displayed when the newsletter
+# subscription failed. The argument will be replaced with request's status code and status
+# text (e.g. "404 - Not Found")
+newsletter.error.common=Subscription request failed (%S).
+
+# LOCALIZATION NOTE (newsletter.error.unknown): error text displayed when the newsletter
+# subscription failed for an unexpected reason.
+newsletter.error.unknown=An unexpected error occurred.
+
+# LOCALIZATION NOTE (newsletter.error.timeout): error text displayed when the newsletter
+# subscription timed out.
+newsletter.error.timeout=Subscription request timed out.