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