diff --git a/browser/base/content/browser-captivePortal.js b/browser/base/content/browser-captivePortal.js index c054c11502fe..7b0606f521f1 100644 --- a/browser/base/content/browser-captivePortal.js +++ b/browser/base/content/browser-captivePortal.js @@ -215,7 +215,6 @@ var CaptivePortalWatcher = { // Returning true prevents the notification from closing. return true; }, - isDefault: true, }, ]; diff --git a/browser/base/content/browser-plugins.js b/browser/base/content/browser-plugins.js index 64d1a2b8e0a3..484234eed32e 100644 --- a/browser/base/content/browser-plugins.js +++ b/browser/base/content/browser-plugins.js @@ -482,8 +482,7 @@ var gPluginHandler = { let crashurl = formatURL("app.support.baseURL", true); crashurl += "plugin-crashed-notificationbar"; link.href = crashurl; - let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); - description.appendChild(link); + notification.messageText.appendChild(link); }, }; diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 5cd7d16c2c99..8e5ee876c620 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -1058,11 +1058,6 @@ browser[tabmodalPromptShowing] { -moz-box-align: end; } -/* Translation */ -notification[value="translation"] { - -moz-binding: url("chrome://browser/content/translation-infobar.xml#translationbar"); -} - /*** Visibility of downloads indicator controls ***/ /* Bug 924050: If we've loaded the indicator, for now we hide it in the menu panel, diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index b4d67d9eaac3..70c4a2a23ff9 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -242,6 +242,11 @@ XPCOMUtils.defineLazyGetter(this, "Win7Features", function() { return null; }); +customElements.setElementCreationCallback("translation-notification", () => { + Services.scriptloader.loadSubScript( + "chrome://browser/content/translation-notification.js", window); +}); + var gBrowser; var gLastValidURLStr = ""; var gInPrintPreviewMode = false; diff --git a/browser/base/content/test/general/browser_decoderDoctor.js b/browser/base/content/test/general/browser_decoderDoctor.js index a7fc29132a9a..478924c2b76f 100644 --- a/browser/base/content/test/general/browser_decoderDoctor.js +++ b/browser/base/content/test/general/browser_decoderDoctor.js @@ -47,10 +47,10 @@ async function test_decoder_doctor_notification(data, notificationMessage, } ok(notification, "Got decoder-doctor-notification notification"); - is(notification.getAttribute("label"), notificationMessage, + is(notification.messageText.textContent, notificationMessage, "notification message should match expectation"); - let button = notification.children[0]; + let button = notification.querySelector("button"); if (!label) { ok(!button, "There should not be button"); return; diff --git a/browser/base/content/test/general/browser_refreshBlocker.js b/browser/base/content/test/general/browser_refreshBlocker.js index 2f579e08c101..d006ece76e52 100644 --- a/browser/base/content/test/general/browser_refreshBlocker.js +++ b/browser/base/content/test/general/browser_refreshBlocker.js @@ -100,7 +100,7 @@ async function testRealRefresh(refreshPage, delay) { let notification = notificationBox.currentNotification; ok(notification, "Notification should be visible"); - is(notification.value, "refresh-blocked", + is(notification.getAttribute("value"), "refresh-blocked", "Should be showing the right notification"); // Then click the button to allow the refresh. diff --git a/browser/base/content/test/plugins/browser_enable_DRM_prompt.js b/browser/base/content/test/plugins/browser_enable_DRM_prompt.js index 42e9c419cbbb..901352eba797 100644 --- a/browser/base/content/test/plugins/browser_enable_DRM_prompt.js +++ b/browser/base/content/test/plugins/browser_enable_DRM_prompt.js @@ -45,7 +45,7 @@ add_task(async function() { let notification = box.currentNotification; ok(notification, "Notification should be visible"); - is(notification.value, "drmContentDisabled", + is(notification.getAttribute("value"), "drmContentDisabled", "Should be showing the right notification"); // Verify the "Enable DRM" button is there. diff --git a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js index ef9f6225b328..da78ce5aec50 100644 --- a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js +++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js @@ -27,7 +27,7 @@ add_task(async function() { ok(notification, "Infobar was shown."); is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM, "Correct priority."); - is(notification.getAttribute("label"), + is(notification.messageText.textContent, "The GlobalTestPlugin plugin has crashed.", "Correct message."); }); diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js index 161df7cb9af8..428e49b69b1c 100644 --- a/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js +++ b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js @@ -14,19 +14,8 @@ function promiseNotification(aBrowser, value, expected, input) { let notificationBox = aBrowser.getNotificationBox(aBrowser.selectedBrowser); if (expected) { info("Waiting for " + value + " notification"); - let checkForNotification = function() { - if (notificationBox.getNotificationWithValue(value)) { - info("Saw the notification"); - notificationObserver.disconnect(); - notificationObserver = null; - resolve(); - } - }; - if (notificationObserver) { - notificationObserver.disconnect(); - } - notificationObserver = new MutationObserver(checkForNotification); - notificationObserver.observe(notificationBox.stack, {childList: true}); + resolve(BrowserTestUtils.waitForNotificationInNotificationBox( + notificationBox, value)); } else { setTimeout(() => { is(notificationBox.getNotificationWithValue(value), null, @@ -152,7 +141,7 @@ function get_test_function_for_localhost_with_hostname(hostName, isPrivate) { let notificationBox = browser.getNotificationBox(tab.linkedBrowser); let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup"); let docLoadPromise = waitForDocLoadAndStopIt("http://" + hostName + "/", tab.linkedBrowser); - notification.querySelector(".notification-button-default").click(); + notification.querySelector("button").click(); // check pref value let prefValue = Services.prefs.getBoolPref(pref); diff --git a/browser/components/extensions/test/browser/browser_ext_slow_script.js b/browser/components/extensions/test/browser/browser_ext_slow_script.js index 46c650511797..3c776bec8e4d 100644 --- a/browser/components/extensions/test/browser/browser_ext_slow_script.js +++ b/browser/components/extensions/test/browser/browser_ext_slow_script.js @@ -49,7 +49,7 @@ add_task(async function test_slow_content_script() { let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/"); let notification = await BrowserTestUtils.waitForGlobalNotificationBar(window, "process-hang"); - let text = document.getAnonymousElementByAttribute(notification, "anonid", "messageText").textContent; + let text = notification.messageText.textContent; ok(text.includes("\u201cSlow Script Extension\u201d"), "Label is correct"); diff --git a/browser/components/feeds/test/browser/browser_registerProtocolHandler_notification.js b/browser/components/feeds/test/browser/browser_registerProtocolHandler_notification.js index 8b93a2b9f375..920765145a81 100644 --- a/browser/components/feeds/test/browser/browser_registerProtocolHandler_notification.js +++ b/browser/components/feeds/test/browser/browser_registerProtocolHandler_notification.js @@ -22,13 +22,12 @@ add_task(async function() { finish(); return; } - is(notification.type, "info", "We expect this notification to have the type of 'info'."); - isnot(notification.image, null, "We expect this notification to have an icon."); + is(notification.getAttribute("type"), "info", + "We expect this notification to have the type of 'info'."); + ok(notification.messageImage.getAttribute("src"), + "We expect this notification to have an icon."); - let buttons = notification.getElementsByClassName("notification-button-default"); - is(buttons.length, 1, "We expect see one default button."); - - buttons = notification.getElementsByClassName("notification-button"); + let buttons = notification.getElementsByClassName("notification-button"); is(buttons.length, 1, "We expect see one button."); let button = buttons[0]; diff --git a/browser/components/tests/browser/browser_bug538331.js b/browser/components/tests/browser/browser_bug538331.js index 6189c60ab1a2..f88c3a54461c 100644 --- a/browser/components/tests/browser/browser_bug538331.js +++ b/browser/components/tests/browser/browser_bug538331.js @@ -333,7 +333,8 @@ function testShowNotification() { ok(updateBox, "Update notification box should have been displayed"); if (updateBox) { if (testCase.notificationText) { - is(updateBox.label, testCase.notificationText, "Update notification box " + + is(updateBox.messageText.textContent, testCase.notificationText, + "Update notification box " + "should have the label provided by the update"); } if (testCase.notificationButtonLabel) { diff --git a/browser/components/translation/Translation.jsm b/browser/components/translation/Translation.jsm index 97dc127ff668..0f527ac1cc25 100644 --- a/browser/components/translation/Translation.jsm +++ b/browser/components/translation/Translation.jsm @@ -228,7 +228,8 @@ TranslationUI.prototype = { showTranslationInfoBar() { let notificationBox = this.notificationBox; let notif = notificationBox.appendNotification("", "translation", null, - notificationBox.PRIORITY_INFO_HIGH); + notificationBox.PRIORITY_INFO_HIGH, null, null, + "translation-notification"); notif.init(this); return notif; }, diff --git a/browser/components/translation/content/.eslintrc.js b/browser/components/translation/content/.eslintrc.js new file mode 100644 index 000000000000..9098437090a0 --- /dev/null +++ b/browser/components/translation/content/.eslintrc.js @@ -0,0 +1,11 @@ +"use strict"; + +module.exports = { + "env": { + "mozilla/browser-window": true, + }, + + "plugins": [ + "mozilla", + ] +}; diff --git a/browser/components/translation/jar.mn b/browser/components/translation/content/jar.mn similarity index 84% rename from browser/components/translation/jar.mn rename to browser/components/translation/content/jar.mn index be744cb9ef12..135d39458edb 100644 --- a/browser/components/translation/jar.mn +++ b/browser/components/translation/content/jar.mn @@ -2,5 +2,5 @@ # 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/. browser.jar: - content/browser/translation-infobar.xml + content/browser/translation-notification.js content/browser/microsoft-translator-attribution.png diff --git a/browser/components/translation/microsoft-translator-attribution.png b/browser/components/translation/content/microsoft-translator-attribution.png similarity index 100% rename from browser/components/translation/microsoft-translator-attribution.png rename to browser/components/translation/content/microsoft-translator-attribution.png diff --git a/browser/components/translation/content/moz.build b/browser/components/translation/content/moz.build new file mode 100644 index 000000000000..eb4454d28f88 --- /dev/null +++ b/browser/components/translation/content/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file diff --git a/browser/components/translation/content/translation-notification.js b/browser/components/translation/content/translation-notification.js new file mode 100644 index 000000000000..2dba966300fe --- /dev/null +++ b/browser/components/translation/content/translation-notification.js @@ -0,0 +1,349 @@ +/* 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"; + +class MozTranslationNotification extends MozElements.Notification { + connectedCallback() { + this.appendChild(MozXULElement.parseXULToFragment(` + + + + + + + + + + + `, [ + "chrome://global/locale/notification.dtd", + "chrome://browser/locale/translation.dtd", + ])); + + for (let [propertyName, selector] of [ + ["details", "[anonid=details]"], + ["messageImage", ".messageImage"], + ["spacer", "[anonid=spacer]"], + ]) { + this[propertyName] = this.querySelector(selector); + } + } + + set state(val) { + let deck = this._getAnonElt("translationStates"); + + let activeElt = document.activeElement; + if (activeElt && deck.contains(activeElt)) + activeElt.blur(); + + let stateName; + for (let name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) { + if (Translation["STATE_" + name] == val) { + stateName = name.toLowerCase(); + break; + } + } + this.setAttribute("state", stateName); + + if (val == Translation.STATE_TRANSLATED) + this._handleButtonHiding(); + + deck.selectedIndex = val; + } + + get state() { + return this._getAnonElt("translationStates").selectedIndex; + } + + init(aTranslation) { + this.translation = aTranslation; + + let sortByLocalizedName = function(aList) { + let names = Services.intl.getLanguageDisplayNames(undefined, aList); + return aList.map((code, i) => [code, names[i]]) + .sort((a, b) => a[1].localeCompare(b[1])); + }; + + // Fill the lists of supported source languages. + let detectedLanguage = this._getAnonElt("detectedLanguage"); + let fromLanguage = this._getAnonElt("fromLanguage"); + let sourceLanguages = + sortByLocalizedName(Translation.supportedSourceLanguages); + for (let [code, name] of sourceLanguages) { + detectedLanguage.appendItem(name, code); + fromLanguage.appendItem(name, code); + } + detectedLanguage.value = this.translation.detectedLanguage; + + // translatedFrom is only set if we have already translated this page. + if (aTranslation.translatedFrom) + fromLanguage.value = aTranslation.translatedFrom; + + // Fill the list of supported target languages. + let toLanguage = this._getAnonElt("toLanguage"); + let targetLanguages = + sortByLocalizedName(Translation.supportedTargetLanguages); + for (let [code, name] of targetLanguages) + toLanguage.appendItem(name, code); + + if (aTranslation.translatedTo) + toLanguage.value = aTranslation.translatedTo; + + if (aTranslation.state) + this.state = aTranslation.state; + + // Show attribution for the preferred translator. + let engineIndex = Object.keys(Translation.supportedEngines) + .indexOf(Translation.translationEngine); + // We currently only have attribution for the Bing and Yandex engines. + if (engineIndex >= 0) { + --engineIndex; + } + let attributionNode = this._getAnonElt("translationEngine"); + if (engineIndex != -1) { + attributionNode.selectedIndex = engineIndex; + } else { + // Hide the attribution menuitem + let footer = attributionNode.parentNode; + footer.hidden = true; + // Make the 'Translation preferences' item the new footer. + footer = footer.previousSibling; + footer.setAttribute("class", "subviewbutton panel-subview-footer"); + // And hide the menuseparator. + footer.previousSibling.hidden = true; + } + + const kWelcomePref = "browser.translation.ui.welcomeMessageShown"; + if (Services.prefs.prefHasUserValue(kWelcomePref) || + this.translation.browser != gBrowser.selectedBrowser) + return; + + this.addEventListener("transitionend", function() { + // These strings are hardcoded because they need to reach beta + // without riding the trains. + let localizedStrings = { + en: ["Hey look! It's something new!", + "Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!", + "Learn more.", + "Thanks", + ], + "es-AR": ["\xA1Mir\xE1! \xA1Hay algo nuevo!", + "Ahora la web es a\xFAn m\xE1s accesible con nuestra nueva funcionalidad de traducci\xF3n integrada. \xA1Hac\xE9 clic en el bot\xF3n traducir para probarla!", + "Conoc\xE9 m\xE1s.", + "Gracias", + ], + "es-ES": ["\xA1Mira! \xA1Hay algo nuevo!", + "Con la nueva funcionalidad de traducci\xF3n integrada, ahora la Web es a\xFAn m\xE1s accesible. \xA1Pulsa el bot\xF3n Traducir y pru\xE9bala!", + "M\xE1s informaci\xF3n.", + "Gracias", + ], + pl: ["Sp\xF3jrz tutaj! To co\u015B nowego!", + "Sie\u0107 sta\u0142a si\u0119 w\u0142a\u015Bnie jeszcze bardziej dost\u0119pna dzi\u0119ki opcji bezpo\u015Bredniego t\u0142umaczenia stron. Kliknij przycisk t\u0142umaczenia, aby spr\xF3bowa\u0107!", + "Dowiedz si\u0119 wi\u0119cej", + "Dzi\u0119kuj\u0119", + ], + tr: ["Bak\u0131n, burada yeni bir \u015Fey var!", + "Yeni sayfa i\xE7i \xE7eviri \xF6zelli\u011Fimiz sayesinde Web art\u0131k \xE7ok daha anla\u015F\u0131l\u0131r olacak. Denemek i\xE7in \xC7evir d\xFC\u011Fmesine t\u0131klay\u0131n!", + "Daha fazla bilgi al\u0131n.", + "Te\u015Fekk\xFCrler", + ], + vi: ["Nh\xECn n\xE0y! \u0110\u1ED3 m\u1EDBi!", + "Gi\u1EDD \u0111\xE2y ch\xFAng ta c\xF3 th\u1EC3 ti\u1EBFp c\u1EADn web d\u1EC5 d\xE0ng h\u01A1n n\u1EEFa v\u1EDBi t\xEDnh n\u0103ng d\u1ECBch ngay trong trang. Hay nh\u1EA5n n\xFAt d\u1ECBch \u0111\u1EC3 th\u1EED!", + "T\xECm hi\u1EC3u th\xEAm.", + "C\u1EA3m \u01A1n", + ], + }; + + let locale = Services.locale.appLocaleAsLangTag; + if (!(locale in localizedStrings)) + locale = "en"; + let strings = localizedStrings[locale]; + + this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]); + this._getAnonElt("welcomeBody").textContent = strings[1]; + this._getAnonElt("learnMore").setAttribute("value", strings[2]); + this._getAnonElt("thanksButton").setAttribute("label", strings[3]); + + let panel = this._getAnonElt("welcomePanel"); + panel.openPopup(this._getAnonElt("messageImage"), + "bottomcenter topleft"); + + Services.prefs.setBoolPref(kWelcomePref, true); + }, { once: true }); + } + + _getAnonElt(aAnonId) { + return this.querySelector("[anonid=" + aAnonId + "]"); + } + + translate() { + if (this.state == Translation.STATE_OFFER) { + this._getAnonElt("fromLanguage").value = + this._getAnonElt("detectedLanguage").value; + this._getAnonElt("toLanguage").value = + Translation.defaultTargetLanguage; + } + + this.translation.translate(this._getAnonElt("fromLanguage").value, + this._getAnonElt("toLanguage").value); + } + + /** + * To be called when the infobar should be closed per user's wish (e.g. + * by clicking the notification's close button + */ + closeCommand() { + this.close(); + this.translation.infobarClosed(); + } + + _handleButtonHiding() { + let originalShown = this.translation.originalShown; + this._getAnonElt("showOriginal").hidden = originalShown; + this._getAnonElt("showTranslation").hidden = !originalShown; + } + + showOriginal() { + this.translation.showOriginalContent(); + this._handleButtonHiding(); + } + + showTranslation() { + this.translation.showTranslatedContent(); + this._handleButtonHiding(); + } + + optionsShowing() { + // Get the source language name. + let lang; + if (this.state == Translation.STATE_OFFER) + lang = this._getAnonElt("detectedLanguage").value; + else { + lang = this._getAnonElt("fromLanguage").value; + + // If we have never attempted to translate the page before the + // service became unavailable, "fromLanguage" isn't set. + if (!lang && this.state == Translation.STATE_UNAVAILABLE) + lang = this.translation.detectedLanguage; + } + + let langName = Services.intl.getLanguageDisplayNames(undefined, [lang])[0]; + + // Set the label and accesskey on the menuitem. + let bundle = + Services.strings.createBundle("chrome://browser/locale/translation.properties"); + let item = this._getAnonElt("neverForLanguage"); + const kStrId = "translation.options.neverForLanguage"; + item.setAttribute("label", + bundle.formatStringFromName(kStrId + ".label", [langName], 1)); + item.setAttribute("accesskey", + bundle.GetStringFromName(kStrId + ".accesskey")); + item.langCode = lang; + + // We may need to disable the menuitems if they have already been used. + // Check if translation is already disabled for this language: + let neverForLangs = + Services.prefs.getCharPref("browser.translation.neverForLanguages"); + item.disabled = neverForLangs.split(",").includes(lang); + + // Check if translation is disabled for the domain: + let uri = this.translation.browser.currentURI; + let perms = Services.perms; + item = this._getAnonElt("neverForSite"); + item.disabled = + perms.testExactPermission(uri, "translate") == perms.DENY_ACTION; + } + + neverForLanguage() { + const kPrefName = "browser.translation.neverForLanguages"; + + let val = Services.prefs.getCharPref(kPrefName); + if (val) + val += ","; + val += this._getAnonElt("neverForLanguage").langCode; + + Services.prefs.setCharPref(kPrefName, val); + + this.closeCommand(); + } + + neverForSite() { + let uri = this.translation.browser.currentURI; + let perms = Services.perms; + perms.add(uri, "translate", perms.DENY_ACTION); + + this.closeCommand(); + } + + openProviderAttribution() { + Translation.openProviderAttribution(); + } +} + +customElements.define("translation-notification", MozTranslationNotification, + { extends: "notification" }); diff --git a/browser/components/translation/moz.build b/browser/components/translation/moz.build index 2d842e7756b5..11c83b695ce0 100644 --- a/browser/components/translation/moz.build +++ b/browser/components/translation/moz.build @@ -2,6 +2,10 @@ # 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/. +DIRS += [ + 'content', +] + with Files("**"): BUG_COMPONENT = ("Firefox", "Translation") @@ -17,8 +21,6 @@ EXTRA_JS_MODULES.translation = [ 'YandexTranslator.jsm' ] -JAR_MANIFESTS += ['jar.mn'] - BROWSER_CHROME_MANIFESTS += [ 'test/browser.ini' ] diff --git a/browser/components/translation/translation-infobar.xml b/browser/components/translation/translation-infobar.xml deleted file mode 100644 index 126dab21dd27..000000000000 --- a/browser/components/translation/translation-infobar.xml +++ /dev/null @@ -1,439 +0,0 @@ - - - - -%notificationDTD; - -%translationDTD; -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &translation.options.attribution.beforeLogo; - - &translation.options.attribution.afterLogo; - - &translation.options.attribution.yandexTranslate; - - - - - - - - - - - - - - - - - - - [code, names[i]]) - .sort((a, b) => a[1].localeCompare(b[1])); - }; - - // Fill the lists of supported source languages. - let detectedLanguage = this._getAnonElt("detectedLanguage"); - let fromLanguage = this._getAnonElt("fromLanguage"); - let sourceLanguages = - sortByLocalizedName(Translation.supportedSourceLanguages); - for (let [code, name] of sourceLanguages) { - detectedLanguage.appendItem(name, code); - fromLanguage.appendItem(name, code); - } - detectedLanguage.value = this.translation.detectedLanguage; - - // translatedFrom is only set if we have already translated this page. - if (aTranslation.translatedFrom) - fromLanguage.value = aTranslation.translatedFrom; - - // Fill the list of supported target languages. - let toLanguage = this._getAnonElt("toLanguage"); - let targetLanguages = - sortByLocalizedName(Translation.supportedTargetLanguages); - for (let [code, name] of targetLanguages) - toLanguage.appendItem(name, code); - - if (aTranslation.translatedTo) - toLanguage.value = aTranslation.translatedTo; - - if (aTranslation.state) - this.state = aTranslation.state; - - // Show attribution for the preferred translator. - let engineIndex = Object.keys(Translation.supportedEngines) - .indexOf(Translation.translationEngine); - // We currently only have attribution for the Bing and Yandex engines. - if (engineIndex >= 0) { - --engineIndex; - } - let attributionNode = this._getAnonElt("translationEngine"); - if (engineIndex != -1) { - attributionNode.selectedIndex = engineIndex; - } else { - // Hide the attribution menuitem - let footer = attributionNode.parentNode; - footer.hidden = true; - // Make the 'Translation preferences' item the new footer. - footer = footer.previousSibling; - footer.setAttribute("class", "subviewbutton panel-subview-footer"); - // And hide the menuseparator. - footer.previousSibling.hidden = true; - } - - const kWelcomePref = "browser.translation.ui.welcomeMessageShown"; - if (Services.prefs.prefHasUserValue(kWelcomePref) || - this.translation.browser != gBrowser.selectedBrowser) - return; - - this.addEventListener("transitionend", function() { - // These strings are hardcoded because they need to reach beta - // without riding the trains. - let localizedStrings = { - en: ["Hey look! It's something new!", - "Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!", - "Learn more.", - "Thanks"], - "es-AR": ["\xA1Mir\xE1! \xA1Hay algo nuevo!", - "Ahora la web es a\xFAn m\xE1s accesible con nuestra nueva funcionalidad de traducci\xF3n integrada. \xA1Hac\xE9 clic en el bot\xF3n traducir para probarla!", - "Conoc\xE9 m\xE1s.", - "Gracias"], - "es-ES": ["\xA1Mira! \xA1Hay algo nuevo!", - "Con la nueva funcionalidad de traducci\xF3n integrada, ahora la Web es a\xFAn m\xE1s accesible. \xA1Pulsa el bot\xF3n Traducir y pru\xE9bala!", - "M\xE1s informaci\xF3n.", - "Gracias"], - pl: ["Sp\xF3jrz tutaj! To co\u015B nowego!", - "Sie\u0107 sta\u0142a si\u0119 w\u0142a\u015Bnie jeszcze bardziej dost\u0119pna dzi\u0119ki opcji bezpo\u015Bredniego t\u0142umaczenia stron. Kliknij przycisk t\u0142umaczenia, aby spr\xF3bowa\u0107!", - "Dowiedz si\u0119 wi\u0119cej", - "Dzi\u0119kuj\u0119"], - tr: ["Bak\u0131n, burada yeni bir \u015Fey var!", - "Yeni sayfa i\xE7i \xE7eviri \xF6zelli\u011Fimiz sayesinde Web art\u0131k \xE7ok daha anla\u015F\u0131l\u0131r olacak. Denemek i\xE7in \xC7evir d\xFC\u011Fmesine t\u0131klay\u0131n!", - "Daha fazla bilgi al\u0131n.", - "Te\u015Fekk\xFCrler"], - vi: ["Nh\xECn n\xE0y! \u0110\u1ED3 m\u1EDBi!", - "Gi\u1EDD \u0111\xE2y ch\xFAng ta c\xF3 th\u1EC3 ti\u1EBFp c\u1EADn web d\u1EC5 d\xE0ng h\u01A1n n\u1EEFa v\u1EDBi t\xEDnh n\u0103ng d\u1ECBch ngay trong trang. Hay nh\u1EA5n n\xFAt d\u1ECBch \u0111\u1EC3 th\u1EED!", - "T\xECm hi\u1EC3u th\xEAm.", - "C\u1EA3m \u01A1n"], - }; - - let locale = Services.locale.appLocaleAsLangTag; - if (!(locale in localizedStrings)) - locale = "en"; - let strings = localizedStrings[locale]; - - this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]); - this._getAnonElt("welcomeBody").textContent = strings[1]; - this._getAnonElt("learnMore").setAttribute("value", strings[2]); - this._getAnonElt("thanksButton").setAttribute("label", strings[3]); - - let panel = this._getAnonElt("welcomePanel"); - panel.openPopup(this._getAnonElt("messageImage"), - "bottomcenter topleft"); - - Services.prefs.setBoolPref(kWelcomePref, true); - }, {once: true}); - ]]> - - - - - - - return document.getAnonymousElementByAttribute(this, "anonid", aAnonId); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js b/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js index adce44cd8b49..69d7ad450979 100644 --- a/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js +++ b/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js @@ -279,9 +279,7 @@ add_task(async function test_other_ignored() { Assert.ok(notification, "There should be a notification"); // Dismiss notification, creating the .dmp.ignore file - let closeButton = - document.getAnonymousElementByAttribute(notification, "anonid", "close-button"); - closeButton.click(); + notification.querySelector(".messageCloseButton").click(); gNotificationBox.removeNotification(notification, true); await waitForIgnoredReports(toIgnore); @@ -471,9 +469,7 @@ add_task(async function test_can_ignore() { Assert.ok(notification, "There should be a notification"); // Dismiss the notification by clicking on the "X" button. - let closeButton = - document.getAnonymousElementByAttribute(notification, "anonid", "close-button"); - closeButton.click(); + notification.querySelector(".messageCloseButton").click(); // We'll not wait for the notification to finish its transition - // we'll just remove it right away. gNotificationBox.removeNotification(notification, true); @@ -545,9 +541,7 @@ add_task(async function test_shutdown_while_not_showing() { Assert.ok(notification, "There should be a notification"); // Dismiss the notification by clicking on the "X" button. - let closeButton = - document.getAnonymousElementByAttribute(notification, "anonid", "close-button"); - closeButton.click(); + notification.querySelector(".messageCloseButton").click(); // We'll not wait for the notification to finish its transition - // we'll just remove it right away. gNotificationBox.removeNotification(notification, true); diff --git a/browser/themes/shared/translation/infobar.inc.css b/browser/themes/shared/translation/infobar.inc.css index 9a111be7dad2..e4960592ca0b 100644 --- a/browser/themes/shared/translation/infobar.inc.css +++ b/browser/themes/shared/translation/infobar.inc.css @@ -4,25 +4,25 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ %endif notification[value="translation"] .messageImage { - list-style-image: url(chrome://browser/skin/translation-16.png); + list-style-image: url(chrome://browser/skin/translation-16.png) !important; -moz-image-region: rect(0, 32px, 16px, 16px); } @media (min-resolution: 1.25dppx) { notification[value="translation"] .messageImage { - list-style-image: url(chrome://browser/skin/translation-16@2x.png); + list-style-image: url(chrome://browser/skin/translation-16@2x.png) !important; -moz-image-region: rect(0, 64px, 32px, 32px); } } notification[value="translation"][state="translating"] .messageImage { - list-style-image: url(chrome://browser/skin/translating-16.png); + list-style-image: url(chrome://browser/skin/translating-16.png) !important; -moz-image-region: auto; } @media (min-resolution: 1.25dppx) { notification[value="translation"][state="translating"] .messageImage { - list-style-image: url(chrome://browser/skin/translating-16@2x.png); + list-style-image: url(chrome://browser/skin/translating-16@2x.png) !important; } } diff --git a/devtools/client/scratchpad/test/browser_scratchpad_open.js b/devtools/client/scratchpad/test/browser_scratchpad_open.js index 69f52c8bd152..41eb53fd81d6 100644 --- a/devtools/client/scratchpad/test/browser_scratchpad_open.js +++ b/devtools/client/scratchpad/test/browser_scratchpad_open.js @@ -82,8 +82,10 @@ function testOpenTestFile() { is(nb.allNotifications.length, 1, "There is just one notification"); const cn = nb.currentNotification; is(cn.priority, nb.PRIORITY_WARNING_HIGH, "notification priority is correct"); - is(cn.value, "file-import-convert-failed", "notification value is corrent"); - is(cn.type, "warning", "notification type is correct"); + is(cn.getAttribute("value"), "file-import-convert-failed", + "notification value is corrent"); + is(cn.getAttribute("type"), "warning", + "notification type is correct"); done(); }); ok(true, "importFromFile does not cause exception"); diff --git a/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js b/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js index 89d4194da806..c9685d48c3df 100644 --- a/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js +++ b/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js @@ -152,7 +152,7 @@ function testOpenDeletedFile() { "The missing file was successfully removed from the menu."); ok(gScratchpad.notificationBox.currentNotification, "The notification was successfully displayed."); - is(gScratchpad.notificationBox.currentNotification.label, + is(gScratchpad.notificationBox.currentNotification.messageText.textContent, gScratchpad.strings.GetStringFromName("fileNoLongerExists.notification"), "The notification label is correct."); diff --git a/devtools/shared/fronts/csscoverage.js b/devtools/shared/fronts/csscoverage.js index 73250a7db568..b2fc0dea753a 100644 --- a/devtools/shared/fronts/csscoverage.js +++ b/devtools/shared/fronts/csscoverage.js @@ -63,7 +63,7 @@ const CSSUsageFront = protocol.FrontClassWithSpec(cssUsageSpec, { } } else { if (notification) { - notification.remove(); + notification.close(); notification = undefined; } diff --git a/testing/firefox-ui/tests/functional/safebrowsing/test_notification.py b/testing/firefox-ui/tests/functional/safebrowsing/test_notification.py index aa04dbc8b200..3d0cb26d0b28 100644 --- a/testing/firefox-ui/tests/functional/safebrowsing/test_notification.py +++ b/testing/firefox-ui/tests/functional/safebrowsing/test_notification.py @@ -146,8 +146,7 @@ class TestSafeBrowsingNotificationBar(PuppeteerMixin, MarionetteTestCase): # TODO: update to use safe browsing notification bar class when bug 1139544 lands button = (self.marionette.find_element(By.ID, 'tabbrowser-tabbox') .find_element(By.CSS_SELECTOR, 'notification[value=blocked-badware-page]') - .find_element('anon attribute', - {'class': 'messageCloseButton close-icon tabbable'})) + .find_element(By.CSS_SELECTOR, '.messageCloseButton')) button.click() Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( diff --git a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm index 4c7e626f3dc9..5f288912903f 100644 --- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm +++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm @@ -1591,7 +1591,7 @@ var BrowserTestUtils = { waitForNotificationInNotificationBox(notificationBox, notificationValue) { return new Promise((resolve) => { let check = (event) => { - return event.target.value == notificationValue; + return event.target.getAttribute("value") == notificationValue; }; BrowserTestUtils.waitForEvent(notificationBox.stack, "AlertActive", diff --git a/toolkit/components/normandy/lib/Heartbeat.jsm b/toolkit/components/normandy/lib/Heartbeat.jsm index d76ba8ae47a3..c0b418a85785 100644 --- a/toolkit/components/normandy/lib/Heartbeat.jsm +++ b/toolkit/components/normandy/lib/Heartbeat.jsm @@ -173,8 +173,8 @@ var Heartbeat = class { // Build the heartbeat stars if (!this.options.engagementButtonLabel) { const numStars = this.options.engagementButtonLabel ? 0 : 5; - const ratingContainer = this.chromeWindow.document.createXULElement("hbox"); - ratingContainer.id = "star-rating-container"; + this.ratingContainer = this.chromeWindow.document.createXULElement("hbox"); + this.ratingContainer.id = "star-rating-container"; for (let i = 0; i < numStars; i++) { // create a star rating element @@ -193,43 +193,38 @@ var Heartbeat = class { this.userEngaged({type: "stars", score: rating, flowId: this.options.flowId}); }); - ratingContainer.appendChild(ratingElement); + this.ratingContainer.appendChild(ratingElement); } - frag.appendChild(ratingContainer); + frag.appendChild(this.ratingContainer); } - const details = this.chromeWindow.document.getAnonymousElementByAttribute(this.notice, "anonid", "details"); - details.style.overflow = "hidden"; - - this.messageImage = this.chromeWindow.document.getAnonymousElementByAttribute(this.notice, "anonid", "messageImage"); - this.messageImage.classList.add("heartbeat", "pulse-onshow"); - - this.messageText = this.chromeWindow.document.getAnonymousElementByAttribute(this.notice, "anonid", "messageText"); - this.messageText.classList.add("heartbeat"); + this.notice.messageDetails.style.overflow = "hidden"; + this.notice.messageImage.classList.add("heartbeat", "pulse-onshow"); + this.notice.messageText.classList.add("heartbeat"); // Make sure the stars are not pushed to the right by the spacer. - const rightSpacer = this.chromeWindow.document.createXULElement("spacer"); - rightSpacer.flex = 20; - frag.appendChild(rightSpacer); + this.rightSpacer = this.chromeWindow.document.createXULElement("spacer"); + this.rightSpacer.flex = 20; + frag.appendChild(this.rightSpacer); // collapse the space before the stars - this.messageText.flex = 0; - const leftSpacer = this.messageText.nextSibling; - leftSpacer.flex = 0; + this.notice.messageText.flex = 0; + this.notice.spacer.flex = 0; // Add Learn More Link if (this.options.learnMoreMessage && this.options.learnMoreUrl) { - const learnMore = this.chromeWindow.document.createXULElement("label"); - learnMore.className = "text-link"; - learnMore.href = this.options.learnMoreUrl.toString(); - learnMore.setAttribute("value", this.options.learnMoreMessage); - learnMore.addEventListener("click", () => this.maybeNotifyHeartbeat("LearnMore")); - frag.appendChild(learnMore); + this.learnMore = this.chromeWindow.document.createXULElement("label"); + this.learnMore.className = "text-link"; + this.learnMore.href = this.options.learnMoreUrl.toString(); + this.learnMore.setAttribute("value", this.options.learnMoreMessage); + this.learnMore.addEventListener("click", + () => this.maybeNotifyHeartbeat("LearnMore")); + frag.appendChild(this.learnMore); } // Append the fragment and apply the styling - this.notice.appendChild(frag); + this.notice.messageDetails.appendChild(frag); this.notice.classList.add("heartbeat"); // Let the consumer know the notification was shown. @@ -330,12 +325,17 @@ var Heartbeat = class { userEngaged(engagementParams) { // Make the heartbeat icon pulse twice this.notice.label = this.options.thanksMessage; - this.messageImage.classList.remove("pulse-onshow"); - this.messageImage.classList.add("pulse-twice"); + this.notice.messageImage.classList.remove("pulse-onshow"); + this.notice.messageImage.classList.add("pulse-twice"); - // Remove all the children of the notice (rating container, and the flex) - while (this.notice.firstChild) { - this.notice.firstChild.remove(); + // Remove the custom contents of the notice and the buttons + if (this.ratingContainer) { + this.ratingContainer.remove(); + } + this.rightSpacer.remove(); + this.learnMore.remove(); + for (let button of this.notice.querySelectorAll("button")) { + button.remove(); } // Open the engagement tab if we have a valid engagement URL. @@ -381,8 +381,10 @@ var Heartbeat = class { // remove references for garbage collection this.chromeWindow = null; this.notificationBox = null; - this.notification = null; this.notice = null; + this.ratingContainer = null; + this.rightSpacer = null; + this.learnMore = null; this.eventEmitter = null; this.sandboxManager = null; // Ensure we don't re-enter and release the CleanupManager's reference to us: diff --git a/toolkit/components/normandy/skin/shared/Heartbeat.css b/toolkit/components/normandy/skin/shared/Heartbeat.css index fc8eefb4bd60..7f39bc9786c8 100644 --- a/toolkit/components/normandy/skin/shared/Heartbeat.css +++ b/toolkit/components/normandy/skin/shared/Heartbeat.css @@ -77,43 +77,43 @@ notification.heartbeat { } /* Learn More link styles */ -.heartbeat > .text-link { +.heartbeat > hbox > .text-link { color: #0095DD !important; margin-inline-start: 0 !important; } -.heartbeat > .text-link:hover { +.heartbeat > hbox > .text-link:hover { color: #008ACB !important; text-decoration: none !important; } -.heartbeat > .text-link:hover:active { +.heartbeat > hbox > .text-link:hover:active { color: #006B9D !important; } /* Heartbeat UI Rating Star Classes */ -.heartbeat > #star-rating-container { +#star-rating-container { display: -moz-box; margin-bottom: 4px; } -.heartbeat > #star-rating-container > #star5 { +#star-rating-container > #star5 { -moz-box-ordinal-group: 5; } -.heartbeat > #star-rating-container > #star4 { +#star-rating-container > #star4 { -moz-box-ordinal-group: 4; } -.heartbeat > #star-rating-container > #star3 { +#star-rating-container > #star3 { -moz-box-ordinal-group: 3; } -.heartbeat > #star-rating-container > #star2 { +#star-rating-container > #star2 { -moz-box-ordinal-group: 2; } -.heartbeat > #star-rating-container > .star-x { +#star-rating-container > .star-x { background: url("resource://normandy/skin/shared/heartbeat-star-off.svg"); cursor: pointer; height: 16px; @@ -121,7 +121,7 @@ notification.heartbeat { width: 16px; } -.heartbeat > #star-rating-container > .star-x:hover, -.heartbeat > #star-rating-container > .star-x:hover ~ .star-x { +#star-rating-container > .star-x:hover, +#star-rating-container > .star-x:hover ~ .star-x { background: url("resource://normandy/skin/shared/heartbeat-star-lit.svg"); } diff --git a/toolkit/components/normandy/test/browser/browser_Heartbeat.js b/toolkit/components/normandy/test/browser/browser_Heartbeat.js index aebf5dd238e9..01b2f4fbfeaa 100644 --- a/toolkit/components/normandy/test/browser/browser_Heartbeat.js +++ b/toolkit/components/normandy/test/browser/browser_Heartbeat.js @@ -93,13 +93,12 @@ add_task(async function() { // Check UI const learnMoreEl = hb.notice.querySelector(".text-link"); - const messageEl = targetWindow.document.getAnonymousElementByAttribute(hb.notice, "anonid", "messageText"); Assert.equal(notificationBox.allNotifications.length, preCount + 1, "Correct number of notifications open"); Assert.equal(hb.notice.querySelectorAll(".star-x").length, 5, "Correct number of stars"); Assert.equal(hb.notice.querySelectorAll(".notification-button").length, 0, "Engagement button not shown"); Assert.equal(learnMoreEl.href, "https://example.org/learnmore", "Learn more url correct"); Assert.equal(learnMoreEl.value, "Learn More", "Learn more label correct"); - Assert.equal(messageEl.textContent, "test", "Message is correct"); + Assert.equal(hb.notice.messageText.textContent, "test", "Message is correct"); // Check that when clicking the learn more link, a tab opens with the right URL let loadedPromise; diff --git a/toolkit/content/tests/chrome/test_notificationbox.xul b/toolkit/content/tests/chrome/test_notificationbox.xul index d0c8fb917874..247d2fc79fb9 100644 --- a/toolkit/content/tests/chrome/test_notificationbox.xul +++ b/toolkit/content/tests/chrome/test_notificationbox.xul @@ -301,8 +301,8 @@ var tests = return ntf; }, result: function(nb, ntf) { - SimpleTest.is(nb.currentNotification == ntf ? - nb.currentNotification.value : null, "10", "appendNotification order"); + is(nb.currentNotification, ntf, "appendNotification last notification"); + is(nb.currentNotification.getAttribute("value"), "10", "appendNotification order"); return 1; } }, @@ -333,8 +333,8 @@ var tests = }, result: function(nb, arr) { // arr is [testindex, expectedvalue] - SimpleTest.is(nb.currentNotification.value, "" + arr[1], "close order " + arr[0]); - SimpleTest.is(nb.allNotifications.length, 10 - arr[0], "close order " + arr[0] + " count"); + is(nb.currentNotification.getAttribute("value"), "" + arr[1], "close order " + arr[0]); + is(nb.allNotifications.length, 10 - arr[0], "close order " + arr[0] + " count"); if (arr[0] == 6) this.repeat = false; return ++arr[0]; @@ -400,10 +400,10 @@ function testtag_notificationbox_State(nb, testid, expecteditem, expectedcount) function testtag_notification_State(nb, ntf, testid, label, value, image, priority) { - SimpleTest.is(ntf.label, label, testid + " notification.label"); - SimpleTest.is(ntf.value, value, testid + " notification.value"); - SimpleTest.is(ntf.image, image, testid + " notification.image"); - SimpleTest.is(ntf.priority, priority, testid + " notification.priority"); + is(ntf.messageText.textContent, label, testid + " notification label"); + is(ntf.getAttribute("value"), value, testid + " notification value"); + is(ntf.messageImage.getAttribute("src"), image, testid + " notification image"); + is(ntf.priority, priority, testid + " notification priority"); var type; switch (priority) { @@ -424,7 +424,7 @@ function testtag_notification_State(nb, ntf, testid, label, value, image, priori break; } - SimpleTest.is(ntf.type, type, testid + " notification.type"); + is(ntf.getAttribute("type"), type, testid + " notification type"); } function checkPopupTest(nb, ntf) diff --git a/toolkit/content/widgets/notification.xml b/toolkit/content/widgets/notification.xml index 357f9ca1deea..337e15104f07 100644 --- a/toolkit/content/widgets/notification.xml +++ b/toolkit/content/widgets/notification.xml @@ -15,99 +15,6 @@ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html = "http://www.w3.org/1999/xhtml"> - - - - - - - - - - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/toolkit/content/widgets/notificationbox.js b/toolkit/content/widgets/notificationbox.js index b5306876a6be..ab5ece46a0c9 100644 --- a/toolkit/content/widgets/notificationbox.js +++ b/toolkit/content/widgets/notificationbox.js @@ -64,7 +64,49 @@ MozElements.NotificationBox = class NotificationBox { return null; } - appendNotification(aLabel, aValue, aImage, aPriority, aButtons, aEventCallback) { + /** + * Creates a element and shows it. The calling code can modify + * the element synchronously to add features to the notification. + * + * @param aLabel + * The main message text, or a DocumentFragment containing elements to + * add as children of the notification's main element. + * @param aValue + * String identifier of the notification. + * @param aImage + * URL of the icon image to display. If not specified, a default icon + * based on the priority will be shown. + * @param aPriority + * One of the PRIORITY_ constants. These determine the appearance of + * the notification based on severity (using the "type" attribute), and + * only the notification with the highest priority is displayed. + * @param aButtons + * Array of objects defining action buttons: + * { + * label: + * Label of the