forked from mirrors/gecko-dev
		
	 ae436f55ec
			
		
	
	
		ae436f55ec
		
	
	
	
	
		
			
			This is slightly complicated by the fact that the editor code wants to be able to set this from the content process, so we really need separate BrowsingContext and WindowContext flags, the latter of which can be set by the owning process. Differential Revision: https://phabricator.services.mozilla.com/D114899
		
			
				
	
	
		
			1696 lines
		
	
	
	
		
			57 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1696 lines
		
	
	
	
		
			57 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 "jsapi.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/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 "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) {
 | |
|   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->IsRootContentDocument()) {
 | |
|         // 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();
 | |
| }
 | |
| 
 | |
| static void AppendEntry(nsINode* aNode, const nsString& aId,
 | |
|                         const FormEntryValue& aValue,
 | |
|                         sessionstore::FormData& aFormData) {
 | |
|   if (aId.IsEmpty()) {
 | |
|     FormEntry* entry = aFormData.xpath().AppendElement();
 | |
|     entry->value() = aValue;
 | |
|     aNode->GenerateXPath(entry->id());
 | |
|   } else {
 | |
|     aFormData.id().AppendElement(FormEntry{aId, aValue});
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void CollectTextAreaElement(Document* aDocument,
 | |
|                                    sessionstore::FormData& aFormData) {
 | |
|   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;
 | |
|     }
 | |
| 
 | |
|     AppendEntry(textArea, id, TextField{value}, aFormData);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void CollectInputElement(Document* aDocument,
 | |
|                                 sessionstore::FormData& aFormData) {
 | |
|   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;
 | |
|       }
 | |
|       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;
 | |
|       }
 | |
|       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;
 | |
|       }
 | |
|       AppendEntry(input, id, field, aFormData);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void CollectSelectElement(Document* aDocument,
 | |
|                                  sessionstore::FormData& aFormData) {
 | |
|   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);
 | |
|       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;
 | |
|       }
 | |
| 
 | |
|       AppendEntry(select, id, MultipleSelect{selectslist}, aFormData);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void SessionStoreUtils::CollectFormData(Document* aDocument,
 | |
|                                         sessionstore::FormData& aFormData) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aDocument);
 | |
|   CollectTextAreaElement(aDocument, aFormData);
 | |
|   CollectInputElement(aDocument, aFormData);
 | |
|   CollectSelectElement(aDocument, aFormData);
 | |
| 
 | |
|   aFormData.hasData() =
 | |
|       !aFormData.id().IsEmpty() || !aFormData.xpath().IsEmpty();
 | |
| }
 | |
| 
 | |
| /* 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 (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
 | |
|     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;
 | |
|   HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(aElement);
 | |
|   if (textArea) {
 | |
|     textArea->SetValue(aValue, rv);
 | |
|     if (!rv.Failed()) {
 | |
|       nsContentUtils::DispatchInputEvent(aElement);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
|   HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
 | |
|   if (input) {
 | |
|     input->SetValue(aValue, CallerType::NonSystem, rv);
 | |
|     if (!rv.Failed()) {
 | |
|       nsContentUtils::DispatchInputEvent(aElement);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
|   input = HTMLInputElement::FromNodeOrNull(
 | |
|       nsFocusManager::GetRedirectedFocus(aElement));
 | |
|   if (input) {
 | |
|     input->SetValue(aValue, CallerType::NonSystem, rv);
 | |
|     if (!rv.Failed()) {
 | |
|       nsContentUtils::DispatchInputEvent(aElement);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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)) {
 | |
|     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 (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
 | |
|     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));
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT_BOUNDARY
 | |
| /* 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;
 | |
|   }
 | |
|   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();
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| /* static */
 | |
| void SessionStoreUtils::RestoreFormData(
 | |
|     Document& aDocument, const nsString& aInnerHTML,
 | |
|     const nsTArray<SessionStoreRestoreData::Entry>& aEntries) {
 | |
|   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->GetDoc();
 | |
|   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));
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| already_AddRefed<nsISessionStoreRestoreData>
 | |
| SessionStoreUtils::ConstructSessionStoreRestoreData(
 | |
|     const GlobalObject& aGlobal) {
 | |
|   nsCOMPtr<nsISessionStoreRestoreData> data = new SessionStoreRestoreData();
 | |
|   return data.forget();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| MOZ_CAN_RUN_SCRIPT
 | |
| 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->SetCurrentURI(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());
 | |
| 
 | |
|   if (WindowGlobalParent* wgp = aContext.GetCurrentWindowGlobal()) {
 | |
|     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();
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| /* 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;
 | |
|     storagePrincipal->OriginAttributesRef().CreateSuffix(
 | |
|         cacheInit.originAttributes());
 | |
| 
 | |
|     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;
 | |
|   }
 | |
| 
 | |
|   // We wish to remove this step of mapping originAttributes+originKey
 | |
|   // to a storage principal in Bug 1711886 by consolidating the
 | |
|   // storage format in SessionStorageManagerBase and Session Store.
 | |
|   nsTHashMap<nsCStringHashKey, nsIPrincipal*> storagePrincipalList;
 | |
|   aBrowsingContext->PreOrderWalk([&storagePrincipalList](
 | |
|                                      BrowsingContext* aContext) {
 | |
|     WindowGlobalParent* windowParent =
 | |
|         aContext->Canonical()->GetCurrentWindowGlobal();
 | |
|     if (!windowParent) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsIPrincipal* storagePrincipal = windowParent->DocumentStoragePrincipal();
 | |
|     if (!storagePrincipal) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const OriginAttributes& originAttributes =
 | |
|         storagePrincipal->OriginAttributesRef();
 | |
|     nsAutoCString originAttributesSuffix;
 | |
|     originAttributes.CreateSuffix(originAttributesSuffix);
 | |
| 
 | |
|     nsAutoCString originKey;
 | |
|     storagePrincipal->GetStorageOriginKey(originKey);
 | |
| 
 | |
|     storagePrincipalList.InsertOrUpdate(originAttributesSuffix + originKey,
 | |
|                                         storagePrincipal);
 | |
|   });
 | |
| 
 | |
|   for (const auto& value : aValues) {
 | |
|     nsIPrincipal* storagePrincipal =
 | |
|         storagePrincipalList.Get(value.originAttributes() + value.originKey());
 | |
|     if (!storagePrincipal) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     auto entry = aRecord.Entries().AppendElement();
 | |
| 
 | |
|     if (!entry->mValue.Entries().SetCapacity(value.data().Length(), fallible)) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     if (NS_FAILED(storagePrincipal->GetOrigin(entry->mKey))) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     ConstructSessionStorageValue(value.data(), entry->mValue);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* static */ void SessionStoreUtils::ResetSessionStore(
 | |
|     BrowsingContext* aContext) {
 | |
|   MOZ_RELEASE_ASSERT(NATIVE_LISTENER);
 | |
|   WindowContext* windowContext = aContext->GetCurrentWindowContext();
 | |
|   if (!windowContext) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   WindowGlobalChild* windowChild = windowContext->GetWindowGlobalChild();
 | |
|   if (!windowChild || !windowChild->CanSend()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   uint32_t epoch = aContext->GetSessionStoreEpoch();
 | |
| 
 | |
|   Unused << windowChild->SendResetSessionStore(epoch);
 | |
| }
 |