Bug 1407568 - Add a spotlight indicator to a specific section and UI component. r=jaws,lchang

MozReview-Commit-ID: 4AgAFq2r418

--HG--
extra : rebase_source : 055fb2881c1a91aff108fb4a32a7fff842443bc7
This commit is contained in:
Evan Tseng 2017-11-30 17:42:40 +08:00
Родитель 606f156e4a
Коммит 8663ce0fcb
8 изменённых файлов: 195 добавлений и 32 удалений

Просмотреть файл

@ -27,6 +27,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
"resource://gre/modules/ExtensionSettingsStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "formAutofillParent",
"resource://formautofill/FormAutofillParent.jsm");
var gLastHash = "";
@ -174,17 +176,19 @@ function gotoPref(aCategory) {
categories.clearSelection();
}
window.history.replaceState(category, document.title);
search(category, "data-category", subcategory, "data-subcategory");
search(category, "data-category");
let mainContent = document.querySelector(".main-content");
mainContent.scrollTop = 0;
spotlight(subcategory);
Services.telemetry
.getHistogramById("FX_PREFERENCES_CATEGORY_OPENED_V2")
.add(telemetryBucketForCategory(friendlyName));
}
function search(aQuery, aAttribute, aSubquery, aSubAttribute) {
function search(aQuery, aAttribute) {
let mainPrefPane = document.getElementById("mainPrefPane");
let elements = mainPrefPane.children;
for (let element of elements) {
@ -196,14 +200,7 @@ function search(aQuery, aAttribute, aSubquery, aSubAttribute) {
element.getAttribute("data-subpanel") == "true") {
let attributeValue = element.getAttribute(aAttribute);
if (attributeValue == aQuery) {
if (!element.classList.contains("header") &&
element.localName !== "preferences" &&
aSubquery && aSubAttribute) {
let subAttributeValue = element.getAttribute(aSubAttribute);
element.hidden = subAttributeValue != aSubquery;
} else {
element.hidden = false;
}
element.hidden = false;
} else {
element.hidden = true;
}
@ -221,6 +218,106 @@ function search(aQuery, aAttribute, aSubquery, aSubAttribute) {
}
}
async function spotlight(subcategory) {
let highlightedElements = document.querySelectorAll(".spotlight");
if (highlightedElements.length) {
for (let element of highlightedElements) {
element.classList.remove("spotlight");
}
}
if (subcategory) {
if (!gSearchResultsPane.categoriesInitialized) {
await waitForSystemAddonInjectionsFinished([{
isGoingToInject: formAutofillParent.initialized,
elementId: "formAutofillGroup",
}]);
}
scrollAndHighlight(subcategory);
}
/**
* Wait for system addons finished their dom injections.
* @param {Array} addons - The system addon information array.
* For example, the element is looked like
* { isGoingToInject: true, elementId: "formAutofillGroup" }.
* The `isGoingToInject` means the system addon will be visible or not,
* and the `elementId` means the id of the element will be injected into the dom
* if the `isGoingToInject` is true.
* @returns {Promise} Will resolve once all injections are finished.
*/
function waitForSystemAddonInjectionsFinished(addons) {
return new Promise(resolve => {
let elementIdSet = new Set();
for (let addon of addons) {
if (addon.isGoingToInject) {
elementIdSet.add(addon.elementId);
}
}
if (elementIdSet.size) {
let observer = new MutationObserver(mutations => {
for (let mutation of mutations) {
for (let node of mutation.addedNodes) {
elementIdSet.delete(node.id);
if (elementIdSet.size === 0) {
observer.disconnect();
resolve();
}
}
}
});
let mainContent = document.querySelector(".main-content");
observer.observe(mainContent, {childList: true, subtree: true});
// Disconnect the mutation observer once there is any user input.
mainContent.addEventListener("scroll", disconnectMutationObserver);
window.addEventListener("mousedown", disconnectMutationObserver);
window.addEventListener("keydown", disconnectMutationObserver);
function disconnectMutationObserver() {
mainContent.removeEventListener("scroll", disconnectMutationObserver);
window.removeEventListener("mousedown", disconnectMutationObserver);
window.removeEventListener("keydown", disconnectMutationObserver);
observer.disconnect();
}
} else {
resolve();
}
});
}
}
function scrollAndHighlight(subcategory) {
let element = document.querySelector(`[data-subcategory="${subcategory}"]`);
if (element) {
let header = getClosestDisplayedHeader(element);
scrollContentTo(header);
element.classList.add("spotlight");
}
}
/**
* If there is no visible second level header it will return first level header,
* otherwise return second level header.
* @returns {Element} - The closest displayed header.
*/
function getClosestDisplayedHeader(element) {
let header = element.closest("groupbox");
let searchHeader = header.querySelector("caption.search-header");
if (searchHeader && searchHeader.hidden &&
header.previousSibling.classList.contains("subcategory")) {
header = header.previousSibling;
}
return header;
}
function scrollContentTo(element) {
const SEARCH_CONTAINER_HEIGHT = document.querySelector(".search-container").clientHeight;
let mainContent = document.querySelector(".main-content");
let top = element.getBoundingClientRect().top - SEARCH_CONTAINER_HEIGHT;
mainContent.scroll({
top,
behavior: "smooth",
});
}
function helpButtonCommand() {
let pane = history.state;
let categories = document.getElementById("categories");

Просмотреть файл

@ -707,20 +707,19 @@
<hbox id="dataCollectionCategory"
class="subcategory"
hidden="true"
data-category="panePrivacy"
data-subcategory="reports">
data-category="panePrivacy">
<label class="header-name" flex="1">&dataCollection.label;</label>
</hbox>
<!-- Firefox Data Collection and Use -->
#ifdef MOZ_DATA_REPORTING
<groupbox id="dataCollectionGroup" data-category="panePrivacy" data-subcategory="reports" hidden="true">
<groupbox id="dataCollectionGroup" data-category="panePrivacy" hidden="true">
<caption class="search-header" hidden="true"><label>&dataCollection.label;</label></caption>
<vbox>
<description>
<label class="tail-with-learn-more">&dataCollectionDesc.label;</label><label id="dataCollectionPrivacyNotice" class="learnMore text-link">&dataCollectionPrivacyNotice.label;</label>
</description>
<description>
<label class="tail-with-learn-more">&dataCollectionDesc.label;</label><label id="dataCollectionPrivacyNotice" class="learnMore text-link">&dataCollectionPrivacyNotice.label;</label>
</description>
<vbox data-subcategory="reports">
<description flex="1">
<checkbox id="submitHealthReportBox" label="&enableHealthReport2.label;"
class="tail-with-learn-more"
@ -731,18 +730,19 @@
#ifndef MOZ_TELEMETRY_REPORTING
<description id="TelemetryDisabledDesc" class="indent tip-caption" control="telemetryGroup">&healthReportingDisabled.label;</description>
#endif
</vbox>
#ifdef MOZ_CRASHREPORTER
<hbox align="center">
<checkbox id="automaticallySubmitCrashesBox"
class="tail-with-learn-more"
preference="browser.crashReports.unsubmittedCheck.autoSubmit"
label="&alwaysSubmitCrashReports1.label;"
accesskey="&alwaysSubmitCrashReports1.accesskey;"/>
<label id="crashReporterLearnMore"
class="learnMore text-link">&crashReporterLearnMore.label;</label>
</hbox>
<hbox align="center">
<checkbox id="automaticallySubmitCrashesBox"
class="tail-with-learn-more"
preference="browser.crashReports.unsubmittedCheck.autoSubmit"
label="&alwaysSubmitCrashReports1.label;"
accesskey="&alwaysSubmitCrashReports1.accesskey;"/>
<label id="crashReporterLearnMore"
class="learnMore text-link">&crashReporterLearnMore.label;</label>
</hbox>
#endif
</vbox>
</groupbox>
#endif

Просмотреть файл

@ -73,6 +73,7 @@ skip-if = e10s
[browser_siteData.js]
[browser_siteData2.js]
[browser_siteData3.js]
[browser_spotlight.js]
[browser_site_login_exceptions.js]
[browser_permissions_dialog.js]
[browser_cookies_dialog.js]

Просмотреть файл

@ -25,7 +25,8 @@ add_task(async function() {
is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected by default");
let doc = gBrowser.contentDocument;
is(doc.location.hash, "#privacy", "The subcategory should be removed from the URI");
ok(doc.querySelector("#locationBarGroup").hidden, "Location Bar prefs should be hidden when only Reports are requested");
await TestUtils.waitForCondition(() => doc.querySelector(".spotlight"), "Wait for the reports section is spotlighted.");
is(doc.querySelector(".spotlight").getAttribute("data-subcategory"), "reports", "The reports section is spotlighted.");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
@ -43,7 +44,8 @@ add_task(async function() {
let selectedPane = gBrowser.contentWindow.history.state;
is(selectedPane, "panePrivacy", "Privacy pane should be selected");
is(doc.location.hash, "#privacy", "The subcategory should be removed from the URI");
ok(doc.querySelector("#locationBarGroup").hidden, "Location Bar prefs should be hidden when only Reports are requested");
await TestUtils.waitForCondition(() => doc.querySelector(".spotlight"), "Wait for the reports section is spotlighted.");
is(doc.querySelector(".spotlight").getAttribute("data-subcategory"), "reports", "The reports section is spotlighted.");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

Просмотреть файл

@ -0,0 +1,37 @@
/* eslint-disable mozilla/no-cpows-in-tests */
add_task(async function test_reports_section() {
let prefs = await openPreferencesViaOpenPreferencesAPI("privacy-reports", {leaveOpen: true});
is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected by default");
let doc = gBrowser.contentDocument;
is(doc.location.hash, "#privacy", "The subcategory should be removed from the URI");
await TestUtils.waitForCondition(() => doc.querySelector(".spotlight"),
"Wait for the reports section is spotlighted.");
is(doc.querySelector(".spotlight").getAttribute("data-subcategory"), "reports",
"The reports section is spotlighted.");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
add_task(async function test_address_autofill_section() {
let prefs = await openPreferencesViaOpenPreferencesAPI("privacy-address-autofill", {leaveOpen: true});
is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected by default");
let doc = gBrowser.contentDocument;
is(doc.location.hash, "#privacy", "The subcategory should be removed from the URI");
await TestUtils.waitForCondition(() => doc.querySelector(".spotlight"),
"Wait for the ddress-autofill section is spotlighted.");
is(doc.querySelector(".spotlight").getAttribute("data-subcategory"), "address-autofill",
"The ddress-autofill section is spotlighted.");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
add_task(async function test_credit_card_autofill_section() {
let prefs = await openPreferencesViaOpenPreferencesAPI("privacy-credit-card-autofill", {leaveOpen: true});
is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected by default");
let doc = gBrowser.contentDocument;
is(doc.location.hash, "#privacy", "The subcategory should be removed from the URI");
await TestUtils.waitForCondition(() => doc.querySelector(".spotlight"),
"Wait for the credit-card-autofill section is spotlighted.");
is(doc.querySelector(".spotlight").getAttribute("data-subcategory"), "credit-card-autofill",
"The credit-card-autofill section is spotlighted.");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

Просмотреть файл

@ -45,8 +45,9 @@ add_UITour_task(async function test_openPrivacyReports() {
let tab = await promiseTabOpened;
await BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "Initialized");
let doc = gBrowser.selectedBrowser.contentDocument;
let reports = doc.querySelector("groupbox[data-subcategory='reports']");
is(doc.location.hash, "#privacy", "Should not display the reports subcategory in the location hash.");
is(reports.hidden, false, "Should open to the reports subcategory in the privacy pane in the new Preferences.");
await TestUtils.waitForCondition(() => doc.querySelector(".spotlight"),
"Wait for the reports section is spotlighted.");
is(doc.querySelector(".spotlight").getAttribute("data-subcategory"), "reports", "The reports section is spotlighted.");
await BrowserTestUtils.removeTab(tab);
});

Просмотреть файл

@ -87,6 +87,7 @@ FormAutofillPreferences.prototype = {
addressAutofill.id = "addressAutofill";
addressAutofillLearnMore.id = "addressAutofillLearnMore";
addressAutofill.setAttribute("data-subcategory", "address-autofill");
addressAutofillLearnMore.setAttribute("value", this.bundle.GetStringFromName("learnMoreLabel"));
addressAutofillCheckbox.setAttribute("label", this.bundle.GetStringFromName("autofillAddressesCheckbox"));
savedAddressesBtn.setAttribute("label", this.bundle.GetStringFromName("savedAddressesBtnLabel"));
@ -130,6 +131,7 @@ FormAutofillPreferences.prototype = {
creditCardAutofill.id = "creditCardAutofill";
creditCardAutofillLearnMore.id = "creditCardAutofillLearnMore";
creditCardAutofill.setAttribute("data-subcategory", "credit-card-autofill");
creditCardAutofillLearnMore.setAttribute("value", this.bundle.GetStringFromName("learnMoreLabel"));
creditCardAutofillCheckbox.setAttribute("label", this.bundle.GetStringFromName("autofillCreditCardsCheckbox"));
savedCreditCardsBtn.setAttribute("label", this.bundle.GetStringFromName("savedCreditCardsBtnLabel"));

Просмотреть файл

@ -95,6 +95,24 @@ button > hbox > label {
margin: 4px 0;
}
.spotlight {
background-color: rgba(0,200,215,0.3);
/* Show the border to spotlight the components in high-contrast mode. */
border: 1px solid transparent;
border-radius: 2px;
}
[data-subcategory] {
margin-left: -4px;
margin-right: -4px;
padding-left: 4px;
padding-right: 4px;
}
[data-subcategory] > .groupbox-title {
padding-inline-start: 4px;
}
#searchInput {
border-radius: 0;
}
@ -725,9 +743,14 @@ button > hbox > label {
.search-container {
position: sticky;
background-color: var(--in-content-page-background);
width: 100%;
top: 0;
z-index: 1;
/* The search-container should have the capability to cover all spotlight area. */
width: calc(100% + 8px);
margin-left: -4px;
margin-right: -4px;
padding-left: 4px;
padding-right: 4px;
}
#searchInput {