Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

Commit

Permalink
feat(preferences): Add preferences to about:preferences when it loads (
Browse files Browse the repository at this point in the history
…#4015)

Fix Bug 1404890 - Move new tab settings for sections to about:preferences

Bug 1445090 - These strings are probably unused: pocket_description, settings_pane_topstories_options_sponsored, settings_pane_highlights_body2
  • Loading branch information
Mardak committed Mar 14, 2018
1 parent 61fe4cf commit d301c0e
Show file tree
Hide file tree
Showing 6 changed files with 512 additions and 5 deletions.
238 changes: 238 additions & 0 deletions system-addon/lib/AboutPreferences.jsm
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/* 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";

Cu.importGlobalProperties(["fetch"]);
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
const {actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});

const PREFERENCES_LOADED_EVENT = "sync-pane-loaded";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

// These "section" objects are formatted in a way to be similar to the ones from
// SectionsManager to construct the preferences view.
const PREFS_BEFORE_SECTIONS = [
{
id: "search",
pref: {
feed: "showSearch",
titleString: "prefs_search_header"
},
icon: "chrome://browser/skin/search-glass.svg"
},
{
id: "topsites",
pref: {
feed: "showTopSites",
titleString: "settings_pane_topsites_header",
descString: "prefs_topsites_description"
},
icon: "topsites",
maxRows: 2,
rowsPref: "topSitesRows"
}
];
const PREFS_AFTER_SECTIONS = [
{
id: "snippets",
pref: {
feed: "feeds.snippets",
titleString: "settings_pane_snippets_header",
descString: "prefs_snippets_description"
},
icon: "info"
}
];

// This CSS is added to the whole about:preferences page
const CUSTOM_CSS = `
#homeContentsGroup checkbox[src] .checkbox-icon {
margin-inline-end: 8px;
margin-inline-start: 4px;
width: 16px;
}
`;

this.AboutPreferences = class AboutPreferences {
init() {
Services.obs.addObserver(this, PREFERENCES_LOADED_EVENT);
}

uninit() {
Services.obs.removeObserver(this, PREFERENCES_LOADED_EVENT);
}

onAction(action) {
switch (action.type) {
case at.INIT:
this.init();
break;
case at.UNINIT:
this.uninit();
break;
}
}

async observe(window) {
this.renderPreferences(window, await this.strings, [...PREFS_BEFORE_SECTIONS,
...this.store.getState().Sections, ...PREFS_AFTER_SECTIONS]);
}

/**
* Get strings from a js file that the content page would have loaded. The
* file should be a single variable assignment of a JSON/JS object of strings.
*/
get strings() {
return this._strings || (this._strings = new Promise(async resolve => {
let data = {};
try {
const locale = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
.getService(Ci.nsIAboutNewTabService).activityStreamLocale;
const request = await fetch(`resource://activity-stream/prerendered/${locale}/activity-stream-strings.js`);
const text = await request.text();
const [json] = text.match(/{[^]*}/);
data = JSON.parse(json);
} catch (ex) {
Cu.reportError("Failed to load strings for Activity Stream about:preferences");
}
resolve(data);
}));
}

/**
* Render preferences to an about:preferences content window with the provided
* strings and preferences structure.
*/
renderPreferences({document, Preferences}, strings, prefStructure) {
// Helper to create a new element and append it
const createAppend = (tag, parent) => parent.appendChild(
document.createElementNS(XUL_NS, tag));

// Helper to get strings and format with values if necessary
const formatString = id => {
if (typeof id !== "object") {
return strings[id] || id;
}
let string = strings[id.id] || JSON.stringify(id);
if (id.values) {
Object.entries(id.values).forEach(([key, val]) => {
string = string.replace(new RegExp(`{${key}}`, "g"), val);
});
}
return string;
};

// Helper to link a UI element to a preference for updating
const linkPref = (element, name, type) => {
const fullPref = `browser.newtabpage.activity-stream.${name}`;
element.setAttribute("preference", fullPref);
Preferences.add({id: fullPref, type});

// Prevent changing the UI if the preference can't be changed
element.disabled = Preferences.get(fullPref).locked;
};

// Add in custom styling
document.insertBefore(document.createProcessingInstruction("xml-stylesheet",
`href="data:text/css,${encodeURIComponent(CUSTOM_CSS)}" type="text/css"`),
document.documentElement);

// Insert a new group immediately after the homepage one
const homeGroup = document.getElementById("homepageGroup");
const contentsGroup = homeGroup.insertAdjacentElement("afterend", homeGroup.cloneNode());
contentsGroup.id = "homeContentsGroup";
contentsGroup.setAttribute("data-subcategory", "contents");
const caption = createAppend("caption", contentsGroup);
caption.setAttribute("label", formatString("prefs_home_header"));
const description = createAppend("description", contentsGroup);
description.textContent = formatString("prefs_home_description");

// Add preferences for each section
prefStructure.forEach(sectionData => {
const {
id,
pref: prefData,
icon = "webextension",
maxRows,
rowsPref,
shouldHidePref
} = sectionData;
const {
feed: name,
titleString,
descString,
nestedPrefs = []
} = prefData || {};

// Don't show any sections that we don't want to expose in preferences UI
if (shouldHidePref) {
return;
}

// Use full icon spec for certain protocols or fall back to packaged icon
const iconUrl = !icon.search(/^(chrome|moz-extension|resource):/) ? icon :
`resource://activity-stream/data/content/assets/glyph-${icon}-16.svg`;

// Add the main preference for turning on/off a section
const sectionVbox = createAppend("vbox", contentsGroup);
sectionVbox.setAttribute("data-subcategory", id);
const checkbox = createAppend("checkbox", sectionVbox);
checkbox.setAttribute("label", formatString(titleString));
checkbox.setAttribute("src", iconUrl);
linkPref(checkbox, name, "bool");

// Add more details for the section (e.g., description, more prefs)
const detailVbox = createAppend("vbox", sectionVbox);
detailVbox.classList.add("indent");
detailVbox.classList.add("tip-caption");
if (descString) {
const label = createAppend("label", detailVbox);
label.classList.add("indent");
label.textContent = formatString(descString);

// Add a rows dropdown if we have a pref to control and a maximum
if (rowsPref && maxRows) {
const detailHbox = createAppend("hbox", detailVbox);
detailHbox.setAttribute("align", "center");
label.setAttribute("flex", 1);
detailHbox.appendChild(label);

// Add appropriate number of localized entries to the dropdown
const menulist = createAppend("menulist", detailHbox);
for (let num = 1; num <= maxRows; num++) {
const plurals = formatString({id: "prefs_section_rows_option", values: {num}});
menulist.appendItem(PluralForm.get(num, plurals), num);
}
linkPref(menulist, rowsPref, "int");
}
}

// Add a checkbox pref for any nested preferences
nestedPrefs.forEach(nested => {
const subcheck = createAppend("checkbox", detailVbox);
subcheck.classList.add("indent");
subcheck.setAttribute("label", formatString(nested.titleString));
linkPref(subcheck, nested.name, "bool");

// Specially add a link for sponsored content
if (nested.name === "showSponsored") {
const sponsoredHbox = createAppend("hbox", detailVbox);
sponsoredHbox.setAttribute("align", "center");
sponsoredHbox.appendChild(subcheck);
subcheck.classList.add("tail-with-learn-more");

const link = createAppend("label", sponsoredHbox);
link.classList.add("learn-sponsored");
link.classList.add("text-link");
link.setAttribute("href", sectionData.disclaimer.link.href);
link.textContent = formatString("prefs_topstories_sponsored_learn_more");
}
});
});
}
};

this.PREFERENCES_LOADED_EVENT = PREFERENCES_LOADED_EVENT;
const EXPORTED_SYMBOLS = ["AboutPreferences", "PREFERENCES_LOADED_EVENT"];
9 changes: 7 additions & 2 deletions system-addon/lib/ActivityStream.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ChromeUtils.defineModuleGetter(this, "AppConstants",
// NB: Eagerly load modules that will be loaded/constructed/initialized in the
// common case to avoid the overhead of wrapping and detecting lazy loading.
const {actionCreators: ac, actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});
const {AboutPreferences} = ChromeUtils.import("resource://activity-stream/lib/AboutPreferences.jsm", {});
const {DefaultPrefs} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
const {ManualMigration} = ChromeUtils.import("resource://activity-stream/lib/ManualMigration.jsm", {});
const {NewTabInit} = ChromeUtils.import("resource://activity-stream/lib/NewTabInit.jsm", {});
Expand Down Expand Up @@ -54,13 +55,11 @@ const PREFS_CONFIG = new Map([
api_key_pref: "extensions.pocket.oAuthConsumerKey",
// Use the opposite value as what default value the feed would have used
hidden: !PREFS_CONFIG.get("feeds.section.topstories").getValue(args),
provider_description: "pocket_description",
provider_icon: "pocket",
provider_name: "Pocket",
read_more_endpoint: "https://getpocket.com/explore/trending?src=fx_new_tab",
stories_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=${args.locale}`,
stories_referrer: "https://getpocket.com/recommendations",
privacy_notice_link: "https://www.mozilla.org/privacy/firefox/#suggest-relevant-content",
disclaimer_link: "https://getpocket.com/firefox/new_tab_learn_more",
topics_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/trending-topics?version=2&consumer_key=$apiKey&locale_lang=${args.locale}`,
show_spocs: false,
Expand Down Expand Up @@ -157,6 +156,12 @@ const PREFS_CONFIG = new Map([

// Array of each feed's FEEDS_CONFIG factory and values to add to PREFS_CONFIG
const FEEDS_DATA = [
{
name: "aboutpreferences",
factory: () => new AboutPreferences(),
title: "about:preferences rendering",
value: true
},
{
name: "migration",
factory: () => new ManualMigration(),
Expand Down
6 changes: 3 additions & 3 deletions system-addon/lib/SectionsManager.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ const BUILT_IN_SECTIONS = {
id: "topstories",
pref: {
titleString: {id: "header_recommended_by", values: {provider: options.provider_name}},
descString: {id: options.provider_description || "pocket_description"},
descString: {id: options.provider_description || "prefs_topstories_description"},
nestedPrefs: options.show_spocs ? [{
name: "showSponsored",
titleString: {id: "settings_pane_topstories_options_sponsored"},
titleString: {id: "prefs_topstories_show_sponsored_label", values: {provider: options.provider_name}},
icon: "icon-info"
}] : []
},
Expand Down Expand Up @@ -54,7 +54,7 @@ const BUILT_IN_SECTIONS = {
id: "highlights",
pref: {
titleString: {id: "settings_pane_highlights_header"},
descString: {id: "settings_pane_highlights_body2"}
descString: {id: "prefs_highlights_description"}
},
shouldHidePref: false,
eventSource: "HIGHLIGHTS",
Expand Down
Loading

0 comments on commit d301c0e

Please sign in to comment.