mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 20:30:41 +00:00
Backed out 2 changesets (bug 1576188) for causing browser-chrome failures on browser_persist_cross_origin_iframe.js
CLOSED TREE Backed out changeset d2c102f8d898 (bug 1576188) Backed out changeset 9ddd9a63d178 (bug 1576188)
This commit is contained in:
parent
15756c2e25
commit
c278cf13d3
@ -567,7 +567,6 @@ class ContextMenuChild extends JSWindowActorChild {
|
||||
} = doc;
|
||||
docLocation = docLocation && docLocation.spec;
|
||||
let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
|
||||
let frameBrowsingContextID = doc.defaultView.docShell.browsingContext.id;
|
||||
let loginFillInfo = LoginManagerChild.forWindow(
|
||||
doc.defaultView
|
||||
).getFieldContext(aEvent.composedTarget);
|
||||
@ -684,7 +683,6 @@ class ContextMenuChild extends JSWindowActorChild {
|
||||
customMenuItems,
|
||||
contentDisposition,
|
||||
frameOuterWindowID,
|
||||
frameBrowsingContextID,
|
||||
disableSetDesktopBackground,
|
||||
parentAllowsMixedContent,
|
||||
};
|
||||
@ -904,9 +902,6 @@ class ContextMenuChild extends JSWindowActorChild {
|
||||
context.target.ownerGlobal
|
||||
);
|
||||
|
||||
context.frameBrowsingContextID =
|
||||
context.target.ownerGlobal.docShell.browsingContext.id;
|
||||
|
||||
// Check if we are in the PDF Viewer.
|
||||
context.inPDFViewer =
|
||||
context.target.ownerDocument.nodePrincipal.origin == "resource://pdf.js";
|
||||
|
@ -52,7 +52,6 @@ function openContextMenu(aMessage, aBrowser, aActor) {
|
||||
contentType: data.contentType,
|
||||
contentDisposition: data.contentDisposition,
|
||||
frameOuterWindowID: data.frameOuterWindowID,
|
||||
frameBrowsingContext: BrowsingContext.get(data.frameBrowsingContextID),
|
||||
selectionInfo: data.selectionInfo,
|
||||
disableSetDesktopBackground: data.disableSetDesktopBackground,
|
||||
loginFillInfo: data.loginFillInfo,
|
||||
@ -232,9 +231,6 @@ class nsContextMenu {
|
||||
this.principal = context.principal;
|
||||
this.storagePrincipal = context.storagePrincipal;
|
||||
this.frameOuterWindowID = context.frameOuterWindowID;
|
||||
this.frameBrowsingContext = BrowsingContext.get(
|
||||
context.frameBrowsingContextID
|
||||
);
|
||||
|
||||
this.inSyntheticDoc = context.inSyntheticDoc;
|
||||
this.inAboutDevtoolsToolbox = context.inAboutDevtoolsToolbox;
|
||||
@ -1399,7 +1395,7 @@ class nsContextMenu {
|
||||
|
||||
// Save URL of clicked-on frame.
|
||||
saveFrame() {
|
||||
saveBrowser(this.browser, false, this.frameBrowsingContext);
|
||||
saveBrowser(this.browser, false, this.frameOuterWindowID);
|
||||
}
|
||||
|
||||
// Helper function to wait for appropriate MIME-type headers and
|
||||
|
@ -1320,7 +1320,7 @@ const JsonView = {
|
||||
// principal is from the child. Null principals don't survive crossing
|
||||
// over IPC, so there's no other principal that'll work.
|
||||
const persistable = browser.frameLoader;
|
||||
persistable.startPersistence(null, {
|
||||
persistable.startPersistence(0, {
|
||||
onDocumentReady(doc) {
|
||||
const uri = chrome.makeURI(doc.documentURI, doc.characterSet);
|
||||
const filename = chrome.getDefaultFileName(undefined, uri, doc, null);
|
||||
|
@ -93,16 +93,6 @@ static void Register(BrowsingContext* aBrowsingContext) {
|
||||
aBrowsingContext->Group()->Register(aBrowsingContext);
|
||||
}
|
||||
|
||||
bool BrowsingContext::IsInSubtreeOf(BrowsingContext* aContext) {
|
||||
BrowsingContext* bc = this;
|
||||
do {
|
||||
if (bc == aContext) {
|
||||
return true;
|
||||
}
|
||||
} while ((bc = bc->mParent));
|
||||
return false;
|
||||
}
|
||||
|
||||
BrowsingContext* BrowsingContext::Top() {
|
||||
BrowsingContext* bc = this;
|
||||
while (bc->mParent) {
|
||||
|
@ -214,12 +214,6 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
void SetDocShell(nsIDocShell* aDocShell);
|
||||
void ClearDocShell() { mDocShell = nullptr; }
|
||||
|
||||
// Get the Document for this BrowsingContext if it is in-process, or
|
||||
// null if it's not.
|
||||
Document* GetDocument() const {
|
||||
return mDocShell ? mDocShell->GetDocument() : nullptr;
|
||||
}
|
||||
|
||||
// This cleans up remote outer window proxies that might have been left behind
|
||||
// when the browsing context went from being remote to local. It does this by
|
||||
// turning them into cross-compartment wrappers to aOuter. If there is already
|
||||
@ -298,8 +292,6 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
|
||||
bool IsTopContent() const { return IsContent() && !GetParent(); }
|
||||
|
||||
bool IsInSubtreeOf(BrowsingContext* aContext);
|
||||
|
||||
bool IsContentSubframe() const { return IsContent() && GetParent(); }
|
||||
uint64_t Id() const { return mBrowsingContextId; }
|
||||
|
||||
|
@ -3209,27 +3209,25 @@ void nsFrameLoader::DestroyBrowserFrameScripts() {
|
||||
}
|
||||
|
||||
void nsFrameLoader::StartPersistence(
|
||||
BrowsingContext* aContext, nsIWebBrowserPersistDocumentReceiver* aRecv,
|
||||
uint64_t aOuterWindowID, nsIWebBrowserPersistDocumentReceiver* aRecv,
|
||||
ErrorResult& aRv) {
|
||||
MOZ_ASSERT(aRecv);
|
||||
RefPtr<BrowsingContext> context = aContext ? aContext : GetBrowsingContext();
|
||||
|
||||
if (!context || !context->IsInSubtreeOf(GetBrowsingContext())) {
|
||||
aRecv->OnError(NS_ERROR_NO_CONTENT);
|
||||
if (auto* browserParent = GetBrowserParent()) {
|
||||
browserParent->StartPersistence(aOuterWindowID, aRecv, aRv);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context->GetDocShell() && XRE_IsParentProcess()) {
|
||||
CanonicalBrowsingContext* canonical =
|
||||
CanonicalBrowsingContext::Cast(context);
|
||||
RefPtr<BrowserParent> browserParent =
|
||||
canonical->GetCurrentWindowGlobal()->GetBrowserParent();
|
||||
browserParent->StartPersistence(canonical, aRecv, aRv);
|
||||
return;
|
||||
nsCOMPtr<Document> rootDoc =
|
||||
GetDocShell() ? GetDocShell()->GetDocument() : nullptr;
|
||||
nsCOMPtr<Document> foundDoc;
|
||||
if (aOuterWindowID) {
|
||||
foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc,
|
||||
aOuterWindowID);
|
||||
} else {
|
||||
foundDoc = rootDoc;
|
||||
}
|
||||
|
||||
nsCOMPtr<Document> foundDoc = context->GetDocument();
|
||||
|
||||
if (!foundDoc) {
|
||||
aRecv->OnError(NS_ERROR_NO_CONTENT);
|
||||
} else {
|
||||
|
@ -219,7 +219,7 @@ class nsFrameLoader final : public nsStubMutationObserver,
|
||||
nsIWebProgressListener* aProgressListener,
|
||||
mozilla::ErrorResult& aRv);
|
||||
|
||||
void StartPersistence(BrowsingContext* aContext,
|
||||
void StartPersistence(uint64_t aOuterWindowID,
|
||||
nsIWebBrowserPersistDocumentReceiver* aRecv,
|
||||
mozilla::ErrorResult& aRv);
|
||||
|
||||
|
@ -1,210 +0,0 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/.
|
||||
*/
|
||||
|
||||
interface LoadContext;
|
||||
interface RemoteTab;
|
||||
interface URI;
|
||||
interface nsIDocShell;
|
||||
interface nsIPrintSettings;
|
||||
interface nsIWebBrowserPersistDocumentReceiver;
|
||||
interface nsIWebProgressListener;
|
||||
|
||||
[ChromeOnly,
|
||||
Exposed=Window]
|
||||
interface FrameLoader {
|
||||
/**
|
||||
* Get the docshell from the frame loader.
|
||||
*/
|
||||
[GetterThrows]
|
||||
readonly attribute nsIDocShell? docShell;
|
||||
|
||||
/**
|
||||
* Get this frame loader's RemoteTab, if it has a remote frame. Otherwise,
|
||||
* returns null.
|
||||
*/
|
||||
readonly attribute RemoteTab? remoteTab;
|
||||
|
||||
/**
|
||||
* Get an nsILoadContext for the top-level docshell. For remote
|
||||
* frames, a shim is returned that contains private browsing and app
|
||||
* information.
|
||||
*/
|
||||
readonly attribute LoadContext loadContext;
|
||||
|
||||
/**
|
||||
* Get the root BrowsingContext within the frame.
|
||||
* This may be null immediately after creating a remote frame.
|
||||
*/
|
||||
readonly attribute BrowsingContext? browsingContext;
|
||||
|
||||
/**
|
||||
* Find out whether the loader's frame is at too great a depth in
|
||||
* the frame tree. This can be used to decide what operations may
|
||||
* or may not be allowed on the loader's docshell.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute boolean depthTooGreat;
|
||||
|
||||
/**
|
||||
* Find out whether the loader's frame is a remote frame.
|
||||
*/
|
||||
readonly attribute boolean isRemoteFrame;
|
||||
|
||||
/**
|
||||
* Activate remote frame.
|
||||
* Throws an exception with non-remote frames.
|
||||
*/
|
||||
[Throws]
|
||||
void activateRemoteFrame();
|
||||
|
||||
/**
|
||||
* Deactivate remote frame.
|
||||
* Throws an exception with non-remote frames.
|
||||
*/
|
||||
[Throws]
|
||||
void deactivateRemoteFrame();
|
||||
|
||||
/**
|
||||
* @see nsIDOMWindowUtils sendMouseEvent.
|
||||
*/
|
||||
[Throws]
|
||||
void sendCrossProcessMouseEvent(DOMString aType,
|
||||
float aX,
|
||||
float aY,
|
||||
long aButton,
|
||||
long aClickCount,
|
||||
long aModifiers,
|
||||
optional boolean aIgnoreRootScrollFrame = false);
|
||||
|
||||
/**
|
||||
* Activate event forwarding from client (remote frame) to parent.
|
||||
*/
|
||||
[Throws]
|
||||
void activateFrameEvent(DOMString aType, boolean capture);
|
||||
|
||||
// Note, when frameloaders are swapped, also messageManagers are swapped.
|
||||
readonly attribute MessageSender? messageManager;
|
||||
|
||||
/**
|
||||
* Request that the next time a remote layer transaction has been
|
||||
* received by the Compositor, a MozAfterRemoteFrame event be sent
|
||||
* to the window.
|
||||
*/
|
||||
void requestNotifyAfterRemotePaint();
|
||||
|
||||
/**
|
||||
* Force a remote browser to recompute its dimension and screen position.
|
||||
*/
|
||||
[Throws]
|
||||
void requestUpdatePosition();
|
||||
|
||||
/**
|
||||
* Force a TabStateFlush from native sessionStoreListeners.
|
||||
* Return true if the flush requires async ipc call.
|
||||
*/
|
||||
boolean requestTabStateFlush(unsigned long aFlushId);
|
||||
|
||||
/**
|
||||
* Force Epoch update in native sessionStoreListeners.
|
||||
*/
|
||||
void requestEpochUpdate(unsigned long aEpoch);
|
||||
|
||||
/**
|
||||
* Request a session history update in native sessionStoreListeners.
|
||||
*/
|
||||
void requestSHistoryUpdate(boolean aImmediately);
|
||||
|
||||
/**
|
||||
* Print the current document.
|
||||
*
|
||||
* @param aOuterWindowID the ID of the outer window to print
|
||||
* @param aPrintSettings optional print settings to use; printSilent can be
|
||||
* set to prevent prompting.
|
||||
* @param aProgressListener optional print progress listener.
|
||||
*/
|
||||
[Throws]
|
||||
void print(unsigned long long aOuterWindowID,
|
||||
nsIPrintSettings aPrintSettings,
|
||||
optional nsIWebProgressListener? aProgressListener = null);
|
||||
|
||||
/**
|
||||
* The element which owns this frame loader.
|
||||
*
|
||||
* For example, if this is a frame loader for an <iframe>, this attribute
|
||||
* returns the iframe element.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute Element? ownerElement;
|
||||
|
||||
|
||||
/**
|
||||
* Cached childID of the ContentParent owning the RemoteTab in this frame
|
||||
* loader. This can be used to obtain the childID after the RemoteTab died.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute unsigned long long childID;
|
||||
|
||||
/**
|
||||
* Find out whether the owner content really is a mozbrowser. <xul:browser>
|
||||
* is not considered to be a mozbrowser frame.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute boolean ownerIsMozBrowserFrame;
|
||||
|
||||
/**
|
||||
* The last known width of the frame. Reading this property will not trigger
|
||||
* a reflow, and therefore may not reflect the current state of things. It
|
||||
* should only be used in asynchronous APIs where values are not guaranteed
|
||||
* to be up-to-date when received.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute unsigned long lazyWidth;
|
||||
|
||||
/**
|
||||
* The last known height of the frame. Reading this property will not trigger
|
||||
* a reflow, and therefore may not reflect the current state of things. It
|
||||
* should only be used in asynchronous APIs where values are not guaranteed
|
||||
* to be up-to-date when received.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute unsigned long lazyHeight;
|
||||
|
||||
/**
|
||||
* Is `true` if the frameloader is dead (destroy has been called on it)
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute boolean isDead;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for objects which represent a document that can be
|
||||
* serialized with nsIWebBrowserPersist. This interface is
|
||||
* asynchronous because the actual document can be in another process
|
||||
* (e.g., if this object is a FrameLoader for an out-of-process
|
||||
* frame).
|
||||
*
|
||||
* @see nsIWebBrowserPersistDocumentReceiver
|
||||
* @see nsIWebBrowserPersistDocument
|
||||
* @see nsIWebBrowserPersist
|
||||
*
|
||||
* @param aContext
|
||||
* The browsing context of the subframe we'd like to persist.
|
||||
* If set to nullptr, WebBrowserPersistable will attempt to persist
|
||||
* the top-level document. If the browsing context is for a subframe
|
||||
* that is not held beneath the WebBrowserPersistable, aRecv's onError
|
||||
* method will be called with NS_ERROR_NO_CONTENT.
|
||||
* @param aRecv
|
||||
* The nsIWebBrowserPersistDocumentReceiver is a callback that
|
||||
* will be fired once the document is ready for persisting.
|
||||
*/
|
||||
interface mixin WebBrowserPersistable
|
||||
{
|
||||
[Throws]
|
||||
void startPersistence(BrowsingContext? aContext,
|
||||
nsIWebBrowserPersistDocumentReceiver aRecv);
|
||||
};
|
||||
|
||||
FrameLoader includes WebBrowserPersistable;
|
@ -45,7 +45,6 @@ WEBIDL_FILES = [
|
||||
'DOMLocalization.webidl',
|
||||
'Flex.webidl',
|
||||
'Fluent.webidl',
|
||||
'FrameLoader.webidl',
|
||||
'HeapSnapshot.webidl',
|
||||
'InspectorUtils.webidl',
|
||||
'IteratorResult.webidl',
|
||||
|
@ -3896,12 +3896,12 @@ bool BrowserParent::AsyncPanZoomEnabled() const {
|
||||
}
|
||||
|
||||
void BrowserParent::StartPersistence(
|
||||
CanonicalBrowsingContext* aContext,
|
||||
nsIWebBrowserPersistDocumentReceiver* aRecv, ErrorResult& aRv) {
|
||||
uint64_t aOuterWindowID, nsIWebBrowserPersistDocumentReceiver* aRecv,
|
||||
ErrorResult& aRv) {
|
||||
auto* actor = new WebBrowserPersistDocumentParent();
|
||||
actor->SetOnReady(aRecv);
|
||||
bool ok = Manager()->SendPWebBrowserPersistDocumentConstructor(actor, this,
|
||||
aContext);
|
||||
bool ok = Manager()->SendPWebBrowserPersistDocumentConstructor(
|
||||
actor, this, aOuterWindowID);
|
||||
if (!ok) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
}
|
||||
|
@ -612,7 +612,7 @@ class BrowserParent final : public PBrowserParent,
|
||||
|
||||
bool GetGlobalJSObject(JSContext* cx, JSObject** globalp);
|
||||
|
||||
void StartPersistence(CanonicalBrowsingContext* aContext,
|
||||
void StartPersistence(uint64_t aOuterWindowID,
|
||||
nsIWebBrowserPersistDocumentReceiver* aRecv,
|
||||
ErrorResult& aRv);
|
||||
|
||||
|
@ -2949,24 +2949,26 @@ bool ContentChild::DeallocPContentPermissionRequestChild(
|
||||
|
||||
PWebBrowserPersistDocumentChild*
|
||||
ContentChild::AllocPWebBrowserPersistDocumentChild(
|
||||
PBrowserChild* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext) {
|
||||
PBrowserChild* aBrowser, const uint64_t& aOuterWindowID) {
|
||||
return new WebBrowserPersistDocumentChild();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ContentChild::RecvPWebBrowserPersistDocumentConstructor(
|
||||
PWebBrowserPersistDocumentChild* aActor, PBrowserChild* aBrowser,
|
||||
const MaybeDiscarded<BrowsingContext>& aContext) {
|
||||
const uint64_t& aOuterWindowID) {
|
||||
if (NS_WARN_IF(!aBrowser)) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
|
||||
if (aContext.IsNullOrDiscarded()) {
|
||||
aActor->SendInitFailure(NS_ERROR_NO_CONTENT);
|
||||
return IPC_OK();
|
||||
nsCOMPtr<Document> rootDoc =
|
||||
static_cast<BrowserChild*>(aBrowser)->GetTopLevelDocument();
|
||||
nsCOMPtr<Document> foundDoc;
|
||||
if (aOuterWindowID) {
|
||||
foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc,
|
||||
aOuterWindowID);
|
||||
} else {
|
||||
foundDoc = rootDoc;
|
||||
}
|
||||
|
||||
nsCOMPtr<Document> foundDoc = aContext.get()->GetDocument();
|
||||
|
||||
if (!foundDoc) {
|
||||
aActor->SendInitFailure(NS_ERROR_NO_CONTENT);
|
||||
} else {
|
||||
|
@ -216,11 +216,11 @@ class ContentChild final
|
||||
const FileDescriptor& aGCLog, const FileDescriptor& aCCLog) override;
|
||||
|
||||
PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild(
|
||||
PBrowserChild* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext);
|
||||
PBrowserChild* aBrowser, const uint64_t& aOuterWindowID);
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvPWebBrowserPersistDocumentConstructor(
|
||||
PWebBrowserPersistDocumentChild* aActor, PBrowserChild* aBrowser,
|
||||
const MaybeDiscarded<BrowsingContext>& aContext) override;
|
||||
const uint64_t& aOuterWindowID) override;
|
||||
|
||||
bool DeallocPWebBrowserPersistDocumentChild(
|
||||
PWebBrowserPersistDocumentChild* aActor);
|
||||
|
@ -4544,7 +4544,7 @@ bool ContentParent::DeallocPContentPermissionRequestParent(
|
||||
|
||||
PWebBrowserPersistDocumentParent*
|
||||
ContentParent::AllocPWebBrowserPersistDocumentParent(
|
||||
PBrowserParent* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext) {
|
||||
PBrowserParent* aBrowser, const uint64_t& aOuterWindowID) {
|
||||
return new WebBrowserPersistDocumentParent();
|
||||
}
|
||||
|
||||
|
@ -972,8 +972,7 @@ class ContentParent final
|
||||
#endif
|
||||
|
||||
PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent(
|
||||
PBrowserParent* aBrowser,
|
||||
const MaybeDiscarded<BrowsingContext>& aContext);
|
||||
PBrowserParent* aBrowser, const uint64_t& aOuterWindowID);
|
||||
|
||||
bool DeallocPWebBrowserPersistDocumentParent(
|
||||
PWebBrowserPersistDocumentParent* aActor);
|
||||
|
@ -429,12 +429,12 @@ child:
|
||||
both:
|
||||
async PFileDescriptorSet(FileDescriptor fd);
|
||||
|
||||
// For parent->child, aBrowser must be non-null; aContext can
|
||||
// be null to indicate the browser's current root document, or non-null
|
||||
// For parent->child, aBrowser must be non-null; aOuterWindowID can
|
||||
// be 0 to indicate the browser's current root document, or nonzero
|
||||
// to persist a subdocument. For child->parent, arguments are
|
||||
// ignored and should be null.
|
||||
// ignored and should be null/zero.
|
||||
async PWebBrowserPersistDocument(nullable PBrowser aBrowser,
|
||||
MaybeDiscardedBrowsingContext aContext);
|
||||
uint64_t aOuterWindowID);
|
||||
|
||||
child:
|
||||
async InitGMPService(Endpoint<PGMPServiceChild> service);
|
||||
|
@ -10,7 +10,6 @@ support-files =
|
||||
worker_bug1004814.js
|
||||
geo_leak_test.html
|
||||
dummy.html
|
||||
dummy.png
|
||||
test_largeAllocation.html
|
||||
test_largeAllocation.html^headers^
|
||||
test_largeAllocation2.html
|
||||
@ -72,10 +71,12 @@ skip-if =
|
||||
support-files =
|
||||
set-samesite-cookies-and-redirect.sjs
|
||||
mimeme.sjs
|
||||
skip-if = fission
|
||||
[browser_persist_image_accept.js]
|
||||
[browser_persist_mixed_content_image.js]
|
||||
support-files =
|
||||
test_mixed_content_image.html
|
||||
dummy.png
|
||||
[browser_pointerlock_warning.js]
|
||||
[browser_test_focus_after_modal_state.js]
|
||||
skip-if = verify
|
||||
@ -108,6 +109,3 @@ skip-if = webrender
|
||||
support-files =
|
||||
file_postMessage_parent.html
|
||||
[browser_navigate_replace_browsingcontext.js]
|
||||
[browser_persist_cross_origin_iframe.js]
|
||||
support-files =
|
||||
image.html
|
||||
|
@ -98,7 +98,7 @@ add_task(async function() {
|
||||
info("done showCallback");
|
||||
};
|
||||
saveBrowser(browser);
|
||||
await new Promise(async (resolve, reject) => {
|
||||
await new Promise(async resolve => {
|
||||
let dls = await Downloads.getList(Downloads.PUBLIC);
|
||||
dls.addView({
|
||||
onDownloadChanged(download) {
|
||||
@ -106,8 +106,6 @@ add_task(async function() {
|
||||
dls.removeView(this);
|
||||
dls.removeFinished();
|
||||
resolve();
|
||||
} else if (download.error) {
|
||||
reject("Download failed");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -1,193 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PATH = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
"https://example.org"
|
||||
);
|
||||
const TEST_PATH2 = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
"https://example.com"
|
||||
);
|
||||
|
||||
var MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
MockFilePicker.init(window);
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
info("Running the cleanup code");
|
||||
MockFilePicker.cleanup();
|
||||
if (gTestDir && gTestDir.exists()) {
|
||||
// On Windows, sometimes nsIFile.remove() throws, probably because we're
|
||||
// still writing to the directory we're trying to remove, despite
|
||||
// waiting for the download to complete. Just retry a bit later...
|
||||
let succeeded = false;
|
||||
while (!succeeded) {
|
||||
try {
|
||||
gTestDir.remove(true);
|
||||
succeeded = true;
|
||||
} catch (ex) {
|
||||
await new Promise(requestAnimationFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let gTestDir = null;
|
||||
|
||||
function createTemporarySaveDirectory() {
|
||||
var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
saveDir.append("testsavedir");
|
||||
if (!saveDir.exists()) {
|
||||
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
||||
}
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
function checkContents(dir, expected, str) {
|
||||
let stack = [dir];
|
||||
let files = [];
|
||||
while (stack.length) {
|
||||
for (let file of stack.pop().directoryEntries) {
|
||||
if (file.isDirectory()) {
|
||||
stack.push(file);
|
||||
}
|
||||
|
||||
files.push(file.path);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.isDeeply(
|
||||
files.sort(),
|
||||
expected.sort(),
|
||||
str + "Should contain downloaded files in correct place."
|
||||
);
|
||||
}
|
||||
|
||||
async function addFrame(browser, path, selector) {
|
||||
await SpecialPowers.spawn(browser, [path, selector], async function(
|
||||
path,
|
||||
selector
|
||||
) {
|
||||
let document = content.document;
|
||||
let target = document.querySelector(selector);
|
||||
if (target instanceof content.HTMLIFrameElement) {
|
||||
document = target.contentDocument;
|
||||
target = document.body;
|
||||
}
|
||||
let element = document.createElement("iframe");
|
||||
element.src = path;
|
||||
await new Promise(resolve => {
|
||||
element.onload = resolve;
|
||||
target.appendChild(element);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function handleResult(expected, str) {
|
||||
let dls = await Downloads.getList(Downloads.PUBLIC);
|
||||
return new Promise((resolve, reject) => {
|
||||
dls.addView({
|
||||
onDownloadChanged(download) {
|
||||
if (download.succeeded) {
|
||||
checkContents(gTestDir, expected, str);
|
||||
|
||||
dls.removeView(this);
|
||||
dls.removeFinished();
|
||||
resolve();
|
||||
} else if (download.error) {
|
||||
reject("Download failed");
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
await BrowserTestUtils.withNewTab(TEST_PATH + "image.html", async function(
|
||||
browser
|
||||
) {
|
||||
await addFrame(browser, TEST_PATH + "image.html", "body");
|
||||
await addFrame(browser, TEST_PATH2 + "image.html", "body>iframe");
|
||||
|
||||
gTestDir = createTemporarySaveDirectory();
|
||||
|
||||
MockFilePicker.displayDirectory = gTestDir;
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
let destFile = gTestDir.clone();
|
||||
destFile.append("first.html");
|
||||
MockFilePicker.setFiles([destFile]);
|
||||
MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
|
||||
};
|
||||
|
||||
let expected = [
|
||||
"/tmp/testsavedir/first.html",
|
||||
"/tmp/testsavedir/first_files",
|
||||
"/tmp/testsavedir/first_files/image.html",
|
||||
"/tmp/testsavedir/first_files/dummy.png",
|
||||
"/tmp/testsavedir/first_files/image_data",
|
||||
"/tmp/testsavedir/first_files/image_data/image.html",
|
||||
"/tmp/testsavedir/first_files/image_data/image_data",
|
||||
"/tmp/testsavedir/first_files/image_data/image_data/dummy.png",
|
||||
];
|
||||
|
||||
// This saves the top-level document contained in `browser`
|
||||
saveBrowser(browser);
|
||||
await handleResult(expected, "Check toplevel: ");
|
||||
|
||||
// Instead of deleting previously saved files, we update our list
|
||||
// of expected files for the next part of the test. To not clash
|
||||
// we make sure to save to a different file name.
|
||||
expected = expected.concat([
|
||||
"/tmp/testsavedir/second.html",
|
||||
"/tmp/testsavedir/second_files",
|
||||
"/tmp/testsavedir/second_files/dummy.png",
|
||||
"/tmp/testsavedir/second_files/image.html",
|
||||
"/tmp/testsavedir/second_files/image_data",
|
||||
"/tmp/testsavedir/second_files/image_data/dummy.png",
|
||||
]);
|
||||
|
||||
MockFilePicker.displayDirectory = gTestDir;
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
let destFile = gTestDir.clone();
|
||||
destFile.append("second.html");
|
||||
MockFilePicker.setFiles([destFile]);
|
||||
MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
|
||||
};
|
||||
|
||||
// This saves the sub-document of the iframe contained in the
|
||||
// top-level document, as indicated by passing a child browsing
|
||||
// context as target for the save.
|
||||
saveBrowser(browser, false, browser.browsingContext.children[0]);
|
||||
await handleResult(expected, "Check subframe: ");
|
||||
|
||||
// Instead of deleting previously saved files, we update our list
|
||||
// of expected files for the next part of the test. To not clash
|
||||
// we make sure to save to a different file name.
|
||||
expected = expected.concat([
|
||||
"/tmp/testsavedir/third.html",
|
||||
"/tmp/testsavedir/third_files",
|
||||
"/tmp/testsavedir/third_files/dummy.png",
|
||||
]);
|
||||
|
||||
MockFilePicker.displayDirectory = gTestDir;
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
let destFile = gTestDir.clone();
|
||||
destFile.append("third.html");
|
||||
MockFilePicker.setFiles([destFile]);
|
||||
MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
|
||||
};
|
||||
|
||||
// This saves the sub-document of the iframe contained in the
|
||||
// first sub-document, as indicated by passing a child browsing
|
||||
// context as target for the save. That frame is special, because
|
||||
// it's cross-process.
|
||||
saveBrowser(
|
||||
browser,
|
||||
false,
|
||||
browser.browsingContext.children[0].children[0]
|
||||
);
|
||||
await handleResult(expected, "Check subframe: ");
|
||||
});
|
||||
});
|
@ -1,2 +0,0 @@
|
||||
<!doctype html>
|
||||
<img src="dummy.png"></img>
|
@ -5,8 +5,6 @@
|
||||
|
||||
include protocol PWebBrowserPersistDocument;
|
||||
|
||||
using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h";
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// == nsIWebBrowserPersistResourceVisitor
|
||||
@ -21,8 +19,6 @@ parent:
|
||||
// before exposing it with a visitDocument call.
|
||||
async VisitDocument(PWebBrowserPersistDocument aSubDocument);
|
||||
|
||||
async VisitBrowsingContext(MaybeDiscardedBrowsingContext aContext);
|
||||
|
||||
// This reflects the endVisit method.
|
||||
async __delete__(nsresult aStatus);
|
||||
};
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include "WebBrowserPersistDocumentParent.h"
|
||||
|
||||
#include "mozilla/dom/Attr.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
#include "mozilla/dom/Comment.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/HTMLAnchorElement.h"
|
||||
@ -288,17 +287,12 @@ nsresult ResourceReader::OnWalkSubframe(nsINode* aNode) {
|
||||
RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
|
||||
NS_ENSURE_STATE(loader);
|
||||
|
||||
RefPtr<dom::BrowsingContext> context = loader->GetBrowsingContext();
|
||||
NS_ENSURE_STATE(context);
|
||||
|
||||
if (loader->IsRemoteFrame()) {
|
||||
mVisitor->VisitBrowsingContext(mParent, context);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
++mOutstandingDocuments;
|
||||
// Pass in 0 as the outer window ID so that we start
|
||||
// persisting the root of this subframe, and not some other
|
||||
// subframe child of this subframe.
|
||||
ErrorResult err;
|
||||
loader->StartPersistence(context, this, err);
|
||||
loader->StartPersistence(0, this, err);
|
||||
nsresult rv = err.StealNSResult();
|
||||
if (NS_FAILED(rv)) {
|
||||
if (rv == NS_ERROR_NO_CONTENT) {
|
||||
|
@ -38,7 +38,7 @@ WebBrowserPersistResourcesChild::VisitDocument(
|
||||
// persistence started does not necessarily exist at this point;
|
||||
// see bug 1203602.
|
||||
if (!Manager()->Manager()->SendPWebBrowserPersistDocumentConstructor(
|
||||
subActor, nullptr, nullptr)) {
|
||||
subActor, nullptr, 0)) {
|
||||
// NOTE: subActor is freed at this point.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -57,14 +57,6 @@ WebBrowserPersistResourcesChild::VisitDocument(
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebBrowserPersistResourcesChild::VisitBrowsingContext(
|
||||
nsIWebBrowserPersistDocument* aDocument,
|
||||
dom::BrowsingContext* aBrowsingContext) {
|
||||
SendVisitBrowsingContext(aBrowsingContext);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebBrowserPersistResourcesChild::EndVisit(
|
||||
nsIWebBrowserPersistDocument* aDocument, nsresult aStatus) {
|
||||
|
@ -8,11 +8,6 @@
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include "mozilla/dom/BrowserParent.h"
|
||||
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
||||
#include "mozilla/dom/ContentParent.h"
|
||||
#include "mozilla/dom/WindowGlobalParent.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesParent,
|
||||
@ -63,18 +58,6 @@ mozilla::ipc::IPCResult WebBrowserPersistResourcesParent::RecvVisitDocument(
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebBrowserPersistResourcesParent::RecvVisitBrowsingContext(
|
||||
const dom::MaybeDiscarded<dom::BrowsingContext>& aContext) {
|
||||
if (aContext.IsNullOrDiscarded()) {
|
||||
// Nothing useful to do but ignore the discarded context.
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mVisitor->VisitBrowsingContext(mDocument, aContext.get());
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebBrowserPersistResourcesParent::OnDocumentReady(
|
||||
nsIWebBrowserPersistDocument* aSubDocument) {
|
||||
|
@ -30,9 +30,6 @@ class WebBrowserPersistResourcesParent final
|
||||
virtual mozilla::ipc::IPCResult RecvVisitDocument(
|
||||
PWebBrowserPersistDocumentParent* aSubDocument) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvVisitBrowsingContext(
|
||||
const dom::MaybeDiscarded<dom::BrowsingContext>& aContext) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult Recv__delete__(
|
||||
const nsresult& aStatus) override;
|
||||
|
||||
|
@ -16,8 +16,6 @@ interface nsIWebBrowserPersistWriteCompletion;
|
||||
interface nsIReferrerInfo;
|
||||
interface nsISHEntry;
|
||||
|
||||
webidl BrowsingContext;
|
||||
|
||||
native SHEntryRef(already_AddRefed<nsISHEntry>);
|
||||
|
||||
/**
|
||||
@ -149,7 +147,6 @@ interface nsIWebBrowserPersistResourceVisitor : nsISupports
|
||||
void visitResource(in nsIWebBrowserPersistDocument aDocument,
|
||||
in AUTF8String aURI,
|
||||
in nsContentPolicyType aContentPolicyType);
|
||||
|
||||
/**
|
||||
* Indicates a subdocument resource; e.g., a frame or iframe.
|
||||
*
|
||||
@ -159,16 +156,6 @@ interface nsIWebBrowserPersistResourceVisitor : nsISupports
|
||||
void visitDocument(in nsIWebBrowserPersistDocument aDocument,
|
||||
in nsIWebBrowserPersistDocument aSubDocument);
|
||||
|
||||
/**
|
||||
* Indicates a cross origin subdocument resource; e.g., a frame
|
||||
* or iframe loaded in another process.
|
||||
*
|
||||
* @param aDocument The document containing the reference.
|
||||
* @param aContext The referenced document's browsing context.
|
||||
*/
|
||||
void visitBrowsingContext(in nsIWebBrowserPersistDocument aDocument,
|
||||
in BrowsingContext aContext);
|
||||
|
||||
/**
|
||||
* Indicates that the document traversal is complete.
|
||||
*
|
||||
|
@ -139,11 +139,7 @@ class nsWebBrowserPersist::OnWalk final
|
||||
: public nsIWebBrowserPersistResourceVisitor {
|
||||
public:
|
||||
OnWalk(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aDataPath)
|
||||
: mParent(aParent),
|
||||
mFile(aFile),
|
||||
mDataPath(aDataPath),
|
||||
mPendingDocuments(1),
|
||||
mStatus(NS_OK) {}
|
||||
: mParent(aParent), mFile(aFile), mDataPath(aDataPath) {}
|
||||
|
||||
NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
|
||||
NS_DECL_ISUPPORTS
|
||||
@ -152,34 +148,12 @@ class nsWebBrowserPersist::OnWalk final
|
||||
nsCOMPtr<nsIURI> mFile;
|
||||
nsCOMPtr<nsIFile> mDataPath;
|
||||
|
||||
uint32_t mPendingDocuments;
|
||||
nsresult mStatus;
|
||||
|
||||
virtual ~OnWalk() = default;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk,
|
||||
nsIWebBrowserPersistResourceVisitor)
|
||||
|
||||
class nsWebBrowserPersist::OnRemoteWalk final
|
||||
: public nsIWebBrowserPersistDocumentReceiver {
|
||||
public:
|
||||
OnRemoteWalk(nsIWebBrowserPersistResourceVisitor* aVisitor,
|
||||
nsIWebBrowserPersistDocument* aDocument)
|
||||
: mVisitor(aVisitor), mDocument(aDocument) {}
|
||||
|
||||
NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
|
||||
NS_DECL_ISUPPORTS
|
||||
private:
|
||||
nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
|
||||
nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
|
||||
|
||||
virtual ~OnRemoteWalk() = default;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnRemoteWalk,
|
||||
nsIWebBrowserPersistDocumentReceiver)
|
||||
|
||||
class nsWebBrowserPersist::OnWrite final
|
||||
: public nsIWebBrowserPersistWriteCompletion {
|
||||
public:
|
||||
@ -1594,73 +1568,18 @@ nsWebBrowserPersist::OnWalk::VisitDocument(
|
||||
return mParent->SaveSubframeContent(aSubDoc, aDoc, uriSpec, data);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWebBrowserPersist::OnWalk::VisitBrowsingContext(
|
||||
nsIWebBrowserPersistDocument* aDoc, BrowsingContext* aContext) {
|
||||
RefPtr<dom::CanonicalBrowsingContext> context = aContext->Canonical();
|
||||
|
||||
UniquePtr<WebBrowserPersistDocumentParent> actor(
|
||||
new WebBrowserPersistDocumentParent());
|
||||
|
||||
nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> receiver =
|
||||
new OnRemoteWalk(this, aDoc);
|
||||
actor->SetOnReady(receiver);
|
||||
|
||||
RefPtr<dom::BrowserParent> browserParent =
|
||||
context->GetCurrentWindowGlobal()->GetBrowserParent();
|
||||
|
||||
bool ok =
|
||||
context->GetContentParent()->SendPWebBrowserPersistDocumentConstructor(
|
||||
actor.release(), browserParent, context);
|
||||
|
||||
if (NS_WARN_IF(!ok)) {
|
||||
// (The actor will be destroyed on constructor failure.)
|
||||
EndVisit(nullptr, NS_ERROR_FAILURE);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
++mPendingDocuments;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc,
|
||||
nsresult aStatus) {
|
||||
if (NS_FAILED(mStatus)) {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
if (NS_FAILED(aStatus)) {
|
||||
mStatus = aStatus;
|
||||
mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile);
|
||||
mParent->EndDownload(aStatus);
|
||||
return aStatus;
|
||||
}
|
||||
|
||||
if (--mPendingDocuments) {
|
||||
// We're not done yet, wait for more.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mParent->FinishSaveDocumentInternal(mFile, mDataPath);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWebBrowserPersist::OnRemoteWalk::OnDocumentReady(
|
||||
nsIWebBrowserPersistDocument* aSubDocument) {
|
||||
mVisitor->VisitDocument(mDocument, aSubDocument);
|
||||
mVisitor->EndVisit(mDocument, NS_OK);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWebBrowserPersist::OnRemoteWalk::OnError(nsresult aFailure) {
|
||||
mVisitor->EndVisit(nullptr, aFailure);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile,
|
||||
nsIFile* aDataPath) {
|
||||
// If there are things to persist, create a directory to hold them
|
||||
|
@ -74,7 +74,6 @@ class nsWebBrowserPersist final : public nsIInterfaceRequestor,
|
||||
struct WalkData;
|
||||
|
||||
class OnWalk;
|
||||
class OnRemoteWalk;
|
||||
class OnWrite;
|
||||
class FlatURIMap;
|
||||
friend class OnWalk;
|
||||
|
@ -540,6 +540,7 @@ WEBIDL_FILES = [
|
||||
'FontFaceSet.webidl',
|
||||
'FontFaceSource.webidl',
|
||||
'FormData.webidl',
|
||||
'FrameLoader.webidl',
|
||||
'Function.webidl',
|
||||
'GainNode.webidl',
|
||||
'Gamepad.webidl',
|
||||
|
@ -13,7 +13,7 @@ function one_test(delay, continuation) {
|
||||
BrowserTestUtils.openNewForegroundTab(gBrowser, testPageURL).then(tab => {
|
||||
browser = tab.linkedBrowser;
|
||||
let persistable = browser.frameLoader;
|
||||
persistable.startPersistence(null, {
|
||||
persistable.startPersistence(/* outer window ID: */ 0, {
|
||||
onDocumentReady,
|
||||
onError(status) {
|
||||
ok(false, new Components.Exception("startPersistence failed", status));
|
||||
|
@ -22,7 +22,7 @@ add_task(async function checkFormStateSaved() {
|
||||
await SpecialPowers.spawn(browser, [{ textareas, textboxes }], fillform);
|
||||
let fileURISpec = await new Promise((resolve, reject) => {
|
||||
let stack = Components.stack.caller;
|
||||
browser.frameLoader.startPersistence(null, {
|
||||
browser.frameLoader.startPersistence(0, {
|
||||
onDocumentReady(document) {
|
||||
// Note that 'document' here is going to be an nsIWebBrowserPersistDocument,
|
||||
// not a regular DOM document.
|
||||
|
@ -84,7 +84,7 @@ function saveURL(
|
||||
|
||||
// Save the current document inside any browser/frame-like element,
|
||||
// whether in-process or out-of-process.
|
||||
function saveBrowser(aBrowser, aSkipPrompt, aBrowsingContext = null) {
|
||||
function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID = 0) {
|
||||
if (!aBrowser) {
|
||||
throw new Error("Must have a browser when calling saveBrowser");
|
||||
}
|
||||
@ -119,7 +119,7 @@ function saveBrowser(aBrowser, aSkipPrompt, aBrowsingContext = null) {
|
||||
return;
|
||||
}
|
||||
let stack = Components.stack.caller;
|
||||
persistable.startPersistence(aBrowsingContext, {
|
||||
persistable.startPersistence(aOuterWindowID, {
|
||||
onDocumentReady(document) {
|
||||
if (!document || !(document instanceof Ci.nsIWebBrowserPersistDocument)) {
|
||||
throw new Error("Must have an nsIWebBrowserPersistDocument!");
|
||||
|
Loading…
x
Reference in New Issue
Block a user