зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
606f156e4a
Коммит
8663ce0fcb
|
@ -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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче