/* 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/. */ // Based on the original onboarding code with deep changes from Ruben Rodriguez // Copyright (C) 2018 Ruben Rodriguez /* eslint-env mozilla/frame-script */ "use strict"; const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/Services.jsm"); const ABOUT_HOME_URL = "about:home"; const ABOUT_NEWTAB_URL = "about:newtab"; /** * @param {String} action the action to ask the chrome to do * @param {Array | Object} params the parameters for the action */ function sendMessageToChrome(action, params) { sendAsyncMessage("Onboarding:OnContentMessage", { action, params }); } /** * The script won't be initialized if we turned off onboarding by * setting "browser.onboarding.enabled" to false. */ class Onboarding { constructor(contentWindow) { this.init(contentWindow); this.keylist = { "javascript.enabled": { type: "boolean", name: "javascript.enabled", label: bundle.GetStringFromName("onboarding.disable-javascript.title"), description: bundle.GetStringFromName("onboarding.disable-javascript.description"), defaultvalue: true, onvalue: false, offvalue: true, }, "browser.display.use_document_fonts": { type: "integer", name: "browser.display.use_document_fonts", label: bundle.GetStringFromName("onboarding.custom-fonts.title"), description: bundle.GetStringFromName("onboarding.custom-fonts.description"), defaultvalue: 1, onvalue: 0, offvalue: 1, }, "browser.safebrowsing.provider.mozilla.updateURL": { type: "string", name: "browser.safebrowsing.provider.mozilla.updateURL", label: bundle.GetStringFromName("onboarding.tracking-protection.title"), description: bundle.GetStringFromName("onboarding.tracking-protection.description"), defaultvalue: "", onvalue: "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2", offvalue: "", }, "privacy.firstparty.isolate": { type: "boolean", name: "privacy.firstparty.isolate", label: bundle.GetStringFromName("onboarding.isolate-request-first-party.title"), description: bundle.GetStringFromName("onboarding.isolate-request-first-party.description"), defaultvalue: false, onvalue: true, offvalue: false, }, "extensions.update.enabled": { type: "boolean", name: "extensions.update.enabled", label: bundle.GetStringFromName("onboarding.auto-update-extensions.title"), description: bundle.GetStringFromName("onboarding.auto-update-extensions.description"), defaultvalue: false, onvalue: true, offvalue: false, }, "network.http.referer.spoofSource": { type: "boolean", name: "network.http.referer.spoofSource", label: bundle.GetStringFromName("onboarding.spoof-referers.title"), description: bundle.GetStringFromName("onboarding.spoof-referers.description"), defaultvalue: true, onvalue: true, offvalue: false, }, "captivedetect.canonicalURL": { type: "string", name: "captivedetect.canonicalURL", label: bundle.GetStringFromName("onboarding.detect-captative-portal.title"), description: bundle.GetStringFromName("onboarding.detect-captative-portal.description"), defaultvalue: "", onvalue: "http://detectportal.firefox.com/success.txt", offvalue: "", }, "browser.search.geoip.url": { type: "string", name: "browser.search.geoip.url", label: bundle.GetStringFromName("onboarding.geolocation.title"), description: bundle.GetStringFromName("onboarding.geolocation.description"), defaultvalue: "", onvalue: "https://location.services.mozilla.com/v1/country?key=%MOZILLA_API_KEY%", offvalue: "" }, "webgl.disabled": { type: "boolean", name: "webgl.disabled", label: bundle.GetStringFromName("onboarding.webgl.title"), description: bundle.GetStringFromName("onboarding.webgl.description"), defaultvalue: true, onvalue: false, offvalue: true } } } async init(contentWindow) { this._window = contentWindow; // Destroy on unloading. This is to ensure we remove all the stuff we left. // No any leak out there. this._window.addEventListener("unload", () => this.destroy()); this.uiInitialized = false; this._window.requestIdleCallback(() => this._initUI()); } getPref(type, key, def){ switch (type){ case "integer": return Services.prefs.getIntPref(key, def); break; case "string": return Services.prefs.getStringPref(key, def); break; case "boolean": return Services.prefs.getBoolPref(key, def); break; } } _callback(id, val){ this._window.document.getElementById(id).checked= (val==this.keylist[id].onvalue); } newcheckbox(kind, type, name, label, description, defaultvalue, onvalue, offvalue){ let content = this._window.document.createElement("div"); content.style="border-top: 1px solid #DDDDDD; padding-top:10px"; if (kind == "addon") sendMessageToChrome("check-addon", [{ name: name, }]); else Services.prefs.addObserver(name, () => {this._callback(name, this.getPref(type, name, defaultvalue));}); content.appendChild(this._window.document.createElement("input")); content.appendChild(this._window.document.createElement("label")); content.appendChild(this._window.document.createElement("p")); content.children[0].id=name; content.children[0].name=name; content.children[0].className=kind; content.children[0].type="checkbox"; content.children[0].style="position:relative; top:2px;"; content.children[0].checked=this.getPref(type, name, defaultvalue)==onvalue; content.children[0].addEventListener("click", this); content.children[0].addEventListener("keypress", this); content.children[1].for=name; content.children[1].innerHTML=label; content.children[1].style="font-size:15px"; if (description != ""){ content.children[2].innerHTML=description; content.children[2].style="padding-left:35px; font-size: small; color:#999; margin-top:5px"; } return content; } _initUI() { if (this.uiInitialized) { return; } this.uiInitialized = true; let { body } = this._window.document; let main = this._window.document.getElementsByTagName("main")[0]; let settingsblock = this._window.document.createElement("div"); settingsblock.style=`border:1px solid #D7D7DB; border-radius:3px; margin-bottom:40px; padding:0 15px 10px 15px; color:#4A4A4F; `; let title = this._window.document.createElement("div"); title.innerHTML=`

bundle.GetStringFromName("onboarding.privacy-settings.title")

bundle.GetStringFromName("These options allow you to tune important aspects of the browser's behavior so you can choose the balance between practicality and privacy that fits your needs. Changes to these settings apply to all existing tabs and windows (but you may need to reload them for the changes to show on them).")

`; main.insertBefore(settingsblock, main.childNodes[0]); settingsblock.appendChild(title); for ( var key in this.keylist){ key = this.keylist[key]; settingsblock.appendChild(this.newcheckbox("key", key.type, key.name, key.label, key.description, key.defaultvalue, key.onvalue, key.offvalue)); } settingsblock.appendChild(this.newcheckbox("addon", null, "uBlock0@raymondhill.net", "uBlock Origin", "Block ads and other intrusing trackers.")); settingsblock.appendChild(this.newcheckbox("addon", null, "jid1-KtlZuoiikVfFew@jetpack", "GNU LibreJs", "Block nonfree JavaScript.")); } _clearPrefObserver() { for ( var key in this.keylist){ key = this.keylist[key]; Services.prefs.removeObserver(key.name, () => {this._callbacktest(key.name, this.getPref(key.type, key.name, key.defaultvalue));}); } } handleClick(target) { switch (target.className){ case "addon": sendMessageToChrome("flip-addon", [{ name: target.name }]); return; break; case "key": let value if (target.checked) value = this.keylist[target.name].onvalue else value = this.keylist[target.name].offvalue if (target.name == "captivedetect.canonicalURL" && value != "") this._window.alert("You need to restart the browser to apply this change.\n\nWhen this feature is enabled the browser will automatically\ntry to connect to detectportal.firefox.com at the beginning of each browsing session."); sendMessageToChrome("set-prefs", [{ type: this.keylist[target.name].type, name: target.name, value: value }]); return; break } } /** * Wrap keyboard focus within the dialog. * When moving forward, focus on the first element when the current focused * element is the last one. * When moving backward, focus on the last element when the current focused * element is the first one. * Do nothing if focus is moving in the middle of the list of dialog's focusable * elements. * * @param {DOMNode} current currently focused element * @param {Boolean} back direction * @return {DOMNode} newly focused element if any */ wrapMoveFocus(current, back) { let elms = [...this._dialog.querySelectorAll( `button, input[type="checkbox"], input[type="email"], [tabindex="0"]`)]; let next; if (back) { if (elms.indexOf(current) === 0) { next = elms[elms.length - 1]; next.focus(); } } else if (elms.indexOf(current) === elms.length - 1) { next = elms[0]; next.focus(); } return next; } handleKeypress(event) { let { target, key, shiftKey } = event; if ([" ", "Enter"].includes(key)) { this.handleClick(target); event.preventDefault(); } return; } handleEvent(evt) { switch (evt.type) { case "keypress": this.handleKeypress(evt); break; case "click": this.handleClick(evt.target); break; default: break; } } destroy() { if (!this.uiInitialized) { return; } this.uiInitialized = false; this._clearPrefObserver(); } } addEventListener("load", function onLoad(evt) { if (!content || evt.target != content.document) { return; } var window = evt.target.defaultView; // Set the checked value of addons function handleMessageFromChrome(message) { if (! window) return; var payload = message.data; var cb = window.document.getElementById(payload.id); if (cb == null) return; cb.checked=payload.active; cb.parentNode.hidden = ! payload.installed; } function needsrestart(){ window.alert("This change will be applied when you restart the browser"); } addMessageListener("Onboarding:message-from-chrome", handleMessageFromChrome); addMessageListener("Onboarding:needsrestart", needsrestart); let location = window.location.href; if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) { // We just want to run tests as quickly as possible // so in the automation test, we don't do `requestIdleCallback`. if (Cu.isInAutomation) { new Onboarding(window); return; } window.requestIdleCallback(() => { new Onboarding(window); }); } }, true);