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 http://mozilla.org/MPL/2.0/. */
6 
7 #include "js/Array.h"  // JS::GetArrayLength, JS::IsArrayObject
8 #include "js/JSON.h"
9 #include "jsapi.h"
10 #include "mozilla/PresShell.h"
11 #include "mozilla/dom/AutocompleteInfoBinding.h"
12 #include "mozilla/dom/CanonicalBrowsingContext.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/dom/DocumentInlines.h"
15 #include "mozilla/dom/HTMLInputElement.h"
16 #include "mozilla/dom/HTMLSelectElement.h"
17 #include "mozilla/dom/HTMLTextAreaElement.h"
18 #include "mozilla/dom/RootedDictionary.h"
19 #include "mozilla/dom/SessionStorageManager.h"
20 #include "mozilla/dom/PBackgroundSessionStorageCache.h"
21 #include "mozilla/dom/SessionStoreUtils.h"
22 #include "mozilla/dom/txIXPathContext.h"
23 #include "mozilla/dom/WindowGlobalParent.h"
24 #include "mozilla/dom/WindowProxyHolder.h"
25 #include "mozilla/dom/XPathResult.h"
26 #include "mozilla/dom/XPathEvaluator.h"
27 #include "mozilla/dom/XPathExpression.h"
28 #include "mozilla/dom/PBackgroundSessionStorageCache.h"
29 #include "mozilla/ipc/BackgroundUtils.h"
30 #include "mozilla/ReverseIterator.h"
31 #include "mozilla/UniquePtr.h"
32 #include "nsCharSeparatedTokenizer.h"
33 #include "nsContentList.h"
34 #include "nsContentUtils.h"
35 #include "nsFocusManager.h"
36 #include "nsGlobalWindowOuter.h"
37 #include "nsIDocShell.h"
38 #include "nsIFormControl.h"
39 #include "nsIScrollableFrame.h"
40 #include "nsISHistory.h"
41 #include "nsIXULRuntime.h"
42 #include "nsPresContext.h"
43 #include "nsPrintfCString.h"
44 
45 using namespace mozilla;
46 using namespace mozilla::dom;
47 using namespace mozilla::dom::sessionstore;
48 
49 namespace {
50 
51 class DynamicFrameEventFilter final : public nsIDOMEventListener {
52  public:
53   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter)54   NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter)
55 
56   explicit DynamicFrameEventFilter(EventListener* aListener)
57       : mListener(aListener) {}
58 
HandleEvent(Event * aEvent)59   NS_IMETHODIMP HandleEvent(Event* aEvent) override {
60     if (mListener && TargetInNonDynamicDocShell(aEvent)) {
61       mListener->HandleEvent(*aEvent);
62     }
63 
64     return NS_OK;
65   }
66 
67  private:
68   ~DynamicFrameEventFilter() = default;
69 
TargetInNonDynamicDocShell(Event * aEvent)70   bool TargetInNonDynamicDocShell(Event* aEvent) {
71     EventTarget* target = aEvent->GetTarget();
72     if (!target) {
73       return false;
74     }
75 
76     nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal();
77     if (!outer || !outer->GetDocShell()) {
78       return false;
79     }
80 
81     RefPtr<BrowsingContext> context = outer->GetBrowsingContext();
82     return context && !context->CreatedDynamically();
83   }
84 
85   RefPtr<EventListener> mListener;
86 };
87 
88 NS_IMPL_CYCLE_COLLECTION(DynamicFrameEventFilter, mListener)
89 
90 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DynamicFrameEventFilter)
91   NS_INTERFACE_MAP_ENTRY(nsISupports)
92   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
93 NS_INTERFACE_MAP_END
94 
95 NS_IMPL_CYCLE_COLLECTING_ADDREF(DynamicFrameEventFilter)
96 NS_IMPL_CYCLE_COLLECTING_RELEASE(DynamicFrameEventFilter)
97 
98 }  // anonymous namespace
99 
100 /* static */
ForEachNonDynamicChildFrame(const GlobalObject & aGlobal,WindowProxyHolder & aWindow,SessionStoreUtilsFrameCallback & aCallback,ErrorResult & aRv)101 void SessionStoreUtils::ForEachNonDynamicChildFrame(
102     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
103     SessionStoreUtilsFrameCallback& aCallback, ErrorResult& aRv) {
104   if (!aWindow.get()) {
105     aRv.Throw(NS_ERROR_INVALID_ARG);
106     return;
107   }
108 
109   nsCOMPtr<nsIDocShell> docShell = aWindow.get()->GetDocShell();
110   if (!docShell) {
111     aRv.Throw(NS_ERROR_FAILURE);
112     return;
113   }
114 
115   int32_t length;
116   aRv = docShell->GetInProcessChildCount(&length);
117   if (aRv.Failed()) {
118     return;
119   }
120 
121   for (int32_t i = 0; i < length; ++i) {
122     nsCOMPtr<nsIDocShellTreeItem> item;
123     docShell->GetInProcessChildAt(i, getter_AddRefs(item));
124     if (!item) {
125       aRv.Throw(NS_ERROR_FAILURE);
126       return;
127     }
128 
129     RefPtr<BrowsingContext> context = item->GetBrowsingContext();
130     if (!context) {
131       aRv.Throw(NS_ERROR_FAILURE);
132       return;
133     }
134 
135     if (!context->CreatedDynamically()) {
136       int32_t childOffset = context->ChildOffset();
137       aCallback.Call(WindowProxyHolder(context.forget()), childOffset);
138     }
139   }
140 }
141 
142 /* static */
143 already_AddRefed<nsISupports>
AddDynamicFrameFilteredListener(const GlobalObject & aGlobal,EventTarget & aTarget,const nsAString & aType,JS::Handle<JS::Value> aListener,bool aUseCapture,bool aMozSystemGroup,ErrorResult & aRv)144 SessionStoreUtils::AddDynamicFrameFilteredListener(
145     const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType,
146     JS::Handle<JS::Value> aListener, bool aUseCapture, bool aMozSystemGroup,
147     ErrorResult& aRv) {
148   if (NS_WARN_IF(!aListener.isObject())) {
149     aRv.Throw(NS_ERROR_INVALID_ARG);
150     return nullptr;
151   }
152 
153   JSContext* cx = aGlobal.Context();
154   JS::Rooted<JSObject*> obj(cx, &aListener.toObject());
155   JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
156   RefPtr<EventListener> listener =
157       new EventListener(cx, obj, global, GetIncumbentGlobal());
158 
159   nsCOMPtr<nsIDOMEventListener> filter(new DynamicFrameEventFilter(listener));
160   if (aMozSystemGroup) {
161     aRv = aTarget.AddSystemEventListener(aType, filter, aUseCapture);
162   } else {
163     aRv = aTarget.AddEventListener(aType, filter, aUseCapture);
164   }
165   if (aRv.Failed()) {
166     return nullptr;
167   }
168 
169   return filter.forget();
170 }
171 
172 /* static */
RemoveDynamicFrameFilteredListener(const GlobalObject & global,EventTarget & aTarget,const nsAString & aType,nsISupports * aListener,bool aUseCapture,bool aMozSystemGroup,ErrorResult & aRv)173 void SessionStoreUtils::RemoveDynamicFrameFilteredListener(
174     const GlobalObject& global, EventTarget& aTarget, const nsAString& aType,
175     nsISupports* aListener, bool aUseCapture, bool aMozSystemGroup,
176     ErrorResult& aRv) {
177   nsCOMPtr<nsIDOMEventListener> listener = do_QueryInterface(aListener);
178   if (!listener) {
179     aRv.Throw(NS_ERROR_NO_INTERFACE);
180     return;
181   }
182 
183   if (aMozSystemGroup) {
184     aTarget.RemoveSystemEventListener(aType, listener, aUseCapture);
185   } else {
186     aTarget.RemoveEventListener(aType, listener, aUseCapture);
187   }
188 }
189 
190 /* static */
CollectDocShellCapabilities(const GlobalObject & aGlobal,nsIDocShell * aDocShell,nsCString & aRetVal)191 void SessionStoreUtils::CollectDocShellCapabilities(const GlobalObject& aGlobal,
192                                                     nsIDocShell* aDocShell,
193                                                     nsCString& aRetVal) {
194   bool allow;
195 
196 #define TRY_ALLOWPROP(y)          \
197   PR_BEGIN_MACRO                  \
198   aDocShell->GetAllow##y(&allow); \
199   if (!allow) {                   \
200     if (!aRetVal.IsEmpty()) {     \
201       aRetVal.Append(',');        \
202     }                             \
203     aRetVal.Append(#y);           \
204   }                               \
205   PR_END_MACRO
206 
207   TRY_ALLOWPROP(Plugins);
208   // Bug 1328013 : Don't collect "AllowJavascript" property
209   // TRY_ALLOWPROP(Javascript);
210   TRY_ALLOWPROP(MetaRedirects);
211   TRY_ALLOWPROP(Subframes);
212   TRY_ALLOWPROP(Images);
213   TRY_ALLOWPROP(Media);
214   TRY_ALLOWPROP(DNSPrefetch);
215   TRY_ALLOWPROP(WindowControl);
216   TRY_ALLOWPROP(Auth);
217   TRY_ALLOWPROP(ContentRetargeting);
218   TRY_ALLOWPROP(ContentRetargetingOnChildren);
219 #undef TRY_ALLOWPROP
220 }
221 
222 /* static */
RestoreDocShellCapabilities(nsIDocShell * aDocShell,const nsCString & aDisallowCapabilities)223 void SessionStoreUtils::RestoreDocShellCapabilities(
224     nsIDocShell* aDocShell, const nsCString& aDisallowCapabilities) {
225   aDocShell->SetAllowPlugins(true);
226   aDocShell->SetAllowMetaRedirects(true);
227   aDocShell->SetAllowSubframes(true);
228   aDocShell->SetAllowImages(true);
229   aDocShell->SetAllowMedia(true);
230   aDocShell->SetAllowDNSPrefetch(true);
231   aDocShell->SetAllowWindowControl(true);
232   aDocShell->SetAllowContentRetargeting(true);
233   aDocShell->SetAllowContentRetargetingOnChildren(true);
234 
235   bool allowJavascript = true;
236   for (const nsACString& token :
237        nsCCharSeparatedTokenizer(aDisallowCapabilities, ',').ToRange()) {
238     if (token.EqualsLiteral("Plugins")) {
239       aDocShell->SetAllowPlugins(false);
240     } else if (token.EqualsLiteral("Javascript")) {
241       allowJavascript = false;
242     } else if (token.EqualsLiteral("MetaRedirects")) {
243       aDocShell->SetAllowMetaRedirects(false);
244     } else if (token.EqualsLiteral("Subframes")) {
245       aDocShell->SetAllowSubframes(false);
246     } else if (token.EqualsLiteral("Images")) {
247       aDocShell->SetAllowImages(false);
248     } else if (token.EqualsLiteral("Media")) {
249       aDocShell->SetAllowMedia(false);
250     } else if (token.EqualsLiteral("DNSPrefetch")) {
251       aDocShell->SetAllowDNSPrefetch(false);
252     } else if (token.EqualsLiteral("WindowControl")) {
253       aDocShell->SetAllowWindowControl(false);
254     } else if (token.EqualsLiteral("ContentRetargeting")) {
255       bool allow;
256       aDocShell->GetAllowContentRetargetingOnChildren(&allow);
257       aDocShell->SetAllowContentRetargeting(
258           false);  // will also set AllowContentRetargetingOnChildren
259       aDocShell->SetAllowContentRetargetingOnChildren(
260           allow);  // restore the allowProp to original
261     } else if (token.EqualsLiteral("ContentRetargetingOnChildren")) {
262       aDocShell->SetAllowContentRetargetingOnChildren(false);
263     }
264   }
265 
266   if (!mozilla::SessionHistoryInParent()) {
267     // With SessionHistoryInParent, this is set from the parent process.
268     BrowsingContext* bc = aDocShell->GetBrowsingContext();
269     Unused << bc->SetAllowJavascript(allowJavascript);
270   }
271 }
272 
CollectCurrentScrollPosition(JSContext * aCx,Document & aDocument,Nullable<CollectedData> & aRetVal)273 static void CollectCurrentScrollPosition(JSContext* aCx, Document& aDocument,
274                                          Nullable<CollectedData>& aRetVal) {
275   PresShell* presShell = aDocument.GetPresShell();
276   if (!presShell) {
277     return;
278   }
279   nsPoint scrollPos = presShell->GetVisualViewportOffset();
280   int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
281   int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
282 
283   if ((scrollX != 0) || (scrollY != 0)) {
284     aRetVal.SetValue().mScroll.Construct() =
285         nsPrintfCString("%d,%d", scrollX, scrollY);
286   }
287 }
288 
289 /* static */
RestoreScrollPosition(const GlobalObject & aGlobal,nsGlobalWindowInner & aWindow,const CollectedData & aData)290 void SessionStoreUtils::RestoreScrollPosition(const GlobalObject& aGlobal,
291                                               nsGlobalWindowInner& aWindow,
292                                               const CollectedData& aData) {
293   if (aData.mScroll.WasPassed()) {
294     RestoreScrollPosition(aWindow, aData.mScroll.Value());
295   }
296 }
297 
298 /* static */
RestoreScrollPosition(nsGlobalWindowInner & aWindow,const nsCString & aScrollPosition)299 void SessionStoreUtils::RestoreScrollPosition(
300     nsGlobalWindowInner& aWindow, const nsCString& aScrollPosition) {
301   nsCCharSeparatedTokenizer tokenizer(aScrollPosition, ',');
302   nsAutoCString token(tokenizer.nextToken());
303   int pos_X = atoi(token.get());
304   token = tokenizer.nextToken();
305   int pos_Y = atoi(token.get());
306 
307   aWindow.ScrollTo(pos_X, pos_Y);
308 
309   if (nsCOMPtr<Document> doc = aWindow.GetExtantDoc()) {
310     if (nsPresContext* presContext = doc->GetPresContext()) {
311       if (presContext->IsRootContentDocument()) {
312         // Use eMainThread so this takes precedence over session history
313         // (ScrollFrameHelper::ScrollToRestoredPosition()).
314         presContext->PresShell()->ScrollToVisual(
315             CSSPoint::ToAppUnits(CSSPoint(pos_X, pos_Y)),
316             layers::FrameMetrics::eMainThread, ScrollMode::Instant);
317       }
318     }
319   }
320 }
321 
322 // Implements the Luhn checksum algorithm as described at
323 // http://wikipedia.org/wiki/Luhn_algorithm
324 // Number digit lengths vary with network, but should fall within 12-19 range.
325 // [2] More details at https://en.wikipedia.org/wiki/Payment_card_number
IsValidCCNumber(nsAString & aValue)326 static bool IsValidCCNumber(nsAString& aValue) {
327   uint32_t total = 0;
328   uint32_t numLength = 0;
329   uint32_t strLen = aValue.Length();
330   for (uint32_t i = 0; i < strLen; ++i) {
331     uint32_t idx = strLen - i - 1;
332     // ignore whitespace and dashes)
333     char16_t chr = aValue[idx];
334     if (IsSpaceCharacter(chr) || chr == '-') {
335       continue;
336     }
337     // If our number is too long, note that fact
338     ++numLength;
339     if (numLength > 19) {
340       return false;
341     }
342     // Try to parse the character as a base-10 integer.
343     nsresult rv = NS_OK;
344     uint32_t val = Substring(aValue, idx, 1).ToInteger(&rv, 10);
345     if (NS_FAILED(rv)) {
346       return false;
347     }
348     if (i % 2 == 1) {
349       val *= 2;
350       if (val > 9) {
351         val -= 9;
352       }
353     }
354     total += val;
355   }
356 
357   return numLength >= 12 && total % 10 == 0;
358 }
359 
360 // Limit the number of XPath expressions for performance reasons. See bug
361 // 477564.
362 static const uint16_t kMaxTraversedXPaths = 100;
363 
364 // A helper function to append a element into mId or mXpath of CollectedData
365 static Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
AppendEntryToCollectedData(nsINode * aNode,const nsAString & aId,uint16_t & aGeneratedCount,Nullable<CollectedData> & aRetVal)366 AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
367                            uint16_t& aGeneratedCount,
368                            Nullable<CollectedData>& aRetVal) {
369   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry;
370   if (!aId.IsEmpty()) {
371     if (!aRetVal.SetValue().mId.WasPassed()) {
372       aRetVal.SetValue().mId.Construct();
373     }
374     auto& recordEntries = aRetVal.SetValue().mId.Value().Entries();
375     entry = recordEntries.AppendElement();
376     entry->mKey = aId;
377   } else {
378     if (!aRetVal.SetValue().mXpath.WasPassed()) {
379       aRetVal.SetValue().mXpath.Construct();
380     }
381     auto& recordEntries = aRetVal.SetValue().mXpath.Value().Entries();
382     entry = recordEntries.AppendElement();
383     nsAutoString xpath;
384     aNode->GenerateXPath(xpath);
385     aGeneratedCount++;
386     entry->mKey = xpath;
387   }
388   return entry;
389 }
390 
391 /* for bool value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const bool & aValue,uint16_t & aGeneratedCount,JSContext * aCx,Nullable<CollectedData> & aRetVal)392 static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
393                                        const bool& aValue,
394                                        uint16_t& aGeneratedCount,
395                                        JSContext* aCx,
396                                        Nullable<CollectedData>& aRetVal) {
397   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
398       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
399   entry->mValue.SetAsBoolean() = aValue;
400 }
401 
402 /* for nsString value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const nsString & aValue,uint16_t & aGeneratedCount,Nullable<CollectedData> & aRetVal)403 static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
404                                        const nsString& aValue,
405                                        uint16_t& aGeneratedCount,
406                                        Nullable<CollectedData>& aRetVal) {
407   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
408       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
409   entry->mValue.SetAsString() = aValue;
410 }
411 
412 /* for single select value */
AppendValueToCollectedData(nsINode * aNode,const nsAString & aId,const CollectedNonMultipleSelectValue & aValue,uint16_t & aGeneratedCount,JSContext * aCx,Nullable<CollectedData> & aRetVal)413 static void AppendValueToCollectedData(
414     nsINode* aNode, const nsAString& aId,
415     const CollectedNonMultipleSelectValue& aValue, uint16_t& aGeneratedCount,
416     JSContext* aCx, Nullable<CollectedData>& aRetVal) {
417   JS::Rooted<JS::Value> jsval(aCx);
418   if (!ToJSValue(aCx, aValue, &jsval)) {
419     JS_ClearPendingException(aCx);
420     return;
421   }
422   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
423       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
424   entry->mValue.SetAsObject() = &jsval.toObject();
425 }
426 
427 /* 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)428 static void AppendValueToCollectedData(Document& aDocument, nsINode* aNode,
429                                        const nsAString& aId,
430                                        const nsString& aValue,
431                                        uint16_t& aGeneratedCount,
432                                        JSContext* aCx,
433                                        Nullable<CollectedData>& aRetVal) {
434   if (!aId.IsEmpty()) {
435     // We want to avoid saving data for about:sessionrestore as a string.
436     // Since it's stored in the form as stringified JSON, stringifying
437     // further causes an explosion of escape characters. cf. bug 467409
438     if (aId.EqualsLiteral("sessionData")) {
439       nsAutoCString url;
440       Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
441       if (url.EqualsLiteral("about:sessionrestore") ||
442           url.EqualsLiteral("about:welcomeback")) {
443         JS::Rooted<JS::Value> jsval(aCx);
444         if (JS_ParseJSON(aCx, aValue.get(), aValue.Length(), &jsval) &&
445             jsval.isObject()) {
446           Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
447               AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
448           entry->mValue.SetAsObject() = &jsval.toObject();
449         } else {
450           JS_ClearPendingException(aCx);
451         }
452         return;
453       }
454     }
455   }
456   AppendValueToCollectedData(aNode, aId, aValue, aGeneratedCount, aRetVal);
457 }
458 
459 /* 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)460 static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
461                                        const nsAString& aValueType,
462                                        nsTArray<nsString>& aValue,
463                                        uint16_t& aGeneratedCount,
464                                        JSContext* aCx,
465                                        Nullable<CollectedData>& aRetVal) {
466   JS::Rooted<JS::Value> jsval(aCx);
467   if (aValueType.EqualsLiteral("file")) {
468     CollectedFileListValue val;
469     val.mType = aValueType;
470     val.mFileList = std::move(aValue);
471     if (!ToJSValue(aCx, val, &jsval)) {
472       JS_ClearPendingException(aCx);
473       return;
474     }
475   } else {
476     if (!ToJSValue(aCx, aValue, &jsval)) {
477       JS_ClearPendingException(aCx);
478       return;
479     }
480   }
481   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
482       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
483   entry->mValue.SetAsObject() = &jsval.toObject();
484 }
485 
AppendEntry(nsINode * aNode,const nsString & aId,const FormEntryValue & aValue,sessionstore::FormData & aFormData)486 static void AppendEntry(nsINode* aNode, const nsString& aId,
487                         const FormEntryValue& aValue,
488                         sessionstore::FormData& aFormData) {
489   if (aId.IsEmpty()) {
490     FormEntry* entry = aFormData.xpath().AppendElement();
491     entry->value() = aValue;
492     aNode->GenerateXPath(entry->id());
493   } else {
494     aFormData.id().AppendElement(FormEntry{aId, aValue});
495   }
496 }
497 
CollectTextAreaElement(Document * aDocument,sessionstore::FormData & aFormData)498 static void CollectTextAreaElement(Document* aDocument,
499                                    sessionstore::FormData& aFormData) {
500   RefPtr<nsContentList> textlist =
501       NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"textarea"_ns);
502   uint32_t length = textlist->Length();
503   for (uint32_t i = 0; i < length; ++i) {
504     MOZ_ASSERT(textlist->Item(i), "null item in node list!");
505 
506     HTMLTextAreaElement* textArea =
507         HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i));
508     if (!textArea) {
509       continue;
510     }
511     DOMString autocomplete;
512     textArea->GetAutocomplete(autocomplete);
513     if (autocomplete.AsAString().EqualsLiteral("off")) {
514       continue;
515     }
516     nsAutoString id;
517     textArea->GetId(id);
518     if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
519       continue;
520     }
521     nsString value;
522     textArea->GetValue(value);
523     // In order to reduce XPath generation (which is slow), we only save data
524     // for form fields that have been changed. (cf. bug 537289)
525     if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
526                               eCaseMatters)) {
527       continue;
528     }
529 
530     AppendEntry(textArea, id, TextField{value}, aFormData);
531   }
532 }
533 
CollectInputElement(Document * aDocument,sessionstore::FormData & aFormData)534 static void CollectInputElement(Document* aDocument,
535                                 sessionstore::FormData& aFormData) {
536   RefPtr<nsContentList> inputlist =
537       NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"input"_ns);
538   uint32_t length = inputlist->Length();
539   for (uint32_t i = 0; i < length; ++i) {
540     MOZ_ASSERT(inputlist->Item(i), "null item in node list!");
541     nsCOMPtr<nsIFormControl> formControl =
542         do_QueryInterface(inputlist->Item(i));
543     if (formControl) {
544       auto controlType = formControl->ControlType();
545       if (controlType == FormControlType::InputPassword ||
546           controlType == FormControlType::InputHidden ||
547           controlType == FormControlType::InputButton ||
548           controlType == FormControlType::InputImage ||
549           controlType == FormControlType::InputSubmit ||
550           controlType == FormControlType::InputReset) {
551         continue;
552       }
553     }
554     RefPtr<HTMLInputElement> input =
555         HTMLInputElement::FromNodeOrNull(inputlist->Item(i));
556     if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) {
557       continue;
558     }
559     nsAutoString id;
560     input->GetId(id);
561     if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
562       continue;
563     }
564     Nullable<AutocompleteInfo> aInfo;
565     input->GetAutocompleteInfo(aInfo);
566     if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) {
567       continue;
568     }
569 
570     FormEntryValue value;
571     if (input->ControlType() == FormControlType::InputCheckbox ||
572         input->ControlType() == FormControlType::InputRadio) {
573       bool checked = input->Checked();
574       if (checked == input->DefaultChecked()) {
575         continue;
576       }
577       AppendEntry(input, id, Checkbox{checked}, aFormData);
578     } else if (input->ControlType() == FormControlType::InputFile) {
579       IgnoredErrorResult rv;
580       sessionstore::FileList file;
581       input->MozGetFileNameArray(file.valueList(), rv);
582       if (rv.Failed() || file.valueList().IsEmpty()) {
583         continue;
584       }
585       AppendEntry(input, id, file, aFormData);
586     } else {
587       TextField field;
588       input->GetValue(field.value(), CallerType::System);
589       auto& value = field.value();
590       // In order to reduce XPath generation (which is slow), we only save data
591       // for form fields that have been changed. (cf. bug 537289)
592       // Also, don't want to collect credit card number.
593       if (value.IsEmpty() || IsValidCCNumber(value) ||
594           input->HasBeenTypePassword() ||
595           input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
596                              eCaseMatters)) {
597         continue;
598       }
599       AppendEntry(input, id, field, aFormData);
600     }
601   }
602 }
603 
CollectSelectElement(Document * aDocument,sessionstore::FormData & aFormData)604 static void CollectSelectElement(Document* aDocument,
605                                  sessionstore::FormData& aFormData) {
606   RefPtr<nsContentList> selectlist =
607       NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"select"_ns);
608   uint32_t length = selectlist->Length();
609   for (uint32_t i = 0; i < length; ++i) {
610     MOZ_ASSERT(selectlist->Item(i), "null item in node list!");
611     RefPtr<HTMLSelectElement> select =
612         HTMLSelectElement::FromNodeOrNull(selectlist->Item(i));
613     if (!select) {
614       continue;
615     }
616     nsAutoString id;
617     select->GetId(id);
618     if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
619       continue;
620     }
621     AutocompleteInfo aInfo;
622     select->GetAutocompleteInfo(aInfo);
623     if (!aInfo.mCanAutomaticallyPersist) {
624       continue;
625     }
626 
627     if (!select->Multiple()) {
628       HTMLOptionsCollection* options = select->GetOptions();
629       if (!options) {
630         continue;
631       }
632 
633       uint32_t numOptions = options->Length();
634       int32_t defaultIndex = 0;
635       for (uint32_t idx = 0; idx < numOptions; idx++) {
636         HTMLOptionElement* option = options->ItemAsOption(idx);
637         if (option->DefaultSelected()) {
638           defaultIndex = option->Index();
639         }
640       }
641 
642       int32_t selectedIndex = select->SelectedIndex();
643       if (selectedIndex == defaultIndex || selectedIndex < 0) {
644         continue;
645       }
646 
647       DOMString selectVal;
648       select->GetValue(selectVal);
649       AppendEntry(select, id,
650                   SingleSelect{static_cast<uint32_t>(selectedIndex),
651                                selectVal.AsAString()},
652                   aFormData);
653     } else {
654       HTMLOptionsCollection* options = select->GetOptions();
655       if (!options) {
656         continue;
657       }
658       bool hasDefaultValue = true;
659       nsTArray<nsString> selectslist;
660       uint32_t numOptions = options->Length();
661       for (uint32_t idx = 0; idx < numOptions; idx++) {
662         HTMLOptionElement* option = options->ItemAsOption(idx);
663         bool selected = option->Selected();
664 
665         hasDefaultValue =
666             hasDefaultValue && (selected == option->DefaultSelected());
667 
668         if (!selected) {
669           continue;
670         }
671         option->GetValue(*selectslist.AppendElement());
672       }
673       // In order to reduce XPath generation (which is slow), we only save data
674       // for form fields that have been changed. (cf. bug 537289)
675       if (hasDefaultValue) {
676         continue;
677       }
678 
679       AppendEntry(select, id, MultipleSelect{selectslist}, aFormData);
680     }
681   }
682 }
683 
684 /* static */
CollectFormData(Document * aDocument,sessionstore::FormData & aFormData)685 void SessionStoreUtils::CollectFormData(Document* aDocument,
686                                         sessionstore::FormData& aFormData) {
687   MOZ_DIAGNOSTIC_ASSERT(aDocument);
688   CollectTextAreaElement(aDocument, aFormData);
689   CollectInputElement(aDocument, aFormData);
690   CollectSelectElement(aDocument, aFormData);
691 
692   aFormData.hasData() =
693       !aFormData.id().IsEmpty() || !aFormData.xpath().IsEmpty();
694 }
695 
696 /* static */
697 template <typename... ArgsT>
CollectFromTextAreaElement(Document & aDocument,uint16_t & aGeneratedCount,ArgsT &&...args)698 void SessionStoreUtils::CollectFromTextAreaElement(Document& aDocument,
699                                                    uint16_t& aGeneratedCount,
700                                                    ArgsT&&... args) {
701   RefPtr<nsContentList> textlist =
702       NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"textarea"_ns);
703   uint32_t length = textlist->Length(true);
704   for (uint32_t i = 0; i < length; ++i) {
705     MOZ_ASSERT(textlist->Item(i), "null item in node list!");
706 
707     HTMLTextAreaElement* textArea =
708         HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i));
709     if (!textArea) {
710       continue;
711     }
712     DOMString autocomplete;
713     textArea->GetAutocomplete(autocomplete);
714     if (autocomplete.AsAString().EqualsLiteral("off")) {
715       continue;
716     }
717     nsAutoString id;
718     textArea->GetId(id);
719     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
720       continue;
721     }
722     nsString value;
723     textArea->GetValue(value);
724     // In order to reduce XPath generation (which is slow), we only save data
725     // for form fields that have been changed. (cf. bug 537289)
726     if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
727                               eCaseMatters)) {
728       continue;
729     }
730     AppendValueToCollectedData(textArea, id, value, aGeneratedCount,
731                                std::forward<ArgsT>(args)...);
732   }
733 }
734 
735 /* static */
736 template <typename... ArgsT>
CollectFromInputElement(Document & aDocument,uint16_t & aGeneratedCount,ArgsT &&...args)737 void SessionStoreUtils::CollectFromInputElement(Document& aDocument,
738                                                 uint16_t& aGeneratedCount,
739                                                 ArgsT&&... args) {
740   RefPtr<nsContentList> inputlist =
741       NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"input"_ns);
742   uint32_t length = inputlist->Length(true);
743   for (uint32_t i = 0; i < length; ++i) {
744     MOZ_ASSERT(inputlist->Item(i), "null item in node list!");
745     nsCOMPtr<nsIFormControl> formControl =
746         do_QueryInterface(inputlist->Item(i));
747     if (formControl) {
748       auto controlType = formControl->ControlType();
749       if (controlType == FormControlType::InputPassword ||
750           controlType == FormControlType::InputHidden ||
751           controlType == FormControlType::InputButton ||
752           controlType == FormControlType::InputImage ||
753           controlType == FormControlType::InputSubmit ||
754           controlType == FormControlType::InputReset) {
755         continue;
756       }
757     }
758     RefPtr<HTMLInputElement> input =
759         HTMLInputElement::FromNodeOrNull(inputlist->Item(i));
760     if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) {
761       continue;
762     }
763     nsAutoString id;
764     input->GetId(id);
765     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
766       continue;
767     }
768     Nullable<AutocompleteInfo> aInfo;
769     input->GetAutocompleteInfo(aInfo);
770     if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) {
771       continue;
772     }
773 
774     if (input->ControlType() == FormControlType::InputCheckbox ||
775         input->ControlType() == FormControlType::InputRadio) {
776       bool checked = input->Checked();
777       if (checked == input->DefaultChecked()) {
778         continue;
779       }
780       AppendValueToCollectedData(input, id, checked, aGeneratedCount,
781                                  std::forward<ArgsT>(args)...);
782     } else if (input->ControlType() == FormControlType::InputFile) {
783       IgnoredErrorResult rv;
784       nsTArray<nsString> result;
785       input->MozGetFileNameArray(result, rv);
786       if (rv.Failed() || result.Length() == 0) {
787         continue;
788       }
789       AppendValueToCollectedData(input, id, u"file"_ns, result, aGeneratedCount,
790                                  std::forward<ArgsT>(args)...);
791     } else {
792       nsString value;
793       input->GetValue(value, CallerType::System);
794       // In order to reduce XPath generation (which is slow), we only save data
795       // for form fields that have been changed. (cf. bug 537289)
796       // Also, don't want to collect credit card number.
797       if (value.IsEmpty() || IsValidCCNumber(value) ||
798           input->HasBeenTypePassword() ||
799           input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
800                              eCaseMatters)) {
801         continue;
802       }
803       AppendValueToCollectedData(aDocument, input, id, value, aGeneratedCount,
804                                  std::forward<ArgsT>(args)...);
805     }
806   }
807 }
808 
809 /* static */
810 template <typename... ArgsT>
CollectFromSelectElement(Document & aDocument,uint16_t & aGeneratedCount,ArgsT &&...args)811 void SessionStoreUtils::CollectFromSelectElement(Document& aDocument,
812                                                  uint16_t& aGeneratedCount,
813                                                  ArgsT&&... args) {
814   RefPtr<nsContentList> selectlist =
815       NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"select"_ns);
816   uint32_t length = selectlist->Length(true);
817   for (uint32_t i = 0; i < length; ++i) {
818     MOZ_ASSERT(selectlist->Item(i), "null item in node list!");
819     RefPtr<HTMLSelectElement> select =
820         HTMLSelectElement::FromNodeOrNull(selectlist->Item(i));
821     if (!select) {
822       continue;
823     }
824     nsAutoString id;
825     select->GetId(id);
826     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
827       continue;
828     }
829     AutocompleteInfo aInfo;
830     select->GetAutocompleteInfo(aInfo);
831     if (!aInfo.mCanAutomaticallyPersist) {
832       continue;
833     }
834     nsAutoCString value;
835     if (!select->Multiple()) {
836       // <select>s without the multiple attribute are hard to determine the
837       // default value, so assume we don't have the default.
838       DOMString selectVal;
839       select->GetValue(selectVal);
840       CollectedNonMultipleSelectValue val;
841       val.mSelectedIndex = select->SelectedIndex();
842       val.mValue = selectVal.AsAString();
843       AppendValueToCollectedData(select, id, val, aGeneratedCount,
844                                  std::forward<ArgsT>(args)...);
845     } else {
846       // <select>s with the multiple attribute are easier to determine the
847       // default value since each <option> has a defaultSelected property
848       HTMLOptionsCollection* options = select->GetOptions();
849       if (!options) {
850         continue;
851       }
852       bool hasDefaultValue = true;
853       nsTArray<nsString> selectslist;
854       uint32_t numOptions = options->Length();
855       for (uint32_t idx = 0; idx < numOptions; idx++) {
856         HTMLOptionElement* option = options->ItemAsOption(idx);
857         bool selected = option->Selected();
858         if (!selected) {
859           continue;
860         }
861         option->GetValue(*selectslist.AppendElement());
862         hasDefaultValue =
863             hasDefaultValue && (selected == option->DefaultSelected());
864       }
865       // In order to reduce XPath generation (which is slow), we only save data
866       // for form fields that have been changed. (cf. bug 537289)
867       if (hasDefaultValue) {
868         continue;
869       }
870 
871       AppendValueToCollectedData(select, id, u"multipleSelect"_ns, selectslist,
872                                  aGeneratedCount, std::forward<ArgsT>(args)...);
873     }
874   }
875 }
876 
CollectCurrentFormData(JSContext * aCx,Document & aDocument,Nullable<CollectedData> & aRetVal)877 static void CollectCurrentFormData(JSContext* aCx, Document& aDocument,
878                                    Nullable<CollectedData>& aRetVal) {
879   uint16_t generatedCount = 0;
880   /* textarea element */
881   SessionStoreUtils::CollectFromTextAreaElement(aDocument, generatedCount,
882                                                 aRetVal);
883   /* input element */
884   SessionStoreUtils::CollectFromInputElement(aDocument, generatedCount, aCx,
885                                              aRetVal);
886   /* select element */
887   SessionStoreUtils::CollectFromSelectElement(aDocument, generatedCount, aCx,
888                                               aRetVal);
889 
890   Element* bodyElement = aDocument.GetBody();
891   if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
892     bodyElement->GetInnerHTML(aRetVal.SetValue().mInnerHTML.Construct(),
893                               IgnoreErrors());
894   }
895 
896   if (aRetVal.IsNull()) {
897     return;
898   }
899 
900   // Store the frame's current URL with its form data so that we can compare
901   // it when restoring data to not inject form data into the wrong document.
902   nsIURI* uri = aDocument.GetDocumentURI();
903   if (uri) {
904     uri->GetSpecIgnoringRef(aRetVal.SetValue().mUrl.Construct());
905   }
906 }
907 
908 MOZ_CAN_RUN_SCRIPT
SetElementAsString(Element * aElement,const nsAString & aValue)909 static void SetElementAsString(Element* aElement, const nsAString& aValue) {
910   IgnoredErrorResult rv;
911   HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(aElement);
912   if (textArea) {
913     textArea->SetValue(aValue, rv);
914     if (!rv.Failed()) {
915       nsContentUtils::DispatchInputEvent(aElement);
916     }
917     return;
918   }
919   HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
920   if (input) {
921     input->SetValue(aValue, CallerType::NonSystem, rv);
922     if (!rv.Failed()) {
923       nsContentUtils::DispatchInputEvent(aElement);
924       return;
925     }
926   }
927   input = HTMLInputElement::FromNodeOrNull(
928       nsFocusManager::GetRedirectedFocus(aElement));
929   if (input) {
930     input->SetValue(aValue, CallerType::NonSystem, rv);
931     if (!rv.Failed()) {
932       nsContentUtils::DispatchInputEvent(aElement);
933     }
934   }
935 }
936 
937 MOZ_CAN_RUN_SCRIPT
SetElementAsBool(Element * aElement,bool aValue)938 static void SetElementAsBool(Element* aElement, bool aValue) {
939   HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
940   if (input) {
941     bool checked = input->Checked();
942     if (aValue != checked) {
943       input->SetChecked(aValue);
944       nsContentUtils::DispatchInputEvent(aElement);
945     }
946   }
947 }
948 
949 MOZ_CAN_RUN_SCRIPT
SetElementAsFiles(HTMLInputElement * aElement,const CollectedFileListValue & aValue)950 static void SetElementAsFiles(HTMLInputElement* aElement,
951                               const CollectedFileListValue& aValue) {
952   IgnoredErrorResult rv;
953   aElement->MozSetFileNameArray(aValue.mFileList, rv);
954   if (rv.Failed()) {
955     return;
956   }
957   nsContentUtils::DispatchInputEvent(aElement);
958 }
959 
960 MOZ_CAN_RUN_SCRIPT
SetElementAsSelect(HTMLSelectElement * aElement,const CollectedNonMultipleSelectValue & aValue)961 static void SetElementAsSelect(HTMLSelectElement* aElement,
962                                const CollectedNonMultipleSelectValue& aValue) {
963   HTMLOptionsCollection* options = aElement->GetOptions();
964   if (!options) {
965     return;
966   }
967   int32_t selectIdx = options->SelectedIndex();
968   if (selectIdx >= 0) {
969     nsAutoString selectOptionVal;
970     options->ItemAsOption(selectIdx)->GetValue(selectOptionVal);
971     if (aValue.mValue.Equals(selectOptionVal)) {
972       return;
973     }
974   }
975   uint32_t numOptions = options->Length();
976   for (uint32_t idx = 0; idx < numOptions; idx++) {
977     HTMLOptionElement* option = options->ItemAsOption(idx);
978     nsAutoString optionValue;
979     option->GetValue(optionValue);
980     if (aValue.mValue.Equals(optionValue)) {
981       aElement->SetSelectedIndex(idx);
982       nsContentUtils::DispatchInputEvent(aElement);
983     }
984   }
985 }
986 
987 MOZ_CAN_RUN_SCRIPT
SetElementAsMultiSelect(HTMLSelectElement * aElement,const nsTArray<nsString> & aValueArray)988 static void SetElementAsMultiSelect(HTMLSelectElement* aElement,
989                                     const nsTArray<nsString>& aValueArray) {
990   bool fireEvent = false;
991   HTMLOptionsCollection* options = aElement->GetOptions();
992   if (!options) {
993     return;
994   }
995   uint32_t numOptions = options->Length();
996   for (uint32_t idx = 0; idx < numOptions; idx++) {
997     HTMLOptionElement* option = options->ItemAsOption(idx);
998     nsAutoString optionValue;
999     option->GetValue(optionValue);
1000     for (uint32_t i = 0, l = aValueArray.Length(); i < l; ++i) {
1001       if (optionValue.Equals(aValueArray[i])) {
1002         option->SetSelected(true);
1003         if (!option->DefaultSelected()) {
1004           fireEvent = true;
1005         }
1006       }
1007     }
1008   }
1009   if (fireEvent) {
1010     nsContentUtils::DispatchInputEvent(aElement);
1011   }
1012 }
1013 
1014 MOZ_CAN_RUN_SCRIPT
SetElementAsObject(JSContext * aCx,Element * aElement,JS::Handle<JS::Value> aObject)1015 static void SetElementAsObject(JSContext* aCx, Element* aElement,
1016                                JS::Handle<JS::Value> aObject) {
1017   RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aElement);
1018   if (input) {
1019     if (input->ControlType() == FormControlType::InputFile) {
1020       CollectedFileListValue value;
1021       if (value.Init(aCx, aObject)) {
1022         SetElementAsFiles(input, value);
1023       } else {
1024         JS_ClearPendingException(aCx);
1025       }
1026     }
1027     return;
1028   }
1029   RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aElement);
1030   if (select) {
1031     // For Single Select Element
1032     if (!select->Multiple()) {
1033       CollectedNonMultipleSelectValue value;
1034       if (value.Init(aCx, aObject)) {
1035         SetElementAsSelect(select, value);
1036       } else {
1037         JS_ClearPendingException(aCx);
1038       }
1039       return;
1040     }
1041 
1042     // For Multiple Selects Element
1043     bool isArray = false;
1044     JS::IsArrayObject(aCx, aObject, &isArray);
1045     if (!isArray) {
1046       return;
1047     }
1048     JS::Rooted<JSObject*> arrayObj(aCx, &aObject.toObject());
1049     uint32_t arrayLength = 0;
1050     if (!JS::GetArrayLength(aCx, arrayObj, &arrayLength)) {
1051       JS_ClearPendingException(aCx);
1052       return;
1053     }
1054     nsTArray<nsString> array(arrayLength);
1055     for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) {
1056       JS::Rooted<JS::Value> element(aCx);
1057       if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) {
1058         JS_ClearPendingException(aCx);
1059         return;
1060       }
1061       if (!element.isString()) {
1062         return;
1063       }
1064       nsAutoJSString value;
1065       if (!value.init(aCx, element)) {
1066         JS_ClearPendingException(aCx);
1067         return;
1068       }
1069       array.AppendElement(value);
1070     }
1071     SetElementAsMultiSelect(select, array);
1072   }
1073 }
1074 
1075 MOZ_CAN_RUN_SCRIPT
SetSessionData(JSContext * aCx,Element * aElement,JS::MutableHandle<JS::Value> aObject)1076 static void SetSessionData(JSContext* aCx, Element* aElement,
1077                            JS::MutableHandle<JS::Value> aObject) {
1078   nsAutoString data;
1079   if (nsContentUtils::StringifyJSON(aCx, aObject, data)) {
1080     SetElementAsString(aElement, data);
1081   } else {
1082     JS_ClearPendingException(aCx);
1083   }
1084 }
1085 
1086 MOZ_CAN_RUN_SCRIPT
SetInnerHTML(Document & aDocument,const nsString & aInnerHTML)1087 static void SetInnerHTML(Document& aDocument, const nsString& aInnerHTML) {
1088   RefPtr<Element> bodyElement = aDocument.GetBody();
1089   if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
1090     IgnoredErrorResult rv;
1091     bodyElement->SetInnerHTML(aInnerHTML, aDocument.NodePrincipal(), rv);
1092     if (!rv.Failed()) {
1093       nsContentUtils::DispatchInputEvent(bodyElement);
1094     }
1095   }
1096 }
1097 
1098 class FormDataParseContext : public txIParseContext {
1099  public:
FormDataParseContext(bool aCaseInsensitive)1100   explicit FormDataParseContext(bool aCaseInsensitive)
1101       : mIsCaseInsensitive(aCaseInsensitive) {}
1102 
resolveNamespacePrefix(nsAtom * aPrefix,int32_t & aID)1103   nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override {
1104     if (aPrefix == nsGkAtoms::xul) {
1105       aID = kNameSpaceID_XUL;
1106     } else {
1107       MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
1108       aID = kNameSpaceID_XHTML;
1109     }
1110     return NS_OK;
1111   }
1112 
resolveFunctionCall(nsAtom * aName,int32_t aID,FunctionCall ** aFunction)1113   nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
1114                                FunctionCall** aFunction) override {
1115     return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
1116   }
1117 
caseInsensitiveNameTests()1118   bool caseInsensitiveNameTests() override { return mIsCaseInsensitive; }
1119 
SetErrorOffset(uint32_t aOffset)1120   void SetErrorOffset(uint32_t aOffset) override {}
1121 
1122  private:
1123   bool mIsCaseInsensitive;
1124 };
1125 
FindNodeByXPath(Document & aDocument,const nsAString & aExpression)1126 static Element* FindNodeByXPath(Document& aDocument,
1127                                 const nsAString& aExpression) {
1128   FormDataParseContext parsingContext(aDocument.IsHTMLDocument());
1129   IgnoredErrorResult rv;
1130   UniquePtr<XPathExpression> expression(
1131       aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext,
1132                                                    &aDocument, rv));
1133   if (rv.Failed()) {
1134     return nullptr;
1135   }
1136   RefPtr<XPathResult> result = expression->Evaluate(
1137       aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
1138   if (rv.Failed()) {
1139     return nullptr;
1140   }
1141   return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
1142 }
1143 
1144 MOZ_CAN_RUN_SCRIPT_BOUNDARY
1145 /* static */
RestoreFormData(const GlobalObject & aGlobal,Document & aDocument,const CollectedData & aData)1146 bool SessionStoreUtils::RestoreFormData(const GlobalObject& aGlobal,
1147                                         Document& aDocument,
1148                                         const CollectedData& aData) {
1149   if (!aData.mUrl.WasPassed()) {
1150     return true;
1151   }
1152   // Don't restore any data for the given frame if the URL
1153   // stored in the form data doesn't match its current URL.
1154   nsAutoCString url;
1155   Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
1156   if (!aData.mUrl.Value().Equals(url)) {
1157     return false;
1158   }
1159   if (aData.mInnerHTML.WasPassed()) {
1160     SetInnerHTML(aDocument, aData.mInnerHTML.Value());
1161   }
1162   if (aData.mId.WasPassed()) {
1163     for (auto& entry : aData.mId.Value().Entries()) {
1164       RefPtr<Element> node = aDocument.GetElementById(entry.mKey);
1165       if (node == nullptr) {
1166         continue;
1167       }
1168       if (entry.mValue.IsString()) {
1169         SetElementAsString(node, entry.mValue.GetAsString());
1170       } else if (entry.mValue.IsBoolean()) {
1171         SetElementAsBool(node, entry.mValue.GetAsBoolean());
1172       } else {
1173         // For about:{sessionrestore,welcomeback} we saved the field as JSON to
1174         // avoid nested instances causing humongous sessionstore.js files.
1175         // cf. bug 467409
1176         JSContext* cx = aGlobal.Context();
1177         if (entry.mKey.EqualsLiteral("sessionData")) {
1178           if (url.EqualsLiteral("about:sessionrestore") ||
1179               url.EqualsLiteral("about:welcomeback")) {
1180             JS::Rooted<JS::Value> object(
1181                 cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
1182             SetSessionData(cx, node, &object);
1183             continue;
1184           }
1185         }
1186         JS::Rooted<JS::Value> object(
1187             cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
1188         SetElementAsObject(cx, node, object);
1189       }
1190     }
1191   }
1192 
1193   if (aData.mXpath.WasPassed()) {
1194     for (auto& entry : aData.mXpath.Value().Entries()) {
1195       RefPtr<Element> node = FindNodeByXPath(aDocument, entry.mKey);
1196       if (node == nullptr) {
1197         continue;
1198       }
1199       if (entry.mValue.IsString()) {
1200         SetElementAsString(node, entry.mValue.GetAsString());
1201       } else if (entry.mValue.IsBoolean()) {
1202         SetElementAsBool(node, entry.mValue.GetAsBoolean());
1203       } else {
1204         JS::Rooted<JS::Value> object(
1205             aGlobal.Context(), JS::ObjectValue(*entry.mValue.GetAsObject()));
1206         SetElementAsObject(aGlobal.Context(), node, object);
1207       }
1208     }
1209   }
1210 
1211   return true;
1212 }
1213 
1214 MOZ_CAN_RUN_SCRIPT
RestoreFormEntry(Element * aNode,const FormEntryValue & aValue)1215 void RestoreFormEntry(Element* aNode, const FormEntryValue& aValue) {
1216   using Type = sessionstore::FormEntryValue::Type;
1217   switch (aValue.type()) {
1218     case Type::TCheckbox:
1219       SetElementAsBool(aNode, aValue.get_Checkbox().value());
1220       break;
1221     case Type::TTextField:
1222       SetElementAsString(aNode, aValue.get_TextField().value());
1223       break;
1224     case Type::TFileList: {
1225       if (RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aNode);
1226           input && input->ControlType() == FormControlType::InputFile) {
1227         CollectedFileListValue value;
1228         value.mFileList = aValue.get_FileList().valueList().Clone();
1229         SetElementAsFiles(input, value);
1230       }
1231       break;
1232     }
1233     case Type::TSingleSelect: {
1234       if (RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aNode);
1235           select && !select->Multiple()) {
1236         CollectedNonMultipleSelectValue value;
1237         value.mSelectedIndex = aValue.get_SingleSelect().index();
1238         value.mValue = aValue.get_SingleSelect().value();
1239         SetElementAsSelect(select, value);
1240       }
1241       break;
1242     }
1243     case Type::TMultipleSelect: {
1244       if (RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aNode);
1245           select && select->Multiple()) {
1246         SetElementAsMultiSelect(select,
1247                                 aValue.get_MultipleSelect().valueList());
1248       }
1249       break;
1250     }
1251     default:
1252       MOZ_ASSERT_UNREACHABLE();
1253   }
1254 }
1255 
1256 MOZ_CAN_RUN_SCRIPT
1257 /* static */
RestoreFormData(Document & aDocument,const nsString & aInnerHTML,const nsTArray<SessionStoreRestoreData::Entry> & aEntries)1258 void SessionStoreUtils::RestoreFormData(
1259     Document& aDocument, const nsString& aInnerHTML,
1260     const nsTArray<SessionStoreRestoreData::Entry>& aEntries) {
1261   if (!aInnerHTML.IsEmpty()) {
1262     SetInnerHTML(aDocument, aInnerHTML);
1263   }
1264 
1265   for (const auto& entry : aEntries) {
1266     RefPtr<Element> node = entry.mIsXPath
1267                                ? FindNodeByXPath(aDocument, entry.mData.id())
1268                                : aDocument.GetElementById(entry.mData.id());
1269     if (node) {
1270       RestoreFormEntry(node, entry.mData.value());
1271     }
1272   }
1273 }
1274 
1275 typedef void (*CollectorFunc)(JSContext* aCx, Document& aDocument,
1276                               Nullable<CollectedData>& aRetVal);
1277 
1278 /**
1279  * A function that will recursively call |CollectorFunc| to collect data for all
1280  * non-dynamic frames in the current frame/docShell tree.
1281  */
CollectFrameTreeData(JSContext * aCx,BrowsingContext * aBrowsingContext,Nullable<CollectedData> & aRetVal,CollectorFunc aFunc)1282 static void CollectFrameTreeData(JSContext* aCx,
1283                                  BrowsingContext* aBrowsingContext,
1284                                  Nullable<CollectedData>& aRetVal,
1285                                  CollectorFunc aFunc) {
1286   if (aBrowsingContext->CreatedDynamically()) {
1287     return;
1288   }
1289 
1290   nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
1291   if (!window || !window->GetDocShell()) {
1292     return;
1293   }
1294 
1295   Document* document = window->GetExtantDoc();
1296   if (!document) {
1297     return;
1298   }
1299 
1300   /* Collect data from current frame */
1301   aFunc(aCx, *document, aRetVal);
1302 
1303   /* Collect data from all child frame */
1304   nsTArray<JSObject*> childrenData;
1305   SequenceRooter<JSObject*> rooter(aCx, &childrenData);
1306   uint32_t trailingNullCounter = 0;
1307 
1308   // This is not going to work for fission. Bug 1572084 for tracking it.
1309   for (auto& child : aBrowsingContext->Children()) {
1310     NullableRootedDictionary<CollectedData> data(aCx);
1311     CollectFrameTreeData(aCx, child, data, aFunc);
1312     if (data.IsNull()) {
1313       childrenData.AppendElement(nullptr);
1314       trailingNullCounter++;
1315       continue;
1316     }
1317     JS::Rooted<JS::Value> jsval(aCx);
1318     if (!ToJSValue(aCx, data.SetValue(), &jsval)) {
1319       JS_ClearPendingException(aCx);
1320       continue;
1321     }
1322     childrenData.AppendElement(&jsval.toObject());
1323     trailingNullCounter = 0;
1324   }
1325 
1326   if (trailingNullCounter != childrenData.Length()) {
1327     childrenData.TruncateLength(childrenData.Length() - trailingNullCounter);
1328     aRetVal.SetValue().mChildren.Construct() = std::move(childrenData);
1329   }
1330 }
1331 
CollectScrollPosition(const GlobalObject & aGlobal,WindowProxyHolder & aWindow,Nullable<CollectedData> & aRetVal)1332 /* static */ void SessionStoreUtils::CollectScrollPosition(
1333     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
1334     Nullable<CollectedData>& aRetVal) {
1335   CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
1336                        CollectCurrentScrollPosition);
1337 }
1338 
CollectFormData(const GlobalObject & aGlobal,WindowProxyHolder & aWindow,Nullable<CollectedData> & aRetVal)1339 /* static */ void SessionStoreUtils::CollectFormData(
1340     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
1341     Nullable<CollectedData>& aRetVal) {
1342   CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
1343                        CollectCurrentFormData);
1344 }
1345 
ComposeInputData(const nsTArray<CollectedInputDataValue> & aData,InputElementData & ret)1346 /* static */ void SessionStoreUtils::ComposeInputData(
1347     const nsTArray<CollectedInputDataValue>& aData, InputElementData& ret) {
1348   nsTArray<int> selectedIndex, valueIdx;
1349   nsTArray<nsString> id, selectVal, strVal, type;
1350   nsTArray<bool> boolVal;
1351 
1352   for (const CollectedInputDataValue& data : aData) {
1353     id.AppendElement(data.id);
1354     type.AppendElement(data.type);
1355 
1356     if (data.value.is<mozilla::dom::CollectedNonMultipleSelectValue>()) {
1357       valueIdx.AppendElement(selectVal.Length());
1358       selectedIndex.AppendElement(
1359           data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
1360               .mSelectedIndex);
1361       selectVal.AppendElement(
1362           data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
1363               .mValue);
1364     } else if (data.value.is<CopyableTArray<nsString>>()) {
1365       // The first valueIdx is "index of the first string value"
1366       valueIdx.AppendElement(strVal.Length());
1367       strVal.AppendElements(data.value.as<CopyableTArray<nsString>>());
1368       // The second valueIdx is "index of the last string value" + 1
1369       id.AppendElement(data.id);
1370       type.AppendElement(data.type);
1371       valueIdx.AppendElement(strVal.Length());
1372     } else if (data.value.is<nsString>()) {
1373       valueIdx.AppendElement(strVal.Length());
1374       strVal.AppendElement(data.value.as<nsString>());
1375     } else if (data.type.EqualsLiteral("bool")) {
1376       valueIdx.AppendElement(boolVal.Length());
1377       boolVal.AppendElement(data.value.as<bool>());
1378     }
1379   }
1380 
1381   if (selectedIndex.Length() != 0) {
1382     ret.mSelectedIndex.Construct(std::move(selectedIndex));
1383   }
1384   if (valueIdx.Length() != 0) {
1385     ret.mValueIdx.Construct(std::move(valueIdx));
1386   }
1387   if (id.Length() != 0) {
1388     ret.mId.Construct(std::move(id));
1389   }
1390   if (selectVal.Length() != 0) {
1391     ret.mSelectVal.Construct(std::move(selectVal));
1392   }
1393   if (strVal.Length() != 0) {
1394     ret.mStrVal.Construct(std::move(strVal));
1395   }
1396   if (type.Length() != 0) {
1397     ret.mType.Construct(std::move(type));
1398   }
1399   if (boolVal.Length() != 0) {
1400     ret.mBoolVal.Construct(std::move(boolVal));
1401   }
1402 }
1403 
1404 MOZ_CAN_RUN_SCRIPT
1405 already_AddRefed<nsISessionStoreRestoreData>
ConstructSessionStoreRestoreData(const GlobalObject & aGlobal)1406 SessionStoreUtils::ConstructSessionStoreRestoreData(
1407     const GlobalObject& aGlobal) {
1408   nsCOMPtr<nsISessionStoreRestoreData> data = new SessionStoreRestoreData();
1409   return data.forget();
1410 }
1411 
1412 /* static */
1413 MOZ_CAN_RUN_SCRIPT
InitializeRestore(const GlobalObject & aGlobal,CanonicalBrowsingContext & aContext,nsISessionStoreRestoreData * aData,ErrorResult & aError)1414 already_AddRefed<Promise> SessionStoreUtils::InitializeRestore(
1415     const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
1416     nsISessionStoreRestoreData* aData, ErrorResult& aError) {
1417   if (!mozilla::SessionHistoryInParent()) {
1418     MOZ_CRASH("why were we called?");
1419   }
1420 
1421   MOZ_DIAGNOSTIC_ASSERT(aContext.IsTop());
1422 
1423   MOZ_DIAGNOSTIC_ASSERT(aData);
1424   nsCOMPtr<SessionStoreRestoreData> data = do_QueryInterface(aData);
1425   aContext.SetRestoreData(data, aError);
1426   if (aError.Failed()) {
1427     return nullptr;
1428   }
1429 
1430   MOZ_DIAGNOSTIC_ASSERT(aContext.GetSessionHistory());
1431   aContext.GetSessionHistory()->ReloadCurrentEntry();
1432 
1433   return aContext.GetRestorePromise();
1434 }
1435 
1436 /* static */
RestoreDocShellState(nsIDocShell * aDocShell,const DocShellRestoreState & aState)1437 void SessionStoreUtils::RestoreDocShellState(
1438     nsIDocShell* aDocShell, const DocShellRestoreState& aState) {
1439   if (aDocShell) {
1440     if (aState.URI()) {
1441       aDocShell->SetCurrentURI(aState.URI());
1442     }
1443     RestoreDocShellCapabilities(aDocShell, aState.docShellCaps());
1444   }
1445 }
1446 
1447 /* static */
RestoreDocShellState(const GlobalObject & aGlobal,CanonicalBrowsingContext & aContext,const nsACString & aURL,const nsCString & aDocShellCaps,ErrorResult & aError)1448 already_AddRefed<Promise> SessionStoreUtils::RestoreDocShellState(
1449     const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
1450     const nsACString& aURL, const nsCString& aDocShellCaps,
1451     ErrorResult& aError) {
1452   MOZ_RELEASE_ASSERT(mozilla::SessionHistoryInParent());
1453   MOZ_RELEASE_ASSERT(aContext.IsTop());
1454 
1455   if (WindowGlobalParent* wgp = aContext.GetCurrentWindowGlobal()) {
1456     nsCOMPtr<nsIGlobalObject> global =
1457         do_QueryInterface(aGlobal.GetAsSupports());
1458     MOZ_DIAGNOSTIC_ASSERT(global);
1459 
1460     RefPtr<Promise> promise = Promise::Create(global, aError);
1461     if (aError.Failed()) {
1462       return nullptr;
1463     }
1464 
1465     nsCOMPtr<nsIURI> uri;
1466     if (!aURL.IsEmpty()) {
1467       if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aURL))) {
1468         aError.Throw(NS_ERROR_FAILURE);
1469         return nullptr;
1470       }
1471     }
1472 
1473     bool allowJavascript = true;
1474     for (const nsACString& token :
1475          nsCCharSeparatedTokenizer(aDocShellCaps, ',').ToRange()) {
1476       if (token.EqualsLiteral("Javascript")) {
1477         allowJavascript = false;
1478       }
1479     }
1480 
1481     Unused << aContext.SetAllowJavascript(allowJavascript);
1482 
1483     DocShellRestoreState state = {uri, aDocShellCaps};
1484 
1485     // TODO (anny): Investigate removing this roundtrip.
1486     wgp->SendRestoreDocShellState(state)->Then(
1487         GetMainThreadSerialEventTarget(), __func__,
1488         [promise](void) { promise->MaybeResolveWithUndefined(); },
1489         [promise](void) { promise->MaybeRejectWithUndefined(); });
1490 
1491     return promise.forget();
1492   }
1493 
1494   return nullptr;
1495 }
1496 
1497 /* static */
RestoreSessionStorageFromParent(const GlobalObject & aGlobal,const CanonicalBrowsingContext & aContext,const Record<nsCString,Record<nsString,nsString>> & aSessionStorage)1498 void SessionStoreUtils::RestoreSessionStorageFromParent(
1499     const GlobalObject& aGlobal, const CanonicalBrowsingContext& aContext,
1500     const Record<nsCString, Record<nsString, nsString>>& aSessionStorage) {
1501   nsTArray<SSCacheCopy> cacheInitList;
1502   for (const auto& originEntry : aSessionStorage.Entries()) {
1503     nsCOMPtr<nsIPrincipal> storagePrincipal =
1504         BasePrincipal::CreateContentPrincipal(originEntry.mKey);
1505 
1506     nsCString originKey;
1507     nsresult rv = storagePrincipal->GetStorageOriginKey(originKey);
1508     if (NS_FAILED(rv)) {
1509       continue;
1510     }
1511 
1512     SSCacheCopy& cacheInit = *cacheInitList.AppendElement();
1513 
1514     cacheInit.originKey() = originKey;
1515     storagePrincipal->OriginAttributesRef().CreateSuffix(
1516         cacheInit.originAttributes());
1517 
1518     for (const auto& entry : originEntry.mValue.Entries()) {
1519       SSSetItemInfo& setItemInfo = *cacheInit.data().AppendElement();
1520       setItemInfo.key() = entry.mKey;
1521       setItemInfo.value() = entry.mValue;
1522     }
1523   }
1524 
1525   BackgroundSessionStorageManager::LoadData(aContext.Id(), cacheInitList);
1526 }
1527 
1528 /* static */
ConstructFormDataValues(JSContext * aCx,const nsTArray<sessionstore::FormEntry> & aValues,nsTArray<Record<nsString,OwningStringOrBooleanOrObject>::EntryType> & aEntries,bool aParseSessionData)1529 nsresult SessionStoreUtils::ConstructFormDataValues(
1530     JSContext* aCx, const nsTArray<sessionstore::FormEntry>& aValues,
1531     nsTArray<Record<nsString, OwningStringOrBooleanOrObject>::EntryType>&
1532         aEntries,
1533     bool aParseSessionData) {
1534   using EntryType = Record<nsString, OwningStringOrBooleanOrObject>::EntryType;
1535 
1536   if (!aEntries.SetCapacity(aValues.Length(), fallible)) {
1537     return NS_ERROR_FAILURE;
1538   }
1539 
1540   for (const auto& value : aValues) {
1541     EntryType* entry = aEntries.AppendElement();
1542 
1543     using Type = sessionstore::FormEntryValue::Type;
1544     switch (value.value().type()) {
1545       case Type::TCheckbox:
1546         entry->mValue.SetAsBoolean() = value.value().get_Checkbox().value();
1547         break;
1548       case Type::TTextField: {
1549         if (aParseSessionData && value.id() == u"sessionData"_ns) {
1550           JS::Rooted<JS::Value> jsval(aCx);
1551           const auto& fieldValue = value.value().get_TextField().value();
1552           if (!JS_ParseJSON(aCx, fieldValue.get(), fieldValue.Length(),
1553                             &jsval) ||
1554               !jsval.isObject()) {
1555             return NS_ERROR_FAILURE;
1556           }
1557           entry->mValue.SetAsObject() = &jsval.toObject();
1558         } else {
1559           entry->mValue.SetAsString() = value.value().get_TextField().value();
1560         }
1561         break;
1562       }
1563       case Type::TFileList: {
1564         CollectedFileListValue file;
1565         file.mFileList = value.value().get_FileList().valueList().Clone();
1566 
1567         JS::Rooted<JS::Value> jsval(aCx);
1568         if (!ToJSValue(aCx, file, &jsval) || !jsval.isObject()) {
1569           return NS_ERROR_FAILURE;
1570         }
1571         entry->mValue.SetAsObject() = &jsval.toObject();
1572         break;
1573       }
1574       case Type::TSingleSelect: {
1575         CollectedNonMultipleSelectValue select;
1576         select.mSelectedIndex = value.value().get_SingleSelect().index();
1577         select.mValue = value.value().get_SingleSelect().value();
1578 
1579         JS::Rooted<JS::Value> jsval(aCx);
1580         if (!ToJSValue(aCx, select, &jsval) || !jsval.isObject()) {
1581           return NS_ERROR_FAILURE;
1582         }
1583         entry->mValue.SetAsObject() = &jsval.toObject();
1584         break;
1585       }
1586       case Type::TMultipleSelect: {
1587         JS::Rooted<JS::Value> jsval(aCx);
1588         if (!ToJSValue(aCx, value.value().get_MultipleSelect().valueList(),
1589                        &jsval) ||
1590             !jsval.isObject()) {
1591           return NS_ERROR_FAILURE;
1592         }
1593         entry->mValue.SetAsObject() = &jsval.toObject();
1594         break;
1595       }
1596       default:
1597         break;
1598     }
1599 
1600     entry->mKey = value.id();
1601   }
1602 
1603   return NS_OK;
1604 }
1605 
ConstructSessionStorageValue(const nsTArray<SSSetItemInfo> & aValues,Record<nsString,nsString> & aRecord)1606 static nsresult ConstructSessionStorageValue(
1607     const nsTArray<SSSetItemInfo>& aValues,
1608     Record<nsString, nsString>& aRecord) {
1609   auto& entries = aRecord.Entries();
1610   for (const auto& value : aValues) {
1611     auto entry = entries.AppendElement();
1612     entry->mKey = value.key();
1613     entry->mValue = value.value();
1614   }
1615 
1616   return NS_OK;
1617 }
1618 
1619 /* static */
ConstructSessionStorageValues(CanonicalBrowsingContext * aBrowsingContext,const nsTArray<SSCacheCopy> & aValues,Record<nsCString,Record<nsString,nsString>> & aRecord)1620 nsresult SessionStoreUtils::ConstructSessionStorageValues(
1621     CanonicalBrowsingContext* aBrowsingContext,
1622     const nsTArray<SSCacheCopy>& aValues,
1623     Record<nsCString, Record<nsString, nsString>>& aRecord) {
1624   if (!aRecord.Entries().SetCapacity(aValues.Length(), fallible)) {
1625     return NS_ERROR_FAILURE;
1626   }
1627 
1628   // We wish to remove this step of mapping originAttributes+originKey
1629   // to a storage principal in Bug 1711886 by consolidating the
1630   // storage format in SessionStorageManagerBase and Session Store.
1631   nsTHashMap<nsCStringHashKey, nsIPrincipal*> storagePrincipalList;
1632   aBrowsingContext->PreOrderWalk([&storagePrincipalList](
1633                                      BrowsingContext* aContext) {
1634     WindowGlobalParent* windowParent =
1635         aContext->Canonical()->GetCurrentWindowGlobal();
1636     if (!windowParent) {
1637       return;
1638     }
1639 
1640     nsIPrincipal* storagePrincipal = windowParent->DocumentStoragePrincipal();
1641     if (!storagePrincipal) {
1642       return;
1643     }
1644 
1645     const OriginAttributes& originAttributes =
1646         storagePrincipal->OriginAttributesRef();
1647     nsAutoCString originAttributesSuffix;
1648     originAttributes.CreateSuffix(originAttributesSuffix);
1649 
1650     nsAutoCString originKey;
1651     storagePrincipal->GetStorageOriginKey(originKey);
1652 
1653     storagePrincipalList.InsertOrUpdate(originAttributesSuffix + originKey,
1654                                         storagePrincipal);
1655   });
1656 
1657   for (const auto& value : aValues) {
1658     nsIPrincipal* storagePrincipal =
1659         storagePrincipalList.Get(value.originAttributes() + value.originKey());
1660     if (!storagePrincipal) {
1661       continue;
1662     }
1663 
1664     auto entry = aRecord.Entries().AppendElement();
1665 
1666     if (!entry->mValue.Entries().SetCapacity(value.data().Length(), fallible)) {
1667       return NS_ERROR_FAILURE;
1668     }
1669 
1670     if (NS_FAILED(storagePrincipal->GetOrigin(entry->mKey))) {
1671       return NS_ERROR_FAILURE;
1672     }
1673 
1674     ConstructSessionStorageValue(value.data(), entry->mValue);
1675   }
1676 
1677   return NS_OK;
1678 }
1679 
ResetSessionStore(BrowsingContext * aContext)1680 /* static */ void SessionStoreUtils::ResetSessionStore(
1681     BrowsingContext* aContext) {
1682   MOZ_RELEASE_ASSERT(NATIVE_LISTENER);
1683   WindowContext* windowContext = aContext->GetCurrentWindowContext();
1684   if (!windowContext) {
1685     return;
1686   }
1687 
1688   WindowGlobalChild* windowChild = windowContext->GetWindowGlobalChild();
1689   if (!windowChild || !windowChild->CanSend()) {
1690     return;
1691   }
1692 
1693   uint32_t epoch = aContext->GetSessionStoreEpoch();
1694 
1695   Unused << windowChild->SendResetSessionStore(epoch);
1696 }
1697