Bug 1503657 - Implement Fluent DOMOverlays in C++. r=smaug,Pike

Differential Revision: https://phabricator.services.mozilla.com/D27200

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Zibi Braniecki 2019-04-24 05:05:11 +00:00
parent d6b38f2162
commit 0890bbe4c2
17 changed files with 757 additions and 14 deletions

View File

@ -239,6 +239,10 @@ DOMInterfaces = {
'headerFile': 'mozilla/dom/DOMMatrix.h',
},
'DOMOverlays': {
'nativeType': 'mozilla::dom::l10n::DOMOverlays',
},
'DOMPointReadOnly': {
'headerFile': 'mozilla/dom/DOMPoint.h',
},

View File

@ -0,0 +1,21 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
dictionary DOMOverlaysError {
short code;
DOMString translatedElementName;
DOMString sourceElementName;
DOMString l10nName;
};
[ChromeOnly]
namespace DOMOverlays {
const unsigned short ERROR_FORBIDDEN_TYPE = 1;
const unsigned short ERROR_NAMED_ELEMENT_MISSING = 2;
const unsigned short ERROR_NAMED_ELEMENT_TYPE_MISMATCH = 3;
const unsigned short ERROR_UNKNOWN = 4;
sequence<DOMOverlaysError>? translateElement(Element element, optional L10nValue translation);
};

View File

@ -36,6 +36,7 @@ WEBIDL_FILES = [
'BrowsingContext.webidl',
'ChannelWrapper.webidl',
'DominatorTree.webidl',
'DOMOverlays.webidl',
'Flex.webidl',
'HeapSnapshot.webidl',
'InspectorUtils.webidl',

View File

@ -22,6 +22,8 @@ class HTMLTemplateElement final : public nsGenericHTMLElement {
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLTemplateElement, _template);
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTemplateElement,
nsGenericHTMLElement)

483
dom/l10n/DOMOverlays.cpp Normal file
View File

@ -0,0 +1,483 @@
#include "DOMOverlays.h"
#include "mozilla/dom/HTMLTemplateElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "HTMLSplitOnSpacesTokenizer.h"
#include "nsHtml5StringParser.h"
#include "nsTextNode.h"
using namespace mozilla::dom::l10n;
using namespace mozilla::dom;
using namespace mozilla;
bool DOMOverlays::IsAttrNameLocalizable(
const nsAtom* nameAtom, Element* aElement,
nsTArray<nsString>* aExplicitlyAllowed) {
nsAutoString name;
nameAtom->ToString(name);
if (aExplicitlyAllowed->Contains(name)) {
return true;
}
nsAtom* elemName = aElement->NodeInfo()->NameAtom();
uint32_t nameSpace = aElement->NodeInfo()->NamespaceID();
if (nameSpace == kNameSpaceID_XHTML) {
// Is it a globally safe attribute?
if (nameAtom == nsGkAtoms::title || nameAtom == nsGkAtoms::aria_label ||
nameAtom == nsGkAtoms::aria_valuetext) {
return true;
}
// Is it allowed on this element?
if (elemName == nsGkAtoms::a) {
return nameAtom == nsGkAtoms::download;
}
if (elemName == nsGkAtoms::area) {
return nameAtom == nsGkAtoms::download || nameAtom == nsGkAtoms::alt;
}
if (elemName == nsGkAtoms::input) {
// Special case for value on HTML inputs with type button, reset, submit
if (nameAtom == nsGkAtoms::value) {
HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
if (input) {
uint32_t type = input->ControlType();
if (type == NS_FORM_INPUT_SUBMIT || type == NS_FORM_INPUT_BUTTON ||
type == NS_FORM_INPUT_RESET) {
return true;
}
}
}
return nameAtom == nsGkAtoms::alt || nameAtom == nsGkAtoms::placeholder;
}
if (elemName == nsGkAtoms::menuitem) {
return nameAtom == nsGkAtoms::label;
}
if (elemName == nsGkAtoms::menu) {
return nameAtom == nsGkAtoms::label;
}
if (elemName == nsGkAtoms::optgroup) {
return nameAtom == nsGkAtoms::label;
}
if (elemName == nsGkAtoms::option) {
return nameAtom == nsGkAtoms::label;
}
if (elemName == nsGkAtoms::track) {
return nameAtom == nsGkAtoms::label;
}
if (elemName == nsGkAtoms::img) {
return nameAtom == nsGkAtoms::alt;
}
if (elemName == nsGkAtoms::textarea) {
return nameAtom == nsGkAtoms::placeholder;
}
if (elemName == nsGkAtoms::th) {
return nameAtom == nsGkAtoms::abbr;
}
} else if (nameSpace == kNameSpaceID_XUL) {
// Is it a globally safe attribute?
if (nameAtom == nsGkAtoms::accesskey || nameAtom == nsGkAtoms::aria_label ||
nameAtom == nsGkAtoms::aria_valuetext || nameAtom == nsGkAtoms::label ||
nameAtom == nsGkAtoms::title || nameAtom == nsGkAtoms::tooltiptext) {
return true;
}
// Is it allowed on this element?
if (elemName == nsGkAtoms::description) {
return nameAtom == nsGkAtoms::value;
}
if (elemName == nsGkAtoms::key) {
return nameAtom == nsGkAtoms::key || nameAtom == nsGkAtoms::keycode;
}
if (elemName == nsGkAtoms::label) {
return nameAtom == nsGkAtoms::value;
}
if (elemName == nsGkAtoms::textbox) {
return nameAtom == nsGkAtoms::placeholder || nameAtom == nsGkAtoms::value;
}
}
return false;
}
already_AddRefed<nsINode> DOMOverlays::CreateTextNodeFromTextContent(
Element* aElement, ErrorResult& aRv) {
nsAutoString content;
aElement->GetTextContent(content, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return aElement->OwnerDoc()->CreateTextNode(content);
}
class AttributeNameValueComparator {
public:
bool Equals(const AttributeNameValue& aAttribute,
const nsAttrName* aAttrName) const {
return aAttrName->Equals(aAttribute.mName);
}
};
void DOMOverlays::OverlayAttributes(
const Nullable<Sequence<AttributeNameValue>>& aTranslation,
Element* aToElement, ErrorResult& aRv) {
nsTArray<nsString> explicitlyAllowed;
nsAutoString l10nAttrs;
aToElement->GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nattrs, l10nAttrs);
HTMLSplitOnSpacesTokenizer tokenizer(l10nAttrs, ',');
while (tokenizer.hasMoreTokens()) {
const nsAString& token = tokenizer.nextToken();
if (!token.IsEmpty() && !explicitlyAllowed.Contains(token)) {
explicitlyAllowed.AppendElement(token);
}
}
uint32_t i = aToElement->GetAttrCount();
while (i > 0) {
const nsAttrName* attrName = aToElement->GetAttrNameAt(i - 1);
if (IsAttrNameLocalizable(attrName->LocalName(), aToElement,
&explicitlyAllowed) &&
(aTranslation.IsNull() ||
!aTranslation.Value().Contains(attrName,
AttributeNameValueComparator()))) {
nsAutoString name;
attrName->LocalName()->ToString(name);
aToElement->RemoveAttribute(name, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
i--;
}
if (aTranslation.IsNull()) {
return;
}
for (auto& attribute : aTranslation.Value()) {
nsString attrName = attribute.mName;
RefPtr<nsAtom> nameAtom = NS_Atomize(attrName);
if (IsAttrNameLocalizable(nameAtom, aToElement, &explicitlyAllowed)) {
nsString value = attribute.mValue;
if (!aToElement->AttrValueIs(kNameSpaceID_None, nameAtom, value,
eCaseMatters)) {
aToElement->SetAttr(nameAtom, value, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
}
}
}
void DOMOverlays::OverlayAttributes(Element* aFromElement, Element* aToElement,
ErrorResult& aRv) {
Nullable<Sequence<AttributeNameValue>> attributes;
uint32_t attrCount = aFromElement->GetAttrCount();
if (attrCount == 0) {
attributes.SetNull();
} else {
Sequence<AttributeNameValue> sequence;
uint32_t i = 0;
while (BorrowedAttrInfo info = aFromElement->GetAttrInfoAt(i++)) {
AttributeNameValue* attr = sequence.AppendElement(fallible);
MOZ_ASSERT(info.mName->NamespaceEquals(kNameSpaceID_None),
"No namespaced attributes allowed.");
info.mName->LocalName()->ToString(attr->mName);
info.mValue->ToString(attr->mValue);
}
attributes.SetValue(sequence);
}
return OverlayAttributes(attributes, aToElement, aRv);
}
void DOMOverlays::ShallowPopulateUsing(Element* aFromElement,
Element* aToElement, ErrorResult& aRv) {
nsAutoString content;
aFromElement->GetTextContent(content, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
aToElement->SetTextContent(content, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
OverlayAttributes(aFromElement, aToElement, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
already_AddRefed<nsINode> DOMOverlays::GetNodeForNamedElement(
Element* aSourceElement, Element* aTranslatedChild,
nsTArray<DOMOverlaysError>& aErrors, ErrorResult& aRv) {
nsAutoString childName;
aTranslatedChild->GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nname,
childName);
RefPtr<Element> sourceChild = nullptr;
nsINodeList* childNodes = aSourceElement->ChildNodes();
for (uint32_t i = 0; i < childNodes->Length(); i++) {
nsINode* childNode = childNodes->Item(i);
if (!childNode->IsElement()) {
continue;
}
Element* childElement = childNode->AsElement();
if (childElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datal10nname,
childName, eCaseMatters)) {
sourceChild = childElement;
break;
}
}
if (!sourceChild) {
DOMOverlaysError error;
error.mCode.Construct(DOMOverlays_Binding::ERROR_NAMED_ELEMENT_MISSING);
error.mL10nName.Construct(childName);
aErrors.AppendElement(error);
return CreateTextNodeFromTextContent(aTranslatedChild, aRv);
}
nsAtom* sourceChildName = sourceChild->NodeInfo()->NameAtom();
nsAtom* translatedChildName = aTranslatedChild->NodeInfo()->NameAtom();
if (sourceChildName != translatedChildName &&
// Create a specific exception for img vs. image mismatches,
// see bug 1543493
!(translatedChildName == nsGkAtoms::img &&
sourceChildName == nsGkAtoms::image)) {
DOMOverlaysError error;
error.mCode.Construct(
DOMOverlays_Binding::ERROR_NAMED_ELEMENT_TYPE_MISMATCH);
error.mL10nName.Construct(childName);
error.mTranslatedElementName.Construct(
aTranslatedChild->NodeInfo()->LocalName());
error.mSourceElementName.Construct(sourceChild->NodeInfo()->LocalName());
aErrors.AppendElement(error);
return CreateTextNodeFromTextContent(aTranslatedChild, aRv);
}
aSourceElement->RemoveChild(*sourceChild, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
RefPtr<nsINode> clone = sourceChild->CloneNode(false, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
ShallowPopulateUsing(aTranslatedChild, clone->AsElement(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return clone.forget();
}
bool DOMOverlays::IsElementAllowed(Element* aElement) {
uint32_t nameSpace = aElement->NodeInfo()->NamespaceID();
if (nameSpace != kNameSpaceID_XHTML) {
return false;
}
nsAtom* nameAtom = aElement->NodeInfo()->NameAtom();
return nameAtom == nsGkAtoms::em || nameAtom == nsGkAtoms::strong ||
nameAtom == nsGkAtoms::small || nameAtom == nsGkAtoms::s ||
nameAtom == nsGkAtoms::cite || nameAtom == nsGkAtoms::q ||
nameAtom == nsGkAtoms::dfn || nameAtom == nsGkAtoms::abbr ||
nameAtom == nsGkAtoms::data || nameAtom == nsGkAtoms::time ||
nameAtom == nsGkAtoms::code || nameAtom == nsGkAtoms::var ||
nameAtom == nsGkAtoms::samp || nameAtom == nsGkAtoms::kbd ||
nameAtom == nsGkAtoms::sub || nameAtom == nsGkAtoms::sup ||
nameAtom == nsGkAtoms::i || nameAtom == nsGkAtoms::b ||
nameAtom == nsGkAtoms::u || nameAtom == nsGkAtoms::mark ||
nameAtom == nsGkAtoms::bdi || nameAtom == nsGkAtoms::bdo ||
nameAtom == nsGkAtoms::span || nameAtom == nsGkAtoms::br ||
nameAtom == nsGkAtoms::wbr;
}
already_AddRefed<Element> DOMOverlays::CreateSanitizedElement(
Element* aElement, ErrorResult& aRv) {
// Start with an empty element of the same type to remove nested children
// and non-localizable attributes defined by the translation.
ElementCreationOptionsOrString options;
RefPtr<Element> clone = aElement->OwnerDoc()->CreateElement(
aElement->NodeInfo()->LocalName(), options, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
ShallowPopulateUsing(aElement, clone, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return clone.forget();
}
void DOMOverlays::OverlayChildNodes(DocumentFragment* aFromFragment,
Element* aToElement,
nsTArray<DOMOverlaysError>& aErrors,
ErrorResult& aRv) {
nsINodeList* childNodes = aFromFragment->ChildNodes();
for (uint32_t i = 0; i < childNodes->Length(); i++) {
nsINode* childNode = childNodes->Item(i);
if (!childNode->IsElement()) {
// Keep the translated text node.
continue;
}
RefPtr<Element> childElement = childNode->AsElement();
if (childElement->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nname)) {
RefPtr<nsINode> sanitized =
GetNodeForNamedElement(aToElement, childElement, aErrors, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
aFromFragment->ReplaceChild(*sanitized, *childNode, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
continue;
}
if (IsElementAllowed(childElement)) {
RefPtr<Element> sanitized = CreateSanitizedElement(childElement, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
aFromFragment->ReplaceChild(*sanitized, *childNode, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
continue;
}
DOMOverlaysError error;
error.mCode.Construct(DOMOverlays_Binding::ERROR_FORBIDDEN_TYPE);
error.mTranslatedElementName.Construct(
childElement->NodeInfo()->LocalName());
aErrors.AppendElement(error);
// If all else fails, replace the element with its text content.
RefPtr<nsINode> textNode = CreateTextNodeFromTextContent(childElement, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
aFromFragment->ReplaceChild(*textNode, *childNode, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
while (aToElement->HasChildren()) {
aToElement->RemoveChildNode(aToElement->GetLastChild(), true);
}
aToElement->AppendChild(*aFromFragment, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
void DOMOverlays::TranslateElement(
const GlobalObject& aGlobal, Element& aElement,
const L10nValue& aTranslation,
Nullable<nsTArray<DOMOverlaysError>>& aErrors) {
nsTArray<DOMOverlaysError> errors;
ErrorResult rv;
TranslateElement(aElement, aTranslation, errors, rv);
if (NS_WARN_IF(rv.Failed())) {
DOMOverlaysError error;
error.mCode.Construct(DOMOverlays_Binding::ERROR_UNKNOWN);
errors.AppendElement(error);
}
if (!errors.IsEmpty()) {
aErrors.SetValue(errors);
}
}
bool DOMOverlays::ContainsMarkup(const nsAString& aStr) {
// We use our custom ContainsMarkup rather than the
// one from FragmentOrElement.cpp, because we don't
// want to trigger HTML parsing on every `Preferences & Options`
// type of string.
const char16_t* start = aStr.BeginReading();
const char16_t* end = aStr.EndReading();
while (start != end) {
char16_t c = *start;
if (c == char16_t('<')) {
return true;
}
++start;
if (c == char16_t('&') && start != end) {
c = *start;
if (c == char16_t('#') || (c >= char16_t('0') && c <= char16_t('9')) ||
(c >= char16_t('a') && c <= char16_t('z')) ||
(c >= char16_t('A') && c <= char16_t('Z'))) {
return true;
}
++start;
}
}
return false;
}
void DOMOverlays::TranslateElement(Element& aElement,
const L10nValue& aTranslation,
nsTArray<DOMOverlaysError>& aErrors,
ErrorResult& aRv) {
if (!aTranslation.mValue.IsVoid()) {
if (!ContainsMarkup(aTranslation.mValue)) {
// If the translation doesn't contain any markup skip the overlay logic.
aElement.SetTextContent(aTranslation.mValue, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
// Else parse the translation's HTML into a DocumentFragment,
// sanitize it and replace the element's content.
RefPtr<DocumentFragment> fragment =
new DocumentFragment(aElement.OwnerDoc()->NodeInfoManager());
nsContentUtils::ParseFragmentHTML(
aTranslation.mValue, fragment, nsGkAtoms::_template,
kNameSpaceID_XHTML, false, true);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
OverlayChildNodes(fragment, &aElement, aErrors, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
}
// Even if the translation doesn't define any localizable attributes, run
// overlayAttributes to remove any localizable attributes set by previous
// translations.
OverlayAttributes(aTranslation.mAttributes, &aElement, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}

118
dom/l10n/DOMOverlays.h Normal file
View File

@ -0,0 +1,118 @@
#ifndef mozilla_dom_l10n_DOMOverlays_h__
#define mozilla_dom_l10n_DOMOverlays_h__
#include "mozilla/dom/Element.h"
#include "mozilla/dom/L10nUtilsBinding.h"
#include "mozilla/dom/DOMOverlaysBinding.h"
namespace mozilla {
namespace dom {
namespace l10n {
class DOMOverlays {
public:
/**
* Translate an element.
*
* Translate the element's text content and attributes. Some HTML markup is
* allowed in the translation. The element's children with the data-l10n-name
* attribute will be treated as arguments to the translation. If the
* translation defines the same children, their attributes and text contents
* will be used for translating the matching source child.
*/
static void TranslateElement(
const GlobalObject& aGlobal, Element& aElement,
const L10nValue& aTranslation,
Nullable<nsTArray<mozilla::dom::DOMOverlaysError>>& aErrors);
static void TranslateElement(
Element& aElement, const L10nValue& aTranslation,
nsTArray<mozilla::dom::DOMOverlaysError>& aErrors, ErrorResult& aRv);
private:
/**
* Check if attribute is allowed for the given element.
*
* This method is used by the sanitizer when the translation markup contains
* DOM attributes, or when the translation has traits which map to DOM
* attributes.
*
* `aExplicitlyAllowed` can be passed as a list of attributes explicitly
* allowed on this element.
*/
static bool IsAttrNameLocalizable(const nsAtom* nameAtom, Element* aElement,
nsTArray<nsString>* aExplicitlyAllowed);
/**
* Create a text node from text content of an Element.
*/
static already_AddRefed<nsINode> CreateTextNodeFromTextContent(
Element* aElement, ErrorResult& aRv);
/**
* Transplant localizable attributes of an element to another element.
*
* Any localizable attributes already set on the target element will be
* cleared.
*/
static void OverlayAttributes(
const Nullable<Sequence<AttributeNameValue>>& aTranslation,
Element* aToElement, ErrorResult& aRv);
static void OverlayAttributes(Element* aFromElement, Element* aToElement,
ErrorResult& aRv);
/**
* Helper to set textContent and localizable attributes on an element.
*/
static void ShallowPopulateUsing(Element* aFromElement, Element* aToElement,
ErrorResult& aRv);
/**
* Sanitize a child element created by the translation.
*
* Try to find a corresponding child in sourceElement and use it as the base
* for the sanitization. This will preserve functional attributes defined on
* the child element in the source HTML.
*/
static already_AddRefed<nsINode> GetNodeForNamedElement(
Element* aSourceElement, Element* aTranslatedChild,
nsTArray<DOMOverlaysError>& aErrors, ErrorResult& aRv);
/**
* Check if element is allowed in the translation.
*
* This method is used by the sanitizer when the translation markup contains
* an element which is not present in the source code.
*/
static bool IsElementAllowed(Element* aElement);
/**
* Sanitize an allowed element.
*
* Text-level elements allowed in translations may only use safe attributes
* and will have any nested markup stripped to text content.
*/
static already_AddRefed<Element> CreateSanitizedElement(Element* aElement,
ErrorResult& aRv);
/**
* Replace child nodes of an element with child nodes of another element.
*
* The contents of the target element will be cleared and fully replaced with
* sanitized contents of the source element.
*/
static void OverlayChildNodes(DocumentFragment* aFromFragment,
Element* aToElement,
nsTArray<DOMOverlaysError>& aErrors,
ErrorResult& aRv);
/**
* A helper used to test if the string contains HTML markup.
*/
static bool ContainsMarkup(const nsAString& aStr);
};
} // namespace l10n
} // namespace dom
} // namespace mozilla
#endif

8
dom/l10n/components.conf Normal file
View File

@ -0,0 +1,8 @@
Classes = [
{
'cid': '{8d85597c-3a92-11e9-9ffc-73d225b2d53f}',
'contract_ids': ['@mozilla.org/dom/l10n/domoverlays;1'],
'type': 'mozilla::dom::l10n::DOMOverlays',
'headers': ['/dom/l10n/DOMOverlays.h'],
},
]

View File

@ -7,4 +7,21 @@
with Files("**"):
BUG_COMPONENT = ("Core", "Internationalization")
EXPORTS.mozilla.dom.l10n += [
'DOMOverlays.h',
]
UNIFIED_SOURCES += [
'DOMOverlays.cpp',
]
LOCAL_INCLUDES += [
'/dom/base',
]
FINAL_LIBRARY = 'xul'
MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
if CONFIG['ENABLE_TESTS']:
DIRS += ['tests/gtest']

View File

@ -0,0 +1,81 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "gtest/gtest.h"
#include "mozilla/dom/l10n/DOMOverlays.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DOMOverlaysBinding.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/L10nUtilsBinding.h"
#include "mozilla/NullPrincipal.h"
#include "nsNetUtil.h"
using mozilla::NullPrincipal;
using namespace mozilla::dom;
using namespace mozilla::dom::l10n;
already_AddRefed<Document> SetUpDocument() {
nsCOMPtr<nsIURI> uri;
NS_NewURI(getter_AddRefs(uri), "about:blank");
nsCOMPtr<nsIPrincipal> principal =
NullPrincipal::CreateWithoutOriginAttributes();
nsCOMPtr<Document> document;
nsresult rv = NS_NewDOMDocument(getter_AddRefs(document),
EmptyString(), // aNamespaceURI
EmptyString(), // aQualifiedName
nullptr, // aDoctype
uri, uri, principal,
false, // aLoadedAsData
nullptr, // aEventObject
DocumentFlavorHTML);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
return document.forget();
}
/**
* This test verifies that the basic C++ DOMOverlays API
* works correctly.
*/
TEST(DOM_L10n_DOMOverlays, Initial)
{
mozilla::ErrorResult rv;
// 1. Set up an HTML document.
nsCOMPtr<Document> doc = SetUpDocument();
// 2. Create a simple Element with a child.
//
// <div>
// <a data-l10n-name="link" href="https://www.mozilla.org"></a>
// </div>
//
RefPtr<Element> elem = doc->CreateHTMLElement(nsGkAtoms::div);
RefPtr<Element> span = doc->CreateHTMLElement(nsGkAtoms::a);
span->SetAttribute(NS_LITERAL_STRING("data-l10n-name"),
NS_LITERAL_STRING("link"), rv);
span->SetAttribute(NS_LITERAL_STRING("href"),
NS_LITERAL_STRING("https://www.mozilla.org"), rv);
elem->AppendChild(*span, rv);
// 3. Create an L10nValue with a translation for the element.
L10nValue translation;
translation.mValue.AssignLiteral(
"Hello <a data-l10n-name=\"link\">World</a>.");
// 4. Translate the element.
nsTArray<DOMOverlaysError> errors;
DOMOverlays::TranslateElement(*elem, translation, errors, rv);
nsAutoString textContent;
elem->GetInnerHTML(textContent, rv);
// 5. Verify that the innerHTML matches the expectations.
ASSERT_STREQ(NS_ConvertUTF16toUTF8(textContent).get(),
"Hello <a data-l10n-name=\"link\" "
"href=\"https://www.mozilla.org\">World</a>.");
}

View File

@ -0,0 +1,11 @@
# -*- 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/.
UNIFIED_SOURCES += [
'TestDOMOverlays.cpp',
]
FINAL_LIBRARY = 'xul-gtest'

View File

@ -16,7 +16,7 @@
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript">
<![CDATA[
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
/* global DOMOverlays */
function elem(name) {
return function(str) {
@ -27,7 +27,7 @@
};
}
const { translateElement } = DOMLocalization.DOMOverlays;
const { translateElement } = DOMOverlays;
SimpleTest.waitForExplicitFinish();

View File

@ -6,10 +6,9 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
/* global DOMOverlays */
"use strict";
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
function elem(name) {
return function(str) {
const element = document.createElement(name);
@ -19,7 +18,7 @@
};
}
const { translateElement } = DOMLocalization.DOMOverlays;
const { translateElement } = DOMOverlays;
{
// Allowed attribute

View File

@ -6,10 +6,9 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
/* global DOMOverlays */
"use strict";
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
function elem(name) {
return function(str) {
const element = document.createElement(name);
@ -19,7 +18,7 @@
};
}
const { translateElement } = DOMLocalization.DOMOverlays;
const { translateElement } = DOMOverlays;
// Localized text markup
{

View File

@ -6,8 +6,8 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
/* global DOMOverlays */
"use strict";
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
function elem(name) {
return function(str) {
@ -18,7 +18,7 @@
};
}
const { translateElement } = DOMLocalization.DOMOverlays;
const { translateElement } = DOMOverlays;
// Child without name
{

View File

@ -6,8 +6,8 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
/* global DOMOverlays */
"use strict";
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
function elem(name) {
return function(str) {
@ -18,7 +18,7 @@
};
}
const { translateElement } = DOMLocalization.DOMOverlays;
const { translateElement } = DOMOverlays;
{
// without data-l10n-name

View File

@ -854,8 +854,6 @@ class DOMLocalization extends Localization {
}
}
DOMLocalization.DOMOverlays = { translateElement };
/**
* Helper function which allows us to construct a new
* DOMLocalization from DocumentL10n.

View File

@ -289,6 +289,7 @@ STATIC_ATOMS = [
Atom("datal10nid", "data-l10n-id"),
Atom("datal10nargs", "data-l10n-args"),
Atom("datal10nattrs", "data-l10n-attrs"),
Atom("datal10nname", "data-l10n-name"),
Atom("dataType", "data-type"),
Atom("dateTime", "date-time"),
Atom("date", "date"),