зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
d6b38f2162
Коммит
0890bbe4c2
|
@ -239,6 +239,10 @@ DOMInterfaces = {
|
||||||
'headerFile': 'mozilla/dom/DOMMatrix.h',
|
'headerFile': 'mozilla/dom/DOMMatrix.h',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'DOMOverlays': {
|
||||||
|
'nativeType': 'mozilla::dom::l10n::DOMOverlays',
|
||||||
|
},
|
||||||
|
|
||||||
'DOMPointReadOnly': {
|
'DOMPointReadOnly': {
|
||||||
'headerFile': 'mozilla/dom/DOMPoint.h',
|
'headerFile': 'mozilla/dom/DOMPoint.h',
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
|
@ -36,6 +36,7 @@ WEBIDL_FILES = [
|
||||||
'BrowsingContext.webidl',
|
'BrowsingContext.webidl',
|
||||||
'ChannelWrapper.webidl',
|
'ChannelWrapper.webidl',
|
||||||
'DominatorTree.webidl',
|
'DominatorTree.webidl',
|
||||||
|
'DOMOverlays.webidl',
|
||||||
'Flex.webidl',
|
'Flex.webidl',
|
||||||
'HeapSnapshot.webidl',
|
'HeapSnapshot.webidl',
|
||||||
'InspectorUtils.webidl',
|
'InspectorUtils.webidl',
|
||||||
|
|
|
@ -22,6 +22,8 @@ class HTMLTemplateElement final : public nsGenericHTMLElement {
|
||||||
// nsISupports
|
// nsISupports
|
||||||
NS_DECL_ISUPPORTS_INHERITED
|
NS_DECL_ISUPPORTS_INHERITED
|
||||||
|
|
||||||
|
NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLTemplateElement, _template);
|
||||||
|
|
||||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTemplateElement,
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTemplateElement,
|
||||||
nsGenericHTMLElement)
|
nsGenericHTMLElement)
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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'],
|
||||||
|
},
|
||||||
|
]
|
|
@ -7,4 +7,21 @@
|
||||||
with Files("**"):
|
with Files("**"):
|
||||||
BUG_COMPONENT = ("Core", "Internationalization")
|
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']
|
MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
|
||||||
|
|
||||||
|
if CONFIG['ENABLE_TESTS']:
|
||||||
|
DIRS += ['tests/gtest']
|
||||||
|
|
|
@ -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>.");
|
||||||
|
}
|
|
@ -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'
|
|
@ -16,7 +16,7 @@
|
||||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
|
/* global DOMOverlays */
|
||||||
|
|
||||||
function elem(name) {
|
function elem(name) {
|
||||||
return function(str) {
|
return function(str) {
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { translateElement } = DOMLocalization.DOMOverlays;
|
const { translateElement } = DOMOverlays;
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,9 @@
|
||||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
<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">
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
|
/* global DOMOverlays */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
|
|
||||||
|
|
||||||
function elem(name) {
|
function elem(name) {
|
||||||
return function(str) {
|
return function(str) {
|
||||||
const element = document.createElement(name);
|
const element = document.createElement(name);
|
||||||
|
@ -19,7 +18,7 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { translateElement } = DOMLocalization.DOMOverlays;
|
const { translateElement } = DOMOverlays;
|
||||||
|
|
||||||
{
|
{
|
||||||
// Allowed attribute
|
// Allowed attribute
|
||||||
|
|
|
@ -6,10 +6,9 @@
|
||||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
<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">
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
|
/* global DOMOverlays */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
|
|
||||||
|
|
||||||
function elem(name) {
|
function elem(name) {
|
||||||
return function(str) {
|
return function(str) {
|
||||||
const element = document.createElement(name);
|
const element = document.createElement(name);
|
||||||
|
@ -19,7 +18,7 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { translateElement } = DOMLocalization.DOMOverlays;
|
const { translateElement } = DOMOverlays;
|
||||||
|
|
||||||
// Localized text markup
|
// Localized text markup
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
<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">
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
|
/* global DOMOverlays */
|
||||||
"use strict";
|
"use strict";
|
||||||
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
|
|
||||||
|
|
||||||
function elem(name) {
|
function elem(name) {
|
||||||
return function(str) {
|
return function(str) {
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { translateElement } = DOMLocalization.DOMOverlays;
|
const { translateElement } = DOMOverlays;
|
||||||
|
|
||||||
// Child without name
|
// Child without name
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
<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">
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
|
/* global DOMOverlays */
|
||||||
"use strict";
|
"use strict";
|
||||||
const {DOMLocalization} = ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
|
|
||||||
|
|
||||||
function elem(name) {
|
function elem(name) {
|
||||||
return function(str) {
|
return function(str) {
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { translateElement } = DOMLocalization.DOMOverlays;
|
const { translateElement } = DOMOverlays;
|
||||||
|
|
||||||
{
|
{
|
||||||
// without data-l10n-name
|
// without data-l10n-name
|
||||||
|
|
|
@ -854,8 +854,6 @@ class DOMLocalization extends Localization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DOMLocalization.DOMOverlays = { translateElement };
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function which allows us to construct a new
|
* Helper function which allows us to construct a new
|
||||||
* DOMLocalization from DocumentL10n.
|
* DOMLocalization from DocumentL10n.
|
||||||
|
|
|
@ -289,6 +289,7 @@ STATIC_ATOMS = [
|
||||||
Atom("datal10nid", "data-l10n-id"),
|
Atom("datal10nid", "data-l10n-id"),
|
||||||
Atom("datal10nargs", "data-l10n-args"),
|
Atom("datal10nargs", "data-l10n-args"),
|
||||||
Atom("datal10nattrs", "data-l10n-attrs"),
|
Atom("datal10nattrs", "data-l10n-attrs"),
|
||||||
|
Atom("datal10nname", "data-l10n-name"),
|
||||||
Atom("dataType", "data-type"),
|
Atom("dataType", "data-type"),
|
||||||
Atom("dateTime", "date-time"),
|
Atom("dateTime", "date-time"),
|
||||||
Atom("date", "date"),
|
Atom("date", "date"),
|
||||||
|
|
Загрузка…
Ссылка в новой задаче