2019-07-05 20:05:57 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
|
|
/* 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/. */
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
#include "L10nMutations.h"
|
2019-05-21 22:21:54 +03:00
|
|
|
#include "mozilla/dom/DocumentInlines.h"
|
2020-08-05 00:17:50 +03:00
|
|
|
#include "nsRefreshDriver.h"
|
2019-05-21 22:21:54 +03:00
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
using namespace mozilla::dom;
|
2019-05-21 22:21:54 +03:00
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(L10nMutations)
|
2019-05-21 22:21:54 +03:00
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(L10nMutations)
|
2019-05-21 22:21:54 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingElements)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingElementsHash)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(L10nMutations)
|
2019-05-21 22:21:54 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingElements)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingElementsHash)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nMutations)
|
2019-05-21 22:21:54 +03:00
|
|
|
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
|
|
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
|
|
NS_INTERFACE_MAP_END
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nMutations)
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nMutations)
|
2019-05-21 22:21:54 +03:00
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
L10nMutations::L10nMutations(DOMLocalization* aDOMLocalization)
|
2019-06-06 19:31:25 +03:00
|
|
|
: mDOMLocalization(aDOMLocalization) {
|
2019-05-21 22:21:54 +03:00
|
|
|
mObserving = true;
|
|
|
|
}
|
|
|
|
|
2020-08-05 00:17:50 +03:00
|
|
|
L10nMutations::~L10nMutations() {
|
|
|
|
StopRefreshObserver();
|
|
|
|
MOZ_ASSERT(!mDOMLocalization,
|
|
|
|
"DOMLocalization<-->L10nMutations cycle should be broken.");
|
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
|
|
|
|
nsAtom* aAttribute, int32_t aModType,
|
|
|
|
const nsAttrValue* aOldValue) {
|
2019-05-21 22:21:54 +03:00
|
|
|
if (!mObserving) {
|
|
|
|
return;
|
|
|
|
}
|
2020-03-31 22:57:21 +03:00
|
|
|
|
|
|
|
if (aNameSpaceID == kNameSpaceID_None &&
|
|
|
|
(aAttribute == nsGkAtoms::datal10nid ||
|
|
|
|
aAttribute == nsGkAtoms::datal10nargs)) {
|
|
|
|
if (IsInRoots(aElement)) {
|
2019-05-21 22:21:54 +03:00
|
|
|
L10nElementChanged(aElement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::ContentAppended(nsIContent* aChild) {
|
2019-05-21 22:21:54 +03:00
|
|
|
if (!mObserving) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsINode* node = aChild;
|
2020-03-31 22:57:21 +03:00
|
|
|
if (!IsInRoots(node)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorResult rv;
|
|
|
|
Sequence<OwningNonNull<Element>> elements;
|
2019-05-21 22:21:54 +03:00
|
|
|
while (node) {
|
|
|
|
if (node->IsElement()) {
|
2020-03-31 22:57:21 +03:00
|
|
|
DOMLocalization::GetTranslatables(*node, elements, rv);
|
2019-05-21 22:21:54 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
node = node->GetNextSibling();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& elem : elements) {
|
|
|
|
L10nElementChanged(elem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::ContentInserted(nsIContent* aChild) {
|
2019-05-21 22:21:54 +03:00
|
|
|
if (!mObserving) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ErrorResult rv;
|
|
|
|
Sequence<OwningNonNull<Element>> elements;
|
|
|
|
|
|
|
|
if (!aChild->IsElement()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Element* elem = aChild->AsElement();
|
|
|
|
|
2020-03-31 22:57:21 +03:00
|
|
|
if (!IsInRoots(elem)) {
|
2019-05-21 22:21:54 +03:00
|
|
|
return;
|
|
|
|
}
|
2019-06-06 19:31:25 +03:00
|
|
|
DOMLocalization::GetTranslatables(*aChild, elements, rv);
|
2019-05-21 22:21:54 +03:00
|
|
|
|
|
|
|
for (auto& elem : elements) {
|
|
|
|
L10nElementChanged(elem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::L10nElementChanged(Element* aElement) {
|
2019-05-21 22:21:54 +03:00
|
|
|
if (!mPendingElementsHash.Contains(aElement)) {
|
|
|
|
mPendingElements.AppendElement(aElement);
|
|
|
|
mPendingElementsHash.PutEntry(aElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mRefreshObserver) {
|
|
|
|
StartRefreshObserver();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::PauseObserving() { mObserving = false; }
|
2019-05-21 22:21:54 +03:00
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::ResumeObserving() { mObserving = true; }
|
2019-05-21 22:21:54 +03:00
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::WillRefresh(mozilla::TimeStamp aTime) {
|
2019-05-21 22:21:54 +03:00
|
|
|
StopRefreshObserver();
|
|
|
|
FlushPendingTranslations();
|
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::FlushPendingTranslations() {
|
2019-06-06 19:31:25 +03:00
|
|
|
if (!mDOMLocalization) {
|
2019-05-21 22:21:54 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorResult rv;
|
|
|
|
|
|
|
|
Sequence<OwningNonNull<Element>> elements;
|
|
|
|
|
|
|
|
for (auto& elem : mPendingElements) {
|
|
|
|
if (!elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-04-24 17:34:15 +03:00
|
|
|
if (!elements.AppendElement(*elem, fallible)) {
|
|
|
|
mozalloc_handle_oom(0);
|
|
|
|
}
|
2019-05-21 22:21:54 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
mPendingElementsHash.Clear();
|
|
|
|
mPendingElements.Clear();
|
|
|
|
|
2019-06-06 19:31:25 +03:00
|
|
|
RefPtr<Promise> promise = mDOMLocalization->TranslateElements(elements, rv);
|
2019-05-21 22:21:54 +03:00
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::Disconnect() {
|
2019-05-21 22:21:54 +03:00
|
|
|
StopRefreshObserver();
|
2019-06-06 19:31:25 +03:00
|
|
|
mDOMLocalization = nullptr;
|
2019-05-21 22:21:54 +03:00
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::StartRefreshObserver() {
|
2019-06-06 19:31:25 +03:00
|
|
|
if (!mDOMLocalization || mRefreshObserver) {
|
2019-05-21 22:21:54 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mRefreshDriver) {
|
2019-06-06 19:31:25 +03:00
|
|
|
nsPIDOMWindowInner* innerWindow =
|
|
|
|
mDOMLocalization->GetParentObject()->AsInnerWindow();
|
|
|
|
Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
|
|
|
|
if (doc) {
|
|
|
|
nsPresContext* ctx = doc->GetPresContext();
|
|
|
|
if (ctx) {
|
|
|
|
mRefreshDriver = ctx->RefreshDriver();
|
|
|
|
}
|
2019-05-21 22:21:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-03 13:00:47 +03:00
|
|
|
// If we can't start the refresh driver, it means
|
|
|
|
// that the presContext is not available yet.
|
|
|
|
// In that case, we'll trigger the flush of pending
|
|
|
|
// elements in Document::CreatePresShell.
|
2019-05-21 22:21:54 +03:00
|
|
|
if (mRefreshDriver) {
|
2020-09-25 05:36:29 +03:00
|
|
|
mRefreshDriver->AddRefreshObserver(this, FlushType::Style,
|
|
|
|
"L10n mutations");
|
2019-05-21 22:21:54 +03:00
|
|
|
mRefreshObserver = true;
|
2019-06-06 19:31:25 +03:00
|
|
|
} else {
|
|
|
|
NS_WARNING("[l10n][mutations] Failed to start a refresh observer.");
|
2019-05-21 22:21:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::StopRefreshObserver() {
|
2019-06-06 19:31:25 +03:00
|
|
|
if (!mDOMLocalization) {
|
2019-05-21 22:21:54 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mRefreshDriver) {
|
|
|
|
mRefreshDriver->RemoveRefreshObserver(this, FlushType::Style);
|
|
|
|
mRefreshObserver = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-06 19:32:58 +03:00
|
|
|
void L10nMutations::OnCreatePresShell() {
|
2019-06-03 13:00:47 +03:00
|
|
|
if (!mPendingElements.IsEmpty()) {
|
|
|
|
StartRefreshObserver();
|
|
|
|
}
|
|
|
|
}
|
2020-03-31 22:57:21 +03:00
|
|
|
|
|
|
|
bool L10nMutations::IsInRoots(nsINode* aNode) {
|
|
|
|
// If the root of the mutated element is in the light DOM,
|
|
|
|
// we know it must be covered by our observer directly.
|
|
|
|
//
|
|
|
|
// Otherwise, we need to check if its subtree root is the same
|
|
|
|
// as any of the `DOMLocalization::mRoots` subtree roots.
|
|
|
|
nsINode* root = aNode->SubtreeRoot();
|
|
|
|
|
|
|
|
// If element is in light DOM, it must be covered by one of
|
|
|
|
// the DOMLocalization roots to end up here.
|
|
|
|
MOZ_ASSERT_IF(!root->IsShadowRoot(),
|
|
|
|
mDOMLocalization->SubtreeRootInRoots(root));
|
|
|
|
|
|
|
|
return !root->IsShadowRoot() || mDOMLocalization->SubtreeRootInRoots(root);
|
|
|
|
}
|