Bug 1455649 - DocumentL10n, part 1 - Add C++ DocumentL10n API.
DocumentL10n is a DOM C++ API which serves as a bridge between
nsIDocument and mozDOMLocalization APIs.
MozReview-Commit-ID: 8LfOR4Haqlu
new file mode 100644
--- /dev/null
+++ b/dom/webidl/DocumentL10n.webidl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; 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 L10nKey {
+ required DOMString id;
+
+ object? args = null;
+};
+
+[NoInterfaceObject]
+interface DocumentL10n {
+ void onDocumentParsed();
+
+ [NewObject] Promise<sequence<DOMString>> formatMessages(sequence<L10nKey> aKeys);
+ [NewObject] Promise<sequence<DOMString>> formatValues(sequence<L10nKey> aKeys);
+ [NewObject] Promise<DOMString> formatValue(DOMString aId, optional object aArgs);
+
+ [Throws] void setAttributes(Element aElement, DOMString aId, optional object aArgs);
+ [Throws] L10nKey getAttributes(Element aElement);
+
+ [NewObject] Promise<void> translateFragment(Element aElement);
+ [NewObject] Promise<void> translateElements(sequence<Element> aElements);
+
+ [Throws] readonly attribute Promise<any> ready;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -464,16 +464,17 @@ WEBIDL_FILES = [
'DataTransferItemList.webidl',
'DecoderDoctorNotification.webidl',
'DedicatedWorkerGlobalScope.webidl',
'DelayNode.webidl',
'DeviceMotionEvent.webidl',
'Directory.webidl',
'Document.webidl',
'DocumentFragment.webidl',
+ 'DocumentL10n.webidl',
'DocumentOrShadowRoot.webidl',
'DocumentTimeline.webidl',
'DocumentType.webidl',
'DOMError.webidl',
'DOMException.webidl',
'DOMImplementation.webidl',
'DOMMatrix.webidl',
'DOMParser.webidl',
new file mode 100644
--- /dev/null
+++ b/intl/l10n/DocumentL10n.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/DocumentL10n.h"
+#include "mozilla/dom/DocumentL10nBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/PaymentRequestUtils.h" // SerializeFromJSVal
+#include "nsQueryObject.h"
+#include "mozilla/dom/Promise.h"
+#include "nsISupports.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentL10n, mDocument, mDOMLocalization)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentL10n)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentL10n)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentL10n)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/**
+ * This can get lazily initialized at multiple points during the lifecycle
+ * of the document. It can get initialized when the first link is registered,
+ * or while document is still being parsed, or much later.
+ *
+ * For that reason we initialize the `DocumentLocalization` with information
+ * about those two states, and if either of them is `false` at the time
+ * of initialization, the corresponding method is expected to be fired
+ * by the parser later.
+ */
+DocumentL10n::DocumentL10n(nsIDocument* aDocument)
+ : mDocument(aDocument),
+ mState(DocumentL10nState::Initialized)
+{
+ nsresult rv;
+ nsCOMPtr<mozIDOMLocalization> domL10n = do_CreateInstance("@mozilla.org/intl/domlocalization;1", &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ mDOMLocalization = domL10n;
+}
+
+DocumentL10n::~DocumentL10n()
+{
+ mDOMLocalization = nullptr;
+}
+
+JSObject*
+DocumentL10n::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DocumentL10n_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+uint32_t
+DocumentL10n::AddResourceIds(nsTArray<nsString>& aResourceIds)
+{
+ uint32_t ret;
+ mDOMLocalization->AddResourceIds(aResourceIds, &ret);
+ return ret;
+}
+
+uint32_t
+DocumentL10n::RemoveResourceIds(nsTArray<nsString>& aResourceIds)
+{
+ if (!mDOMLocalization) {
+ return 0;
+ }
+
+ uint32_t ret;
+ mDOMLocalization->RemoveResourceIds(aResourceIds, &ret);
+ return ret;
+}
+
+already_AddRefed<Promise>
+DocumentL10n::FormatMessages(JSContext* cx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv)
+{
+ nsTArray<JS::Value> jsKeys;
+ SequenceRooter<JS::Value> rooter(cx, &jsKeys);
+ for (auto& key : aKeys) {
+ JS::RootedValue jsKey(cx);
+ if (!ToJSValue(cx, key, &jsKey)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ jsKeys.AppendElement(jsKey);
+ }
+
+ RefPtr<Promise> promise;
+ aRv = mDOMLocalization->FormatMessages(jsKeys, getter_AddRefs(promise));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+DocumentL10n::FormatValues(JSContext* cx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv)
+{
+ nsTArray<JS::Value> jsKeys;
+ SequenceRooter<JS::Value> rooter(cx, &jsKeys);
+ for (auto& key : aKeys) {
+ JS::RootedValue jsKey(cx);
+ if (!ToJSValue(cx, key, &jsKey)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ jsKeys.AppendElement(jsKey);
+ }
+
+ RefPtr<Promise> promise;
+ aRv = mDOMLocalization->FormatValues(jsKeys, getter_AddRefs(promise));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+DocumentL10n::FormatValue(JSContext* cx, const nsAString& aId, const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv)
+{
+ JS::Rooted<JS::Value> args(cx);
+
+ if (aArgs.WasPassed()) {
+ args = JS::ObjectValue(*aArgs.Value());
+ } else {
+ args = JS::UndefinedValue();
+ }
+
+ RefPtr<Promise> promise;
+ nsresult rv = mDOMLocalization->FormatValue(aId, args, getter_AddRefs(promise));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ return promise.forget();
+}
+
+void
+DocumentL10n::SetAttributes(JSContext* cx, Element& aElement, const nsAString& aId, const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv)
+{
+ aElement.SetAttribute(NS_LITERAL_STRING("data-l10n-id"), aId, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ if (aArgs.WasPassed()) {
+ nsAutoString data;
+ //XXX(Mossop): We need to stringify the aArgs here and write them to `data-l10n-args`
+ JS::Rooted<JS::Value> val(cx, JS::ObjectValue(*aArgs.Value()));
+ nsresult rv = SerializeFromJSVal(cx, val, data);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ aElement.SetAttribute(NS_LITERAL_STRING("data-l10n-args"), data, aRv);
+ }
+}
+
+void
+DocumentL10n::GetAttributes(JSContext* cx, Element& aElement, L10nKey& aResult, ErrorResult& aRv)
+{
+ nsAutoString l10nId;
+ nsAutoString l10nArgs;
+
+ aElement.GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nid, l10nId);
+ aResult.mId = l10nId;
+
+ aElement.GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, l10nArgs);
+ if (!l10nArgs.IsEmpty()) {
+ JS::Rooted<JS::Value> json(cx);
+ if (!JS_ParseJSON(cx, l10nArgs.get(), l10nArgs.Length(), &json) ||
+ !json.isObject()) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+ aResult.mArgs = &json.toObject();
+ }
+}
+
+already_AddRefed<Promise>
+DocumentL10n::TranslateFragment(Element& aElement, ErrorResult& aRv)
+{
+ RefPtr<Promise> promise;
+ nsresult rv = mDOMLocalization->TranslateFragment(&aElement, getter_AddRefs(promise));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+DocumentL10n::TranslateElements(const Sequence<OwningNonNull<Element>>& aElements, ErrorResult& aRv)
+{
+ AutoTArray<RefPtr<Element>, 10> elements;
+ elements.SetCapacity(aElements.Length());
+ for (auto& element : aElements) {
+ elements.AppendElement(element);
+ }
+ RefPtr<Promise> promise;
+ aRv = mDOMLocalization->TranslateElements(
+ elements, getter_AddRefs(promise));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return promise.forget();
+}
+
+void
+DocumentL10n::OnDocumentParsed()
+{
+ if (mState == DocumentL10nState::DocumentParsed) {
+ return;
+ }
+
+ mDOMLocalization->ConnectRoot(mDocument->GetDocumentElement());
+
+ RefPtr<Promise> promise;
+ mDOMLocalization->TranslateRoots(getter_AddRefs(promise));
+ mState = DocumentL10nState::DocumentParsed;
+}
+
+already_AddRefed<Promise>
+DocumentL10n::GetReady(ErrorResult& aRv)
+{
+ RefPtr<Promise> promise;
+
+ nsresult rv = mDOMLocalization->GetReady(getter_AddRefs(promise));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/intl/l10n/DocumentL10n.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_DocumentL10n_h
+#define mozilla_dom_DocumentL10n_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsIDocument.h"
+#include "mozIDOMLocalization.h"
+
+namespace mozilla {
+namespace dom {
+
+class Element;
+class Promise;
+struct L10nKey;
+
+enum class DocumentL10nState {
+ Initialized = -1,
+ DocumentParsed
+};
+
+class DocumentL10n final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DocumentL10n)
+
+public:
+ explicit DocumentL10n(nsIDocument* aDocument);
+
+protected:
+ virtual ~DocumentL10n();
+
+ nsCOMPtr<nsIDocument> mDocument;
+ nsTArray<RefPtr<Promise>> mEarlyPromises;
+ DocumentL10nState mState;
+ nsCOMPtr<mozIDOMLocalization> mDOMLocalization;
+
+public:
+ nsIDocument* GetParentObject() const { return mDocument; };
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ uint32_t AddResourceIds(nsTArray<nsString>& aResourceIds);
+ uint32_t RemoveResourceIds(nsTArray<nsString>& aResourceIds);
+
+ already_AddRefed<Promise> FormatMessages(JSContext* cx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv);
+ already_AddRefed<Promise> FormatValues(JSContext* cx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv);
+ already_AddRefed<Promise> FormatValue(JSContext* cx, const nsAString& aId, const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv);
+
+ void SetAttributes(JSContext* cx, Element& aElement, const nsAString& aId, const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv);
+ void GetAttributes(JSContext* cx, Element& aElement, L10nKey& aResult, ErrorResult& aRv);
+
+ already_AddRefed<Promise> TranslateFragment(Element& aElement, ErrorResult& aRv);
+ already_AddRefed<Promise> TranslateElements(const Sequence<OwningNonNull<Element>>& aElements, ErrorResult& aRv);
+
+ already_AddRefed<Promise> GetReady(ErrorResult& aRv);
+
+ void OnDocumentParsed();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DocumentL10n_h
--- a/intl/l10n/moz.build
+++ b/intl/l10n/moz.build
@@ -17,16 +17,28 @@ XPIDL_SOURCES += [
XPIDL_MODULE = 'locale'
EXTRA_COMPONENTS += [
'mozDOMLocalization.js',
'mozDOMLocalization.manifest',
]
+EXPORTS.mozilla.dom += [
+ 'DocumentL10n.h',
+]
+
+UNIFIED_SOURCES += [
+ 'DocumentL10n.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+]
+
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
JAR_MANIFESTS += ['jar.mn']
SPHINX_TREES['l10n'] = 'docs'