diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 85c850c2257b..ebacad983245 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -211,6 +211,7 @@ #include "mozilla/dom/ClientInfo.h" #include "mozilla/dom/ClientState.h" #include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/DocumentL10n.h" #include "mozilla/dom/DocumentTimeline.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/HTMLBodyElement.h" @@ -1885,6 +1886,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n) // Traverse all nsDocument nsCOMPtrs. NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser) @@ -2031,6 +2033,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle); NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n); tmp->mParentDocument = nullptr; @@ -2229,6 +2232,23 @@ nsIDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) mChannel = aChannel; } +/** + * DocumentL10n is currently allowed for system + * principal. + * + * In the future we'll want to expose it to non-web-exposed + * about:* pages. + */ +bool +PrincipalAllowsL10n(nsIPrincipal* principal) +{ + if (nsContentUtils::IsSystemPrincipal(principal)) { + return true; + } + + return false; +} + void nsIDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, @@ -3303,6 +3323,102 @@ nsIDocument::GetAllowPlugins() return true; } +void +nsIDocument::InitializeLocalization(nsTArray& aResourceIds) +{ + MOZ_ASSERT(!mDocumentL10n, "mDocumentL10n should not be initialized yet"); + + DocumentL10n* l10n = new DocumentL10n(this); + MOZ_ALWAYS_TRUE(l10n->Init(aResourceIds)); + mDocumentL10n = l10n; +} + +DocumentL10n* +nsIDocument::GetL10n() +{ + return mDocumentL10n; +} + +bool +nsDocument::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) +{ + return PrincipalAllowsL10n(nsContentUtils::SubjectPrincipal(aCx)); +} + +void +nsIDocument::LocalizationLinkAdded(Element* aLinkElement) +{ + if (!PrincipalAllowsL10n(NodePrincipal())) { + return; + } + + nsAutoString href; + aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href); + // If the link is added after the DocumentL10n instance + // has been initialized, just pass the resource ID to it. + if (mDocumentL10n) { + AutoTArray resourceIds; + resourceIds.AppendElement(href); + mDocumentL10n->AddResourceIds(resourceIds); + } else { + // Otherwise, we're still parsing the document. + // In that case, add it to the pending list. This list + // will be resolved once the end of l10n resource + // container is reached. + mL10nResources.AppendElement(href); + } +} + +void +nsIDocument::LocalizationLinkRemoved(Element* aLinkElement) +{ + if (!PrincipalAllowsL10n(NodePrincipal())) { + return; + } + + nsAutoString href; + aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href); + if (mDocumentL10n) { + AutoTArray resourceIds; + resourceIds.AppendElement(href); + uint32_t remaining = mDocumentL10n->RemoveResourceIds(resourceIds); + if (remaining == 0) { + mDocumentL10n = nullptr; + } + } else { + mL10nResources.RemoveElement(href); + } +} + +/** + * This method should be called once the end of the l10n + * resource container has been parsed. + * + * In XUL this is the end of the first , + * In XHTML/HTML this is the end of . + * + * This milestone is used to allow for batch + * localization context I/O and building done + * once when all resources in the document have been + * collected. + */ +void +nsIDocument::OnL10nResourceContainerParsed() +{ + if (!mL10nResources.IsEmpty()) { + InitializeLocalization(mL10nResources); + mL10nResources.Clear(); + } +} + +void +nsIDocument::TriggerInitialDocumentTranslation() +{ + if (mDocumentL10n) { + mDocumentL10n->TriggerInitialDocumentTranslation(); + } +} + bool nsDocument::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) { diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 1a0d85d181ef..ea3cb4cf6cee 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -158,6 +158,7 @@ public: virtual void StopDocumentLoad() override; + static bool DocumentSupportsL10n(JSContext* aCx, JSObject* aObject); static bool IsWebAnimationsEnabled(JSContext* aCx, JSObject* aObject); static bool IsWebAnimationsEnabled(mozilla::dom::CallerType aCallerType); static bool IsWebAnimationsGetAnimationsEnabled(JSContext* aCx, diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index b93a72945826..824491fd9083 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -147,6 +147,7 @@ class CDATASection; class Comment; struct CustomElementDefinition; class DocGroup; +class DocumentL10n; class DocumentFragment; class DocumentTimeline; class DocumentType; @@ -3562,6 +3563,69 @@ public: // For more information on Flash classification, see // toolkit/components/url-classifier/flash-block-lists.rst mozilla::dom::FlashClassification DocumentFlashClassification(); + + /** + * Localization + * + * For more information on DocumentL10n see + * intl/l10n/docs/fluent_tutorial.rst + */ + +public: + /** + * This is a public method exposed on Document WebIDL + * to chrome only documents. + */ + mozilla::dom::DocumentL10n* GetL10n(); + + /** + * This method should be called when the container + * of l10n resources parsing is completed. + * + * It triggers initial async fetch of the resources + * as early as possible. + * + * In HTML case this is . + * In XUL case this is . + */ + void OnL10nResourceContainerParsed(); + + /** + * This method should be called when a link element + * with rel="localization" is being added to the + * l10n resource container element. + */ + void LocalizationLinkAdded(Element* aLinkElement); + + /** + * This method should be called when a link element + * with rel="localization" is being removed. + */ + void LocalizationLinkRemoved(Element* aLinkElement); + +protected: + /** + * This method should be collect as soon as the + * parsing of the document is completed. + * + * In HTML this happens when readyState becomes + * `interactive`. + * In XUL it happens at `DoneWalking`, during + * `MozBeforeInitialXULLayout`. + * + * It triggers the initial translation of the + * document. + */ + void TriggerInitialDocumentTranslation(); + + RefPtr mDocumentL10n; + +private: + void InitializeLocalization(nsTArray& aResourceIds); + + nsTArray mL10nResources; + +public: bool IsThirdParty(); bool IsScopedStyleEnabled(); diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl index 7468786dd581..b7290dd84c44 100644 --- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -525,6 +525,10 @@ partial interface Document { [ChromeOnly] readonly attribute unsigned long numTrackersBlocked; }; +partial interface Document { + [Func="nsDocument::DocumentSupportsL10n"] readonly attribute DocumentL10n? l10n; +}; + Document implements XPathEvaluator; Document implements GlobalEventHandlers; Document implements DocumentAndElementEventHandlers;