forked from mirrors/gecko-dev
		
	 b4b82098b4
			
		
	
	
		b4b82098b4
		
	
	
	
	
		
			
			Depends on D173976 Differential Revision: https://phabricator.services.mozilla.com/D173978
		
			
				
	
	
		
			1737 lines
		
	
	
	
		
			58 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1737 lines
		
	
	
	
		
			58 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "js/Array.h"  // JS::GetArrayLength, JS::IsArrayObject
 | |
| #include "js/JSON.h"
 | |
| #include "js/PropertyAndElement.h"  // JS_GetElement
 | |
| #include "js/TypeDecls.h"
 | |
| #include "jsapi.h"
 | |
| #include "mozilla/BasePrincipal.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/dom/AutocompleteInfoBinding.h"
 | |
| #include "mozilla/dom/CanonicalBrowsingContext.h"
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "mozilla/dom/DocumentInlines.h"
 | |
| #include "mozilla/dom/HTMLInputElement.h"
 | |
| #include "mozilla/dom/HTMLSelectElement.h"
 | |
| #include "mozilla/dom/HTMLTextAreaElement.h"
 | |
| #include "mozilla/dom/RootedDictionary.h"
 | |
| #include "mozilla/dom/SessionStorageManager.h"
 | |
| #include "mozilla/dom/PBackgroundSessionStorageCache.h"
 | |
| #include "mozilla/dom/SessionStoreChangeListener.h"
 | |
| #include "mozilla/dom/SessionStoreChild.h"
 | |
| #include "mozilla/dom/SessionStoreUtils.h"
 | |
| #include "mozilla/dom/txIXPathContext.h"
 | |
| #include "mozilla/dom/WindowGlobalParent.h"
 | |
| #include "mozilla/dom/WindowProxyHolder.h"
 | |
| #include "mozilla/dom/XPathResult.h"
 | |
| #include "mozilla/dom/XPathEvaluator.h"
 | |
| #include "mozilla/dom/XPathExpression.h"
 | |
| #include "mozilla/dom/PBackgroundSessionStorageCache.h"
 | |
| #include "mozilla/ipc/BackgroundUtils.h"
 | |
| #include "mozilla/ReverseIterator.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| #include "nsCharSeparatedTokenizer.h"
 | |
| #include "nsContentList.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsFocusManager.h"
 | |
| #include "nsGlobalWindowOuter.h"
 | |
| #include "nsIContentInlines.h"
 | |
| #include "nsIDocShell.h"
 | |
| #include "nsIFormControl.h"
 | |
| #include "nsIScrollableFrame.h"
 | |
| #include "nsISHistory.h"
 | |
| #include "nsIXULRuntime.h"
 | |
| #include "nsPresContext.h"
 | |
| #include "nsPrintfCString.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::dom;
 | |
| using namespace mozilla::dom::sessionstore;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| class DynamicFrameEventFilter final : public nsIDOMEventListener {
 | |
|  public:
 | |
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | |
|   NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter)
 | |
| 
 | |
|   explicit DynamicFrameEventFilter(EventListener* aListener)
 | |
|       : mListener(aListener) {}
 | |
| 
 | |
|   NS_IMETHODIMP HandleEvent(Event* aEvent) override {
 | |
|     if (mListener && TargetInNonDynamicDocShell(aEvent)) {
 | |
|       mListener->HandleEvent(*aEvent);
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~DynamicFrameEventFilter() = default;
 | |
| 
 | |
|   bool TargetInNonDynamicDocShell(Event* aEvent) {
 | |
|     EventTarget* target = aEvent->GetTarget();
 | |
|     if (!target) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal();
 | |
|     if (!outer || !outer->GetDocShell()) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     RefPtr<BrowsingContext> context = outer->GetBrowsingContext();
 | |
|     return context && !context->CreatedDynamically();
 | |
|   }
 | |
| 
 | |
|   RefPtr<EventListener> mListener;
 | |
| };
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION(DynamicFrameEventFilter, mListener)
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DynamicFrameEventFilter)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISupports)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(DynamicFrameEventFilter)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(DynamicFrameEventFilter)
 | |
| 
 | |
| }  // anonymous namespace
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::ForEachNonDynamicChildFrame(
 | |
|     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
 | |
|     SessionStoreUtilsFrameCallback& aCallback, ErrorResult& aRv) {
 | |
|   if (!aWindow.get()) {
 | |
|     aRv.Throw(NS_ERROR_INVALID_ARG);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShell> docShell = aWindow.get()->GetDocShell();
 | |
|   if (!docShell) {
 | |
|     aRv.Throw(NS_ERROR_FAILURE);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   int32_t length;
 | |
|   aRv = docShell->GetInProcessChildCount(&length);
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (int32_t i = 0; i < length; ++i) {
 | |
|     nsCOMPtr<nsIDocShellTreeItem> item;
 | |
|     docShell->GetInProcessChildAt(i, getter_AddRefs(item));
 | |
|     if (!item) {
 | |
|       aRv.Throw(NS_ERROR_FAILURE);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     RefPtr<BrowsingContext> context = item->GetBrowsingContext();
 | |
|     if (!context) {
 | |
|       aRv.Throw(NS_ERROR_FAILURE);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!context->CreatedDynamically()) {
 | |
|       int32_t childOffset = context->ChildOffset();
 | |
|       aCallback.Call(WindowProxyHolder(context.forget()), childOffset);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| already_AddRefed<nsISupports>
 | |
| SessionStoreUtils::AddDynamicFrameFilteredListener(
 | |
|     const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType,
 | |
|     JS::Handle<JS::Value> aListener, bool aUseCapture, bool aMozSystemGroup,
 | |
|     ErrorResult& aRv) {
 | |
|   if (NS_WARN_IF(!aListener.isObject())) {
 | |
|     aRv.Throw(NS_ERROR_INVALID_ARG);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   JSContext* cx = aGlobal.Context();
 | |
|   JS::Rooted<JSObject*> obj(cx, &aListener.toObject());
 | |
|   JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
 | |
|   RefPtr<EventListener> listener =
 | |
|       new EventListener(cx, obj, global, GetIncumbentGlobal());
 | |
| 
 | |
|   nsCOMPtr<nsIDOMEventListener> filter(new DynamicFrameEventFilter(listener));
 | |
|   if (aMozSystemGroup) {
 | |
|     aRv = aTarget.AddSystemEventListener(aType, filter, aUseCapture);
 | |
|   } else {
 | |
|     aRv = aTarget.AddEventListener(aType, filter, aUseCapture);
 | |
|   }
 | |
|   if (aRv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return filter.forget();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::RemoveDynamicFrameFilteredListener(
 | |
|     const GlobalObject& global, EventTarget& aTarget, const nsAString& aType,
 | |
|     nsISupports* aListener, bool aUseCapture, bool aMozSystemGroup,
 | |
|     ErrorResult& aRv) {
 | |
|   nsCOMPtr<nsIDOMEventListener> listener = do_QueryInterface(aListener);
 | |
|   if (!listener) {
 | |
|     aRv.Throw(NS_ERROR_NO_INTERFACE);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aMozSystemGroup) {
 | |
|     aTarget.RemoveSystemEventListener(aType, listener, aUseCapture);
 | |
|   } else {
 | |
|     aTarget.RemoveEventListener(aType, listener, aUseCapture);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::CollectDocShellCapabilities(const GlobalObject& aGlobal,
 | |
|                                                     nsIDocShell* aDocShell,
 | |
|                                                     nsCString& aRetVal) {
 | |
|   bool allow;
 | |
| 
 | |
| #define TRY_ALLOWPROP(y)          \
 | |
|   PR_BEGIN_MACRO                  \
 | |
|   aDocShell->GetAllow##y(&allow); \
 | |
|   if (!allow) {                   \
 | |
|     if (!aRetVal.IsEmpty()) {     \
 | |
|       aRetVal.Append(',');        \
 | |
|     }                             \
 | |
|     aRetVal.Append(#y);           \
 | |
|   }                               \
 | |
|   PR_END_MACRO
 | |
| 
 | |
|   TRY_ALLOWPROP(Plugins);
 | |
|   // Bug 1328013 : Don't collect "AllowJavascript" property
 | |
|   // TRY_ALLOWPROP(Javascript);
 | |
|   TRY_ALLOWPROP(MetaRedirects);
 | |
|   TRY_ALLOWPROP(Subframes);
 | |
|   TRY_ALLOWPROP(Images);
 | |
|   TRY_ALLOWPROP(Media);
 | |
|   TRY_ALLOWPROP(DNSPrefetch);
 | |
|   TRY_ALLOWPROP(WindowControl);
 | |
|   TRY_ALLOWPROP(Auth);
 | |
|   TRY_ALLOWPROP(ContentRetargeting);
 | |
|   TRY_ALLOWPROP(ContentRetargetingOnChildren);
 | |
| #undef TRY_ALLOWPROP
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::RestoreDocShellCapabilities(
 | |
|     nsIDocShell* aDocShell, const nsCString& aDisallowCapabilities) {
 | |
|   aDocShell->SetAllowPlugins(true);
 | |
|   aDocShell->SetAllowMetaRedirects(true);
 | |
|   aDocShell->SetAllowSubframes(true);
 | |
|   aDocShell->SetAllowImages(true);
 | |
|   aDocShell->SetAllowMedia(true);
 | |
|   aDocShell->SetAllowDNSPrefetch(true);
 | |
|   aDocShell->SetAllowWindowControl(true);
 | |
|   aDocShell->SetAllowContentRetargeting(true);
 | |
|   aDocShell->SetAllowContentRetargetingOnChildren(true);
 | |
| 
 | |
|   bool allowJavascript = true;
 | |
|   for (const nsACString& token :
 | |
|        nsCCharSeparatedTokenizer(aDisallowCapabilities, ',').ToRange()) {
 | |
|     if (token.EqualsLiteral("Plugins")) {
 | |
|       aDocShell->SetAllowPlugins(false);
 | |
|     } else if (token.EqualsLiteral("Javascript")) {
 | |
|       allowJavascript = false;
 | |
|     } else if (token.EqualsLiteral("MetaRedirects")) {
 | |
|       aDocShell->SetAllowMetaRedirects(false);
 | |
|     } else if (token.EqualsLiteral("Subframes")) {
 | |
|       aDocShell->SetAllowSubframes(false);
 | |
|     } else if (token.EqualsLiteral("Images")) {
 | |
|       aDocShell->SetAllowImages(false);
 | |
|     } else if (token.EqualsLiteral("Media")) {
 | |
|       aDocShell->SetAllowMedia(false);
 | |
|     } else if (token.EqualsLiteral("DNSPrefetch")) {
 | |
|       aDocShell->SetAllowDNSPrefetch(false);
 | |
|     } else if (token.EqualsLiteral("WindowControl")) {
 | |
|       aDocShell->SetAllowWindowControl(false);
 | |
|     } else if (token.EqualsLiteral("ContentRetargeting")) {
 | |
|       bool allow;
 | |
|       aDocShell->GetAllowContentRetargetingOnChildren(&allow);
 | |
|       aDocShell->SetAllowContentRetargeting(
 | |
|           false);  // will also set AllowContentRetargetingOnChildren
 | |
|       aDocShell->SetAllowContentRetargetingOnChildren(
 | |
|           allow);  // restore the allowProp to original
 | |
|     } else if (token.EqualsLiteral("ContentRetargetingOnChildren")) {
 | |
|       aDocShell->SetAllowContentRetargetingOnChildren(false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mozilla::SessionHistoryInParent()) {
 | |
|     // With SessionHistoryInParent, this is set from the parent process.
 | |
|     BrowsingContext* bc = aDocShell->GetBrowsingContext();
 | |
|     Unused << bc->SetAllowJavascript(allowJavascript);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void CollectCurrentScrollPosition(JSContext* aCx, Document& aDocument,
 | |
|                                          Nullable<CollectedData>& aRetVal) {
 | |
|   PresShell* presShell = aDocument.GetPresShell();
 | |
|   if (!presShell) {
 | |
|     return;
 | |
|   }
 | |
|   nsPoint scrollPos = presShell->GetVisualViewportOffset();
 | |
|   int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
 | |
|   int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
 | |
| 
 | |
|   if ((scrollX != 0) || (scrollY != 0)) {
 | |
|     aRetVal.SetValue().mScroll.Construct() =
 | |
|         nsPrintfCString("%d,%d", scrollX, scrollY);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::RestoreScrollPosition(const GlobalObject& aGlobal,
 | |
|                                               nsGlobalWindowInner& aWindow,
 | |
|                                               const CollectedData& aData) {
 | |
|   if (aData.mScroll.WasPassed()) {
 | |
|     RestoreScrollPosition(aWindow, aData.mScroll.Value());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::RestoreScrollPosition(
 | |
|     nsGlobalWindowInner& aWindow, const nsCString& aScrollPosition) {
 | |
|   using Change = mozilla::dom::SessionStoreChangeListener::Change;
 | |
|   SessionStoreChangeListener::CollectSessionStoreData(
 | |
|       aWindow.GetWindowContext(), EnumSet<Change>(Change::Scroll));
 | |
| 
 | |
|   nsCCharSeparatedTokenizer tokenizer(aScrollPosition, ',');
 | |
|   nsAutoCString token(tokenizer.nextToken());
 | |
|   int pos_X = atoi(token.get());
 | |
|   token = tokenizer.nextToken();
 | |
|   int pos_Y = atoi(token.get());
 | |
| 
 | |
|   aWindow.ScrollTo(pos_X, pos_Y);
 | |
| 
 | |
|   if (nsCOMPtr<Document> doc = aWindow.GetExtantDoc()) {
 | |
|     if (nsPresContext* presContext = doc->GetPresContext()) {
 | |
|       if (presContext->IsRootContentDocumentCrossProcess()) {
 | |
|         // Use eMainThread so this takes precedence over session history
 | |
|         // (ScrollFrameHelper::ScrollToRestoredPosition()).
 | |
|         presContext->PresShell()->ScrollToVisual(
 | |
|             CSSPoint::ToAppUnits(CSSPoint(pos_X, pos_Y)),
 | |
|             layers::FrameMetrics::eMainThread, ScrollMode::Instant);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Implements the Luhn checksum algorithm as described at
 | |
| // http://wikipedia.org/wiki/Luhn_algorithm
 | |
| // Number digit lengths vary with network, but should fall within 12-19 range.
 | |
| // [2] More details at https://en.wikipedia.org/wiki/Payment_card_number
 | |
| static bool IsValidCCNumber(nsAString& aValue) {
 | |
|   uint32_t total = 0;
 | |
|   uint32_t numLength = 0;
 | |
|   uint32_t strLen = aValue.Length();
 | |
|   for (uint32_t i = 0; i < strLen; ++i) {
 | |
|     uint32_t idx = strLen - i - 1;
 | |
|     // ignore whitespace and dashes)
 | |
|     char16_t chr = aValue[idx];
 | |
|     if (IsSpaceCharacter(chr) || chr == '-') {
 | |
|       continue;
 | |
|     }
 | |
|     // If our number is too long, note that fact
 | |
|     ++numLength;
 | |
|     if (numLength > 19) {
 | |
|       return false;
 | |
|     }
 | |
|     // Try to parse the character as a base-10 integer.
 | |
|     nsresult rv = NS_OK;
 | |
|     uint32_t val = Substring(aValue, idx, 1).ToInteger(&rv, 10);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return false;
 | |
|     }
 | |
|     if (i % 2 == 1) {
 | |
|       val *= 2;
 | |
|       if (val > 9) {
 | |
|         val -= 9;
 | |
|       }
 | |
|     }
 | |
|     total += val;
 | |
|   }
 | |
| 
 | |
|   return numLength >= 12 && total % 10 == 0;
 | |
| }
 | |
| 
 | |
| // Limit the number of XPath expressions for performance reasons. See bug
 | |
| // 477564.
 | |
| static const uint16_t kMaxTraversedXPaths = 100;
 | |
| 
 | |
| // A helper function to append a element into mId or mXpath of CollectedData
 | |
| static Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
 | |
| AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
 | |
|                            uint16_t& aGeneratedCount,
 | |
|                            Nullable<CollectedData>& aRetVal) {
 | |
|   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry;
 | |
|   if (!aId.IsEmpty()) {
 | |
|     if (!aRetVal.SetValue().mId.WasPassed()) {
 | |
|       aRetVal.SetValue().mId.Construct();
 | |
|     }
 | |
|     auto& recordEntries = aRetVal.SetValue().mId.Value().Entries();
 | |
|     entry = recordEntries.AppendElement();
 | |
|     entry->mKey = aId;
 | |
|   } else {
 | |
|     if (!aRetVal.SetValue().mXpath.WasPassed()) {
 | |
|       aRetVal.SetValue().mXpath.Construct();
 | |
|     }
 | |
|     auto& recordEntries = aRetVal.SetValue().mXpath.Value().Entries();
 | |
|     entry = recordEntries.AppendElement();
 | |
|     nsAutoString xpath;
 | |
|     aNode->GenerateXPath(xpath);
 | |
|     aGeneratedCount++;
 | |
|     entry->mKey = xpath;
 | |
|   }
 | |
|   return entry;
 | |
| }
 | |
| 
 | |
| /* for bool value */
 | |
| static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
 | |
|                                        const bool& aValue,
 | |
|                                        uint16_t& aGeneratedCount,
 | |
|                                        JSContext* aCx,
 | |
|                                        Nullable<CollectedData>& aRetVal) {
 | |
|   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
 | |
|       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
 | |
|   entry->mValue.SetAsBoolean() = aValue;
 | |
| }
 | |
| 
 | |
| /* for nsString value */
 | |
| static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
 | |
|                                        const nsString& aValue,
 | |
|                                        uint16_t& aGeneratedCount,
 | |
|                                        Nullable<CollectedData>& aRetVal) {
 | |
|   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
 | |
|       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
 | |
|   entry->mValue.SetAsString() = aValue;
 | |
| }
 | |
| 
 | |
| /* for single select value */
 | |
| static void AppendValueToCollectedData(
 | |
|     nsINode* aNode, const nsAString& aId,
 | |
|     const CollectedNonMultipleSelectValue& aValue, uint16_t& aGeneratedCount,
 | |
|     JSContext* aCx, Nullable<CollectedData>& aRetVal) {
 | |
|   JS::Rooted<JS::Value> jsval(aCx);
 | |
|   if (!ToJSValue(aCx, aValue, &jsval)) {
 | |
|     JS_ClearPendingException(aCx);
 | |
|     return;
 | |
|   }
 | |
|   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
 | |
|       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
 | |
|   entry->mValue.SetAsObject() = &jsval.toObject();
 | |
| }
 | |
| 
 | |
| /* special handing for input element with string type */
 | |
| static void AppendValueToCollectedData(Document& aDocument, nsINode* aNode,
 | |
|                                        const nsAString& aId,
 | |
|                                        const nsString& aValue,
 | |
|                                        uint16_t& aGeneratedCount,
 | |
|                                        JSContext* aCx,
 | |
|                                        Nullable<CollectedData>& aRetVal) {
 | |
|   if (!aId.IsEmpty()) {
 | |
|     // We want to avoid saving data for about:sessionrestore as a string.
 | |
|     // Since it's stored in the form as stringified JSON, stringifying
 | |
|     // further causes an explosion of escape characters. cf. bug 467409
 | |
|     if (aId.EqualsLiteral("sessionData")) {
 | |
|       nsAutoCString url;
 | |
|       Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
 | |
|       if (url.EqualsLiteral("about:sessionrestore") ||
 | |
|           url.EqualsLiteral("about:welcomeback")) {
 | |
|         JS::Rooted<JS::Value> jsval(aCx);
 | |
|         if (JS_ParseJSON(aCx, aValue.get(), aValue.Length(), &jsval) &&
 | |
|             jsval.isObject()) {
 | |
|           Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
 | |
|               AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
 | |
|           entry->mValue.SetAsObject() = &jsval.toObject();
 | |
|         } else {
 | |
|           JS_ClearPendingException(aCx);
 | |
|         }
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   AppendValueToCollectedData(aNode, aId, aValue, aGeneratedCount, aRetVal);
 | |
| }
 | |
| 
 | |
| /* for nsTArray<nsString>: file and multipleSelect */
 | |
| static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
 | |
|                                        const nsAString& aValueType,
 | |
|                                        nsTArray<nsString>& aValue,
 | |
|                                        uint16_t& aGeneratedCount,
 | |
|                                        JSContext* aCx,
 | |
|                                        Nullable<CollectedData>& aRetVal) {
 | |
|   JS::Rooted<JS::Value> jsval(aCx);
 | |
|   if (aValueType.EqualsLiteral("file")) {
 | |
|     CollectedFileListValue val;
 | |
|     val.mType = aValueType;
 | |
|     val.mFileList = std::move(aValue);
 | |
|     if (!ToJSValue(aCx, val, &jsval)) {
 | |
|       JS_ClearPendingException(aCx);
 | |
|       return;
 | |
|     }
 | |
|   } else {
 | |
|     if (!ToJSValue(aCx, aValue, &jsval)) {
 | |
|       JS_ClearPendingException(aCx);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
|   Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
 | |
|       AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
 | |
|   entry->mValue.SetAsObject() = &jsval.toObject();
 | |
| }
 | |
| 
 | |
| // This isn't size as in binary size, just a heuristic to not store too large
 | |
| // fields in session store. See StaticPrefs::browser_sessionstore_dom_form_limit
 | |
| static uint32_t SizeOfFormEntry(const FormEntryValue& aValue) {
 | |
|   uint32_t size = 0;
 | |
|   switch (aValue.type()) {
 | |
|     case FormEntryValue::TCheckbox:
 | |
|       size = aValue.get_Checkbox().value() ? 4 : 5;
 | |
|       break;
 | |
|     case FormEntryValue::TTextField:
 | |
|       size = aValue.get_TextField().value().Length();
 | |
|       break;
 | |
|     case FormEntryValue::TFileList: {
 | |
|       for (const auto& value : aValue.get_FileList().valueList()) {
 | |
|         size += value.Length();
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case FormEntryValue::TSingleSelect:
 | |
|       size = aValue.get_SingleSelect().value().Length();
 | |
|       break;
 | |
|     case FormEntryValue::TMultipleSelect: {
 | |
|       for (const auto& value : aValue.get_MultipleSelect().valueList()) {
 | |
|         size += value.Length();
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
|   return size;
 | |
| }
 | |
| 
 | |
| static uint32_t AppendEntry(nsINode* aNode, const nsString& aId,
 | |
|                             const FormEntryValue& aValue,
 | |
|                             sessionstore::FormData& aFormData) {
 | |
|   uint32_t size = SizeOfFormEntry(aValue);
 | |
|   if (size > StaticPrefs::browser_sessionstore_dom_form_limit()) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (aId.IsEmpty()) {
 | |
|     FormEntry* entry = aFormData.xpath().AppendElement();
 | |
|     entry->value() = aValue;
 | |
|     aNode->GenerateXPath(entry->id());
 | |
|     size += entry->id().Length();
 | |
|   } else {
 | |
|     aFormData.id().AppendElement(FormEntry{aId, aValue});
 | |
|     size += aId.Length();
 | |
|   }
 | |
| 
 | |
|   return size;
 | |
| }
 | |
| 
 | |
| static uint32_t CollectTextAreaElement(Document* aDocument,
 | |
|                                        sessionstore::FormData& aFormData) {
 | |
|   uint32_t size = 0;
 | |
|   RefPtr<nsContentList> textlist =
 | |
|       NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"textarea"_ns);
 | |
|   uint32_t length = textlist->Length();
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     MOZ_ASSERT(textlist->Item(i), "null item in node list!");
 | |
| 
 | |
|     HTMLTextAreaElement* textArea =
 | |
|         HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i));
 | |
|     if (!textArea) {
 | |
|       continue;
 | |
|     }
 | |
|     DOMString autocomplete;
 | |
|     textArea->GetAutocomplete(autocomplete);
 | |
|     if (autocomplete.AsAString().EqualsLiteral("off")) {
 | |
|       continue;
 | |
|     }
 | |
|     nsAutoString id;
 | |
|     textArea->GetId(id);
 | |
|     if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
 | |
|       continue;
 | |
|     }
 | |
|     nsString value;
 | |
|     textArea->GetValue(value);
 | |
|     // In order to reduce XPath generation (which is slow), we only save data
 | |
|     // for form fields that have been changed. (cf. bug 537289)
 | |
|     if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
 | |
|                               eCaseMatters)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     size += AppendEntry(textArea, id, TextField{value}, aFormData);
 | |
|   }
 | |
| 
 | |
|   return size;
 | |
| }
 | |
| 
 | |
| static uint32_t CollectInputElement(Document* aDocument,
 | |
|                                     sessionstore::FormData& aFormData) {
 | |
|   uint32_t size = 0;
 | |
|   RefPtr<nsContentList> inputlist =
 | |
|       NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"input"_ns);
 | |
|   uint32_t length = inputlist->Length();
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     MOZ_ASSERT(inputlist->Item(i), "null item in node list!");
 | |
|     nsCOMPtr<nsIFormControl> formControl =
 | |
|         do_QueryInterface(inputlist->Item(i));
 | |
|     if (formControl) {
 | |
|       auto controlType = formControl->ControlType();
 | |
|       if (controlType == FormControlType::InputPassword ||
 | |
|           controlType == FormControlType::InputHidden ||
 | |
|           controlType == FormControlType::InputButton ||
 | |
|           controlType == FormControlType::InputImage ||
 | |
|           controlType == FormControlType::InputSubmit ||
 | |
|           controlType == FormControlType::InputReset) {
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
|     RefPtr<HTMLInputElement> input =
 | |
|         HTMLInputElement::FromNodeOrNull(inputlist->Item(i));
 | |
|     if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) {
 | |
|       continue;
 | |
|     }
 | |
|     nsAutoString id;
 | |
|     input->GetId(id);
 | |
|     if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
 | |
|       continue;
 | |
|     }
 | |
|     Nullable<AutocompleteInfo> aInfo;
 | |
|     input->GetAutocompleteInfo(aInfo);
 | |
|     if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     FormEntryValue value;
 | |
|     if (input->ControlType() == FormControlType::InputCheckbox ||
 | |
|         input->ControlType() == FormControlType::InputRadio) {
 | |
|       bool checked = input->Checked();
 | |
|       if (checked == input->DefaultChecked()) {
 | |
|         continue;
 | |
|       }
 | |
|       size += AppendEntry(input, id, Checkbox{checked}, aFormData);
 | |
|     } else if (input->ControlType() == FormControlType::InputFile) {
 | |
|       IgnoredErrorResult rv;
 | |
|       sessionstore::FileList file;
 | |
|       input->MozGetFileNameArray(file.valueList(), rv);
 | |
|       if (rv.Failed() || file.valueList().IsEmpty()) {
 | |
|         continue;
 | |
|       }
 | |
|       size += AppendEntry(input, id, file, aFormData);
 | |
|     } else {
 | |
|       TextField field;
 | |
|       input->GetValue(field.value(), CallerType::System);
 | |
|       auto& value = field.value();
 | |
|       // In order to reduce XPath generation (which is slow), we only save data
 | |
|       // for form fields that have been changed. (cf. bug 537289)
 | |
|       // Also, don't want to collect credit card number.
 | |
|       if (value.IsEmpty() || IsValidCCNumber(value) ||
 | |
|           input->HasBeenTypePassword() ||
 | |
|           input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
 | |
|                              eCaseMatters)) {
 | |
|         continue;
 | |
|       }
 | |
|       size += AppendEntry(input, id, field, aFormData);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return size;
 | |
| }
 | |
| 
 | |
| static uint32_t CollectSelectElement(Document* aDocument,
 | |
|                                      sessionstore::FormData& aFormData) {
 | |
|   uint32_t size = 0;
 | |
|   RefPtr<nsContentList> selectlist =
 | |
|       NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"select"_ns);
 | |
|   uint32_t length = selectlist->Length();
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     MOZ_ASSERT(selectlist->Item(i), "null item in node list!");
 | |
|     RefPtr<HTMLSelectElement> select =
 | |
|         HTMLSelectElement::FromNodeOrNull(selectlist->Item(i));
 | |
|     if (!select) {
 | |
|       continue;
 | |
|     }
 | |
|     nsAutoString id;
 | |
|     select->GetId(id);
 | |
|     if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
 | |
|       continue;
 | |
|     }
 | |
|     AutocompleteInfo aInfo;
 | |
|     select->GetAutocompleteInfo(aInfo);
 | |
|     if (!aInfo.mCanAutomaticallyPersist) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!select->Multiple()) {
 | |
|       HTMLOptionsCollection* options = select->GetOptions();
 | |
|       if (!options) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       uint32_t numOptions = options->Length();
 | |
|       int32_t defaultIndex = 0;
 | |
|       for (uint32_t idx = 0; idx < numOptions; idx++) {
 | |
|         HTMLOptionElement* option = options->ItemAsOption(idx);
 | |
|         if (option->DefaultSelected()) {
 | |
|           defaultIndex = option->Index();
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       int32_t selectedIndex = select->SelectedIndex();
 | |
|       if (selectedIndex == defaultIndex || selectedIndex < 0) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       DOMString selectVal;
 | |
|       select->GetValue(selectVal);
 | |
|       size += AppendEntry(select, id,
 | |
|                           SingleSelect{static_cast<uint32_t>(selectedIndex),
 | |
|                                        selectVal.AsAString()},
 | |
|                           aFormData);
 | |
|     } else {
 | |
|       HTMLOptionsCollection* options = select->GetOptions();
 | |
|       if (!options) {
 | |
|         continue;
 | |
|       }
 | |
|       bool hasDefaultValue = true;
 | |
|       nsTArray<nsString> selectslist;
 | |
|       uint32_t numOptions = options->Length();
 | |
|       for (uint32_t idx = 0; idx < numOptions; idx++) {
 | |
|         HTMLOptionElement* option = options->ItemAsOption(idx);
 | |
|         bool selected = option->Selected();
 | |
| 
 | |
|         hasDefaultValue =
 | |
|             hasDefaultValue && (selected == option->DefaultSelected());
 | |
| 
 | |
|         if (!selected) {
 | |
|           continue;
 | |
|         }
 | |
|         option->GetValue(*selectslist.AppendElement());
 | |
|       }
 | |
|       // In order to reduce XPath generation (which is slow), we only save data
 | |
|       // for form fields that have been changed. (cf. bug 537289)
 | |
|       if (hasDefaultValue) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       size += AppendEntry(select, id, MultipleSelect{selectslist}, aFormData);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return size;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| uint32_t SessionStoreUtils::CollectFormData(Document* aDocument,
 | |
|                                             sessionstore::FormData& aFormData) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aDocument);
 | |
|   uint32_t size = 0;
 | |
|   size += CollectTextAreaElement(aDocument, aFormData);
 | |
|   size += CollectInputElement(aDocument, aFormData);
 | |
|   size += CollectSelectElement(aDocument, aFormData);
 | |
| 
 | |
|   aFormData.hasData() =
 | |
|       !aFormData.id().IsEmpty() || !aFormData.xpath().IsEmpty();
 | |
| 
 | |
|   return size;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| template <typename... ArgsT>
 | |
| void SessionStoreUtils::CollectFromTextAreaElement(Document& aDocument,
 | |
|                                                    uint16_t& aGeneratedCount,
 | |
|                                                    ArgsT&&... args) {
 | |
|   RefPtr<nsContentList> textlist =
 | |
|       NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"textarea"_ns);
 | |
|   uint32_t length = textlist->Length(true);
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     MOZ_ASSERT(textlist->Item(i), "null item in node list!");
 | |
| 
 | |
|     HTMLTextAreaElement* textArea =
 | |
|         HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i));
 | |
|     if (!textArea) {
 | |
|       continue;
 | |
|     }
 | |
|     DOMString autocomplete;
 | |
|     textArea->GetAutocomplete(autocomplete);
 | |
|     if (autocomplete.AsAString().EqualsLiteral("off")) {
 | |
|       continue;
 | |
|     }
 | |
|     nsAutoString id;
 | |
|     textArea->GetId(id);
 | |
|     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
 | |
|       continue;
 | |
|     }
 | |
|     nsString value;
 | |
|     textArea->GetValue(value);
 | |
|     // In order to reduce XPath generation (which is slow), we only save data
 | |
|     // for form fields that have been changed. (cf. bug 537289)
 | |
|     if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
 | |
|                               eCaseMatters)) {
 | |
|       continue;
 | |
|     }
 | |
|     AppendValueToCollectedData(textArea, id, value, aGeneratedCount,
 | |
|                                std::forward<ArgsT>(args)...);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| template <typename... ArgsT>
 | |
| void SessionStoreUtils::CollectFromInputElement(Document& aDocument,
 | |
|                                                 uint16_t& aGeneratedCount,
 | |
|                                                 ArgsT&&... args) {
 | |
|   RefPtr<nsContentList> inputlist =
 | |
|       NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"input"_ns);
 | |
|   uint32_t length = inputlist->Length(true);
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     MOZ_ASSERT(inputlist->Item(i), "null item in node list!");
 | |
|     nsCOMPtr<nsIFormControl> formControl =
 | |
|         do_QueryInterface(inputlist->Item(i));
 | |
|     if (formControl) {
 | |
|       auto controlType = formControl->ControlType();
 | |
|       if (controlType == FormControlType::InputPassword ||
 | |
|           controlType == FormControlType::InputHidden ||
 | |
|           controlType == FormControlType::InputButton ||
 | |
|           controlType == FormControlType::InputImage ||
 | |
|           controlType == FormControlType::InputSubmit ||
 | |
|           controlType == FormControlType::InputReset) {
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
|     RefPtr<HTMLInputElement> input =
 | |
|         HTMLInputElement::FromNodeOrNull(inputlist->Item(i));
 | |
|     if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) {
 | |
|       continue;
 | |
|     }
 | |
|     nsAutoString id;
 | |
|     input->GetId(id);
 | |
|     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
 | |
|       continue;
 | |
|     }
 | |
|     Nullable<AutocompleteInfo> aInfo;
 | |
|     input->GetAutocompleteInfo(aInfo);
 | |
|     if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (input->ControlType() == FormControlType::InputCheckbox ||
 | |
|         input->ControlType() == FormControlType::InputRadio) {
 | |
|       bool checked = input->Checked();
 | |
|       if (checked == input->DefaultChecked()) {
 | |
|         continue;
 | |
|       }
 | |
|       AppendValueToCollectedData(input, id, checked, aGeneratedCount,
 | |
|                                  std::forward<ArgsT>(args)...);
 | |
|     } else if (input->ControlType() == FormControlType::InputFile) {
 | |
|       IgnoredErrorResult rv;
 | |
|       nsTArray<nsString> result;
 | |
|       input->MozGetFileNameArray(result, rv);
 | |
|       if (rv.Failed() || result.Length() == 0) {
 | |
|         continue;
 | |
|       }
 | |
|       AppendValueToCollectedData(input, id, u"file"_ns, result, aGeneratedCount,
 | |
|                                  std::forward<ArgsT>(args)...);
 | |
|     } else {
 | |
|       nsString value;
 | |
|       input->GetValue(value, CallerType::System);
 | |
|       // In order to reduce XPath generation (which is slow), we only save data
 | |
|       // for form fields that have been changed. (cf. bug 537289)
 | |
|       // Also, don't want to collect credit card number.
 | |
|       if (value.IsEmpty() || IsValidCCNumber(value) ||
 | |
|           input->HasBeenTypePassword() ||
 | |
|           input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
 | |
|                              eCaseMatters)) {
 | |
|         continue;
 | |
|       }
 | |
|       AppendValueToCollectedData(aDocument, input, id, value, aGeneratedCount,
 | |
|                                  std::forward<ArgsT>(args)...);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| template <typename... ArgsT>
 | |
| void SessionStoreUtils::CollectFromSelectElement(Document& aDocument,
 | |
|                                                  uint16_t& aGeneratedCount,
 | |
|                                                  ArgsT&&... args) {
 | |
|   RefPtr<nsContentList> selectlist =
 | |
|       NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"select"_ns);
 | |
|   uint32_t length = selectlist->Length(true);
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     MOZ_ASSERT(selectlist->Item(i), "null item in node list!");
 | |
|     RefPtr<HTMLSelectElement> select =
 | |
|         HTMLSelectElement::FromNodeOrNull(selectlist->Item(i));
 | |
|     if (!select) {
 | |
|       continue;
 | |
|     }
 | |
|     nsAutoString id;
 | |
|     select->GetId(id);
 | |
|     if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
 | |
|       continue;
 | |
|     }
 | |
|     AutocompleteInfo aInfo;
 | |
|     select->GetAutocompleteInfo(aInfo);
 | |
|     if (!aInfo.mCanAutomaticallyPersist) {
 | |
|       continue;
 | |
|     }
 | |
|     nsAutoCString value;
 | |
|     if (!select->Multiple()) {
 | |
|       // <select>s without the multiple attribute are hard to determine the
 | |
|       // default value, so assume we don't have the default.
 | |
|       DOMString selectVal;
 | |
|       select->GetValue(selectVal);
 | |
|       CollectedNonMultipleSelectValue val;
 | |
|       val.mSelectedIndex = select->SelectedIndex();
 | |
|       val.mValue = selectVal.AsAString();
 | |
|       AppendValueToCollectedData(select, id, val, aGeneratedCount,
 | |
|                                  std::forward<ArgsT>(args)...);
 | |
|     } else {
 | |
|       // <select>s with the multiple attribute are easier to determine the
 | |
|       // default value since each <option> has a defaultSelected property
 | |
|       HTMLOptionsCollection* options = select->GetOptions();
 | |
|       if (!options) {
 | |
|         continue;
 | |
|       }
 | |
|       bool hasDefaultValue = true;
 | |
|       nsTArray<nsString> selectslist;
 | |
|       uint32_t numOptions = options->Length();
 | |
|       for (uint32_t idx = 0; idx < numOptions; idx++) {
 | |
|         HTMLOptionElement* option = options->ItemAsOption(idx);
 | |
|         bool selected = option->Selected();
 | |
|         if (!selected) {
 | |
|           continue;
 | |
|         }
 | |
|         option->GetValue(*selectslist.AppendElement());
 | |
|         hasDefaultValue =
 | |
|             hasDefaultValue && (selected == option->DefaultSelected());
 | |
|       }
 | |
|       // In order to reduce XPath generation (which is slow), we only save data
 | |
|       // for form fields that have been changed. (cf. bug 537289)
 | |
|       if (hasDefaultValue) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       AppendValueToCollectedData(select, id, u"multipleSelect"_ns, selectslist,
 | |
|                                  aGeneratedCount, std::forward<ArgsT>(args)...);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void CollectCurrentFormData(JSContext* aCx, Document& aDocument,
 | |
|                                    Nullable<CollectedData>& aRetVal) {
 | |
|   uint16_t generatedCount = 0;
 | |
|   /* textarea element */
 | |
|   SessionStoreUtils::CollectFromTextAreaElement(aDocument, generatedCount,
 | |
|                                                 aRetVal);
 | |
|   /* input element */
 | |
|   SessionStoreUtils::CollectFromInputElement(aDocument, generatedCount, aCx,
 | |
|                                              aRetVal);
 | |
|   /* select element */
 | |
|   SessionStoreUtils::CollectFromSelectElement(aDocument, generatedCount, aCx,
 | |
|                                               aRetVal);
 | |
| 
 | |
|   Element* bodyElement = aDocument.GetBody();
 | |
|   if (bodyElement && bodyElement->IsInDesignMode()) {
 | |
|     bodyElement->GetInnerHTML(aRetVal.SetValue().mInnerHTML.Construct(),
 | |
|                               IgnoreErrors());
 | |
|   }
 | |
| 
 | |
|   if (aRetVal.IsNull()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Store the frame's current URL with its form data so that we can compare
 | |
|   // it when restoring data to not inject form data into the wrong document.
 | |
|   nsIURI* uri = aDocument.GetDocumentURI();
 | |
|   if (uri) {
 | |
|     uri->GetSpecIgnoringRef(aRetVal.SetValue().mUrl.Construct());
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| static void SetElementAsString(Element* aElement, const nsAString& aValue) {
 | |
|   IgnoredErrorResult rv;
 | |
|   if (auto* textArea = HTMLTextAreaElement::FromNode(aElement)) {
 | |
|     textArea->SetValue(aValue, rv);
 | |
|     if (!rv.Failed()) {
 | |
|       nsContentUtils::DispatchInputEvent(aElement);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
|   if (auto* input = HTMLInputElement::FromNode(aElement)) {
 | |
|     input->SetValue(aValue, CallerType::NonSystem, rv);
 | |
|     if (!rv.Failed()) {
 | |
|       nsContentUtils::DispatchInputEvent(aElement);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| static void SetElementAsBool(Element* aElement, bool aValue) {
 | |
|   HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
 | |
|   if (input) {
 | |
|     bool checked = input->Checked();
 | |
|     if (aValue != checked) {
 | |
|       input->SetChecked(aValue);
 | |
|       nsContentUtils::DispatchInputEvent(aElement);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| static void SetElementAsFiles(HTMLInputElement* aElement,
 | |
|                               const CollectedFileListValue& aValue) {
 | |
|   IgnoredErrorResult rv;
 | |
|   aElement->MozSetFileNameArray(aValue.mFileList, rv);
 | |
|   if (rv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
|   nsContentUtils::DispatchInputEvent(aElement);
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| static void SetElementAsSelect(HTMLSelectElement* aElement,
 | |
|                                const CollectedNonMultipleSelectValue& aValue) {
 | |
|   HTMLOptionsCollection* options = aElement->GetOptions();
 | |
|   if (!options) {
 | |
|     return;
 | |
|   }
 | |
|   int32_t selectIdx = options->SelectedIndex();
 | |
|   if (selectIdx >= 0) {
 | |
|     nsAutoString selectOptionVal;
 | |
|     options->ItemAsOption(selectIdx)->GetValue(selectOptionVal);
 | |
|     if (aValue.mValue.Equals(selectOptionVal)) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
|   uint32_t numOptions = options->Length();
 | |
|   for (uint32_t idx = 0; idx < numOptions; idx++) {
 | |
|     HTMLOptionElement* option = options->ItemAsOption(idx);
 | |
|     nsAutoString optionValue;
 | |
|     option->GetValue(optionValue);
 | |
|     if (aValue.mValue.Equals(optionValue)) {
 | |
|       aElement->SetSelectedIndex(idx);
 | |
|       nsContentUtils::DispatchInputEvent(aElement);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| static void SetElementAsMultiSelect(HTMLSelectElement* aElement,
 | |
|                                     const nsTArray<nsString>& aValueArray) {
 | |
|   bool fireEvent = false;
 | |
|   HTMLOptionsCollection* options = aElement->GetOptions();
 | |
|   if (!options) {
 | |
|     return;
 | |
|   }
 | |
|   uint32_t numOptions = options->Length();
 | |
|   for (uint32_t idx = 0; idx < numOptions; idx++) {
 | |
|     HTMLOptionElement* option = options->ItemAsOption(idx);
 | |
|     nsAutoString optionValue;
 | |
|     option->GetValue(optionValue);
 | |
|     for (uint32_t i = 0, l = aValueArray.Length(); i < l; ++i) {
 | |
|       if (optionValue.Equals(aValueArray[i])) {
 | |
|         option->SetSelected(true);
 | |
|         if (!option->DefaultSelected()) {
 | |
|           fireEvent = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (fireEvent) {
 | |
|     nsContentUtils::DispatchInputEvent(aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| static void SetElementAsObject(JSContext* aCx, Element* aElement,
 | |
|                                JS::Handle<JS::Value> aObject) {
 | |
|   RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aElement);
 | |
|   if (input) {
 | |
|     if (input->ControlType() == FormControlType::InputFile) {
 | |
|       CollectedFileListValue value;
 | |
|       if (value.Init(aCx, aObject)) {
 | |
|         SetElementAsFiles(input, value);
 | |
|       } else {
 | |
|         JS_ClearPendingException(aCx);
 | |
|       }
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
|   RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aElement);
 | |
|   if (select) {
 | |
|     // For Single Select Element
 | |
|     if (!select->Multiple()) {
 | |
|       CollectedNonMultipleSelectValue value;
 | |
|       if (value.Init(aCx, aObject)) {
 | |
|         SetElementAsSelect(select, value);
 | |
|       } else {
 | |
|         JS_ClearPendingException(aCx);
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // For Multiple Selects Element
 | |
|     bool isArray = false;
 | |
|     JS::IsArrayObject(aCx, aObject, &isArray);
 | |
|     if (!isArray) {
 | |
|       return;
 | |
|     }
 | |
|     JS::Rooted<JSObject*> arrayObj(aCx, &aObject.toObject());
 | |
|     uint32_t arrayLength = 0;
 | |
|     if (!JS::GetArrayLength(aCx, arrayObj, &arrayLength)) {
 | |
|       JS_ClearPendingException(aCx);
 | |
|       return;
 | |
|     }
 | |
|     nsTArray<nsString> array(arrayLength);
 | |
|     for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) {
 | |
|       JS::Rooted<JS::Value> element(aCx);
 | |
|       if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) {
 | |
|         JS_ClearPendingException(aCx);
 | |
|         return;
 | |
|       }
 | |
|       if (!element.isString()) {
 | |
|         return;
 | |
|       }
 | |
|       nsAutoJSString value;
 | |
|       if (!value.init(aCx, element)) {
 | |
|         JS_ClearPendingException(aCx);
 | |
|         return;
 | |
|       }
 | |
|       array.AppendElement(value);
 | |
|     }
 | |
|     SetElementAsMultiSelect(select, array);
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| static void SetSessionData(JSContext* aCx, Element* aElement,
 | |
|                            JS::MutableHandle<JS::Value> aObject) {
 | |
|   nsAutoString data;
 | |
|   if (nsContentUtils::StringifyJSON(aCx, aObject, data,
 | |
|                                     UndefinedIsNullStringLiteral)) {
 | |
|     SetElementAsString(aElement, data);
 | |
|   } else {
 | |
|     JS_ClearPendingException(aCx);
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| static void SetInnerHTML(Document& aDocument, const nsString& aInnerHTML) {
 | |
|   RefPtr<Element> bodyElement = aDocument.GetBody();
 | |
|   if (bodyElement && bodyElement->IsInDesignMode()) {
 | |
|     IgnoredErrorResult rv;
 | |
|     bodyElement->SetInnerHTML(aInnerHTML, aDocument.NodePrincipal(), rv);
 | |
|     if (!rv.Failed()) {
 | |
|       nsContentUtils::DispatchInputEvent(bodyElement);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class FormDataParseContext : public txIParseContext {
 | |
|  public:
 | |
|   explicit FormDataParseContext(bool aCaseInsensitive)
 | |
|       : mIsCaseInsensitive(aCaseInsensitive) {}
 | |
| 
 | |
|   nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override {
 | |
|     if (aPrefix == nsGkAtoms::xul) {
 | |
|       aID = kNameSpaceID_XUL;
 | |
|     } else {
 | |
|       MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
 | |
|       aID = kNameSpaceID_XHTML;
 | |
|     }
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
 | |
|                                FunctionCall** aFunction) override {
 | |
|     return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
 | |
|   }
 | |
| 
 | |
|   bool caseInsensitiveNameTests() override { return mIsCaseInsensitive; }
 | |
| 
 | |
|   void SetErrorOffset(uint32_t aOffset) override {}
 | |
| 
 | |
|  private:
 | |
|   bool mIsCaseInsensitive;
 | |
| };
 | |
| 
 | |
| static Element* FindNodeByXPath(Document& aDocument,
 | |
|                                 const nsAString& aExpression) {
 | |
|   FormDataParseContext parsingContext(aDocument.IsHTMLDocument());
 | |
|   IgnoredErrorResult rv;
 | |
|   UniquePtr<XPathExpression> expression(
 | |
|       aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext,
 | |
|                                                    &aDocument, rv));
 | |
|   if (rv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   RefPtr<XPathResult> result = expression->Evaluate(
 | |
|       aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
 | |
|   if (rv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool SessionStoreUtils::RestoreFormData(const GlobalObject& aGlobal,
 | |
|                                         Document& aDocument,
 | |
|                                         const CollectedData& aData) {
 | |
|   if (!aData.mUrl.WasPassed()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Don't restore any data for the given frame if the URL
 | |
|   // stored in the form data doesn't match its current URL.
 | |
|   nsAutoCString url;
 | |
|   Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
 | |
|   if (!aData.mUrl.Value().Equals(url)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   using Change = SessionStoreChangeListener::Change;
 | |
|   SessionStoreChangeListener::CollectSessionStoreData(
 | |
|       aDocument.GetWindowContext(), EnumSet<Change>(Change::Input));
 | |
| 
 | |
|   if (aData.mInnerHTML.WasPassed()) {
 | |
|     SetInnerHTML(aDocument, aData.mInnerHTML.Value());
 | |
|   }
 | |
|   if (aData.mId.WasPassed()) {
 | |
|     for (auto& entry : aData.mId.Value().Entries()) {
 | |
|       RefPtr<Element> node = aDocument.GetElementById(entry.mKey);
 | |
|       if (node == nullptr) {
 | |
|         continue;
 | |
|       }
 | |
|       if (entry.mValue.IsString()) {
 | |
|         SetElementAsString(node, entry.mValue.GetAsString());
 | |
|       } else if (entry.mValue.IsBoolean()) {
 | |
|         SetElementAsBool(node, entry.mValue.GetAsBoolean());
 | |
|       } else {
 | |
|         // For about:{sessionrestore,welcomeback} we saved the field as JSON to
 | |
|         // avoid nested instances causing humongous sessionstore.js files.
 | |
|         // cf. bug 467409
 | |
|         JSContext* cx = aGlobal.Context();
 | |
|         if (entry.mKey.EqualsLiteral("sessionData")) {
 | |
|           if (url.EqualsLiteral("about:sessionrestore") ||
 | |
|               url.EqualsLiteral("about:welcomeback")) {
 | |
|             JS::Rooted<JS::Value> object(
 | |
|                 cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
 | |
|             SetSessionData(cx, node, &object);
 | |
|             continue;
 | |
|           }
 | |
|         }
 | |
|         JS::Rooted<JS::Value> object(
 | |
|             cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
 | |
|         SetElementAsObject(cx, node, object);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aData.mXpath.WasPassed()) {
 | |
|     for (auto& entry : aData.mXpath.Value().Entries()) {
 | |
|       RefPtr<Element> node = FindNodeByXPath(aDocument, entry.mKey);
 | |
|       if (node == nullptr) {
 | |
|         continue;
 | |
|       }
 | |
|       if (entry.mValue.IsString()) {
 | |
|         SetElementAsString(node, entry.mValue.GetAsString());
 | |
|       } else if (entry.mValue.IsBoolean()) {
 | |
|         SetElementAsBool(node, entry.mValue.GetAsBoolean());
 | |
|       } else {
 | |
|         JS::Rooted<JS::Value> object(
 | |
|             aGlobal.Context(), JS::ObjectValue(*entry.mValue.GetAsObject()));
 | |
|         SetElementAsObject(aGlobal.Context(), node, object);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| void RestoreFormEntry(Element* aNode, const FormEntryValue& aValue) {
 | |
|   using Type = sessionstore::FormEntryValue::Type;
 | |
|   switch (aValue.type()) {
 | |
|     case Type::TCheckbox:
 | |
|       SetElementAsBool(aNode, aValue.get_Checkbox().value());
 | |
|       break;
 | |
|     case Type::TTextField:
 | |
|       SetElementAsString(aNode, aValue.get_TextField().value());
 | |
|       break;
 | |
|     case Type::TFileList: {
 | |
|       if (RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aNode);
 | |
|           input && input->ControlType() == FormControlType::InputFile) {
 | |
|         CollectedFileListValue value;
 | |
|         value.mFileList = aValue.get_FileList().valueList().Clone();
 | |
|         SetElementAsFiles(input, value);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case Type::TSingleSelect: {
 | |
|       if (RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aNode);
 | |
|           select && !select->Multiple()) {
 | |
|         CollectedNonMultipleSelectValue value;
 | |
|         value.mSelectedIndex = aValue.get_SingleSelect().index();
 | |
|         value.mValue = aValue.get_SingleSelect().value();
 | |
|         SetElementAsSelect(select, value);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case Type::TMultipleSelect: {
 | |
|       if (RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aNode);
 | |
|           select && select->Multiple()) {
 | |
|         SetElementAsMultiSelect(select,
 | |
|                                 aValue.get_MultipleSelect().valueList());
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::RestoreFormData(
 | |
|     Document& aDocument, const nsString& aInnerHTML,
 | |
|     const nsTArray<SessionStoreRestoreData::Entry>& aEntries) {
 | |
|   using Change = SessionStoreChangeListener::Change;
 | |
|   SessionStoreChangeListener::CollectSessionStoreData(
 | |
|       aDocument.GetWindowContext(), EnumSet<Change>(Change::Input));
 | |
| 
 | |
|   if (!aInnerHTML.IsEmpty()) {
 | |
|     SetInnerHTML(aDocument, aInnerHTML);
 | |
|   }
 | |
| 
 | |
|   for (const auto& entry : aEntries) {
 | |
|     RefPtr<Element> node = entry.mIsXPath
 | |
|                                ? FindNodeByXPath(aDocument, entry.mData.id())
 | |
|                                : aDocument.GetElementById(entry.mData.id());
 | |
|     if (node) {
 | |
|       RestoreFormEntry(node, entry.mData.value());
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| typedef void (*CollectorFunc)(JSContext* aCx, Document& aDocument,
 | |
|                               Nullable<CollectedData>& aRetVal);
 | |
| 
 | |
| /**
 | |
|  * A function that will recursively call |CollectorFunc| to collect data for all
 | |
|  * non-dynamic frames in the current frame/docShell tree.
 | |
|  */
 | |
| static void CollectFrameTreeData(JSContext* aCx,
 | |
|                                  BrowsingContext* aBrowsingContext,
 | |
|                                  Nullable<CollectedData>& aRetVal,
 | |
|                                  CollectorFunc aFunc) {
 | |
|   if (aBrowsingContext->CreatedDynamically()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
 | |
|   if (!window || !window->GetDocShell()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Document* document = window->GetExtantDoc();
 | |
|   if (!document) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   /* Collect data from current frame */
 | |
|   aFunc(aCx, *document, aRetVal);
 | |
| 
 | |
|   /* Collect data from all child frame */
 | |
|   nsTArray<JSObject*> childrenData;
 | |
|   SequenceRooter<JSObject*> rooter(aCx, &childrenData);
 | |
|   uint32_t trailingNullCounter = 0;
 | |
| 
 | |
|   // This is not going to work for fission. Bug 1572084 for tracking it.
 | |
|   for (auto& child : aBrowsingContext->Children()) {
 | |
|     NullableRootedDictionary<CollectedData> data(aCx);
 | |
|     CollectFrameTreeData(aCx, child, data, aFunc);
 | |
|     if (data.IsNull()) {
 | |
|       childrenData.AppendElement(nullptr);
 | |
|       trailingNullCounter++;
 | |
|       continue;
 | |
|     }
 | |
|     JS::Rooted<JS::Value> jsval(aCx);
 | |
|     if (!ToJSValue(aCx, data.SetValue(), &jsval)) {
 | |
|       JS_ClearPendingException(aCx);
 | |
|       continue;
 | |
|     }
 | |
|     childrenData.AppendElement(&jsval.toObject());
 | |
|     trailingNullCounter = 0;
 | |
|   }
 | |
| 
 | |
|   if (trailingNullCounter != childrenData.Length()) {
 | |
|     childrenData.TruncateLength(childrenData.Length() - trailingNullCounter);
 | |
|     aRetVal.SetValue().mChildren.Construct() = std::move(childrenData);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */ void SessionStoreUtils::CollectScrollPosition(
 | |
|     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
 | |
|     Nullable<CollectedData>& aRetVal) {
 | |
|   CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
 | |
|                        CollectCurrentScrollPosition);
 | |
| }
 | |
| 
 | |
| /* static */ void SessionStoreUtils::CollectFormData(
 | |
|     const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
 | |
|     Nullable<CollectedData>& aRetVal) {
 | |
|   CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
 | |
|                        CollectCurrentFormData);
 | |
| }
 | |
| 
 | |
| /* static */ void SessionStoreUtils::ComposeInputData(
 | |
|     const nsTArray<CollectedInputDataValue>& aData, InputElementData& ret) {
 | |
|   nsTArray<int> selectedIndex, valueIdx;
 | |
|   nsTArray<nsString> id, selectVal, strVal, type;
 | |
|   nsTArray<bool> boolVal;
 | |
| 
 | |
|   for (const CollectedInputDataValue& data : aData) {
 | |
|     id.AppendElement(data.id);
 | |
|     type.AppendElement(data.type);
 | |
| 
 | |
|     if (data.value.is<mozilla::dom::CollectedNonMultipleSelectValue>()) {
 | |
|       valueIdx.AppendElement(selectVal.Length());
 | |
|       selectedIndex.AppendElement(
 | |
|           data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
 | |
|               .mSelectedIndex);
 | |
|       selectVal.AppendElement(
 | |
|           data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
 | |
|               .mValue);
 | |
|     } else if (data.value.is<CopyableTArray<nsString>>()) {
 | |
|       // The first valueIdx is "index of the first string value"
 | |
|       valueIdx.AppendElement(strVal.Length());
 | |
|       strVal.AppendElements(data.value.as<CopyableTArray<nsString>>());
 | |
|       // The second valueIdx is "index of the last string value" + 1
 | |
|       id.AppendElement(data.id);
 | |
|       type.AppendElement(data.type);
 | |
|       valueIdx.AppendElement(strVal.Length());
 | |
|     } else if (data.value.is<nsString>()) {
 | |
|       valueIdx.AppendElement(strVal.Length());
 | |
|       strVal.AppendElement(data.value.as<nsString>());
 | |
|     } else if (data.type.EqualsLiteral("bool")) {
 | |
|       valueIdx.AppendElement(boolVal.Length());
 | |
|       boolVal.AppendElement(data.value.as<bool>());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (selectedIndex.Length() != 0) {
 | |
|     ret.mSelectedIndex.Construct(std::move(selectedIndex));
 | |
|   }
 | |
|   if (valueIdx.Length() != 0) {
 | |
|     ret.mValueIdx.Construct(std::move(valueIdx));
 | |
|   }
 | |
|   if (id.Length() != 0) {
 | |
|     ret.mId.Construct(std::move(id));
 | |
|   }
 | |
|   if (selectVal.Length() != 0) {
 | |
|     ret.mSelectVal.Construct(std::move(selectVal));
 | |
|   }
 | |
|   if (strVal.Length() != 0) {
 | |
|     ret.mStrVal.Construct(std::move(strVal));
 | |
|   }
 | |
|   if (type.Length() != 0) {
 | |
|     ret.mType.Construct(std::move(type));
 | |
|   }
 | |
|   if (boolVal.Length() != 0) {
 | |
|     ret.mBoolVal.Construct(std::move(boolVal));
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsISessionStoreRestoreData>
 | |
| SessionStoreUtils::ConstructSessionStoreRestoreData(
 | |
|     const GlobalObject& aGlobal) {
 | |
|   nsCOMPtr<nsISessionStoreRestoreData> data = new SessionStoreRestoreData();
 | |
|   return data.forget();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| already_AddRefed<Promise> SessionStoreUtils::InitializeRestore(
 | |
|     const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
 | |
|     nsISessionStoreRestoreData* aData, ErrorResult& aError) {
 | |
|   if (!mozilla::SessionHistoryInParent()) {
 | |
|     MOZ_CRASH("why were we called?");
 | |
|   }
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aContext.IsTop());
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aData);
 | |
|   nsCOMPtr<SessionStoreRestoreData> data = do_QueryInterface(aData);
 | |
|   aContext.SetRestoreData(data, aError);
 | |
|   if (aError.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aContext.GetSessionHistory());
 | |
|   aContext.GetSessionHistory()->ReloadCurrentEntry();
 | |
| 
 | |
|   return aContext.GetRestorePromise();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::RestoreDocShellState(
 | |
|     nsIDocShell* aDocShell, const DocShellRestoreState& aState) {
 | |
|   if (aDocShell) {
 | |
|     if (aState.URI()) {
 | |
|       aDocShell->SetCurrentURIForSessionStore(aState.URI());
 | |
|     }
 | |
|     RestoreDocShellCapabilities(aDocShell, aState.docShellCaps());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| already_AddRefed<Promise> SessionStoreUtils::RestoreDocShellState(
 | |
|     const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
 | |
|     const nsACString& aURL, const nsCString& aDocShellCaps,
 | |
|     ErrorResult& aError) {
 | |
|   MOZ_RELEASE_ASSERT(mozilla::SessionHistoryInParent());
 | |
|   MOZ_RELEASE_ASSERT(aContext.IsTop());
 | |
| 
 | |
|   WindowGlobalParent* wgp = aContext.GetCurrentWindowGlobal();
 | |
|   if (!wgp) {
 | |
|     aError.Throw(NS_ERROR_FAILURE);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(global);
 | |
| 
 | |
|   RefPtr<Promise> promise = Promise::Create(global, aError);
 | |
|   if (aError.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   if (!aURL.IsEmpty()) {
 | |
|     if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aURL))) {
 | |
|       aError.Throw(NS_ERROR_FAILURE);
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool allowJavascript = true;
 | |
|   for (const nsACString& token :
 | |
|        nsCCharSeparatedTokenizer(aDocShellCaps, ',').ToRange()) {
 | |
|     if (token.EqualsLiteral("Javascript")) {
 | |
|       allowJavascript = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Unused << aContext.SetAllowJavascript(allowJavascript);
 | |
| 
 | |
|   DocShellRestoreState state = {uri, aDocShellCaps};
 | |
| 
 | |
|   // TODO (anny): Investigate removing this roundtrip.
 | |
|   wgp->SendRestoreDocShellState(state)->Then(
 | |
|       GetMainThreadSerialEventTarget(), __func__,
 | |
|       [promise](void) { promise->MaybeResolveWithUndefined(); },
 | |
|       [promise](void) { promise->MaybeRejectWithUndefined(); });
 | |
| 
 | |
|   return promise.forget();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::RestoreSessionStorageFromParent(
 | |
|     const GlobalObject& aGlobal, const CanonicalBrowsingContext& aContext,
 | |
|     const Record<nsCString, Record<nsString, nsString>>& aSessionStorage) {
 | |
|   nsTArray<SSCacheCopy> cacheInitList;
 | |
|   for (const auto& originEntry : aSessionStorage.Entries()) {
 | |
|     nsCOMPtr<nsIPrincipal> storagePrincipal =
 | |
|         BasePrincipal::CreateContentPrincipal(originEntry.mKey);
 | |
| 
 | |
|     nsCString originKey;
 | |
|     nsresult rv = storagePrincipal->GetStorageOriginKey(originKey);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     SSCacheCopy& cacheInit = *cacheInitList.AppendElement();
 | |
| 
 | |
|     cacheInit.originKey() = originKey;
 | |
|     PrincipalToPrincipalInfo(storagePrincipal, &cacheInit.principalInfo());
 | |
| 
 | |
|     for (const auto& entry : originEntry.mValue.Entries()) {
 | |
|       SSSetItemInfo& setItemInfo = *cacheInit.data().AppendElement();
 | |
|       setItemInfo.key() = entry.mKey;
 | |
|       setItemInfo.value() = entry.mValue;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   BackgroundSessionStorageManager::LoadData(aContext.Id(), cacheInitList);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| nsresult SessionStoreUtils::ConstructFormDataValues(
 | |
|     JSContext* aCx, const nsTArray<sessionstore::FormEntry>& aValues,
 | |
|     nsTArray<Record<nsString, OwningStringOrBooleanOrObject>::EntryType>&
 | |
|         aEntries,
 | |
|     bool aParseSessionData) {
 | |
|   using EntryType = Record<nsString, OwningStringOrBooleanOrObject>::EntryType;
 | |
| 
 | |
|   if (!aEntries.SetCapacity(aValues.Length(), fallible)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   for (const auto& value : aValues) {
 | |
|     EntryType* entry = aEntries.AppendElement();
 | |
| 
 | |
|     using Type = sessionstore::FormEntryValue::Type;
 | |
|     switch (value.value().type()) {
 | |
|       case Type::TCheckbox:
 | |
|         entry->mValue.SetAsBoolean() = value.value().get_Checkbox().value();
 | |
|         break;
 | |
|       case Type::TTextField: {
 | |
|         if (aParseSessionData && value.id() == u"sessionData"_ns) {
 | |
|           JS::Rooted<JS::Value> jsval(aCx);
 | |
|           const auto& fieldValue = value.value().get_TextField().value();
 | |
|           if (!JS_ParseJSON(aCx, fieldValue.get(), fieldValue.Length(),
 | |
|                             &jsval) ||
 | |
|               !jsval.isObject()) {
 | |
|             return NS_ERROR_FAILURE;
 | |
|           }
 | |
|           entry->mValue.SetAsObject() = &jsval.toObject();
 | |
|         } else {
 | |
|           entry->mValue.SetAsString() = value.value().get_TextField().value();
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       case Type::TFileList: {
 | |
|         CollectedFileListValue file;
 | |
|         file.mFileList = value.value().get_FileList().valueList().Clone();
 | |
| 
 | |
|         JS::Rooted<JS::Value> jsval(aCx);
 | |
|         if (!ToJSValue(aCx, file, &jsval) || !jsval.isObject()) {
 | |
|           return NS_ERROR_FAILURE;
 | |
|         }
 | |
|         entry->mValue.SetAsObject() = &jsval.toObject();
 | |
|         break;
 | |
|       }
 | |
|       case Type::TSingleSelect: {
 | |
|         CollectedNonMultipleSelectValue select;
 | |
|         select.mSelectedIndex = value.value().get_SingleSelect().index();
 | |
|         select.mValue = value.value().get_SingleSelect().value();
 | |
| 
 | |
|         JS::Rooted<JS::Value> jsval(aCx);
 | |
|         if (!ToJSValue(aCx, select, &jsval) || !jsval.isObject()) {
 | |
|           return NS_ERROR_FAILURE;
 | |
|         }
 | |
|         entry->mValue.SetAsObject() = &jsval.toObject();
 | |
|         break;
 | |
|       }
 | |
|       case Type::TMultipleSelect: {
 | |
|         JS::Rooted<JS::Value> jsval(aCx);
 | |
|         if (!ToJSValue(aCx, value.value().get_MultipleSelect().valueList(),
 | |
|                        &jsval) ||
 | |
|             !jsval.isObject()) {
 | |
|           return NS_ERROR_FAILURE;
 | |
|         }
 | |
|         entry->mValue.SetAsObject() = &jsval.toObject();
 | |
|         break;
 | |
|       }
 | |
|       default:
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     entry->mKey = value.id();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| static nsresult ConstructSessionStorageValue(
 | |
|     const nsTArray<SSSetItemInfo>& aValues,
 | |
|     Record<nsString, nsString>& aRecord) {
 | |
|   auto& entries = aRecord.Entries();
 | |
|   for (const auto& value : aValues) {
 | |
|     auto entry = entries.AppendElement();
 | |
|     entry->mKey = value.key();
 | |
|     entry->mValue = value.value();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| nsresult SessionStoreUtils::ConstructSessionStorageValues(
 | |
|     CanonicalBrowsingContext* aBrowsingContext,
 | |
|     const nsTArray<SSCacheCopy>& aValues,
 | |
|     Record<nsCString, Record<nsString, nsString>>& aRecord) {
 | |
|   if (!aRecord.Entries().SetCapacity(aValues.Length(), fallible)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   for (const auto& value : aValues) {
 | |
|     auto storagePrincipal = PrincipalInfoToPrincipal(value.principalInfo());
 | |
|     if (storagePrincipal.isErr()) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     auto entry = aRecord.Entries().AppendElement();
 | |
| 
 | |
|     if (!entry->mValue.Entries().SetCapacity(value.data().Length(), fallible)) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     if (NS_FAILED(storagePrincipal.inspect()->GetOrigin(entry->mKey))) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     ConstructSessionStorageValue(value.data(), entry->mValue);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool SessionStoreUtils::CopyProperty(JSContext* aCx, JS::Handle<JSObject*> aDst,
 | |
|                                      JS::Handle<JSObject*> aSrc,
 | |
|                                      const nsAString& aName) {
 | |
|   JS::Rooted<JS::PropertyKey> name(aCx);
 | |
|   const char16_t* data;
 | |
|   size_t length = aName.GetData(&data);
 | |
| 
 | |
|   if (!JS_CharsToId(aCx, JS::TwoByteChars(data, length), &name)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   bool found = false;
 | |
|   if (!JS_HasPropertyById(aCx, aSrc, name, &found) || !found) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   JS::Rooted<JS::Value> value(aCx);
 | |
|   if (!JS_GetPropertyById(aCx, aSrc, name, &value)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (value.isNullOrUndefined()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return JS_DefinePropertyById(aCx, aDst, name, value, JSPROP_ENUMERATE);
 | |
| }
 |