Bug 1616280 - Use shadow DOM to hide Remote L10n translations for from local translations r=k88hudson

Differential Revision: https://phabricator.services.mozilla.com/D63895
This commit is contained in:
Andrei Oprea 2020-04-18 11:24:44 +00:00
Родитель af4a04d2c5
Коммит d88ff7e8b9
7 изменённых файлов: 246 добавлений и 211 удалений

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

@ -0,0 +1,67 @@
/* 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 is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
const { RemoteL10n } = ChromeUtils.import(
"resource://activity-stream/lib/RemoteL10n.jsm"
);
class MozTextParagraph extends HTMLElement {
constructor() {
super();
this._content = null;
}
get fluentAttributeValues() {
const attributes = {};
for (let name of this.getAttributeNames()) {
if (name.startsWith("fluent-variable-")) {
attributes[name.replace(/^fluent-variable-/, "")] = this.getAttribute(
name
);
}
}
return attributes;
}
render() {
if (this.getAttribute("fluent-remote-id") && this._content) {
RemoteL10n.l10n.setAttributes(
this._content,
this.getAttribute("fluent-remote-id"),
this.fluentAttributeValues
);
}
}
static get observedAttributes() {
return ["fluent-remote-id"];
}
attributeChangedCallback(name, oldValue, newValue) {
this.render();
}
connectedCallback() {
if (this.shadowRoot) {
this.render();
return;
}
const shadowRoot = this.attachShadow({ mode: "open" });
this._content = document.createElement("span");
shadowRoot.appendChild(this._content);
this.render();
RemoteL10n.l10n.translateFragment(this._content);
}
}
customElements.define("remote-text", MozTextParagraph);
}

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

@ -26,6 +26,7 @@ browser.jar:
res/activity-stream/data/content/tippytop/ (./data/content/tippytop/*) res/activity-stream/data/content/tippytop/ (./data/content/tippytop/*)
res/activity-stream/data/content/activity-stream.bundle.js (./data/content/activity-stream.bundle.js) res/activity-stream/data/content/activity-stream.bundle.js (./data/content/activity-stream.bundle.js)
res/activity-stream/data/content/newtab-render.js (./data/content/newtab-render.js) res/activity-stream/data/content/newtab-render.js (./data/content/newtab-render.js)
res/activity-stream/data/custom-elements/ (./components/CustomElements/*)
#ifdef XP_MACOSX #ifdef XP_MACOSX
res/activity-stream/css/activity-stream.css (./css/activity-stream-mac.css) res/activity-stream/css/activity-stream.css (./css/activity-stream-mac.css)
#elifdef XP_WIN #elifdef XP_WIN

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

@ -10,7 +10,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
Services: "resource://gre/modules/Services.jsm", Services: "resource://gre/modules/Services.jsm",
EveryWindow: "resource:///modules/EveryWindow.jsm", EveryWindow: "resource:///modules/EveryWindow.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
RemoteL10n: "resource://activity-stream/lib/RemoteL10n.jsm",
}); });
XPCOMUtils.defineLazyServiceGetter( XPCOMUtils.defineLazyServiceGetter(
this, this,
@ -108,6 +107,15 @@ class _ToolbarPanelHub {
win.MozXULElement.insertFTLIfNeeded("browser/branding/sync-brand.ftl"); win.MozXULElement.insertFTLIfNeeded("browser/branding/sync-brand.ftl");
} }
maybeLoadCustomElement(win) {
if (!win.customElements.get("remote-text")) {
Services.scriptloader.loadSubScript(
"resource://activity-stream/data/custom-elements/paragraph.js",
win
);
}
}
// Turns on the Appmenu (hamburger menu) button for all open windows and future windows. // Turns on the Appmenu (hamburger menu) button for all open windows and future windows.
async enableAppmenuButton() { async enableAppmenuButton() {
if ((await this.messages).length) { if ((await this.messages).length) {
@ -170,6 +178,7 @@ class _ToolbarPanelHub {
// Render what's new messages into the panel. // Render what's new messages into the panel.
async renderMessages(win, doc, containerId, options = {}) { async renderMessages(win, doc, containerId, options = {}) {
this.maybeLoadCustomElement(win);
const messages = const messages =
(options.force && options.messages) || (options.force && options.messages) ||
(await this.messages).sort(this._sortWhatsNewMessages); (await this.messages).sort(this._sortWhatsNewMessages);
@ -178,17 +187,13 @@ class _ToolbarPanelHub {
if (messages) { if (messages) {
// Targeting attribute state might have changed making new messages // Targeting attribute state might have changed making new messages
// available and old messages invalid, we need to refresh // available and old messages invalid, we need to refresh
for (const prevMessageEl of container.querySelectorAll( this.removeMessages(win, containerId);
".whatsNew-message"
)) {
container.removeChild(prevMessageEl);
}
let previousDate = 0; let previousDate = 0;
// Get and store any variable part of the message content // Get and store any variable part of the message content
this.state.contentArguments = await this._contentArguments(); this.state.contentArguments = await this._contentArguments();
for (let message of messages) { for (let message of messages) {
container.appendChild( container.appendChild(
await this._createMessageElements(win, doc, message, previousDate) this._createMessageElements(win, doc, message, previousDate)
); );
previousDate = message.content.published_date; previousDate = message.content.published_date;
} }
@ -267,15 +272,15 @@ class _ToolbarPanelHub {
}); });
} }
async _createMessageElements(win, doc, message, previousDate) { _createMessageElements(win, doc, message, previousDate) {
const { content } = message; const { content } = message;
const messageEl = await this._createElement(doc, "div"); const messageEl = this._createElement(doc, "div");
messageEl.classList.add("whatsNew-message"); messageEl.classList.add("whatsNew-message");
// Only render date if it is different from the one rendered before. // Only render date if it is different from the one rendered before.
if (content.published_date !== previousDate) { if (content.published_date !== previousDate) {
messageEl.appendChild( messageEl.appendChild(
await this._createElement(doc, "p", { this._createElement(doc, "p", {
classList: "whatsNew-message-date", classList: "whatsNew-message-date",
content: new Date(content.published_date).toLocaleDateString( content: new Date(content.published_date).toLocaleDateString(
"default", "default",
@ -289,24 +294,28 @@ class _ToolbarPanelHub {
); );
} }
const wrapperEl = await this._createElement(doc, "button"); const wrapperEl = this._createElement(doc, "button");
wrapperEl.doCommand = () => this._dispatchUserAction(win, message); wrapperEl.doCommand = () => this._dispatchUserAction(win, message);
wrapperEl.classList.add("whatsNew-message-body"); wrapperEl.classList.add("whatsNew-message-body");
messageEl.appendChild(wrapperEl); messageEl.appendChild(wrapperEl);
if (content.icon_url) { if (content.icon_url) {
wrapperEl.classList.add("has-icon"); wrapperEl.classList.add("has-icon");
const iconEl = await this._createElement(doc, "img"); const iconEl = this._createElement(doc, "img");
iconEl.src = content.icon_url; iconEl.src = content.icon_url;
iconEl.classList.add("whatsNew-message-icon"); iconEl.classList.add("whatsNew-message-icon");
await this._setTextAttribute(iconEl, "alt", content.icon_alt); if (content.icon_alt && content.icon_alt.string_id) {
doc.l10n.setAttributes(iconEl, content.icon_alt.string_id);
} else {
iconEl.setAttribute("alt", content.icon_alt);
}
wrapperEl.appendChild(iconEl); wrapperEl.appendChild(iconEl);
} }
wrapperEl.appendChild(await this._createMessageContent(win, doc, content)); wrapperEl.appendChild(this._createMessageContent(win, doc, content));
if (content.link_text) { if (content.link_text) {
const anchorEl = await this._createElement(doc, "a", { const anchorEl = this._createElement(doc, "a", {
classList: "text-link", classList: "text-link",
content: content.link_text, content: content.link_text,
}); });
@ -323,11 +332,11 @@ class _ToolbarPanelHub {
/** /**
* Return message title (optional subtitle) and body * Return message title (optional subtitle) and body
*/ */
async _createMessageContent(win, doc, content) { _createMessageContent(win, doc, content) {
const wrapperEl = new win.DocumentFragment(); const wrapperEl = new win.DocumentFragment();
wrapperEl.appendChild( wrapperEl.appendChild(
await this._createElement(doc, "h2", { this._createElement(doc, "h2", {
classList: "whatsNew-message-title", classList: "whatsNew-message-title",
content: content.title, content: content.title,
}) })
@ -335,14 +344,14 @@ class _ToolbarPanelHub {
switch (content.layout) { switch (content.layout) {
case "tracking-protections": case "tracking-protections":
await wrapperEl.appendChild( wrapperEl.appendChild(
await this._createElement(doc, "h4", { this._createElement(doc, "h4", {
classList: "whatsNew-message-subtitle", classList: "whatsNew-message-subtitle",
content: content.subtitle, content: content.subtitle,
}) })
); );
wrapperEl.appendChild( wrapperEl.appendChild(
await this._createElement(doc, "h2", { this._createElement(doc, "h2", {
classList: "whatsNew-message-title-large", classList: "whatsNew-message-title-large",
content: this.state.contentArguments[ content: this.state.contentArguments[
content.layout_title_content_variable content.layout_title_content_variable
@ -353,32 +362,40 @@ class _ToolbarPanelHub {
} }
wrapperEl.appendChild( wrapperEl.appendChild(
await this._createElement(doc, "p", { content: content.body }) this._createElement(doc, "p", {
content: content.body,
classList: "whatsNew-message-content",
})
); );
return wrapperEl; return wrapperEl;
} }
async _createHeroElement(win, doc, message) { _createHeroElement(win, doc, message) {
const messageEl = await this._createElement(doc, "div"); this.maybeLoadCustomElement(win);
const messageEl = this._createElement(doc, "div");
messageEl.setAttribute("id", "protections-popup-message"); messageEl.setAttribute("id", "protections-popup-message");
messageEl.classList.add("whatsNew-hero-message"); messageEl.classList.add("whatsNew-hero-message");
const wrapperEl = await this._createElement(doc, "div"); const wrapperEl = this._createElement(doc, "div");
wrapperEl.classList.add("whatsNew-message-body"); wrapperEl.classList.add("whatsNew-message-body");
messageEl.appendChild(wrapperEl); messageEl.appendChild(wrapperEl);
wrapperEl.appendChild( wrapperEl.appendChild(
await this._createElement(doc, "h2", { this._createElement(doc, "h2", {
classList: "whatsNew-message-title", classList: "whatsNew-message-title",
content: message.content.title, content: message.content.title,
}) })
); );
wrapperEl.appendChild( wrapperEl.appendChild(
await this._createElement(doc, "p", { content: message.content.body }) this._createElement(doc, "p", {
classList: "protections-popup-content",
content: message.content.body,
})
); );
if (message.content.link_text) { if (message.content.link_text) {
let linkEl = await this._createElement(doc, "a", { let linkEl = this._createElement(doc, "a", {
classList: "text-link", classList: "text-link",
content: message.content.link_text, content: message.content.link_text,
}); });
@ -392,14 +409,17 @@ class _ToolbarPanelHub {
return messageEl; return messageEl;
} }
async _createElement(doc, elem, options = {}) { _createElement(doc, elem, options = {}) {
const node = doc.createElementNS("http://www.w3.org/1999/xhtml", elem); let node;
if (options.content && options.content.string_id) {
node = doc.createElement("remote-text");
} else {
node = doc.createElementNS("http://www.w3.org/1999/xhtml", elem);
}
if (options.classList) { if (options.classList) {
node.classList.add(options.classList); node.classList.add(options.classList);
} }
if (options.content) { this._setString(node, options.content);
await this._setString(node, options.content);
}
return node; return node;
} }
@ -447,41 +467,19 @@ class _ToolbarPanelHub {
// If `string_id` is present it means we are relying on fluent for translations. // If `string_id` is present it means we are relying on fluent for translations.
// Otherwise, we have a vanilla string. // Otherwise, we have a vanilla string.
async _setString(el, stringObj) { _setString(el, stringObj) {
if (stringObj && stringObj.string_id) { if (stringObj && stringObj.string_id) {
const [{ value }] = await RemoteL10n.l10n.formatMessages([ for (let [fluentId, value] of Object.entries(
{ this.state.contentArguments || {}
id: stringObj.string_id, )) {
// Pass all available arguments to Fluent el.setAttribute(`fluent-variable-${fluentId}`, value);
args: this.state.contentArguments, }
}, el.setAttribute("fluent-remote-id", stringObj.string_id);
]);
el.textContent = value;
} else { } else {
el.textContent = stringObj; el.textContent = stringObj;
} }
} }
// If `string_id` is present it means we are relying on fluent for translations.
// Otherwise, we have a vanilla string.
async _setTextAttribute(el, attr, stringObj) {
if (stringObj && stringObj.string_id) {
const [{ attributes }] = await RemoteL10n.l10n.formatMessages([
{
id: stringObj.string_id,
// Pass all available arguments to Fluent
args: this.state.contentArguments,
},
]);
if (attributes) {
const { value } = attributes.find(({ name }) => name === attr);
el.setAttribute(attr, value);
}
} else {
el.setAttribute(attr, stringObj);
}
}
async _showAppmenuButton(win) { async _showAppmenuButton(win) {
this.maybeInsertFTL(win); this.maybeInsertFTL(win);
await this._showElement( await this._showElement(
@ -505,10 +503,9 @@ class _ToolbarPanelHub {
this._hideElement(win.browser.ownerDocument, TOOLBAR_BUTTON_ID); this._hideElement(win.browser.ownerDocument, TOOLBAR_BUTTON_ID);
} }
async _showElement(document, id, string_id) { _showElement(document, id, string_id) {
const el = document.getElementById(id); const el = document.getElementById(id);
await this._setTextAttribute(el, "label", { string_id }); document.l10n.setAttributes(el, string_id);
await this._setTextAttribute(el, "tooltiptext", { string_id });
el.removeAttribute("hidden"); el.removeAttribute("hidden");
} }
@ -568,7 +565,7 @@ class _ToolbarPanelHub {
triggerId: "protectionsPanelOpen", triggerId: "protectionsPanelOpen",
}); });
if (message) { if (message) {
const messageEl = await this._createHeroElement(win, doc, message); const messageEl = this._createHeroElement(win, doc, message);
container.appendChild(messageEl); container.appendChild(messageEl);
infoButton.addEventListener("click", toggleMessage); infoButton.addEventListener("click", toggleMessage);
this.sendUserEventTelemetry(win, "IMPRESSION", message); this.sendUserEventTelemetry(win, "IMPRESSION", message);

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

@ -77,6 +77,14 @@ add_task(async function test_with_rs_messages() {
"The message container was not populated with the expected number of msgs" "The message container was not populated with the expected number of msgs"
); );
await BrowserTestUtils.waitForCondition(
() =>
document.querySelector(
"#PanelUI-whatsNew-message-container .whatsNew-message-body remote-text"
).shadowRoot.innerHTML,
"Ensure messages have content"
);
UITour.hideMenu(window, "appMenu"); UITour.hideMenu(window, "appMenu");
// Clean up and remove messages // Clean up and remove messages
ToolbarPanelHub.disableAppmenuButton(); ToolbarPanelHub.disableAppmenuButton();

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

@ -12,6 +12,7 @@ describe("ToolbarPanelHub", () => {
let fakeWindow; let fakeWindow;
let fakeElementById; let fakeElementById;
let createdElements = []; let createdElements = [];
let createdCustomElements = [];
let eventListeners = {}; let eventListeners = {};
let addObserverStub; let addObserverStub;
let removeObserverStub; let removeObserverStub;
@ -24,6 +25,7 @@ describe("ToolbarPanelHub", () => {
let getEventsByDateRangeStub; let getEventsByDateRangeStub;
let handleUserActionStub; let handleUserActionStub;
let defaultSearchStub; let defaultSearchStub;
let scriptloaderStub;
beforeEach(async () => { beforeEach(async () => {
sandbox = sinon.createSandbox(); sandbox = sinon.createSandbox();
@ -56,10 +58,34 @@ describe("ToolbarPanelHub", () => {
}, },
appendChild: sandbox.stub(), appendChild: sandbox.stub(),
setAttribute: sandbox.stub(), setAttribute: sandbox.stub(),
textContent: "",
}; };
createdElements.push(element); createdElements.push(element);
return element; return element;
}, },
createElement: tagName => {
const element = {
tagName,
classList: {},
addEventListener: (ev, fn) => {
eventListeners[ev] = fn;
},
appendChild: sandbox.stub(),
setAttribute: sandbox.stub(),
textContent: "",
};
element.classList.add = sandbox.stub();
element.classList.includes = className =>
element.classList.add.firstCall.args[0] === className;
createdCustomElements.push(element);
return element;
},
l10n: {
translateElements: sandbox.stub(),
translateFragment: sandbox.stub(),
formatMessages: sandbox.stub().resolves([{}]),
setAttributes: sandbox.stub(),
},
}; };
fakeWindow = { fakeWindow = {
// eslint-disable-next-line object-shorthand // eslint-disable-next-line object-shorthand
@ -79,11 +105,13 @@ describe("ToolbarPanelHub", () => {
panel: fakeElementById, panel: fakeElementById,
whatsNewPanel: fakeElementById, whatsNewPanel: fakeElementById,
}, },
customElements: { get: sandbox.stub() },
}; };
everyWindowStub = { everyWindowStub = {
registerCallback: sandbox.stub(), registerCallback: sandbox.stub(),
unregisterCallback: sandbox.stub(), unregisterCallback: sandbox.stub(),
}; };
scriptloaderStub = { loadSubScript: sandbox.stub() };
addObserverStub = sandbox.stub(); addObserverStub = sandbox.stub();
removeObserverStub = sandbox.stub(); removeObserverStub = sandbox.stub();
getBoolPrefStub = sandbox.stub(); getBoolPrefStub = sandbox.stub();
@ -108,6 +136,7 @@ describe("ToolbarPanelHub", () => {
setBoolPref: setBoolPrefStub, setBoolPref: setBoolPrefStub,
}, },
search: defaultSearchStub, search: defaultSearchStub,
scriptloader: scriptloaderStub,
}, },
PrivateBrowsingUtils: { PrivateBrowsingUtils: {
isBrowserPrivate: isBrowserPrivateStub, isBrowserPrivate: isBrowserPrivateStub,
@ -116,13 +145,6 @@ describe("ToolbarPanelHub", () => {
getEarliestRecordedDate: getEarliestRecordedDateStub, getEarliestRecordedDate: getEarliestRecordedDateStub,
getEventsByDateRange: getEventsByDateRangeStub, getEventsByDateRange: getEventsByDateRangeStub,
}, },
RemoteL10n: {
l10n: {
translateElements: sandbox.stub(),
translateFragment: sandbox.stub(),
formatMessages: sandbox.stub().resolves([{}]),
},
},
}); });
}); });
afterEach(() => { afterEach(() => {
@ -131,6 +153,7 @@ describe("ToolbarPanelHub", () => {
globals.restore(); globals.restore();
eventListeners = {}; eventListeners = {};
createdElements = []; createdElements = [];
createdCustomElements = [];
}); });
it("should create an instance", () => { it("should create an instance", () => {
assert.ok(instance); assert.ok(instance);
@ -290,6 +313,32 @@ describe("ToolbarPanelHub", () => {
handleUserAction: handleUserActionStub, handleUserAction: handleUserActionStub,
}); });
}); });
it("should have correct state", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message"
);
getMessagesStub.returns(messages);
const ev1 = sandbox.stub();
ev1.withArgs("type").returns(1); // tracker
ev1.withArgs("count").returns(4);
const ev2 = sandbox.stub();
ev2.withArgs("type").returns(4); // fingerprinter
ev2.withArgs("count").returns(3);
getEventsByDateRangeStub.returns([
{ getResultByName: ev1 },
{ getResultByName: ev2 },
]);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.propertyVal(instance.state.contentArguments, "trackerCount", 4);
assert.propertyVal(
instance.state.contentArguments,
"fingerprinterCount",
3
);
});
it("should render messages to the panel on renderMessages()", async () => { it("should render messages to the panel on renderMessages()", async () => {
const messages = (await PanelTestProvider.getMessages()).filter( const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message" m => m.template === "whatsnew_panel_message"
@ -311,19 +360,30 @@ describe("ToolbarPanelHub", () => {
await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
for (let message of messages) { for (let message of messages) {
assert.ok(createdElements.find(el => el.tagName === "h2")); assert.ok(
createdCustomElements.find(el =>
el.classList.includes("whatsNew-message-title")
)
);
if (message.content.layout === "tracking-protections") { if (message.content.layout === "tracking-protections") {
assert.ok(createdElements.find(el => el.tagName === "h4")); assert.ok(
createdCustomElements.find(el =>
el.classList.includes("whatsNew-message-subtitle")
)
);
} }
if (message.id === "WHATS_NEW_FINGERPRINTER_COUNTER_72") { if (message.id === "WHATS_NEW_FINGERPRINTER_COUNTER_72") {
assert.ok(createdElements.find(el => el.tagName === "h4"));
assert.ok( assert.ok(
createdElements.find( createdElements.find(
el => el.tagName === "h2" && el.textContent === 3 el => el.tagName === "h2" && el.textContent === 3
) )
); );
} }
assert.ok(createdElements.find(el => el.tagName === "p")); assert.ok(
createdCustomElements.find(el =>
el.classList.includes("whatsNew-message-content")
)
);
} }
// Call the click handler to make coverage happy. // Call the click handler to make coverage happy.
eventListeners.mouseup(); eventListeners.mouseup();
@ -333,17 +393,18 @@ describe("ToolbarPanelHub", () => {
const messages = (await PanelTestProvider.getMessages()).filter( const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message" m => m.template === "whatsnew_panel_message"
); );
const removeStub = sandbox.stub();
fakeElementById.querySelectorAll.onCall(0).returns([]); fakeElementById.querySelectorAll.onCall(0).returns([]);
fakeElementById.querySelectorAll.onCall(1).returns(["a", "b", "c"]); fakeElementById.querySelectorAll
.onCall(1)
.returns([{ remove: removeStub }, { remove: removeStub }]);
getMessagesStub.returns(messages); getMessagesStub.returns(messages);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledThrice(fakeElementById.removeChild); assert.calledTwice(removeStub);
assert.equal(fakeElementById.removeChild.firstCall.args[0], "a");
assert.equal(fakeElementById.removeChild.secondCall.args[0], "b");
}); });
it("should sort based on order field value", async () => { it("should sort based on order field value", async () => {
const messages = (await PanelTestProvider.getMessages()).filter( const messages = (await PanelTestProvider.getMessages()).filter(
@ -385,38 +446,7 @@ describe("ToolbarPanelHub", () => {
"Firefox Send Logo" "Firefox Send Logo"
); );
}); });
it("should accept fluent ids for image attributes", async () => { it("should set state values as data-attribute", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.id === "WHATS_NEW_70_1"
);
messages[0].content.icon_alt = { string_id: "foo" };
getMessagesStub.returns(messages);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledWithExactly(global.RemoteL10n.l10n.formatMessages, [
{
id: "foo",
args: instance.state.contentArguments,
},
]);
});
it("handle fluent attributes", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.id === "WHATS_NEW_70_1"
);
messages[0].content.icon_alt = { string_id: "foo" };
getMessagesStub.returns(messages);
global.RemoteL10n.l10n.formatMessages
.withArgs([{ id: "foo", args: sinon.match.object }])
.resolves([{ attributes: [{ name: "alt", value: "bar" }] }]);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
const imgEl = createdElements.find(e => e.tagName === "img");
assert.calledWithExactly(imgEl.setAttribute, "alt", "bar");
});
it("should accept fluent ids for elements attributes", async () => {
const [message] = (await PanelTestProvider.getMessages()).filter( const [message] = (await PanelTestProvider.getMessages()).filter(
m => m =>
m.template === "whatsnew_panel_message" && m.template === "whatsnew_panel_message" &&
@ -427,102 +457,13 @@ describe("ToolbarPanelHub", () => {
await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledWithExactly(global.RemoteL10n.l10n.formatMessages, [ // Currently this.state.contentArguments has 9 different entries
{ assert.callCount(createdCustomElements[0].setAttribute, 9);
id: message.content.subtitle.string_id, assert.calledWithExactly(
args: instance.state.contentArguments, createdCustomElements[0].setAttribute,
}, "fluent-variable-searchEngineName",
]); defaultSearchStub.defaultEngine.name
});
it("should correctly compute blocker trackers and date", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message"
); );
getMessagesStub.returns(messages);
const ev1 = sandbox.stub();
ev1.withArgs("type").returns(2); // cookie
ev1.withArgs("count").returns(4);
const ev2 = sandbox.stub();
ev2.withArgs("type").returns(2); // cookie
ev2.withArgs("count").returns(3);
getEventsByDateRangeStub.returns([
{ getResultByName: ev1 },
{ getResultByName: ev2 },
]);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledWithExactly(global.RemoteL10n.l10n.formatMessages, [
{
id: sinon.match.string,
args: {
blockedCount: 7,
earliestDate: getEarliestRecordedDateStub(),
cookieCount: 7,
cryptominerCount: 0,
socialCount: 0,
trackerCount: 0,
fingerprinterCount: 0,
searchEngineName: Services.search.defaultEngine.name,
},
},
]);
});
it("should correctly compute event counts per type", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message"
);
getMessagesStub.returns(messages);
const ev1 = sandbox.stub();
ev1.withArgs("type").returns(1); // tracker
ev1.withArgs("count").returns(4);
const ev2 = sandbox.stub();
ev2.withArgs("type").returns(4); // fingerprinter
ev2.withArgs("count").returns(3);
getEventsByDateRangeStub.returns([
{ getResultByName: ev1 },
{ getResultByName: ev2 },
]);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledWithExactly(global.RemoteL10n.l10n.formatMessages, [
{
id: sinon.match.string,
args: {
blockedCount: 7,
earliestDate: getEarliestRecordedDateStub(),
trackerCount: 4,
fingerprinterCount: 3,
cookieCount: 0,
cryptominerCount: 0,
socialCount: 0,
searchEngineName: Services.search.defaultEngine.name,
},
},
]);
});
it("should fallback to undefined search engine name", async () => {
globals.set("Services", {
...global.Services,
search: { defaultEngine: null },
});
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message"
);
getMessagesStub.returns(messages);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledWithExactly(global.RemoteL10n.l10n.formatMessages, [
{
id: sinon.match.string,
args: {
...instance.state.contentArguments,
searchEngineName: "undefined",
},
},
]);
}); });
it("should only render unique dates (no duplicates)", async () => { it("should only render unique dates (no duplicates)", async () => {
const messages = (await PanelTestProvider.getMessages()).filter( const messages = (await PanelTestProvider.getMessages()).filter(
@ -745,7 +686,6 @@ describe("ToolbarPanelHub", () => {
it("should call removeMessages when forcing a message to show", () => { it("should call removeMessages when forcing a message to show", () => {
instance.forceShowMessage(browser, messages); instance.forceShowMessage(browser, messages);
assert.calledOnce(removeMessagesSpy);
assert.calledWithExactly(removeMessagesSpy, fakeWindow, panelSelector); assert.calledWithExactly(removeMessagesSpy, fakeWindow, panelSelector);
}); });
it("should call renderMessages when forcing a message to show", () => { it("should call renderMessages when forcing a message to show", () => {

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

@ -345,6 +345,7 @@ const TEST_GLOBAL = {
}, },
ww: { registerNotification() {}, unregisterNotification() {} }, ww: { registerNotification() {}, unregisterNotification() {} },
appinfo: { appBuildID: "20180710100040", version: "69.0a1" }, appinfo: { appBuildID: "20180710100040", version: "69.0a1" },
scriptloader: { loadSubScript: () => {} },
}, },
XPCOMUtils: { XPCOMUtils: {
defineLazyGetter(object, name, f) { defineLazyGetter(object, name, f) {

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

@ -1854,6 +1854,11 @@ toolbarpaletteitem[place="menu-panel"] > .subviewbutton-nav::after {
text-decoration: underline; text-decoration: underline;
} }
#protections-popup-message .protections-popup-content {
display: block;
margin: 12px 0;
}
panelview[mainview] #PanelUI-whatsNew-content { panelview[mainview] #PanelUI-whatsNew-content {
height: 43em; height: 43em;
} }
@ -1868,13 +1873,23 @@ panelview[mainview] #PanelUI-whatsNew-content {
padding: 0; padding: 0;
} }
#PanelUI-whatsNew .whatsNew-message:not(:first-child)::before { /* The following 2 rules show a 1 pixel line separator between What's New
* messages while at the same time ensuring that the first message (which has
* a date header) will not show the separator
*/
#PanelUI-whatsNew .whatsNew-message-body::before {
content: ""; content: "";
display: block; display: block;
height: 1px; height: 1px;
width: 104%;
margin-inline-start: -2%;
background: var(--panel-separator-color); background: var(--panel-separator-color);
} }
#PanelUI-whatsNew .whatsNew-message-date + .whatsNew-message-body::before {
display: none;
}
#PanelUI-whatsNew .whatsNew-message-date { #PanelUI-whatsNew .whatsNew-message-date {
font-size: .85em; font-size: .85em;
margin: 0 -12px; margin: 0 -12px;
@ -1908,17 +1923,18 @@ panelview[mainview] #PanelUI-whatsNew-content {
inset-inline-end: 6px; inset-inline-end: 6px;
height: 32px; height: 32px;
position: absolute; position: absolute;
top: 10px; top: 16px;
width: 32px; width: 32px;
} }
#PanelUI-whatsNew .whatsNew-message-title, #PanelUI-whatsNew .whatsNew-message-title,
#protections-popup-message .whatsNew-message-title { #protections-popup-message .whatsNew-message-title {
display: block;
padding-inline-end: 46px; padding-inline-end: 46px;
font-size: 1.3em; font-size: 1.3em;
font-weight: 600; font-weight: 600;
line-height: 1.4em; line-height: 1.4em;
margin: 2px 0; margin: 8px 0 2px;
} }
#PanelUI-whatsNew .whatsNew-message-title-large { #PanelUI-whatsNew .whatsNew-message-title-large {
@ -1934,6 +1950,11 @@ panelview[mainview] #PanelUI-whatsNew-content {
font-weight: normal; font-weight: normal;
} }
#PanelUI-whatsNew .whatsNew-message-content {
display: block;
margin: 13px 0;
}
#PanelUI-whatsNew .text-link { #PanelUI-whatsNew .text-link {
background: none; background: none;
border: 0; border: 0;