1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "js/Array.h"  // JS::GetArrayLength, JS::IsArrayObject
6 #include "js/JSON.h"
7 #include "jsapi.h"
8 #include "mozilla/PresShell.h"
9 #include "mozilla/dom/Document.h"
10 #include "mozilla/dom/DocumentInlines.h"
11 #include "mozilla/dom/HTMLInputElement.h"
12 #include "mozilla/dom/HTMLSelectElement.h"
13 #include "mozilla/dom/HTMLTextAreaElement.h"
14 #include "mozilla/dom/SessionStoreUtils.h"
15 #include "mozilla/dom/txIXPathContext.h"
16 #include "mozilla/dom/WindowProxyHolder.h"
17 #include "mozilla/dom/XPathResult.h"
18 #include "mozilla/dom/XPathEvaluator.h"
19 #include "mozilla/dom/XPathExpression.h"
20 #include "mozilla/UniquePtr.h"
21 #include "nsCharSeparatedTokenizer.h"
22 #include "nsContentList.h"
23 #include "nsContentUtils.h"
24 #include "nsFocusManager.h"
25 #include "nsGlobalWindowOuter.h"
26 #include "nsIDocShell.h"
27 #include "nsIFormControl.h"
28 #include "nsIScrollableFrame.h"
29 #include "nsPresContext.h"
30 #include "nsPrintfCString.h"
31 
32 using namespace mozilla;
33 using namespace mozilla::dom;
34 
35 namespace {
36 
37 class DynamicFrameEventFilter final : public nsIDOMEventListener {
38  public:
39   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter)40   NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter)
41 
42   explicit DynamicFrameEventFilter(EventListener* aListener)
43       : mListener(aListener) {}
44 
HandleEvent(Event * aEvent)45   NS_IMETHODIMP HandleEvent(Event* aEvent) override {
46     if (mListener && TargetInNonDynamicDocShell(aEvent)) {
47       mListener->HandleEvent(*aEvent);
48     }
49 
50     return NS_OK;
51   }
52 
53  private:
54   ~DynamicFrameEventFilter() = default;
55 
TargetInNonDynamicDocShell(Event * aEvent)56   bool TargetInNonDynamicDocShell(Event* aEvent) {
57     EventTarget* target = aEvent->GetTarget();
58     if (!target) {
59       return false;
60     }
61 
62     nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal();
63     if (!outer) {
64       return false;
65     }
66 
67     nsIDocShell* docShell = outer->GetDocShell();
68     if (!docShell) {
69       return false;
70     }
71 
72     bool isDynamic = false;
73     nsresult rv = docShell->GetCreatedDynamically(&isDynamic);
74     return NS_SUCCEEDED(rv) && !isDynamic;
75   }
76 
77   RefPtr<EventListener> mListener;
78 };
79 
80 NS_IMPL_CYCLE_COLLECTION(DynamicFrameEventFilter, mListener)
81 
82 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DynamicFrameEventFilter)
83   NS_INTERFACE_MAP_ENTRY(nsISupports)
84   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
85 NS_INTERFACE_MAP_END
86 
87 NS_IMPL_CYCLE_COLLECTING_ADDREF(DynamicFrameEventFilter)
88 NS_IMPL_CYCLE_COLLECTING_RELEASE(DynamicFrameEventFilter)
89 
90 }  // anonymous namespace
91 
92 /* static */
ForEachNonDynamicChildFrame(const GlobalObject & aGlobal,WindowProxyHolder & aWindow,SessionStoreUtilsFrameCallback & aCallback,ErrorResult & aRv)93 void SessionStoreUtils::ForEachNonDynamicChildFrame(
94     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
95     SessionStoreUtilsFrameCallback& aCallback, ErrorResult& aRv) {
96   if (!aWindow.get()) {
97     aRv.Throw(NS_ERROR_INVALID_ARG);
98     return;
99   }
100 
101   nsCOMPtr<nsIDocShell> docShell = aWindow.get()->GetDocShell();
102   if (!docShell) {
103     aRv.Throw(NS_ERROR_FAILURE);
104     return;
105   }
106 
107   int32_t length;
108   aRv = docShell->GetInProcessChildCount(&length);
109   if (aRv.Failed()) {
110     return;
111   }
112 
113   for (int32_t i = 0; i < length; ++i) {
114     nsCOMPtr<nsIDocShellTreeItem> item;
115     docShell->GetInProcessChildAt(i, getter_AddRefs(item));
116     if (!item) {
117       aRv.Throw(NS_ERROR_FAILURE);
118       return;
119     }
120 
121     nsCOMPtr<nsIDocShell> childDocShell(do_QueryInterface(item));
122     if (!childDocShell) {
123       aRv.Throw(NS_ERROR_FAILURE);
124       return;
125     }
126 
127     bool isDynamic = false;
128     nsresult rv = childDocShell->GetCreatedDynamically(&isDynamic);
129     if (NS_SUCCEEDED(rv) && isDynamic) {
130       continue;
131     }
132 
133     int32_t childOffset = childDocShell->GetChildOffset();
134     aCallback.Call(WindowProxyHolder(item->GetWindow()->GetBrowsingContext()),
135                    childOffset);
136   }
137 }
138 
139 /* static */
140 already_AddRefed<nsISupports>
AddDynamicFrameFilteredListener(const GlobalObject & aGlobal,EventTarget & aTarget,const nsAString & aType,JS::Handle<JS::Value> aListener,bool aUseCapture,bool aMozSystemGroup,ErrorResult & aRv)141 SessionStoreUtils::AddDynamicFrameFilteredListener(
142     const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType,
143     JS::Handle<JS::Value> aListener, bool aUseCapture, bool aMozSystemGroup,
144     ErrorResult& aRv) {
145   if (NS_WARN_IF(!aListener.isObject())) {
146     aRv.Throw(NS_ERROR_INVALID_ARG);
147     return nullptr;
148   }
149 
150   JSContext* cx = aGlobal.Context();
151   JS::Rooted<JSObject*> obj(cx, &aListener.toObject());
152   JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
153   RefPtr<EventListener> listener =
154       new EventListener(cx, obj, global, GetIncumbentGlobal());
155 
156   nsCOMPtr<nsIDOMEventListener> filter(new DynamicFrameEventFilter(listener));
157   if (aMozSystemGroup) {
158     aRv = aTarget.AddSystemEventListener(aType, filter, aUseCapture);
159   } else {
160     aRv = aTarget.AddEventListener(aType, filter, aUseCapture);
161   }
162   if (aRv.Failed()) {
163     return nullptr;
164   }
165 
166   return filter.forget();
167 }
168 
169 /* static */
RemoveDynamicFrameFilteredListener(const GlobalObject & global,EventTarget & aTarget,const nsAString & aType,nsISupports * aListener,bool aUseCapture,bool aMozSystemGroup,ErrorResult & aRv)170 void SessionStoreUtils::RemoveDynamicFrameFilteredListener(
171     const GlobalObject& global, EventTarget& aTarget, const nsAString& aType,
172     nsISupports* aListener, bool aUseCapture, bool aMozSystemGroup,
173     ErrorResult& aRv) {
174   nsCOMPtr<nsIDOMEventListener> listener = do_QueryInterface(aListener);
175   if (!listener) {
176     aRv.Throw(NS_ERROR_NO_INTERFACE);
177     return;
178   }
179 
180   if (aMozSystemGroup) {
181     aTarget.RemoveSystemEventListener(aType, listener, aUseCapture);
182   } else {
183     aTarget.RemoveEventListener(aType, listener, aUseCapture);
184   }
185 }
186 
187 /* static */
CollectDocShellCapabilities(const GlobalObject & aGlobal,nsIDocShell * aDocShell,nsCString & aRetVal)188 void SessionStoreUtils::CollectDocShellCapabilities(const GlobalObject& aGlobal,
189                                                     nsIDocShell* aDocShell,
190                                                     nsCString& aRetVal) {
191   bool allow;
192 
193 #define TRY_ALLOWPROP(y)          \
194   PR_BEGIN_MACRO                  \
195   aDocShell->GetAllow##y(&allow); \
196   if (!allow) {                   \
197     if (!aRetVal.IsEmpty()) {     \
198       aRetVal.Append(',');        \
199     }                             \
200     aRetVal.Append(#y);           \
201   }                               \
202   PR_END_MACRO
203 
204   TRY_ALLOWPROP(Plugins);
205   // Bug 1328013 : Don't collect "AllowJavascript" property
206   // TRY_ALLOWPROP(Javascript);
207   TRY_ALLOWPROP(MetaRedirects);
208   TRY_ALLOWPROP(Subframes);
209   TRY_ALLOWPROP(Images);
210   TRY_ALLOWPROP(Media);
211   TRY_ALLOWPROP(DNSPrefetch);
212   TRY_ALLOWPROP(WindowControl);
213   TRY_ALLOWPROP(Auth);
214   TRY_ALLOWPROP(ContentRetargeting);
215   TRY_ALLOWPROP(ContentRetargetingOnChildren);
216 #undef TRY_ALLOWPROP
217 }
218 
219 /* static */
RestoreDocShellCapabilities(const GlobalObject & aGlobal,nsIDocShell * aDocShell,const nsCString & aDisallowCapabilities)220 void SessionStoreUtils::RestoreDocShellCapabilities(
221     const GlobalObject& aGlobal, nsIDocShell* aDocShell,
222     const nsCString& aDisallowCapabilities) {
223   aDocShell->SetAllowPlugins(true);
224   aDocShell->SetAllowJavascript(true);
225   aDocShell->SetAllowMetaRedirects(true);
226   aDocShell->SetAllowSubframes(true);
227   aDocShell->SetAllowImages(true);
228   aDocShell->SetAllowMedia(true);
229   aDocShell->SetAllowDNSPrefetch(true);
230   aDocShell->SetAllowWindowControl(true);
231   aDocShell->SetAllowContentRetargeting(true);
232   aDocShell->SetAllowContentRetargetingOnChildren(true);
233 
234   nsCCharSeparatedTokenizer tokenizer(aDisallowCapabilities, ',');
235   while (tokenizer.hasMoreTokens()) {
236     const nsACString& token = tokenizer.nextToken();
237     if (token.EqualsLiteral("Plugins")) {
238       aDocShell->SetAllowPlugins(false);
239     } else if (token.EqualsLiteral("Javascript")) {
240       aDocShell->SetAllowJavascript(false);
241     } else if (token.EqualsLiteral("MetaRedirects")) {
242       aDocShell->SetAllowMetaRedirects(false);
243     } else if (token.EqualsLiteral("Subframes")) {
244       aDocShell->SetAllowSubframes(false);
245     } else if (token.EqualsLiteral("Images")) {
246       aDocShell->SetAllowImages(false);
247     } else if (token.EqualsLiteral("Media")) {
248       aDocShell->SetAllowMedia(false);
249     } else if (token.EqualsLiteral("DNSPrefetch")) {
250       aDocShell->SetAllowDNSPrefetch(false);
251     } else if (token.EqualsLiteral("WindowControl")) {
252       aDocShell->SetAllowWindowControl(false);
253     } else if (token.EqualsLiteral("ContentRetargeting")) {
254       bool allow;
255       aDocShell->GetAllowContentRetargetingOnChildren(&allow);
256       aDocShell->SetAllowContentRetargeting(
257           false);  // will also set AllowContentRetargetingOnChildren
258       aDocShell->SetAllowContentRetargetingOnChildren(
259           allow);  // restore the allowProp to original
260     } else if (token.EqualsLiteral("ContentRetargetingOnChildren")) {
261       aDocShell->SetAllowContentRetargetingOnChildren(false);
262     }
263   }
264 }
265 
CollectCurrentScrollPosition(JSContext * aCx,Document & aDocument,Nullable<CollectedData> & aRetVal)266 static void CollectCurrentScrollPosition(JSContext* aCx, Document& aDocument,
267                                          Nullable<CollectedData>& aRetVal) {
268   PresShell* presShell = aDocument.GetPresShell();
269   if (!presShell) {
270     return;
271   }
272   nsPoint scrollPos = presShell->GetVisualViewportOffset();
273   int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
274   int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
275 
276   if ((scrollX != 0) || (scrollY != 0)) {
277     aRetVal.SetValue().mScroll.Construct() =
278         nsPrintfCString("%d,%d", scrollX, scrollY);
279   }
280 }
281 
282 /* static */
RestoreScrollPosition(const GlobalObject & aGlobal,nsGlobalWindowInner & aWindow,const CollectedData & aData)283 void SessionStoreUtils::RestoreScrollPosition(const GlobalObject& aGlobal,
284                                               nsGlobalWindowInner& aWindow,
285                                               const CollectedData& aData) {
286   if (!aData.mScroll.WasPassed()) {
287     return;
288   }
289 
290   nsCCharSeparatedTokenizer tokenizer(aData.mScroll.Value(), ',');
291   nsAutoCString token(tokenizer.nextToken());
292   int pos_X = atoi(token.get());
293   token = tokenizer.nextToken();
294   int pos_Y = atoi(token.get());
295 
296   aWindow.ScrollTo(pos_X, pos_Y);
297 
298   if (nsCOMPtr<Document> doc = aWindow.GetExtantDoc()) {
299     if (nsPresContext* presContext = doc->GetPresContext()) {
300       if (presContext->IsRootContentDocument()) {
301         // Use eMainThread so this takes precedence over session history
302         // (ScrollFrameHelper::ScrollToRestoredPosition()).
303         presContext->PresShell()->ScrollToVisual(
304             CSSPoint::ToAppUnits(CSSPoint(pos_X, pos_Y)),
305             layers::FrameMetrics::eMainThread, ScrollMode::Instant);
306       }
307     }
308   }
309 }
310 
311 // Implements the Luhn checksum algorithm as described at
312 // http://wikipedia.org/wiki/Luhn_algorithm
313 // Number digit lengths vary with network, but should fall within 12-19 range.
314 // [2] More details at https://en.wikipedia.org/wiki/Payment_card_number
IsValidCCNumber(nsAString & aValue)315 static bool IsValidCCNumber(nsAString& aValue) {
316   uint32_t total = 0;
317   uint32_t numLength = 0;
318   uint32_t strLen = aValue.Length();
319   for (uint32_t i = 0; i < strLen; ++i) {
320     uint32_t idx = strLen - i - 1;
321     // ignore whitespace and dashes)
322     char16_t chr = aValue[idx];
323     if (IsSpaceCharacter(chr) || chr == '-') {
324       continue;
325     }
326     // If our number is too long, note that fact
327     ++numLength;
328     if (numLength > 19) {
329       return false;
330     }
331     // Try to parse the character as a base-10 integer.
332     nsresult rv = NS_OK;
333     uint32_t val = Substring(aValue, idx, 1).ToInteger(&rv, 10);
334     if (NS_FAILED(rv)) {
335       return false;
336     }
337     if (i % 2 == 1) {
338       val *= 2;
339       if (val > 9) {
340         val -= 9;
341       }
342     }
343     total += val;
344   }
345 
346   return numLength >= 12 && total % 10 == 0;
347 }
348 
349 // Limit the number of XPath expressions for performance reasons. See bug
350 // 477564.
351 static const uint16_t kMaxTraversedXPaths = 100;
352 
353 // A helper function to append a element into mId or mXpath of CollectedData
354 static Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
AppendEntryToCollectedData(nsINode * aNode,const nsAString & aId,uint16_t & aGeneratedCount,Nullable<CollectedData> & aRetVal)355 AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
356                            uint16_t& aGeneratedCount,
357                            Nullable<CollectedData>& aRetVal) {
358   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry;
359   if (!aId.IsEmpty()) {
360     if (!aRetVal.SetValue().mId.WasPassed()) {
361       aRetVal.SetValue().mId.Construct();
362     }
363     auto& recordEntries = aRetVal.SetValue().mId.Value().Entries();
364     entry = recordEntries.AppendElement();
365     entry->mKey = aId;
366   } else {
367     if (!aRetVal.SetValue().mXpath.WasPassed()) {
368       aRetVal.SetValue().mXpath.Construct();
369     }
370     auto& recordEntries = aRetVal.SetValue().mXpath.Value().Entries();
371     entry = recordEntries.AppendElement();
372     nsAutoString xpath;
373     aNode->GenerateXPath(xpath);
374     aGeneratedCount++;
375     entry->mKey = xpath;
376   }
377   return entry;
378 }
379 
380 // A helper function to append a element into aXPathVals or aIdVals
AppendEntryToCollectedData(nsINode * aNode,const nsAString & aId,CollectedInputDataValue & aEntry,uint16_t & aNumXPath,uint16_t & aNumId,nsTArray<CollectedInputDataValue> & aXPathVals,nsTArray<CollectedInputDataValue> & aIdVals)381 static void AppendEntryToCollectedData(
382     nsINode* aNode, const nsAString& aId, CollectedInputDataValue& aEntry,
383     uint16_t& aNumXPath, uint16_t& aNumId,
384     nsTArray<CollectedInputDataValue>& aXPathVals,
385     nsTArray<CollectedInputDataValue>& aIdVals) {
386   if (!aId.IsEmpty()) {
387     aEntry.id = aId;
388     aIdVals.AppendElement(aEntry);
389     aNumId++;
390   } else {
391     nsAutoString xpath;
392     aNode->GenerateXPath(xpath);
393     aEntry.id = xpath;
394     aXPathVals.AppendElement(aEntry);
395     aNumXPath++;
396   }
397 }
398 
399 /* for bool value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const bool & aValue,uint16_t & aGeneratedCount,JSContext * aCx,Nullable<CollectedData> & aRetVal)400 static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
401                                        const bool& aValue,
402                                        uint16_t& aGeneratedCount,
403                                        JSContext* aCx,
404                                        Nullable<CollectedData>& aRetVal) {
405   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
406       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
407   entry->mValue.SetAsBoolean() = aValue;
408 }
409 
410 /* for bool value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const bool & aValue,uint16_t & aNumXPath,uint16_t & aNumId,nsTArray<CollectedInputDataValue> & aXPathVals,nsTArray<CollectedInputDataValue> & aIdVals)411 static void AppendValueToCollectedData(
412     nsINode* aNode, const nsAString& aId, const bool& aValue,
413     uint16_t& aNumXPath, uint16_t& aNumId,
414     nsTArray<CollectedInputDataValue>& aXPathVals,
415     nsTArray<CollectedInputDataValue>& aIdVals) {
416   CollectedInputDataValue entry;
417   entry.type = NS_LITERAL_STRING("bool");
418   entry.value = AsVariant(aValue);
419   AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
420                              aIdVals);
421 }
422 
423 /* for nsString value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const nsString & aValue,uint16_t & aGeneratedCount,Nullable<CollectedData> & aRetVal)424 static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
425                                        const nsString& aValue,
426                                        uint16_t& aGeneratedCount,
427                                        Nullable<CollectedData>& aRetVal) {
428   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
429       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
430   entry->mValue.SetAsString() = aValue;
431 }
432 
433 /* for nsString value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const nsString & aValue,uint16_t & aNumXPath,uint16_t & aNumId,nsTArray<CollectedInputDataValue> & aXPathVals,nsTArray<CollectedInputDataValue> & aIdVals)434 static void AppendValueToCollectedData(
435     nsINode* aNode, const nsAString& aId, const nsString& aValue,
436     uint16_t& aNumXPath, uint16_t& aNumId,
437     nsTArray<CollectedInputDataValue>& aXPathVals,
438     nsTArray<CollectedInputDataValue>& aIdVals) {
439   CollectedInputDataValue entry;
440   entry.type = NS_LITERAL_STRING("string");
441   entry.value = AsVariant(aValue);
442   AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
443                              aIdVals);
444 }
445 
446 /* for single select value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const CollectedNonMultipleSelectValue & aValue,uint16_t & aGeneratedCount,JSContext * aCx,Nullable<CollectedData> & aRetVal)447 static void AppendValueToCollectedData(
448     nsINode* aNode, const nsAString& aId,
449     const CollectedNonMultipleSelectValue& aValue, uint16_t& aGeneratedCount,
450     JSContext* aCx, Nullable<CollectedData>& aRetVal) {
451   JS::Rooted<JS::Value> jsval(aCx);
452   if (!ToJSValue(aCx, aValue, &jsval)) {
453     JS_ClearPendingException(aCx);
454     return;
455   }
456   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
457       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
458   entry->mValue.SetAsObject() = &jsval.toObject();
459 }
460 
461 /* for single select value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const CollectedNonMultipleSelectValue & aValue,uint16_t & aNumXPath,uint16_t & aNumId,nsTArray<CollectedInputDataValue> & aXPathVals,nsTArray<CollectedInputDataValue> & aIdVals)462 static void AppendValueToCollectedData(
463     nsINode* aNode, const nsAString& aId,
464     const CollectedNonMultipleSelectValue& aValue, uint16_t& aNumXPath,
465     uint16_t& aNumId, nsTArray<CollectedInputDataValue>& aXPathVals,
466     nsTArray<CollectedInputDataValue>& aIdVals) {
467   CollectedInputDataValue entry;
468   entry.type = NS_LITERAL_STRING("singleSelect");
469   entry.value = AsVariant(aValue);
470   AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
471                              aIdVals);
472 }
473 
474 /* special handing for input element with string type */
AppendValueToCollectedData(Document & aDocument,nsINode * aNode,const nsAString & aId,const nsString & aValue,uint16_t & aGeneratedCount,JSContext * aCx,Nullable<CollectedData> & aRetVal)475 static void AppendValueToCollectedData(Document& aDocument, nsINode* aNode,
476                                        const nsAString& aId,
477                                        const nsString& aValue,
478                                        uint16_t& aGeneratedCount,
479                                        JSContext* aCx,
480                                        Nullable<CollectedData>& aRetVal) {
481   if (!aId.IsEmpty()) {
482     // We want to avoid saving data for about:sessionrestore as a string.
483     // Since it's stored in the form as stringified JSON, stringifying
484     // further causes an explosion of escape characters. cf. bug 467409
485     if (aId.EqualsLiteral("sessionData")) {
486       nsAutoCString url;
487       Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
488       if (url.EqualsLiteral("about:sessionrestore") ||
489           url.EqualsLiteral("about:welcomeback")) {
490         JS::Rooted<JS::Value> jsval(aCx);
491         if (JS_ParseJSON(aCx, aValue.get(), aValue.Length(), &jsval) &&
492             jsval.isObject()) {
493           Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
494               AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
495           entry->mValue.SetAsObject() = &jsval.toObject();
496         } else {
497           JS_ClearPendingException(aCx);
498         }
499         return;
500       }
501     }
502   }
503   AppendValueToCollectedData(aNode, aId, aValue, aGeneratedCount, aRetVal);
504 }
505 
AppendValueToCollectedData(Document & aDocument,nsINode * aNode,const nsAString & aId,const nsString & aValue,uint16_t & aNumXPath,uint16_t & aNumId,nsTArray<CollectedInputDataValue> & aXPathVals,nsTArray<CollectedInputDataValue> & aIdVals)506 static void AppendValueToCollectedData(
507     Document& aDocument, nsINode* aNode, const nsAString& aId,
508     const nsString& aValue, uint16_t& aNumXPath, uint16_t& aNumId,
509     nsTArray<CollectedInputDataValue>& aXPathVals,
510     nsTArray<CollectedInputDataValue>& aIdVals) {
511   CollectedInputDataValue entry;
512   entry.type = NS_LITERAL_STRING("string");
513   entry.value = AsVariant(aValue);
514   AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
515                              aIdVals);
516 }
517 
518 /* for nsTArray<nsString>: file and multipleSelect */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const nsAString & aValueType,nsTArray<nsString> & aValue,uint16_t & aGeneratedCount,JSContext * aCx,Nullable<CollectedData> & aRetVal)519 static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
520                                        const nsAString& aValueType,
521                                        nsTArray<nsString>& aValue,
522                                        uint16_t& aGeneratedCount,
523                                        JSContext* aCx,
524                                        Nullable<CollectedData>& aRetVal) {
525   JS::Rooted<JS::Value> jsval(aCx);
526   if (aValueType.EqualsLiteral("file")) {
527     CollectedFileListValue val;
528     val.mType = aValueType;
529     val.mFileList.SwapElements(aValue);
530     if (!ToJSValue(aCx, val, &jsval)) {
531       JS_ClearPendingException(aCx);
532       return;
533     }
534   } else {
535     if (!ToJSValue(aCx, aValue, &jsval)) {
536       JS_ClearPendingException(aCx);
537       return;
538     }
539   }
540   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
541       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
542   entry->mValue.SetAsObject() = &jsval.toObject();
543 }
544 
545 /* for nsTArray<nsString>: file and multipleSelect */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const nsAString & aValueType,const nsTArray<nsString> & aValue,uint16_t & aNumXPath,uint16_t & aNumId,nsTArray<CollectedInputDataValue> & aXPathVals,nsTArray<CollectedInputDataValue> & aIdVals)546 static void AppendValueToCollectedData(
547     nsINode* aNode, const nsAString& aId, const nsAString& aValueType,
548     const nsTArray<nsString>& aValue, uint16_t& aNumXPath, uint16_t& aNumId,
549     nsTArray<CollectedInputDataValue>& aXPathVals,
550     nsTArray<CollectedInputDataValue>& aIdVals) {
551   CollectedInputDataValue entry;
552   entry.type = aValueType;
553   entry.value = AsVariant(CopyableTArray(aValue.Clone()));
554   AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
555                              aIdVals);
556 }
557 
558 /* static */
559 template <typename... ArgsT>
CollectFromTextAreaElement(Document & aDocument,uint16_t & aGeneratedCount,ArgsT &&...args)560 void SessionStoreUtils::CollectFromTextAreaElement(Document& aDocument,
561                                                    uint16_t& aGeneratedCount,
562                                                    ArgsT&&... args) {
563   RefPtr<nsContentList> textlist = NS_GetContentList(
564       &aDocument, kNameSpaceID_XHTML, NS_LITERAL_STRING("textarea"));
565   uint32_t length = textlist->Length(true);
566   for (uint32_t i = 0; i < length; ++i) {
567     MOZ_ASSERT(textlist->Item(i), "null item in node list!");
568 
569     HTMLTextAreaElement* textArea =
570         HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i));
571     if (!textArea) {
572       continue;
573     }
574     DOMString autocomplete;
575     textArea->GetAutocomplete(autocomplete);
576     if (autocomplete.AsAString().EqualsLiteral("off")) {
577       continue;
578     }
579     nsAutoString id;
580     textArea->GetId(id);
581     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
582       continue;
583     }
584     nsString value;
585     textArea->GetValue(value);
586     // In order to reduce XPath generation (which is slow), we only save data
587     // for form fields that have been changed. (cf. bug 537289)
588     if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
589                               eCaseMatters)) {
590       continue;
591     }
592     AppendValueToCollectedData(textArea, id, value, aGeneratedCount,
593                                std::forward<ArgsT>(args)...);
594   }
595 }
596 
597 /* static */
598 template <typename... ArgsT>
CollectFromInputElement(Document & aDocument,uint16_t & aGeneratedCount,ArgsT &&...args)599 void SessionStoreUtils::CollectFromInputElement(Document& aDocument,
600                                                 uint16_t& aGeneratedCount,
601                                                 ArgsT&&... args) {
602   RefPtr<nsContentList> inputlist = NS_GetContentList(
603       &aDocument, kNameSpaceID_XHTML, NS_LITERAL_STRING("input"));
604   uint32_t length = inputlist->Length(true);
605   for (uint32_t i = 0; i < length; ++i) {
606     MOZ_ASSERT(inputlist->Item(i), "null item in node list!");
607     nsCOMPtr<nsIFormControl> formControl =
608         do_QueryInterface(inputlist->Item(i));
609     if (formControl) {
610       uint8_t controlType = formControl->ControlType();
611       if (controlType == NS_FORM_INPUT_PASSWORD ||
612           controlType == NS_FORM_INPUT_HIDDEN ||
613           controlType == NS_FORM_INPUT_BUTTON ||
614           controlType == NS_FORM_INPUT_IMAGE ||
615           controlType == NS_FORM_INPUT_SUBMIT ||
616           controlType == NS_FORM_INPUT_RESET) {
617         continue;
618       }
619     }
620     RefPtr<HTMLInputElement> input =
621         HTMLInputElement::FromNodeOrNull(inputlist->Item(i));
622     if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) {
623       continue;
624     }
625     nsAutoString id;
626     input->GetId(id);
627     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
628       continue;
629     }
630     Nullable<AutocompleteInfo> aInfo;
631     input->GetAutocompleteInfo(aInfo);
632     if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) {
633       continue;
634     }
635 
636     if (input->ControlType() == NS_FORM_INPUT_CHECKBOX ||
637         input->ControlType() == NS_FORM_INPUT_RADIO) {
638       bool checked = input->Checked();
639       if (checked == input->DefaultChecked()) {
640         continue;
641       }
642       AppendValueToCollectedData(input, id, checked, aGeneratedCount,
643                                  std::forward<ArgsT>(args)...);
644     } else if (input->ControlType() == NS_FORM_INPUT_FILE) {
645       IgnoredErrorResult rv;
646       nsTArray<nsString> result;
647       input->MozGetFileNameArray(result, rv);
648       if (rv.Failed() || result.Length() == 0) {
649         continue;
650       }
651       AppendValueToCollectedData(input, id, NS_LITERAL_STRING("file"), result,
652                                  aGeneratedCount, std::forward<ArgsT>(args)...);
653     } else {
654       nsString value;
655       input->GetValue(value, CallerType::System);
656       // In order to reduce XPath generation (which is slow), we only save data
657       // for form fields that have been changed. (cf. bug 537289)
658       // Also, don't want to collect credit card number.
659       if (value.IsEmpty() || IsValidCCNumber(value) ||
660           input->HasBeenTypePassword() ||
661           input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
662                              eCaseMatters)) {
663         continue;
664       }
665       AppendValueToCollectedData(aDocument, input, id, value, aGeneratedCount,
666                                  std::forward<ArgsT>(args)...);
667     }
668   }
669 }
670 
671 /* static */
672 template <typename... ArgsT>
CollectFromSelectElement(Document & aDocument,uint16_t & aGeneratedCount,ArgsT &&...args)673 void SessionStoreUtils::CollectFromSelectElement(Document& aDocument,
674                                                  uint16_t& aGeneratedCount,
675                                                  ArgsT&&... args) {
676   RefPtr<nsContentList> selectlist = NS_GetContentList(
677       &aDocument, kNameSpaceID_XHTML, NS_LITERAL_STRING("select"));
678   uint32_t length = selectlist->Length(true);
679   for (uint32_t i = 0; i < length; ++i) {
680     MOZ_ASSERT(selectlist->Item(i), "null item in node list!");
681     RefPtr<HTMLSelectElement> select =
682         HTMLSelectElement::FromNodeOrNull(selectlist->Item(i));
683     if (!select) {
684       continue;
685     }
686     nsAutoString id;
687     select->GetId(id);
688     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
689       continue;
690     }
691     AutocompleteInfo aInfo;
692     select->GetAutocompleteInfo(aInfo);
693     if (!aInfo.mCanAutomaticallyPersist) {
694       continue;
695     }
696     nsAutoCString value;
697     if (!select->Multiple()) {
698       // <select>s without the multiple attribute are hard to determine the
699       // default value, so assume we don't have the default.
700       DOMString selectVal;
701       select->GetValue(selectVal);
702       CollectedNonMultipleSelectValue val;
703       val.mSelectedIndex = select->SelectedIndex();
704       val.mValue = selectVal.AsAString();
705       AppendValueToCollectedData(select, id, val, aGeneratedCount,
706                                  std::forward<ArgsT>(args)...);
707     } else {
708       // <select>s with the multiple attribute are easier to determine the
709       // default value since each <option> has a defaultSelected property
710       HTMLOptionsCollection* options = select->GetOptions();
711       if (!options) {
712         continue;
713       }
714       bool hasDefaultValue = true;
715       nsTArray<nsString> selectslist;
716       uint32_t numOptions = options->Length();
717       for (uint32_t idx = 0; idx < numOptions; idx++) {
718         HTMLOptionElement* option = options->ItemAsOption(idx);
719         bool selected = option->Selected();
720         if (!selected) {
721           continue;
722         }
723         option->GetValue(*selectslist.AppendElement());
724         hasDefaultValue =
725             hasDefaultValue && (selected == option->DefaultSelected());
726       }
727       // In order to reduce XPath generation (which is slow), we only save data
728       // for form fields that have been changed. (cf. bug 537289)
729       if (hasDefaultValue) {
730         continue;
731       }
732 
733       AppendValueToCollectedData(
734           select, id, NS_LITERAL_STRING("multipleSelect"), selectslist,
735           aGeneratedCount, std::forward<ArgsT>(args)...);
736     }
737   }
738 }
739 
CollectCurrentFormData(JSContext * aCx,Document & aDocument,Nullable<CollectedData> & aRetVal)740 static void CollectCurrentFormData(JSContext* aCx, Document& aDocument,
741                                    Nullable<CollectedData>& aRetVal) {
742   uint16_t generatedCount = 0;
743   /* textarea element */
744   SessionStoreUtils::CollectFromTextAreaElement(aDocument, generatedCount,
745                                                 aRetVal);
746   /* input element */
747   SessionStoreUtils::CollectFromInputElement(aDocument, generatedCount, aCx,
748                                              aRetVal);
749   /* select element */
750   SessionStoreUtils::CollectFromSelectElement(aDocument, generatedCount, aCx,
751                                               aRetVal);
752 
753   Element* bodyElement = aDocument.GetBody();
754   if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
755     bodyElement->GetInnerHTML(aRetVal.SetValue().mInnerHTML.Construct(),
756                               IgnoreErrors());
757   }
758 
759   if (aRetVal.IsNull()) {
760     return;
761   }
762 
763   // Store the frame's current URL with its form data so that we can compare
764   // it when restoring data to not inject form data into the wrong document.
765   nsIURI* uri = aDocument.GetDocumentURI();
766   if (uri) {
767     uri->GetSpecIgnoringRef(aRetVal.SetValue().mUrl.Construct());
768   }
769 }
770 
771 MOZ_CAN_RUN_SCRIPT
SetElementAsString(Element * aElement,const nsAString & aValue)772 static void SetElementAsString(Element* aElement, const nsAString& aValue) {
773   IgnoredErrorResult rv;
774   HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(aElement);
775   if (textArea) {
776     textArea->SetValue(aValue, rv);
777     if (!rv.Failed()) {
778       nsContentUtils::DispatchInputEvent(aElement);
779     }
780     return;
781   }
782   HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
783   if (input) {
784     input->SetValue(aValue, CallerType::NonSystem, rv);
785     if (!rv.Failed()) {
786       nsContentUtils::DispatchInputEvent(aElement);
787       return;
788     }
789   }
790   input = HTMLInputElement::FromNodeOrNull(
791       nsFocusManager::GetRedirectedFocus(aElement));
792   if (input) {
793     input->SetValue(aValue, CallerType::NonSystem, rv);
794     if (!rv.Failed()) {
795       nsContentUtils::DispatchInputEvent(aElement);
796     }
797   }
798 }
799 
800 MOZ_CAN_RUN_SCRIPT
SetElementAsBool(Element * aElement,bool aValue)801 static void SetElementAsBool(Element* aElement, bool aValue) {
802   HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
803   if (input) {
804     bool checked = input->Checked();
805     if (aValue != checked) {
806       input->SetChecked(aValue);
807       nsContentUtils::DispatchInputEvent(aElement);
808     }
809   }
810 }
811 
812 MOZ_CAN_RUN_SCRIPT
SetElementAsFiles(HTMLInputElement * aElement,const CollectedFileListValue & aValue)813 static void SetElementAsFiles(HTMLInputElement* aElement,
814                               const CollectedFileListValue& aValue) {
815   nsTArray<nsString> fileList;
816   IgnoredErrorResult rv;
817   aElement->MozSetFileNameArray(aValue.mFileList, rv);
818   if (rv.Failed()) {
819     return;
820   }
821   nsContentUtils::DispatchInputEvent(aElement);
822 }
823 
824 MOZ_CAN_RUN_SCRIPT
SetElementAsSelect(HTMLSelectElement * aElement,const CollectedNonMultipleSelectValue & aValue)825 static void SetElementAsSelect(HTMLSelectElement* aElement,
826                                const CollectedNonMultipleSelectValue& aValue) {
827   HTMLOptionsCollection* options = aElement->GetOptions();
828   if (!options) {
829     return;
830   }
831   int32_t selectIdx = options->SelectedIndex();
832   if (selectIdx >= 0) {
833     nsAutoString selectOptionVal;
834     options->ItemAsOption(selectIdx)->GetValue(selectOptionVal);
835     if (aValue.mValue.Equals(selectOptionVal)) {
836       return;
837     }
838   }
839   uint32_t numOptions = options->Length();
840   for (uint32_t idx = 0; idx < numOptions; idx++) {
841     HTMLOptionElement* option = options->ItemAsOption(idx);
842     nsAutoString optionValue;
843     option->GetValue(optionValue);
844     if (aValue.mValue.Equals(optionValue)) {
845       aElement->SetSelectedIndex(idx);
846       nsContentUtils::DispatchInputEvent(aElement);
847     }
848   }
849 }
850 
851 MOZ_CAN_RUN_SCRIPT
SetElementAsMultiSelect(HTMLSelectElement * aElement,const nsTArray<nsString> & aValueArray)852 static void SetElementAsMultiSelect(HTMLSelectElement* aElement,
853                                     const nsTArray<nsString>& aValueArray) {
854   bool fireEvent = false;
855   HTMLOptionsCollection* options = aElement->GetOptions();
856   if (!options) {
857     return;
858   }
859   uint32_t numOptions = options->Length();
860   for (uint32_t idx = 0; idx < numOptions; idx++) {
861     HTMLOptionElement* option = options->ItemAsOption(idx);
862     nsAutoString optionValue;
863     option->GetValue(optionValue);
864     for (uint32_t i = 0, l = aValueArray.Length(); i < l; ++i) {
865       if (optionValue.Equals(aValueArray[i])) {
866         option->SetSelected(true);
867         if (!option->DefaultSelected()) {
868           fireEvent = true;
869         }
870       }
871     }
872   }
873   if (fireEvent) {
874     nsContentUtils::DispatchInputEvent(aElement);
875   }
876 }
877 
878 MOZ_CAN_RUN_SCRIPT
SetElementAsObject(JSContext * aCx,Element * aElement,JS::Handle<JS::Value> aObject)879 static void SetElementAsObject(JSContext* aCx, Element* aElement,
880                                JS::Handle<JS::Value> aObject) {
881   RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aElement);
882   if (input) {
883     if (input->ControlType() == NS_FORM_INPUT_FILE) {
884       CollectedFileListValue value;
885       if (value.Init(aCx, aObject)) {
886         SetElementAsFiles(input, value);
887       } else {
888         JS_ClearPendingException(aCx);
889       }
890     }
891     return;
892   }
893   RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aElement);
894   if (select) {
895     // For Single Select Element
896     if (!select->Multiple()) {
897       CollectedNonMultipleSelectValue value;
898       if (value.Init(aCx, aObject)) {
899         SetElementAsSelect(select, value);
900       } else {
901         JS_ClearPendingException(aCx);
902       }
903       return;
904     }
905 
906     // For Multiple Selects Element
907     bool isArray = false;
908     JS::IsArrayObject(aCx, aObject, &isArray);
909     if (!isArray) {
910       return;
911     }
912     JS::Rooted<JSObject*> arrayObj(aCx, &aObject.toObject());
913     uint32_t arrayLength = 0;
914     if (!JS::GetArrayLength(aCx, arrayObj, &arrayLength)) {
915       JS_ClearPendingException(aCx);
916       return;
917     }
918     nsTArray<nsString> array(arrayLength);
919     for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) {
920       JS::Rooted<JS::Value> element(aCx);
921       if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) {
922         JS_ClearPendingException(aCx);
923         return;
924       }
925       if (!element.isString()) {
926         return;
927       }
928       nsAutoJSString value;
929       if (!value.init(aCx, element)) {
930         JS_ClearPendingException(aCx);
931         return;
932       }
933       array.AppendElement(value);
934     }
935     SetElementAsMultiSelect(select, array);
936   }
937 }
938 
939 MOZ_CAN_RUN_SCRIPT
SetRestoreData(JSContext * aCx,Element * aElement,JS::MutableHandle<JS::Value> aObject)940 static void SetRestoreData(JSContext* aCx, Element* aElement,
941                            JS::MutableHandle<JS::Value> aObject) {
942   nsAutoString data;
943   if (nsContentUtils::StringifyJSON(aCx, aObject, data)) {
944     SetElementAsString(aElement, data);
945   } else {
946     JS_ClearPendingException(aCx);
947   }
948 }
949 
950 MOZ_CAN_RUN_SCRIPT
SetInnerHTML(Document & aDocument,const CollectedData & aData)951 static void SetInnerHTML(Document& aDocument, const CollectedData& aData) {
952   RefPtr<Element> bodyElement = aDocument.GetBody();
953   if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
954     IgnoredErrorResult rv;
955     bodyElement->SetInnerHTML(aData.mInnerHTML.Value(),
956                               aDocument.NodePrincipal(), rv);
957     if (!rv.Failed()) {
958       nsContentUtils::DispatchInputEvent(bodyElement);
959     }
960   }
961 }
962 
963 class FormDataParseContext : public txIParseContext {
964  public:
FormDataParseContext(bool aCaseInsensitive)965   explicit FormDataParseContext(bool aCaseInsensitive)
966       : mIsCaseInsensitive(aCaseInsensitive) {}
967 
resolveNamespacePrefix(nsAtom * aPrefix,int32_t & aID)968   nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override {
969     if (aPrefix == nsGkAtoms::xul) {
970       aID = kNameSpaceID_XUL;
971     } else {
972       MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
973       aID = kNameSpaceID_XHTML;
974     }
975     return NS_OK;
976   }
977 
resolveFunctionCall(nsAtom * aName,int32_t aID,FunctionCall ** aFunction)978   nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
979                                FunctionCall** aFunction) override {
980     return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
981   }
982 
caseInsensitiveNameTests()983   bool caseInsensitiveNameTests() override { return mIsCaseInsensitive; }
984 
SetErrorOffset(uint32_t aOffset)985   void SetErrorOffset(uint32_t aOffset) override {}
986 
987  private:
988   bool mIsCaseInsensitive;
989 };
990 
FindNodeByXPath(JSContext * aCx,Document & aDocument,const nsAString & aExpression)991 static Element* FindNodeByXPath(JSContext* aCx, Document& aDocument,
992                                 const nsAString& aExpression) {
993   FormDataParseContext parsingContext(aDocument.IsHTMLDocument());
994   IgnoredErrorResult rv;
995   UniquePtr<XPathExpression> expression(
996       aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext,
997                                                    &aDocument, rv));
998   if (rv.Failed()) {
999     return nullptr;
1000   }
1001   RefPtr<XPathResult> result = expression->Evaluate(
1002       aCx, aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
1003   if (rv.Failed()) {
1004     return nullptr;
1005   }
1006   return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
1007 }
1008 
1009 MOZ_CAN_RUN_SCRIPT_BOUNDARY
1010 /* static */
RestoreFormData(const GlobalObject & aGlobal,Document & aDocument,const CollectedData & aData)1011 bool SessionStoreUtils::RestoreFormData(const GlobalObject& aGlobal,
1012                                         Document& aDocument,
1013                                         const CollectedData& aData) {
1014   if (!aData.mUrl.WasPassed()) {
1015     return true;
1016   }
1017   // Don't restore any data for the given frame if the URL
1018   // stored in the form data doesn't match its current URL.
1019   nsAutoCString url;
1020   Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
1021   if (!aData.mUrl.Value().Equals(url)) {
1022     return false;
1023   }
1024   if (aData.mInnerHTML.WasPassed()) {
1025     SetInnerHTML(aDocument, aData);
1026   }
1027   if (aData.mId.WasPassed()) {
1028     for (auto& entry : aData.mId.Value().Entries()) {
1029       RefPtr<Element> node = aDocument.GetElementById(entry.mKey);
1030       if (node == nullptr) {
1031         continue;
1032       }
1033       if (entry.mValue.IsString()) {
1034         SetElementAsString(node, entry.mValue.GetAsString());
1035       } else if (entry.mValue.IsBoolean()) {
1036         SetElementAsBool(node, entry.mValue.GetAsBoolean());
1037       } else {
1038         // For about:{sessionrestore,welcomeback} we saved the field as JSON to
1039         // avoid nested instances causing humongous sessionstore.js files.
1040         // cf. bug 467409
1041         JSContext* cx = aGlobal.Context();
1042         if (entry.mKey.EqualsLiteral("sessionData")) {
1043           nsAutoCString url;
1044           Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
1045           if (url.EqualsLiteral("about:sessionrestore") ||
1046               url.EqualsLiteral("about:welcomeback")) {
1047             JS::Rooted<JS::Value> object(
1048                 cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
1049             SetRestoreData(cx, node, &object);
1050             continue;
1051           }
1052         }
1053         JS::Rooted<JS::Value> object(
1054             cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
1055         SetElementAsObject(cx, node, object);
1056       }
1057     }
1058   }
1059   if (aData.mXpath.WasPassed()) {
1060     for (auto& entry : aData.mXpath.Value().Entries()) {
1061       RefPtr<Element> node =
1062           FindNodeByXPath(aGlobal.Context(), aDocument, entry.mKey);
1063       if (node == nullptr) {
1064         continue;
1065       }
1066       if (entry.mValue.IsString()) {
1067         SetElementAsString(node, entry.mValue.GetAsString());
1068       } else if (entry.mValue.IsBoolean()) {
1069         SetElementAsBool(node, entry.mValue.GetAsBoolean());
1070       } else {
1071         JS::Rooted<JS::Value> object(
1072             aGlobal.Context(), JS::ObjectValue(*entry.mValue.GetAsObject()));
1073         SetElementAsObject(aGlobal.Context(), node, object);
1074       }
1075     }
1076   }
1077   return true;
1078 }
1079 
1080 /* Read entries in the session storage data contained in a tab's history. */
ReadAllEntriesFromStorage(nsPIDOMWindowOuter * aWindow,nsTArray<nsCString> & aOrigins,nsTArray<nsString> & aKeys,nsTArray<nsString> & aValues)1081 static void ReadAllEntriesFromStorage(nsPIDOMWindowOuter* aWindow,
1082                                       nsTArray<nsCString>& aOrigins,
1083                                       nsTArray<nsString>& aKeys,
1084                                       nsTArray<nsString>& aValues) {
1085   BrowsingContext* const browsingContext = aWindow->GetBrowsingContext();
1086   if (!browsingContext) {
1087     return;
1088   }
1089 
1090   Document* doc = aWindow->GetDoc();
1091   if (!doc) {
1092     return;
1093   }
1094 
1095   nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
1096   if (!principal) {
1097     return;
1098   }
1099 
1100   nsCOMPtr<nsIPrincipal> storagePrincipal = doc->IntrinsicStoragePrincipal();
1101   if (!storagePrincipal) {
1102     return;
1103   }
1104 
1105   nsAutoCString origin;
1106   nsresult rv = storagePrincipal->GetOrigin(origin);
1107   if (NS_FAILED(rv) || aOrigins.Contains(origin)) {
1108     // Don't read a host twice.
1109     return;
1110   }
1111 
1112   /* Completed checking for recursion and is about to read storage*/
1113   const RefPtr<SessionStorageManager> storageManager =
1114       browsingContext->GetSessionStorageManager();
1115   if (!storageManager) {
1116     return;
1117   }
1118   RefPtr<Storage> storage;
1119   storageManager->GetStorage(aWindow->GetCurrentInnerWindow(), principal,
1120                              storagePrincipal, false, getter_AddRefs(storage));
1121   if (!storage) {
1122     return;
1123   }
1124   mozilla::IgnoredErrorResult result;
1125   uint32_t len = storage->GetLength(*principal, result);
1126   if (result.Failed() || len == 0) {
1127     return;
1128   }
1129   int64_t storageUsage = storage->GetOriginQuotaUsage();
1130   if (storageUsage > StaticPrefs::browser_sessionstore_dom_storage_limit()) {
1131     return;
1132   }
1133 
1134   for (uint32_t i = 0; i < len; i++) {
1135     nsString key, value;
1136     mozilla::IgnoredErrorResult res;
1137     storage->Key(i, key, *principal, res);
1138     if (res.Failed()) {
1139       continue;
1140     }
1141 
1142     storage->GetItem(key, value, *principal, res);
1143     if (res.Failed()) {
1144       continue;
1145     }
1146 
1147     aKeys.AppendElement(key);
1148     aValues.AppendElement(value);
1149     aOrigins.AppendElement(origin);
1150   }
1151 }
1152 
1153 /* Collect Collect session storage from current frame and all child frame */
1154 /* static */
CollectedSessionStorage(BrowsingContext * aBrowsingContext,nsTArray<nsCString> & aOrigins,nsTArray<nsString> & aKeys,nsTArray<nsString> & aValues)1155 void SessionStoreUtils::CollectedSessionStorage(
1156     BrowsingContext* aBrowsingContext, nsTArray<nsCString>& aOrigins,
1157     nsTArray<nsString>& aKeys, nsTArray<nsString>& aValues) {
1158   /* Collect session store from current frame */
1159   nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
1160   if (!window) {
1161     return;
1162   }
1163   ReadAllEntriesFromStorage(window, aOrigins, aKeys, aValues);
1164 
1165   /* Collect session storage from all child frame */
1166   nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
1167   if (!docShell) {
1168     return;
1169   }
1170 
1171   // This is not going to work for fission. Bug 1572084 for tracking it.
1172   for (BrowsingContext* child : aBrowsingContext->Children()) {
1173     window = child->GetDOMWindow();
1174     if (!window) {
1175       return;
1176     }
1177     docShell = window->GetDocShell();
1178     if (!docShell) {
1179       return;
1180     }
1181     bool isDynamic = false;
1182     nsresult rv = docShell->GetCreatedDynamically(&isDynamic);
1183     if (NS_SUCCEEDED(rv) && isDynamic) {
1184       continue;
1185     }
1186     SessionStoreUtils::CollectedSessionStorage(child, aOrigins, aKeys, aValues);
1187   }
1188 }
1189 
1190 /* static */
RestoreSessionStorage(const GlobalObject & aGlobal,nsIDocShell * aDocShell,const Record<nsString,Record<nsString,nsString>> & aData)1191 void SessionStoreUtils::RestoreSessionStorage(
1192     const GlobalObject& aGlobal, nsIDocShell* aDocShell,
1193     const Record<nsString, Record<nsString, nsString>>& aData) {
1194   for (auto& entry : aData.Entries()) {
1195     // NOTE: In capture() we record the full origin for the URI which the
1196     // sessionStorage is being captured for. As of bug 1235657 this code
1197     // stopped parsing any origins which have originattributes correctly, as
1198     // it decided to use the origin attributes from the docshell, and try to
1199     // interpret the origin as a URI. Since bug 1353844 this code now correctly
1200     // parses the full origin, and then discards the origin attributes, to
1201     // make the behavior line up with the original intentions in bug 1235657
1202     // while preserving the ability to read all session storage from
1203     // previous versions. In the future, if this behavior is desired, we may
1204     // want to use the spec instead of the origin as the key, and avoid
1205     // transmitting origin attribute information which we then discard when
1206     // restoring.
1207     //
1208     // If changing this logic, make sure to also change the principal
1209     // computation logic in SessionStore::_sendRestoreHistory.
1210 
1211     // OriginAttributes are always after a '^' character
1212     int32_t pos = entry.mKey.RFindChar('^');
1213     nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
1214         NS_ConvertUTF16toUTF8(Substring(entry.mKey, 0, pos)));
1215     BrowsingContext* const browsingContext =
1216         nsDocShell::Cast(aDocShell)->GetBrowsingContext();
1217     if (!browsingContext) {
1218       return;
1219     }
1220 
1221     nsCOMPtr<nsIPrincipal> storagePrincipal =
1222         BasePrincipal::CreateContentPrincipal(
1223             NS_ConvertUTF16toUTF8(entry.mKey));
1224 
1225     const RefPtr<SessionStorageManager> storageManager =
1226         browsingContext->GetSessionStorageManager();
1227     if (!storageManager) {
1228       return;
1229     }
1230     RefPtr<Storage> storage;
1231     // There is no need to pass documentURI, it's only used to fill documentURI
1232     // property of domstorage event, which in this case has no consumer.
1233     // Prevention of events in case of missing documentURI will be solved in a
1234     // followup bug to bug 600307.
1235     // Null window because the current window doesn't match the principal yet
1236     // and loads about:blank.
1237     storageManager->CreateStorage(nullptr, principal, storagePrincipal,
1238                                   EmptyString(), false,
1239                                   getter_AddRefs(storage));
1240     if (!storage) {
1241       continue;
1242     }
1243     for (auto& InnerEntry : entry.mValue.Entries()) {
1244       IgnoredErrorResult result;
1245       storage->SetItem(InnerEntry.mKey, InnerEntry.mValue, *principal, result);
1246       if (result.Failed()) {
1247         NS_WARNING("storage set item failed!");
1248       }
1249     }
1250   }
1251 }
1252 
1253 typedef void (*CollectorFunc)(JSContext* aCx, Document& aDocument,
1254                               Nullable<CollectedData>& aRetVal);
1255 
1256 /**
1257  * A function that will recursively call |CollectorFunc| to collect data for all
1258  * non-dynamic frames in the current frame/docShell tree.
1259  */
CollectFrameTreeData(JSContext * aCx,BrowsingContext * aBrowsingContext,Nullable<CollectedData> & aRetVal,CollectorFunc aFunc)1260 static void CollectFrameTreeData(JSContext* aCx,
1261                                  BrowsingContext* aBrowsingContext,
1262                                  Nullable<CollectedData>& aRetVal,
1263                                  CollectorFunc aFunc) {
1264   nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
1265   if (!window) {
1266     return;
1267   }
1268 
1269   nsIDocShell* docShell = window->GetDocShell();
1270   if (!docShell || docShell->GetCreatedDynamically()) {
1271     return;
1272   }
1273 
1274   Document* document = window->GetDoc();
1275   if (!document) {
1276     return;
1277   }
1278 
1279   /* Collect data from current frame */
1280   aFunc(aCx, *document, aRetVal);
1281 
1282   /* Collect data from all child frame */
1283   nsTArray<JSObject*> childrenData;
1284   SequenceRooter<JSObject*> rooter(aCx, &childrenData);
1285   uint32_t trailingNullCounter = 0;
1286 
1287   // This is not going to work for fission. Bug 1572084 for tracking it.
1288   for (auto& child : aBrowsingContext->Children()) {
1289     NullableRootedDictionary<CollectedData> data(aCx);
1290     CollectFrameTreeData(aCx, child, data, aFunc);
1291     if (data.IsNull()) {
1292       childrenData.AppendElement(nullptr);
1293       trailingNullCounter++;
1294       continue;
1295     }
1296     JS::Rooted<JS::Value> jsval(aCx);
1297     if (!ToJSValue(aCx, data.SetValue(), &jsval)) {
1298       JS_ClearPendingException(aCx);
1299       continue;
1300     }
1301     childrenData.AppendElement(&jsval.toObject());
1302     trailingNullCounter = 0;
1303   }
1304 
1305   if (trailingNullCounter != childrenData.Length()) {
1306     childrenData.TruncateLength(childrenData.Length() - trailingNullCounter);
1307     aRetVal.SetValue().mChildren.Construct().SwapElements(childrenData);
1308   }
1309 }
1310 
CollectScrollPosition(const GlobalObject & aGlobal,WindowProxyHolder & aWindow,Nullable<CollectedData> & aRetVal)1311 /* static */ void SessionStoreUtils::CollectScrollPosition(
1312     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
1313     Nullable<CollectedData>& aRetVal) {
1314   CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
1315                        CollectCurrentScrollPosition);
1316 }
1317 
CollectFormData(const GlobalObject & aGlobal,WindowProxyHolder & aWindow,Nullable<CollectedData> & aRetVal)1318 /* static */ void SessionStoreUtils::CollectFormData(
1319     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
1320     Nullable<CollectedData>& aRetVal) {
1321   CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
1322                        CollectCurrentFormData);
1323 }
1324 
ComposeInputData(const nsTArray<CollectedInputDataValue> & aData,InputElementData & ret)1325 /* static */ void SessionStoreUtils::ComposeInputData(
1326     const nsTArray<CollectedInputDataValue>& aData, InputElementData& ret) {
1327   nsTArray<int> selectedIndex, valueIdx;
1328   nsTArray<nsString> id, selectVal, strVal, type;
1329   nsTArray<bool> boolVal;
1330 
1331   for (const CollectedInputDataValue& data : aData) {
1332     id.AppendElement(data.id);
1333     type.AppendElement(data.type);
1334 
1335     if (data.value.is<mozilla::dom::CollectedNonMultipleSelectValue>()) {
1336       valueIdx.AppendElement(selectVal.Length());
1337       selectedIndex.AppendElement(
1338           data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
1339               .mSelectedIndex);
1340       selectVal.AppendElement(
1341           data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
1342               .mValue);
1343     } else if (data.value.is<CopyableTArray<nsString>>()) {
1344       // The first valueIdx is "index of the first string value"
1345       valueIdx.AppendElement(strVal.Length());
1346       strVal.AppendElements(data.value.as<CopyableTArray<nsString>>());
1347       // The second valueIdx is "index of the last string value" + 1
1348       id.AppendElement(data.id);
1349       type.AppendElement(data.type);
1350       valueIdx.AppendElement(strVal.Length());
1351     } else if (data.value.is<nsString>()) {
1352       valueIdx.AppendElement(strVal.Length());
1353       strVal.AppendElement(data.value.as<nsString>());
1354     } else if (data.type.EqualsLiteral("bool")) {
1355       valueIdx.AppendElement(boolVal.Length());
1356       boolVal.AppendElement(data.value.as<bool>());
1357     }
1358   }
1359 
1360   if (selectedIndex.Length() != 0) {
1361     ret.mSelectedIndex.Construct(std::move(selectedIndex));
1362   }
1363   if (valueIdx.Length() != 0) {
1364     ret.mValueIdx.Construct(std::move(valueIdx));
1365   }
1366   if (id.Length() != 0) {
1367     ret.mId.Construct(std::move(id));
1368   }
1369   if (selectVal.Length() != 0) {
1370     ret.mSelectVal.Construct(std::move(selectVal));
1371   }
1372   if (strVal.Length() != 0) {
1373     ret.mStrVal.Construct(std::move(strVal));
1374   }
1375   if (type.Length() != 0) {
1376     ret.mType.Construct(std::move(type));
1377   }
1378   if (boolVal.Length() != 0) {
1379     ret.mBoolVal.Construct(std::move(boolVal));
1380   }
1381 }
1382