Backed out 2 changesets (bug 1741671) for causing wpt and mochitest failures on events.html CLOSED TREE

Backed out changeset 782650de8717 (bug 1741671)
Backed out changeset 6928411c8730 (bug 1741671)
This commit is contained in:
Cristian Tuns 2022-02-24 14:06:28 -05:00
Родитель 68c69f9585
Коммит 8871543441
17 изменённых файлов: 263 добавлений и 333 удалений

Просмотреть файл

@ -31,6 +31,8 @@ support-files =
inspector-search-data.html
inspector-traversal-data.html
inspector-shadow.html
navigate-first.html
navigate-second.html
storage-cookies-same-name.html
storage-dynamic-windows.html
storage-listings.html
@ -136,6 +138,8 @@ skip-if =
[browser_markers-styles.js]
[browser_markers-timestamp.js]
[browser_memory_allocations_01.js]
[browser_navigateEvents.js]
https_first_disabled = true
[browser_perf-01.js]
[browser_perf-02.js]
[browser_perf-04.js]

Просмотреть файл

@ -0,0 +1,189 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const URL1 = MAIN_DOMAIN + "navigate-first.html";
const URL2 = MAIN_DOMAIN + "navigate-second.html";
const { PromptTestUtils } = ChromeUtils.import(
"resource://testing-common/PromptTestUtils.jsm"
);
var isE10s = Services.appinfo.browserTabsRemoteAutostart;
SpecialPowers.pushPrefEnv({
set: [["dom.require_user_interaction_for_beforeunload", false]],
});
var signalAllEventsReceived;
var onAllEventsReceived = new Promise(resolve => {
signalAllEventsReceived = resolve;
});
// State machine to check events order
var i = 0;
function assertEvent(event, data) {
switch (i++) {
case 0:
is(event, "request", "Get first page load");
is(data, URL1);
break;
case 1:
is(event, "load-new-document", "Ask to load the second page");
break;
case 2:
is(event, "unload-dialog", "We get the dialog on first page unload");
break;
case 3:
is(
event,
"will-navigate",
"The very first event is will-navigate on server side"
);
is(data.newURI, URL2, "newURI property is correct");
break;
case isE10s ? 4 : 5: // When e10s is disabled tabNavigated/request order is swapped
is(
event,
"tabNavigated",
"After the request, the client receive tabNavigated"
);
is(data.state, "start", "state is start");
is(data.url, URL2, "url property is correct");
break;
case isE10s ? 5 : 4:
is(
event,
"request",
"RDP is async with messageManager, the request happens after will-navigate"
);
is(data, URL2);
break;
case 6:
is(event, "DOMContentLoaded");
is(data.readyState, "interactive");
break;
case 7:
is(event, "load");
is(data.readyState, "complete");
break;
case 8:
is(
event,
"navigate",
"Then once the second doc is loaded, we get the navigate event"
);
break;
case 9:
is(event, "tabNavigated", "Finally, the receive the client event");
is(data.state, "stop", "state is stop");
is(data.url, URL2, "url property is correct");
signalAllEventsReceived();
break;
}
}
var httpObserver = function(subject, topic, state) {
const channel = subject.QueryInterface(Ci.nsIHttpChannel);
const url = channel.URI.spec;
// Only listen for our document request, as many other requests can happen
if (url == URL1 || url == URL2) {
assertEvent("request", url);
}
};
Services.obs.addObserver(httpObserver, "http-on-modify-request");
registerCleanupFunction(() => {
Services.obs.removeObserver(httpObserver, "http-on-modify-request");
});
function onMessage({ data }) {
assertEvent(data.event, data.data);
}
async function connectAndAttachTab(tab) {
const target = await createAndAttachTargetForTab(tab);
const actorID = target.actorID;
target.on("tabNavigated", function(packet) {
assertEvent("tabNavigated", packet);
});
// In order to listen to internal will-navigate/navigate events
target.on("will-navigate", function(data) {
assertEvent("will-navigate", {
newURI: data.url,
});
});
target.on("navigate", () => assertEvent("navigate"));
return { target, actorID };
}
add_task(async function() {
// Navigation events (navigate/will-navigate) on the target no longer fire with server targets.
// We should probably drop this test once we stop supporting client side targets (bug 1721852).
await pushPref("devtools.target-switching.server.enabled", false);
// Open a test tab
const browser = await addTab(URL1);
// Listen for alert() call being made in navigate-first during unload
const beforeUnloadPromise = PromptTestUtils.handleNextPrompt(
browser,
{ modalType: Services.prompt.MODAL_TYPE_CONTENT, promptType: "confirmEx" },
{ buttonNumClick: 0 }
).then(() => {
assertEvent("unload-dialog");
});
// Listen for messages sent by the content task
browser.messageManager.addMessageListener("devtools-test:event", onMessage);
const tab = gBrowser.getTabForBrowser(browser);
const { target, actorID } = await connectAndAttachTab(tab);
await ContentTask.spawn(browser, [actorID], async function(actorId) {
// Forward DOMContentLoaded and load events
addEventListener(
"DOMContentLoaded",
function() {
sendSyncMessage("devtools-test:event", {
event: "DOMContentLoaded",
data: { readyState: content.document.readyState },
});
},
{ capture: true }
);
addEventListener(
"load",
function() {
sendSyncMessage("devtools-test:event", {
event: "load",
data: { readyState: content.document.readyState },
});
},
{ capture: true }
);
});
// Load another document in this doc to dispatch these events
assertEvent("load-new-document");
// Use BrowserTestUtils instead of navigateTo as there is no toolbox opened
const onBrowserLoaded = BrowserTestUtils.browserLoaded(
gBrowser.selectedBrowser
);
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, URL2);
await beforeUnloadPromise;
await onBrowserLoaded;
// Wait for all events to be received
await onAllEventsReceived;
// Cleanup
browser.messageManager.removeMessageListener(
"devtools-test:event",
onMessage
);
await target.destroy();
Services.obs.addObserver(httpObserver, "http-on-modify-request");
DevToolsServer.destroy();
});

Просмотреть файл

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
First
<script>
"use strict";
window.onbeforeunload = function(e) {
e.returnValue = "?";
};
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,9 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
Second
</body>
</html>

Просмотреть файл

@ -1324,34 +1324,37 @@ void nsSHistory::LoadURIOrBFCache(LoadEntryResult& aLoadEntry) {
"saving presentation=%i",
canSave));
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);
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);
}
}
}
});
return;
});
return;
}
}
}
}

Просмотреть файл

@ -1,37 +0,0 @@
<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,15 +200,3 @@ 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

Просмотреть файл

@ -1,63 +0,0 @@
<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>

Просмотреть файл

@ -1,65 +0,0 @@
<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>

Просмотреть файл

@ -1,65 +0,0 @@
<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>

Просмотреть файл

@ -11170,15 +11170,10 @@ bool Document::CanSavePresentation(nsIRequest* aNewRequest,
ret = false;
}
if (manager->HasBeforeUnloadListeners()) {
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;
}
MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
("Save of %s blocked due to beforeUnload handlers", uri.get()));
aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
ret = false;
}
}
}

Просмотреть файл

@ -81,7 +81,6 @@
#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,22 +6589,13 @@ void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
mHasVRDisplayActivateEvents = true;
}
if (aType == nsGkAtoms::onunload && mWindowGlobalChild) {
if ((aType == nsGkAtoms::onunload || aType == nsGkAtoms::onbeforeunload) &&
mWindowGlobalChild) {
if (++mUnloadOrBeforeUnloadListenerCount == 1) {
mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
}
}
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)) {
if (aType == nsGkAtoms::onbeforeunload &&
(!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
mWindowGlobalChild->BeforeUnloadAdded();
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() > 0);
}
@ -6627,23 +6617,14 @@ void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
}
void nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType) {
if (aType == nsGkAtoms::onunload && mWindowGlobalChild) {
if ((aType == nsGkAtoms::onunload || aType == nsGkAtoms::onbeforeunload) &&
mWindowGlobalChild) {
MOZ_ASSERT(mUnloadOrBeforeUnloadListenerCount > 0);
if (--mUnloadOrBeforeUnloadListenerCount == 0) {
mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
}
}
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)) {
if (aType == nsGkAtoms::onbeforeunload &&
(!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
mWindowGlobalChild->BeforeUnloadRemoved();
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() >= 0);
}

Просмотреть файл

@ -1516,7 +1516,7 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
RefPtr<mozilla::dom::VREventObserver> mVREventObserver;
// The number of unload and beforeunload event listeners registered on this
// The number of unload and beforeunload even listeners registered on this
// window.
uint64_t mUnloadOrBeforeUnloadListenerCount = 0;

Просмотреть файл

@ -1388,7 +1388,6 @@ 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,12 +408,6 @@ 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,
@ -598,12 +592,6 @@ 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,
@ -799,12 +787,6 @@ 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,13 +1816,6 @@
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."
#---------------------------------------------------------------------------

Просмотреть файл

@ -7,7 +7,9 @@
<script>
// Prevent this page from being stored in the bfcache for the
// browser_httpToFileHistory.js test
window.blockBFCache = new RTCPeerConnection();
window.addEventListener('beforeunload', event => {
// do nothing
});
</script>
</body>
</html>