зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1741671 - Enable BFCache for pages with beforeunload event listeners on Desktop r=smaug
The changes only made it works in SHIP(session-history-in-parent) only. Differential Revision: https://phabricator.services.mozilla.com/D131715
This commit is contained in:
Родитель
3ba72459c3
Коммит
f61ec1c3d7
|
@ -1324,37 +1324,34 @@ void nsSHistory::LoadURIOrBFCache(LoadEntryResult& aLoadEntry) {
|
|||
"saving presentation=%i",
|
||||
canSave));
|
||||
|
||||
if (!canSave) {
|
||||
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
|
||||
do_QueryInterface(canonicalBC->GetEmbedderElement());
|
||||
if (frameLoaderOwner) {
|
||||
RefPtr<nsFrameLoader> currentFrameLoader =
|
||||
frameLoaderOwner->GetFrameLoader();
|
||||
if (currentFrameLoader &&
|
||||
currentFrameLoader->GetMaybePendingBrowsingContext()) {
|
||||
WindowGlobalParent* wgp =
|
||||
currentFrameLoader->GetMaybePendingBrowsingContext()
|
||||
->Canonical()
|
||||
->GetCurrentWindowGlobal();
|
||||
if (wgp) {
|
||||
wgp->PermitUnload([canonicalBC, loadState, she, frameLoader,
|
||||
currentFrameLoader](bool aAllow) {
|
||||
if (aAllow) {
|
||||
FinishRestore(canonicalBC, loadState, she, frameLoader,
|
||||
false);
|
||||
} else if (currentFrameLoader
|
||||
->GetMaybePendingBrowsingContext()) {
|
||||
nsISHistory* shistory =
|
||||
currentFrameLoader->GetMaybePendingBrowsingContext()
|
||||
->Canonical()
|
||||
->GetSessionHistory();
|
||||
if (shistory) {
|
||||
shistory->InternalSetRequestedIndex(-1);
|
||||
}
|
||||
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
|
||||
do_QueryInterface(canonicalBC->GetEmbedderElement());
|
||||
if (frameLoaderOwner) {
|
||||
RefPtr<nsFrameLoader> currentFrameLoader =
|
||||
frameLoaderOwner->GetFrameLoader();
|
||||
if (currentFrameLoader &&
|
||||
currentFrameLoader->GetMaybePendingBrowsingContext()) {
|
||||
if (WindowGlobalParent* wgp =
|
||||
currentFrameLoader->GetMaybePendingBrowsingContext()
|
||||
->Canonical()
|
||||
->GetCurrentWindowGlobal()) {
|
||||
wgp->PermitUnload([canonicalBC, loadState, she, frameLoader,
|
||||
currentFrameLoader, canSave](bool aAllow) {
|
||||
if (aAllow) {
|
||||
FinishRestore(canonicalBC, loadState, she, frameLoader,
|
||||
canSave && canonicalBC->AllowedInBFCache(
|
||||
Nothing(), nullptr));
|
||||
} else if (currentFrameLoader->GetMaybePendingBrowsingContext()) {
|
||||
nsISHistory* shistory =
|
||||
currentFrameLoader->GetMaybePendingBrowsingContext()
|
||||
->Canonical()
|
||||
->GetSessionHistory();
|
||||
if (shistory) {
|
||||
shistory->InternalSetRequestedIndex(-1);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<html>
|
||||
<script>
|
||||
onpageshow = function(pageShowEvent) {
|
||||
var bc = new BroadcastChannel("ship_beforeunload");
|
||||
bc.onmessage = function(event) {
|
||||
if (event.data.action == "register_beforeunload") {
|
||||
onbeforeunload = function() {
|
||||
bc.postMessage("beforeunload_fired");
|
||||
bc.close();
|
||||
}
|
||||
if (event.data.loadNextPageFromSessionHistory) {
|
||||
history.back();
|
||||
} else {
|
||||
location.href += "?differentpage";
|
||||
}
|
||||
} else if (event.data.action == "navigate_to_page_b") {
|
||||
bc.close();
|
||||
if (event.data.blockBFCache) {
|
||||
window.blockBFCache = new RTCPeerConnection();
|
||||
}
|
||||
location.href += "?pageb";
|
||||
} else if (event.data.action == "back_to_page_b") {
|
||||
if (event.data.forwardNavigateToPageB) {
|
||||
history.forward();
|
||||
} else {
|
||||
history.back();
|
||||
}
|
||||
bc.close();
|
||||
} else if (event.data.action == "close") {
|
||||
bc.close();
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
bc.postMessage({type: pageShowEvent.type, persisted: pageShowEvent.persisted});
|
||||
}
|
||||
</script>
|
||||
</html>
|
|
@ -200,3 +200,15 @@ support-files =
|
|||
[test_recursive_frames.html]
|
||||
[test_bug1699721.html]
|
||||
skip-if = !fission # tests fission-only process switching behaviour
|
||||
[test_ship_beforeunload_fired.html]
|
||||
support-files =
|
||||
file_ship_beforeunload_fired.html
|
||||
skip-if = !sessionHistoryInParent
|
||||
[test_ship_beforeunload_fired_2.html]
|
||||
support-files =
|
||||
file_ship_beforeunload_fired.html
|
||||
skip-if = !sessionHistoryInParent
|
||||
[test_ship_beforeunload_fired_3.html]
|
||||
support-files =
|
||||
file_ship_beforeunload_fired.html
|
||||
skip-if = !sessionHistoryInParent
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Test that ensures beforeunload is fired when session-history-in-parent is enabled
|
||||
</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
/*
|
||||
* This test ensures beforeunload is fired on the current page
|
||||
* when it is entering BFCache and the next page is coming out
|
||||
* from BFCache
|
||||
*
|
||||
* (1) The controller page opens a new window, and page A is loaded there.
|
||||
* (2) Page A then navigates to page B, and a beforeunload event
|
||||
* listener is registered on page B.
|
||||
* (3) Page B then navigates back to page A, and the beforeunload handler
|
||||
* should send a message to the controller page.
|
||||
* (4) Page A then navigates back to page B to check if page B has
|
||||
* been successfully added to BFCache.
|
||||
*/
|
||||
|
||||
var bc = new BroadcastChannel("ship_beforeunload");
|
||||
var pageshowCount = 0;
|
||||
var beforeUnloadFired = false;
|
||||
bc.onmessage = function(event) {
|
||||
if (event.data.type == "pageshow") {
|
||||
++pageshowCount;
|
||||
if (pageshowCount == 1) {
|
||||
bc.postMessage({action: "navigate_to_page_b"});
|
||||
} else if (pageshowCount == 2) {
|
||||
ok(!event.data.persisted, "?pageb shouldn't in BFCache because it's the first navigation");
|
||||
bc.postMessage({action: "register_beforeunload", loadNextPageFromSessionHistory: true});
|
||||
} else if (pageshowCount == 3) {
|
||||
ok(event.data.persisted, "navigated back to page A that was in BFCacache from page B");
|
||||
ok(beforeUnloadFired, "beforeunload has fired on page B");
|
||||
bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: true});
|
||||
} else if (pageshowCount == 4) {
|
||||
ok(event.data.persisted, "page B has beforeunload fired and also entered BFCache");
|
||||
bc.postMessage({action: "close"});
|
||||
SimpleTest.finish();
|
||||
}
|
||||
} else if (event.data == "beforeunload_fired") {
|
||||
beforeUnloadFired = true;
|
||||
}
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["fission.bfcacheInParent", true],
|
||||
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
|
||||
]},
|
||||
function() {
|
||||
window.open("file_ship_beforeunload_fired.html", "", "noopener");
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<body onload="runTest()"></body>
|
||||
</html>
|
|
@ -0,0 +1,65 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Test that ensures beforeunload is fired when session-history-in-parent is enabled
|
||||
</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
/*
|
||||
* This test ensures beforeunload is fired on the current page
|
||||
* when it is entering BFCache, and the next page is not coming from
|
||||
* session history and also not coming out from BFCache.
|
||||
*
|
||||
* (1) The controller page opens a new window, and page A is loaded there.
|
||||
* (2) Page A then navigates to page B, and a beforeunload event
|
||||
* listener is registered on page B.
|
||||
* (3) Page B then navigates to page C, and the beforeunload handler
|
||||
* should send a message to the controller page.
|
||||
* (4) Page C then navigates back to page B to check if page B has
|
||||
* been successfully added to BFCache.
|
||||
*/
|
||||
|
||||
var bc = new BroadcastChannel("ship_beforeunload");
|
||||
var pageshowCount = 0;
|
||||
|
||||
var beforeUnloadFired = false;
|
||||
bc.onmessage = function(event) {
|
||||
if (event.data.type == "pageshow") {
|
||||
++pageshowCount;
|
||||
if (pageshowCount == 1) {
|
||||
bc.postMessage({action: "navigate_to_page_b"});
|
||||
} else if (pageshowCount == 2) {
|
||||
ok(!event.data.persisted, "?page B shouldn't in BFCache because it's the first navigation");
|
||||
bc.postMessage({action: "register_beforeunload",
|
||||
loadNextPageFromSessionHistory: false});
|
||||
} else if (pageshowCount == 3) {
|
||||
ok(!event.data.persisted, "navigated to page C that was a new page");
|
||||
ok(beforeUnloadFired, "beforeUnload should be fired on page B");
|
||||
bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: false});
|
||||
} else if (pageshowCount == 4) {
|
||||
ok(event.data.persisted, "page B has been successfully added to BFCache");
|
||||
bc.postMessage({action: "close"});
|
||||
SimpleTest.finish();
|
||||
}
|
||||
} else if (event.data == "beforeunload_fired") {
|
||||
beforeUnloadFired = true;
|
||||
}
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["fission.bfcacheInParent", true],
|
||||
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
|
||||
]},
|
||||
function() {
|
||||
window.open("file_ship_beforeunload_fired.html", "", "noopener");
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<body onload="runTest()"></body>
|
||||
</html>
|
|
@ -0,0 +1,65 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Test that ensures beforeunload is fired when session-history-in-parent is enabled
|
||||
</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
/*
|
||||
* This test ensures beforeunload is fired on the current page
|
||||
* when it is entering BFCache, and the next page is not coming out
|
||||
* from BFCache, but coming from session history.
|
||||
*
|
||||
* (1) The controller page opens a new window, and page A is loaded there.
|
||||
* (2) Page A then navigates to page B, and a beforeunload event
|
||||
* listener is registered on page B.
|
||||
* (3) Page B then navigates back to page A, and the beforeunload handler
|
||||
* should send a message to the controller page.
|
||||
* (4) Page A then navigates back to page B to check if page B has
|
||||
* been successfully added to BFCache.
|
||||
*/
|
||||
|
||||
var bc = new BroadcastChannel("ship_beforeunload");
|
||||
var pageshowCount = 0;
|
||||
|
||||
var beforeUnloadFired = false;
|
||||
bc.onmessage = function(event) {
|
||||
if (event.data.type == "pageshow") {
|
||||
++pageshowCount;
|
||||
if (pageshowCount == 1) {
|
||||
bc.postMessage({action: "navigate_to_page_b", blockBFCache: true});
|
||||
} else if (pageshowCount == 2) {
|
||||
ok(!event.data.persisted, "?page B shouldn't in BFCache because it's the first navigation");
|
||||
bc.postMessage({action: "register_beforeunload",
|
||||
loadNextPageFromSessionHistory: true});
|
||||
} else if (pageshowCount == 3) {
|
||||
ok(!event.data.persisted, "navigated back to page A that was session history but not in BFCache");
|
||||
ok(beforeUnloadFired, "beforeUnload should be fired on page B");
|
||||
bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: true});
|
||||
} else if (pageshowCount == 4) {
|
||||
ok(event.data.persisted, "page B has been successfully added to BFCache");
|
||||
bc.postMessage({action: "close"});
|
||||
SimpleTest.finish();
|
||||
}
|
||||
} else if (event.data == "beforeunload_fired") {
|
||||
beforeUnloadFired = true;
|
||||
}
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["fission.bfcacheInParent", true],
|
||||
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
|
||||
]},
|
||||
function() {
|
||||
window.open("file_ship_beforeunload_fired.html", "", "noopener");
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<body onload="runTest()"></body>
|
||||
</html>
|
|
@ -11173,10 +11173,15 @@ bool Document::CanSavePresentation(nsIRequest* aNewRequest,
|
|||
ret = false;
|
||||
}
|
||||
if (manager->HasBeforeUnloadListeners()) {
|
||||
MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
|
||||
("Save of %s blocked due to beforeUnload handlers", uri.get()));
|
||||
aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
|
||||
ret = false;
|
||||
if (!mozilla::SessionHistoryInParent() ||
|
||||
!StaticPrefs::
|
||||
docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
|
||||
MOZ_LOG(
|
||||
gPageCacheLog, mozilla::LogLevel::Verbose,
|
||||
("Save of %s blocked due to beforeUnload handlers", uri.get()));
|
||||
aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@
|
|||
#include "mozilla/SpinEventLoopUntil.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "mozilla/StaticPrefs_browser.h"
|
||||
#include "mozilla/StaticPrefs_docshell.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "mozilla/StaticPrefs_privacy.h"
|
||||
#include "mozilla/StorageAccess.h"
|
||||
|
@ -6590,13 +6591,22 @@ void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
|
|||
mHasVRDisplayActivateEvents = true;
|
||||
}
|
||||
|
||||
if ((aType == nsGkAtoms::onunload || aType == nsGkAtoms::onbeforeunload) &&
|
||||
mWindowGlobalChild) {
|
||||
if (aType == nsGkAtoms::onunload && mWindowGlobalChild) {
|
||||
if (++mUnloadOrBeforeUnloadListenerCount == 1) {
|
||||
mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
|
||||
}
|
||||
if (aType == nsGkAtoms::onbeforeunload &&
|
||||
(!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
|
||||
}
|
||||
|
||||
if (aType == nsGkAtoms::onbeforeunload && mWindowGlobalChild) {
|
||||
if (!mozilla::SessionHistoryInParent() ||
|
||||
!StaticPrefs::
|
||||
docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
|
||||
if (++mUnloadOrBeforeUnloadListenerCount == 1) {
|
||||
mWindowGlobalChild->BlockBFCacheFor(
|
||||
BFCacheStatus::BEFOREUNLOAD_LISTENER);
|
||||
}
|
||||
}
|
||||
if (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
|
||||
mWindowGlobalChild->BeforeUnloadAdded();
|
||||
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() > 0);
|
||||
}
|
||||
|
@ -6618,14 +6628,23 @@ void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
|
|||
}
|
||||
|
||||
void nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType) {
|
||||
if ((aType == nsGkAtoms::onunload || aType == nsGkAtoms::onbeforeunload) &&
|
||||
mWindowGlobalChild) {
|
||||
if (aType == nsGkAtoms::onunload && mWindowGlobalChild) {
|
||||
MOZ_ASSERT(mUnloadOrBeforeUnloadListenerCount > 0);
|
||||
if (--mUnloadOrBeforeUnloadListenerCount == 0) {
|
||||
mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
|
||||
}
|
||||
if (aType == nsGkAtoms::onbeforeunload &&
|
||||
(!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
|
||||
}
|
||||
|
||||
if (aType == nsGkAtoms::onbeforeunload && mWindowGlobalChild) {
|
||||
if (!mozilla::SessionHistoryInParent() ||
|
||||
!StaticPrefs::
|
||||
docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
|
||||
if (--mUnloadOrBeforeUnloadListenerCount == 0) {
|
||||
mWindowGlobalChild->UnblockBFCacheFor(
|
||||
BFCacheStatus::BEFOREUNLOAD_LISTENER);
|
||||
}
|
||||
}
|
||||
if (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
|
||||
mWindowGlobalChild->BeforeUnloadRemoved();
|
||||
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() >= 0);
|
||||
}
|
||||
|
|
|
@ -1520,7 +1520,7 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
|
|||
|
||||
RefPtr<mozilla::dom::VREventObserver> mVREventObserver;
|
||||
|
||||
// The number of unload and beforeunload even listeners registered on this
|
||||
// The number of unload and beforeunload event listeners registered on this
|
||||
// window.
|
||||
uint64_t mUnloadOrBeforeUnloadListenerCount = 0;
|
||||
|
||||
|
|
|
@ -1388,6 +1388,7 @@ nsCString BFCacheStatusToString(uint32_t aFlags) {
|
|||
ADD_BFCACHESTATUS_TO_STRING(HAS_USED_VR);
|
||||
ADD_BFCACHESTATUS_TO_STRING(CONTAINS_REMOTE_SUBFRAMES);
|
||||
ADD_BFCACHESTATUS_TO_STRING(NOT_ONLY_TOPLEVEL_IN_BCG);
|
||||
ADD_BFCACHESTATUS_TO_STRING(BEFOREUNLOAD_LISTENER);
|
||||
ADD_BFCACHESTATUS_TO_STRING(ACTIVE_LOCK);
|
||||
|
||||
#undef ADD_BFCACHESTATUS_TO_STRING
|
||||
|
|
|
@ -408,6 +408,12 @@ function assertHasBeforeUnload(browser, expected) {
|
|||
* inner window is removed from the DOM.
|
||||
*/
|
||||
add_task(async function test_inner_window_scenarios() {
|
||||
// Turn this off because the test expects the page to be not bfcached.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
|
||||
],
|
||||
});
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
|
@ -592,6 +598,12 @@ add_task(async function test_inner_window_scenarios() {
|
|||
* to the iframe DOM nodes instead of the inner windows.
|
||||
*/
|
||||
add_task(async function test_outer_window_scenarios() {
|
||||
// Turn this off because the test expects the page to be not bfcached.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
|
||||
],
|
||||
});
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
|
@ -787,6 +799,12 @@ add_task(async function test_outer_window_scenarios() {
|
|||
* are added on both inner and outer windows.
|
||||
*/
|
||||
add_task(async function test_mixed_inner_and_outer_window_scenarios() {
|
||||
// Turn this off because the test expects the page to be not bfcached.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
|
||||
],
|
||||
});
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
|
|
|
@ -1816,6 +1816,13 @@
|
|||
value: @IS_ANDROID@
|
||||
mirror: always
|
||||
|
||||
# If true, page with beforeunload event listeners can be bfcached.
|
||||
# This only works when sessionHistoryInParent is enabled.
|
||||
- name: docshell.shistory.bfcache.ship_allow_beforeunload_listeners
|
||||
type: bool
|
||||
value: @IS_NIGHTLY_BUILD@
|
||||
mirror: always
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Prefs starting with "dom."
|
||||
#---------------------------------------------------------------------------
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
[events.html]
|
||||
prefs: [docshell.shistory.bfcache.ship_allow_beforeunload_listeners:true]
|
||||
[beforeunload]
|
||||
expected:
|
||||
if os == "android": PASS
|
||||
PRECONDITION_FAILED
|
||||
|
||||
if not sessionHistoryInParent and (os != "android") : PRECONDITION_FAILED
|
||||
[unload]
|
||||
expected:
|
||||
if os == "android": PASS
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
<script>
|
||||
// Prevent this page from being stored in the bfcache for the
|
||||
// browser_httpToFileHistory.js test
|
||||
window.addEventListener('beforeunload', event => {
|
||||
// do nothing
|
||||
});
|
||||
window.blockBFCache = new RTCPeerConnection();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Загрузка…
Ссылка в новой задаче