1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
6 
7 #include "XULPersist.h"
8 
9 #ifdef MOZ_NEW_XULSTORE
10 #  include "mozilla/XULStore.h"
11 #else
12 #  include "nsIXULStore.h"
13 #  include "nsIStringEnumerator.h"
14 #endif
15 #include "mozilla/BasePrincipal.h"
16 #include "nsIAppWindow.h"
17 
18 namespace mozilla {
19 namespace dom {
20 
IsRootElement(Element * aElement)21 static bool IsRootElement(Element* aElement) {
22   return aElement->OwnerDoc()->GetRootElement() == aElement;
23 }
24 
ShouldPersistAttribute(Element * aElement,nsAtom * aAttribute)25 static bool ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute) {
26   if (IsRootElement(aElement)) {
27     // This is not an element of the top document, its owner is
28     // not an AppWindow. Persist it.
29     if (aElement->OwnerDoc()->GetInProcessParentDocument()) {
30       return true;
31     }
32     // The following attributes of xul:window should be handled in
33     // AppWindow::SavePersistentAttributes instead of here.
34     if (aAttribute == nsGkAtoms::screenX || aAttribute == nsGkAtoms::screenY ||
35         aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
36         aAttribute == nsGkAtoms::sizemode) {
37       return false;
38     }
39   }
40   return true;
41 }
42 
NS_IMPL_ISUPPORTS(XULPersist,nsIDocumentObserver)43 NS_IMPL_ISUPPORTS(XULPersist, nsIDocumentObserver)
44 
45 XULPersist::XULPersist(Document* aDocument)
46     : nsStubDocumentObserver(), mDocument(aDocument) {}
47 
48 XULPersist::~XULPersist() = default;
49 
Init()50 void XULPersist::Init() {
51   ApplyPersistentAttributes();
52   mDocument->AddObserver(this);
53 }
54 
DropDocumentReference()55 void XULPersist::DropDocumentReference() {
56   mDocument->RemoveObserver(this);
57   mDocument = nullptr;
58 }
59 
AttributeChanged(dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)60 void XULPersist::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
61                                   nsAtom* aAttribute, int32_t aModType,
62                                   const nsAttrValue* aOldValue) {
63   NS_ASSERTION(aElement->OwnerDoc() == mDocument, "unexpected doc");
64 
65   // See if there is anything we need to persist in the localstore.
66   //
67   // XXX Namespace handling broken :-(
68   nsAutoString persist;
69   // Persistence of attributes of xul:window is handled in AppWindow.
70   if (aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist) &&
71       ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() &&
72       // XXXldb This should check that it's a token, not just a substring.
73       persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
74     // Might not need this, but be safe for now.
75     nsCOMPtr<nsIDocumentObserver> kungFuDeathGrip(this);
76     nsContentUtils::AddScriptRunner(
77         NewRunnableMethod<Element*, int32_t, nsAtom*>(
78             "dom::XULPersist::Persist", this, &XULPersist::Persist, aElement,
79             kNameSpaceID_None, aAttribute));
80   }
81 }
82 
Persist(Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute)83 void XULPersist::Persist(Element* aElement, int32_t aNameSpaceID,
84                          nsAtom* aAttribute) {
85   if (!mDocument) {
86     return;
87   }
88   // For non-chrome documents, persistance is simply broken
89   if (!mDocument->NodePrincipal()->IsSystemPrincipal()) {
90     return;
91   }
92 
93 #ifndef MOZ_NEW_XULSTORE
94   if (!mLocalStore) {
95     mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
96     if (NS_WARN_IF(!mLocalStore)) {
97       return;
98     }
99   }
100 #endif
101 
102   nsAutoString id;
103 
104   aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
105   nsAtomString attrstr(aAttribute);
106 
107   nsAutoString valuestr;
108   aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
109 
110   nsAutoCString utf8uri;
111   nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
112   if (NS_WARN_IF(NS_FAILED(rv))) {
113     return;
114   }
115   NS_ConvertUTF8toUTF16 uri(utf8uri);
116 
117   bool hasAttr;
118 #ifdef MOZ_NEW_XULSTORE
119   rv = XULStore::HasValue(uri, id, attrstr, hasAttr);
120 #else
121   rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
122 #endif
123 
124   if (NS_WARN_IF(NS_FAILED(rv))) {
125     return;
126   }
127 
128   if (hasAttr && valuestr.IsEmpty()) {
129 #ifdef MOZ_NEW_XULSTORE
130     rv = XULStore::RemoveValue(uri, id, attrstr);
131     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value removed");
132 #else
133     mLocalStore->RemoveValue(uri, id, attrstr);
134 #endif
135     return;
136   }
137 
138   // Persisting attributes to top level windows is handled by AppWindow.
139   if (IsRootElement(aElement)) {
140     if (nsCOMPtr<nsIAppWindow> win =
141             mDocument->GetAppWindowIfToplevelChrome()) {
142       return;
143     }
144   }
145 
146 #ifdef MOZ_NEW_XULSTORE
147   rv = XULStore::SetValue(uri, id, attrstr, valuestr);
148 #else
149   mLocalStore->SetValue(uri, id, attrstr, valuestr);
150 #endif
151   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value set");
152 }
153 
ApplyPersistentAttributes()154 nsresult XULPersist::ApplyPersistentAttributes() {
155   if (!mDocument) {
156     return NS_ERROR_NOT_AVAILABLE;
157   }
158   // For non-chrome documents, persistance is simply broken
159   if (!mDocument->NodePrincipal()->IsSystemPrincipal()) {
160     return NS_ERROR_NOT_AVAILABLE;
161   }
162 
163   // Add all of the 'persisted' attributes into the content
164   // model.
165 #ifndef MOZ_NEW_XULSTORE
166   if (!mLocalStore) {
167     mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
168     if (NS_WARN_IF(!mLocalStore)) {
169       return NS_ERROR_NOT_INITIALIZED;
170     }
171   }
172 #endif
173 
174   ApplyPersistentAttributesInternal();
175 
176   return NS_OK;
177 }
178 
ApplyPersistentAttributesInternal()179 nsresult XULPersist::ApplyPersistentAttributesInternal() {
180   nsCOMArray<Element> elements;
181 
182   nsAutoCString utf8uri;
183   nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
184   if (NS_WARN_IF(NS_FAILED(rv))) {
185     return rv;
186   }
187   NS_ConvertUTF8toUTF16 uri(utf8uri);
188 
189   // Get a list of element IDs for which persisted values are available
190 #ifdef MOZ_NEW_XULSTORE
191   UniquePtr<XULStoreIterator> ids;
192   rv = XULStore::GetIDs(uri, ids);
193 #else
194   nsCOMPtr<nsIStringEnumerator> ids;
195   rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
196 #endif
197   if (NS_WARN_IF(NS_FAILED(rv))) {
198     return rv;
199   }
200 
201 #ifdef MOZ_NEW_XULSTORE
202   while (ids->HasMore()) {
203     nsAutoString id;
204     rv = ids->GetNext(&id);
205     if (NS_WARN_IF(NS_FAILED(rv))) {
206       return rv;
207     }
208 #else
209   while (1) {
210     bool hasmore = false;
211     ids->HasMore(&hasmore);
212     if (!hasmore) {
213       break;
214     }
215 
216     nsAutoString id;
217     ids->GetNext(id);
218 #endif
219 
220     // We want to hold strong refs to the elements while applying
221     // persistent attributes, just in case.
222     const nsTArray<Element*>* allElements = mDocument->GetAllElementsForId(id);
223     if (!allElements) {
224       continue;
225     }
226     elements.Clear();
227     elements.SetCapacity(allElements->Length());
228     for (Element* element : *allElements) {
229       elements.AppendObject(element);
230     }
231 
232     rv = ApplyPersistentAttributesToElements(id, elements);
233     if (NS_WARN_IF(NS_FAILED(rv))) {
234       return rv;
235     }
236   }
237 
238   return NS_OK;
239 }
240 
241 nsresult XULPersist::ApplyPersistentAttributesToElements(
242     const nsAString& aID, nsCOMArray<Element>& aElements) {
243   nsAutoCString utf8uri;
244   nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
245   if (NS_WARN_IF(NS_FAILED(rv))) {
246     return rv;
247   }
248   NS_ConvertUTF8toUTF16 uri(utf8uri);
249 
250   // Get a list of attributes for which persisted values are available
251 #ifdef MOZ_NEW_XULSTORE
252   UniquePtr<XULStoreIterator> attrs;
253   rv = XULStore::GetAttrs(uri, aID, attrs);
254 #else
255   nsCOMPtr<nsIStringEnumerator> attrs;
256   rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
257 #endif
258   if (NS_WARN_IF(NS_FAILED(rv))) {
259     return rv;
260   }
261 
262 #ifdef MOZ_NEW_XULSTORE
263   while (attrs->HasMore()) {
264     nsAutoString attrstr;
265     rv = attrs->GetNext(&attrstr);
266     if (NS_WARN_IF(NS_FAILED(rv))) {
267       return rv;
268     }
269 
270     nsAutoString value;
271     rv = XULStore::GetValue(uri, aID, attrstr, value);
272 #else
273   while (1) {
274     bool hasmore = PR_FALSE;
275     attrs->HasMore(&hasmore);
276     if (!hasmore) {
277       break;
278     }
279 
280     nsAutoString attrstr;
281     attrs->GetNext(attrstr);
282 
283     nsAutoString value;
284     rv = mLocalStore->GetValue(uri, aID, attrstr, value);
285 #endif
286     if (NS_WARN_IF(NS_FAILED(rv))) {
287       return rv;
288     }
289 
290     RefPtr<nsAtom> attr = NS_Atomize(attrstr);
291     if (NS_WARN_IF(!attr)) {
292       return NS_ERROR_OUT_OF_MEMORY;
293     }
294 
295     uint32_t cnt = aElements.Length();
296     for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
297       Element* element = aElements.SafeElementAt(i);
298       if (!element) {
299         continue;
300       }
301 
302       // Applying persistent attributes to top level windows is handled
303       // by AppWindow.
304       if (IsRootElement(element)) {
305         if (nsCOMPtr<nsIAppWindow> win =
306                 mDocument->GetAppWindowIfToplevelChrome()) {
307           continue;
308         }
309       }
310 
311       Unused << element->SetAttr(kNameSpaceID_None, attr, value, true);
312     }
313   }
314 
315   return NS_OK;
316 }
317 
318 }  // namespace dom
319 }  // namespace mozilla
320