--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7966,16 +7966,18 @@ nsDocShell::CaptureState()
if (!mScriptGlobal) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsISupports> windowState = mScriptGlobal->SaveWindowState();
NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE);
+ mOSHE->SaveUserGestureActivationState();
+
#ifdef DEBUG_PAGE_CACHE
nsCOMPtr<nsIURI> uri;
mOSHE->GetURI(getter_AddRefs(uri));
nsAutoCString spec;
if (uri) {
uri->GetSpec(spec);
}
printf("Saving presentation into session history\n");
@@ -8765,25 +8767,25 @@ nsDocShell::CreateContentViewer(const ns
// Notify the current document that it is about to be unloaded!!
//
// It is important to fire the unload() notification *before* any state
// is changed within the DocShell - otherwise, javascript will get the
// wrong information :-(
//
- nsCOMPtr<nsIPrincipal> oldPrincipal;
+ // nsCOMPtr<nsIPrincipal> oldPrincipal = viewer->GetDocument()->NodePrincipal();
if (mSavingOldViewer) {
// We determined that it was safe to cache the document presentation
// at the time we initiated the new load. We need to check whether
// it's still safe to do so, since there may have been DOM mutations
// or new requests initiated.
nsCOMPtr<nsIDocument> doc = viewer->GetDocument();
- oldPrincipal = doc->NodePrincipal();
+ // oldPrincipal = doc->NodePrincipal();
mSavingOldViewer = CanSavePresentation(mLoadType, aRequest, doc);
}
NS_ASSERTION(!mLoadingURI, "Re-entering unload?");
nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
if (aOpenedChannel) {
aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI));
@@ -8978,19 +8980,49 @@ nsDocShell::NewContentViewerObj(const ns
aContentHandler,
aViewer);
NS_ENSURE_SUCCESS(rv, rv);
(*aViewer)->SetContainer(this);
return NS_OK;
}
+
+// static nsCString
+// OriginOf(nsIPrincipal* principal)
+// {
+// if (!principal) {
+// return NS_LITERAL_CSTRING("null");
+// }
+// nsCString origin;
+// if (NS_FAILED(principal->GetOrigin(origin))) {
+// return NS_LITERAL_CSTRING("null");
+// }
+// return origin;
+// }
+
+// static nsCString
+// OriginOf(nsIDocument* doc)
+// {
+// nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+// if (!principal) {
+// return NS_LITERAL_CSTRING("null");
+// }
+// nsCString origin;
+// if (NS_FAILED(principal->GetOrigin(origin))) {
+// return NS_LITERAL_CSTRING("null");
+// }
+// return origin;
+// }
+
nsresult
nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer)
{
+ // printf("%p %s mSavingOldViewer=%d\n", this, __func__, mSavingOldViewer);
+
MOZ_ASSERT(!mIsBeingDestroyed);
//
// Copy content viewer state from previous or parent content viewer.
//
// The following logic is mirrored in nsHTMLDocument::StartDocumentLoad!
//
// Do NOT to maintain a reference to the old content viewer outside
@@ -9052,16 +9084,21 @@ nsDocShell::SetupNewViewer(nsIContentVie
}
if (oldCv) {
// Record the user-gesture activation state from the old document, and
// its principal. This ensures we can forward the unblock-autoplay-media
// state into the new document.
nsIDocument* doc = oldCv->GetDocument();
activatedByUserGesture = doc && doc->HasBeenUserActivated();
+ if (mSavingOldViewer && mOSHE) {
+ mOSHE->SetUserGestureActivated(activatedByUserGesture);
+ // printf("Saved gesture activation, doc=%p (%s) activated=%d\n",
+ // doc, OriginOf(doc).get(), activatedByUserGesture);
+ }
oldPrincipal = doc ? doc->NodePrincipal() : nullptr;
newCv = aNewViewer;
if (newCv) {
forceCharset = oldCv->GetForceCharset();
hintCharset = oldCv->GetHintCharset();
NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSetSource(&hintCharsetSource),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(oldCv->GetMinFontSize(&minFontSize),
@@ -9073,18 +9110,18 @@ nsDocShell::SetupNewViewer(nsIContentVie
NS_ENSURE_SUCCESS(oldCv->GetOverrideDPPX(&overrideDPPX),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(oldCv->GetAuthorStyleDisabled(&styleDisabled),
NS_ERROR_FAILURE);
}
}
}
- // printf("%p %s activatedByUserGesture=%d oldPrincipal=%p\n",
- // this, __func__, activatedByUserGesture, oldPrincipal);
+ // printf("%p %s activatedByUserGesture=%d oldPrincipal=%p (%s)\n",
+ // this, __func__, activatedByUserGesture, oldPrincipal, OriginOf(oldPrincipal).get());
nscolor bgcolor = NS_RGBA(0, 0, 0, 0);
bool isActive = false;
// Ensure that the content viewer is destroyed *after* the GC - bug 71515
nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer;
if (contentViewer) {
// Stop any activity that may be happening in the old document before
// releasing it...
@@ -9146,24 +9183,38 @@ nsDocShell::SetupNewViewer(nsIContentVie
NS_ENSURE_SUCCESS(newCv->SetOverrideDPPX(overrideDPPX),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(newCv->SetAuthorStyleDisabled(styleDisabled),
NS_ERROR_FAILURE);
// Restore activated-by-user-gesture state, to propogate autoplay media
// state into the new if it's same origin.
nsIDocument* doc = newCv->GetDocument();
- if (activatedByUserGesture && doc) {
- nsCOMPtr<nsIPrincipal> newPrincipal = doc->NodePrincipal();
- if (oldPrincipal->Equals(newPrincipal)) {
- // TODO: Should use Equals()? Subsumes?
- doc->NotifyUserActivation();
- }
+ if (mLSHE) {
+ bool activated = false;
+ if (NS_SUCCEEDED(mLSHE->GetUserGestureActivated(&activated)) && activated) {
+ // printf("mLSHE was gesture activated.\n");
+ activatedByUserGesture = activated;
+ }
+ // } else {
+ // printf("Null mLSHE\n");
+ }
+ if (activatedByUserGesture) {
+ if (doc) {
+ nsCOMPtr<nsIPrincipal> newPrincipal = doc->NodePrincipal();
+ if (oldPrincipal->Equals(newPrincipal)) {
+ // TODO: Should use Equals()? Subsumes?
+ doc->NotifyUserActivation();
+ // } else {
+ // printf("Unequal prinicipals new=%s old=%s.\n",
+ // OriginOf(newPrincipal).get(), OriginOf(oldPrincipal).get());
+ }
// } else {
- // printf("!doc\n");
+ // printf("!doc, but was activated\n");
+ }
}
}
// Stuff the bgcolor from the old pres shell into the new
// pres shell. This improves page load continuity.
nsCOMPtr<nsIPresShell> shell;
mContentViewer->GetPresShell(getter_AddRefs(shell));
@@ -11928,17 +11979,17 @@ nsDocShell::AddState(JS::Handle<JS::Valu
// one. This operation may modify mOSHE, which we need later, so we
// keep a reference here.
NS_ENSURE_TRUE(mOSHE, NS_ERROR_FAILURE);
nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;
mLoadType = LOAD_PUSHSTATE;
nsCOMPtr<nsISHEntry> newSHEntry;
- if (!aReplace) {
+ if (!aReplace) { // TODO: Save gesture-activation here too? Where to restore?
// Save the current scroll position (bug 590573).
nscoord cx = 0, cy = 0;
GetCurScrollPos(ScrollOrientation_X, &cx);
GetCurScrollPos(ScrollOrientation_Y, &cy);
mOSHE->SetScrollPosition(cx, cy);
bool scrollRestorationIsManual = false;
mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -27,17 +27,17 @@ interface nsISHistory;
#include "nsRect.h"
class nsDocShellEditorData;
class nsSHEntryShared;
%}
[ref] native nsIntRect(nsIntRect);
[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
[ptr] native nsSHEntryShared(nsSHEntryShared);
-[scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)]
+[scriptable, uuid(1e46717b-b94d-4d54-abcc-80acb68dc4dc)]
interface nsISHEntry : nsISupports
{
/**
* A readonly property that returns the URI
* of the current entry. The object returned is
* of type nsIURI
*/
readonly attribute nsIURI URI;
@@ -341,16 +341,24 @@ interface nsISHEntry : nsISupports
readonly attribute boolean loadedInThisProcess;
/**
* The session history it belongs to. It's usually only set on root entries.
* SHEntry is strictly bound to the SHistory it belongs to; it should not be
* changed once set to a non-null value.
*/
[noscript] attribute nsISHistory SHistory;
+
+ /**
+ * Whether this document was activated by a user gesture (mouse or key
+ * event).
+ */
+ attribute boolean userGestureActivated;
+
+ [noscript] void SaveUserGestureActivationState();
};
[scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]
interface nsISHEntryInternal : nsISupports
{
[notxpcom] void RemoveFromBFCacheAsync();
[notxpcom] void RemoveFromBFCacheSync();
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -418,16 +418,37 @@ nsSHEntry::GetExpirationStatus(bool* aFl
NS_IMETHODIMP
nsSHEntry::SetExpirationStatus(bool aFlag)
{
mShared->mExpired = aFlag;
return NS_OK;
}
NS_IMETHODIMP
+nsSHEntry::GetUserGestureActivated(bool* aOutUserGestureActivated)
+{
+ *aOutUserGestureActivated = mShared->mUserGestureActivated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetUserGestureActivated(bool aUserGestureActivated)
+{
+ mShared->mUserGestureActivated = aUserGestureActivated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SaveUserGestureActivationState()
+{
+ mShared->SaveUserGestureActivationState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
nsSHEntry::GetContentType(nsACString& aContentType)
{
aContentType = mShared->mContentType;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetContentType(const nsACString& aContentType)
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -39,16 +39,17 @@ nsSHEntryShared::nsSHEntryShared()
: mDocShellID({0})
, mLastTouched(0)
, mID(gSHEntrySharedID++)
, mViewerBounds(0, 0, 0, 0)
, mIsFrameNavigation(false)
, mSaveLayoutState(true)
, mSticky(true)
, mDynamicallyCreated(false)
+ , mUserGestureActivated(false)
, mExpired(false)
{
}
nsSHEntryShared::~nsSHEntryShared()
{
// The destruction can be caused by either the entry is removed from session
// history and no one holds the reference, or the whole session history is on
@@ -130,16 +131,27 @@ nsSHEntryShared::DropPresentationState()
mSticky = true;
mWindowState = nullptr;
mViewerBounds.SetRect(0, 0, 0, 0);
mChildShells.Clear();
mRefreshURIList = nullptr;
mEditorData = nullptr;
}
+void
+nsSHEntryShared::SaveUserGestureActivationState()
+{
+ if (!mDocument) {
+ return;
+ }
+ mUserGestureActivated = mDocument->HasBeenUserActivated();
+ printf("Saved gesture activation state %d for doc=%p\n",
+ mUserGestureActivated, mDocument.get());
+}
+
nsresult
nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer)
{
NS_PRECONDITION(!aViewer || !mContentViewer,
"SHEntryShared already contains viewer");
if (mContentViewer || !aViewer) {
DropPresentationState();
--- a/docshell/shistory/nsSHEntryShared.h
+++ b/docshell/shistory/nsSHEntryShared.h
@@ -55,16 +55,17 @@ private:
friend class nsSHEntry;
static already_AddRefed<nsSHEntryShared> Duplicate(nsSHEntryShared* aEntry);
void RemoveFromExpirationTracker();
nsresult SyncPresentationState();
void DropPresentationState();
+ void SaveUserGestureActivationState();
nsresult SetContentViewer(nsIContentViewer* aViewer);
// See nsISHEntry.idl for an explanation of these members.
// These members are copied by nsSHEntryShared::Duplicate(). If you add a
// member here, be sure to update the Duplicate() implementation.
nsID mDocShellID;
@@ -88,14 +89,15 @@ private:
nsExpirationState mExpirationState;
nsAutoPtr<nsDocShellEditorData> mEditorData;
nsWeakPtr mSHistory;
bool mIsFrameNavigation;
bool mSaveLayoutState;
bool mSticky;
bool mDynamicallyCreated;
+ bool mUserGestureActivated;
// This flag is about necko cache, not bfcache.
bool mExpired;
};
#endif
--- a/dom/media/test/AutoplayTestUtils.js
+++ b/dom/media/test/AutoplayTestUtils.js
@@ -1,21 +1,34 @@
-function playAndPostResult(muted, parent_window) {
+// Enumerated actions for navigation tests.
+const Activate = 0;
+const NavigateSameOrigin = 1;
+const NavigateCrossOrigin = 2;
+const NavigateBack = 3;
+const NavigateForward = 4;
+const Reload = 5;
+
+async function playAndPostResult(muted, parent_window) {
let element = document.createElement("video");
element.preload = "auto";
element.muted = muted;
element.src = "short.mp4";
element.id = "video";
+ element.load();
document.body.appendChild(element);
+
+ // await nextEvent(element, "loadedmetadata");
element.play().then(
() => {
+ // parent_window.postMessage("played", "*");
parent_window.postMessage({played: true}, "*");
},
() => {
+ // parent_window.postMessage("blocked", "*");
parent_window.postMessage({played: false}, "*");
}
);
}
function nextEvent(eventTarget, eventName) {
return new Promise(function(resolve, reject) {
let f = function(event) {
@@ -26,12 +39,13 @@ function nextEvent(eventTarget, eventNam
});
}
function nextWindowMessage() {
return nextEvent(window, "message");
}
function log(msg) {
- var log_pane = document.body;
- log_pane.appendChild(document.createTextNode(msg));
- log_pane.appendChild(document.createElement("br"));
+ // var log_pane = document.body;
+ // log_pane.appendChild(document.createTextNode(msg));
+ // log_pane.appendChild(document.createElement("br"));
+ dump(msg + "\n");
}
new file mode 100644
--- /dev/null
+++ b/dom/media/test/file_autoplay_policy_navigation_window.html
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Autoplay policy window</title>
+ <style>
+ video {
+ width: 50%;
+ height: 50%;
+ }
+ </style>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body>
+ <pre id="test">
+ <script>
+
+ // SimpleTest.requestFlakyTimeout("Because there's not a good way to detect iframe history navigation complete");
+
+
+ // function awhile() {
+ // return new Promise(function(resolve, reject) {
+ // setTimeout(()=>{resolve();}, 1000);
+ // });
+ // }
+
+ // async function testAutoplayNavigation(test_case, parent_window) {
+ // log("testAutoplayNavigation: " + test_case.name);
+
+ // let navigationHistory = new NavigationHistory();
+ // let counter = 1;
+
+ // // Create a child iframe...
+ // var frame = document.createElement("iframe");
+ // const resource = "/tests/dom/media/test/file_autoplay_policy_activation_frame.html";
+ // const sameOriginResource = "http://mochi.test:8888" + resource;
+ // const crossOriginResource = "http://example.org" + resource;
+ // frame.src = resource + "?" + counter++;
+ // navigationHistory.push(frame.src);
+
+ // // Wait for it to load...
+ // document.body.appendChild(frame);
+ // await once(frame, "load");
+
+ // // Perform test case's actions.
+ // for (let action of test_case.actions) {
+ // switch (action) {
+ // case Activate: {
+ // synthesizeMouseAtCenter(frame, {});
+ // break;
+ // }
+ // case NavigateSameOrigin: {
+ // let uri = sameOriginResource + "?" + counter++;
+ // frame.contentWindow.location = uri;
+ // navigationHistory.push(uri);
+ // await once(frame, "load");
+ // break;
+ // }
+ // case NavigateCrossOrigin: {
+ // let uri = crossOriginResource + "?" + counter++;
+ // frame.contentWindow.location = uri;
+ // navigationHistory.push(uri);
+ // await once(frame, "load");
+ // break;
+ // }
+ // case NavigateBack: {
+ // // log("going back");
+ // // document.body.addEventListener("load", (e)=>{
+ // // log("frame load");
+ // // }, true);
+ // log("frame.contentWindow.location=" + SpecialPowers.wrap(frame.contentWindow).location);
+ // SpecialPowers.wrap(frame.contentWindow).history.back();
+ // let uri = navigationHistory.back();
+ // log("Awaiting location change");
+ // await locationChangedTo(frame, uri);
+ // log("frame.contentWindow.location=" + SpecialPowers.wrap(frame.contentWindow).location);
+ // // log("awaiting go back complete");
+ // // await once(frame, "load");
+ // // log("finished going back");
+ // // await awhile();
+ // break;
+ // }
+ // case NavigateForward: {
+ // SpecialPowers.wrap(frame.contentWindow).history.forward();
+ // let uri = navigationHistory.forward();
+ // log("Awaiting location change to " + uri);
+ // await locationChangedTo(frame, uri);
+ // break;
+ // }
+ // case Reload: {
+ // frame.contentWindow.location.reload(true);
+ // await once(frame, "load");
+ // break;
+ // }
+ // }
+ // }
+
+ // // Ask the newly loaded frame to play a video.
+ // frame.contentWindow.postMessage(test_case, "*");
+
+ // // Wait for the frame to tell us whether it could play video.
+ // let result = await nextWindowMessage();
+
+ // // Report whether the iframe could play to the parent.
+ // parent_window.postMessage(result.data, "*");
+ // }
+
+ // nextWindowMessage().then(
+ // (event) => {
+ // let test_case = event.data;
+ // let parent_window = event.source;
+ // testAutoplayNavigation(test_case, parent_window);
+ // });
+
+ window.addEventListener("mousedown", ()=>{
+ dump("mousedown\n");
+ }, false);
+
+ window.addEventListener("mouseup", ()=>{
+ dump("mouseup\n");
+ }, false);
+
+ window.addEventListener("message",
+ (event) => {
+ dump("message: " + event.data + "\n");
+ if (event.data == "play-audible") {
+ playAndPostResult(false, event.source);
+ } else if (event.data == "click") {
+ synthesizeMouseAtCenter(document.body, {});
+ window.opener.postMessage("clicked", "*");
+ } else if (event.data == "navigate-back") {
+ history.back();
+ }
+ });
+
+ // Onload we fire a "ready" message back to the opener.
+ dump("Firing 'ready'\n");
+ window.opener.postMessage("ready", "*");
+
+
+ window.addEventListener("pagehide", (e)=>{
+ dump("pagehide\n");
+ });
+
+ window.addEventListener("pageshow", (e)=>{
+ dump("pageshow\n");
+ window.opener.postMessage("pageshow", "*");
+ });
+
+ </script>
+ </pre>
+ </body>
+</html>
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -436,16 +436,17 @@ support-files =
dirac.ogg^headers^
dynamic_redirect.sjs
dynamic_resource.sjs
eme.js
file_access_controls.html
file_autoplay_policy_unmute_pauses.html
file_autoplay_policy_activation_window.html
file_autoplay_policy_activation_frame.html
+ file_autoplay_policy_navigation_window.html
flac-s24.flac
flac-s24.flac^headers^
flac-noheader-s16.flac
flac-noheader-s16.flac^headers^
fragment_noplay.js
fragment_play.js
gizmo.mp4
gizmo.mp4^headers^
@@ -687,16 +688,18 @@ skip-if = true # bug 475110 - disabled s
[test_autoplay_contentEditable.html]
skip-if = android_version == '15' || android_version == '17' || android_version == '22' # android(bug 1232305, bug 1232318, bug 1372457)
[test_autoplay_policy.html]
skip-if = android_version == '23' # bug 1424903
[test_autoplay_policy_activation.html]
skip-if = android_version == '23' # bug 1424903
[test_autoplay_policy_unmute_pauses.html]
skip-if = android_version == '23' # bug 1424903
+[test_autoplay_policy_navigation.html]
+skip-if = android_version == '23' # bug 1424903
[test_buffered.html]
skip-if = android_version == '15' || android_version == '22' # bug 1308388, android(bug 1232305)
[test_bug448534.html]
[test_bug463162.xhtml]
[test_bug465498.html]
skip-if = toolkit == 'android' # android(bug 1232305)
[test_bug495145.html]
skip-if = (os == 'mac' && os_version == '10.6') || (toolkit == 'android') # bug 1311229, android(bug 1232305)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_autoplay_policy_navigation.html
@@ -0,0 +1,271 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Autoplay policy test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+ </head>
+ <body>
+ <pre id="test">
+ <script>
+
+ // Tests that videos can only play audibly in windows/frames
+ // which have been activated by same-origin user gesture.
+
+ gTestPrefs.push(["media.autoplay.enabled", false],
+ ["media.autoplay.enabled.user-gestures-needed", true]);
+
+ SpecialPowers.pushPrefEnv({'set': gTestPrefs}, () => {
+ runTest();
+ });
+
+ let test_cases = [
+ {
+ name: "Activated document navigating same origin should be able to play after navigation.",
+ actions: [Activate, NavigateSameOrigin],
+ should_play: true,
+ },
+ {
+ name: "Unactivated document navigating same origin should not be able to play after navigation.",
+ actions: [NavigateSameOrigin],
+ should_play: false,
+ },
+ {
+ name: "Activated document navigating cross origin should not be able to play after navigation.",
+ actions: [Activate, NavigateCrossOrigin],
+ should_play: false,
+ },
+ {
+ name: "Unactivated document navigating cross origin should not be able to play after navigation.",
+ actions: [NavigateCrossOrigin],
+ should_play: false,
+ },
+ {
+ name: "Unactivated document after reloading should still be blocked.",
+ actions: [Reload],
+ should_play: false,
+ },
+ {
+ name: "Activated document reloading should still be able to play after reload.",
+ actions: [Activate, Reload],
+ should_play: true,
+ },
+
+ // busted
+ {
+ name: "Navigating cross origin back to an activated document should still be able to play.",
+ actions: [Activate, NavigateCrossOrigin, NavigateBack],
+ should_play: true,
+ },
+
+ {
+ name: "Navigating same origin back to an activated document should still be able to play.",
+ actions: [Activate, NavigateSameOrigin, NavigateBack],
+ should_play: true,
+ },
+
+ {
+ name: "Navigating cross origin and activate should play.",
+ actions: [NavigateCrossOrigin, Activate],
+ should_play: true,
+ },
+
+ // busted
+ {
+ name: "Navigating cross origin forward to a previously activated document should still be able to play.",
+ actions: [NavigateCrossOrigin, Activate, NavigateBack, NavigateForward],
+ should_play: true,
+ },
+
+ ];
+
+ function NavigationHistory() {
+
+ this.stack = [];
+ this.index = -1;
+
+ this.push = function(url) {
+ this.stack.push(url);
+ this.index += 1;
+ }
+
+ this.forward = function() {
+ if (this.index + 1 < this.stack.length) {
+ this.index++;
+ }
+ return this.stack[this.index];
+ }
+
+ this.back = function() {
+ if (this.index - 1 >= 0) {
+ this.index--;
+ }
+ return this.stack[this.index];
+ }
+ }
+
+ const same_origin = "http://mochi.test:8888";
+ const cross_origin = "http://example.org";
+ const path = "/tests/dom/media/test/file_autoplay_policy_navigation_window.html";
+ const same_origin_url = same_origin + path;
+ const cross_origin_url = cross_origin + path;
+
+
+ function nextNamedMessage(name) {
+ return new Promise(function(resolve, reject) {
+ let f = (event) => {
+ if (event.data == name) {
+ window.removeEventListener("message", f, false);
+ resolve(event);
+ }
+ };
+ window.addEventListener("message", f, false);
+ });
+ }
+
+ function nextReadyMessage() {
+ return nextNamedMessage("ready");
+ }
+
+ function nextPageShowMessage() {
+ return nextNamedMessage("pageshow");
+ }
+
+ function nextClickedMessage() {
+ return nextNamedMessage("clicked");
+ }
+
+ function nextPlayedOrBlockedMessage() {
+ return new Promise(function(resolve, reject) {
+ let f = (event) => {
+ dump("nextPlayedMessage event.data.played=" + event.data.played + "\n");
+ if (event.data.played != undefined) {
+ window.removeEventListener("message", f, false);
+ resolve(event);
+ }
+ };
+ window.addEventListener("message", f, false);
+ });
+ }
+
+ async function runTest() {
+ try {
+ for (test_case of test_cases) {
+ log("case '" + test_case.name + "'");
+ // Run each test in a new window, to ensure its user gesture
+ // activation state isn't tainted by preceeding tests.
+ let navigationHistory = new NavigationHistory();
+ let counter = 1;
+
+ let child = window.open(same_origin_url, "", "width=500,height=500");
+ child.addEventListener('load', ()=>{log("child load");}, false);
+ navigationHistory.push(same_origin_url);
+ await nextReadyMessage();
+ log("Got window message");
+ // await once(child, "load");
+
+ // Perform test case's actions.
+ for (let action of test_case.actions) {
+ switch (action) {
+ case Activate: {
+ log("Activate")
+ child.postMessage("click", "*");
+ await nextClickedMessage();
+ log("Activate complete");
+ break;
+ }
+ case NavigateSameOrigin: {
+ log("NavigateSameOrigin")
+ let uri = same_origin_url + "?" + counter++;
+ SpecialPowers.wrap(child).location = uri;
+ navigationHistory.push(uri);
+ log("Awaiting loaded")
+ // await nextPageShowMessage();
+ await nextReadyMessage();
+ log("NavigateSameOrigin complete");
+ break;
+ }
+ case NavigateCrossOrigin: {
+ log("NavigateCrossOrigin")
+ let uri = cross_origin_url + "?" + counter++;
+ // let p = once(child, "load");
+ child.location = uri;
+ navigationHistory.push(uri);
+ // await once(child, "load");
+ log("Awaiting loaded")
+ // await p;
+ // await nextPageShowMessage();
+ await nextReadyMessage();
+ log("NavigateCrossOrigin complete");
+ break;
+ }
+ case NavigateBack: {
+ log("NavigateBack")
+ // log("going back");
+ // document.body.addEventListener("load", (e)=>{
+ // log("frame load");
+ // }, true);
+
+ child.postMessage("navigate-back", "*");
+
+ log("child.location=" + SpecialPowers.wrap(child).location);
+ // SpecialPowers.wrap(child).history.back();
+ let uri = navigationHistory.back();
+ log("Awaiting location change");
+ // await locationChangedTo(child, uri);
+ await nextPageShowMessage();
+ log("child.location=" + SpecialPowers.wrap(child).location);
+ // log("awaiting go back complete");
+ // await once(frame, "load");
+ // log("finished going back");
+ // await awhile();
+ log("NavigateBack complete");
+ break;
+ }
+ case NavigateForward: {
+ log("NavigateForward")
+ SpecialPowers.wrap(child).history.forward();
+ let uri = navigationHistory.forward();
+ log("Awaiting location change to " + uri);
+ // await nextWindowMessage();
+ await nextPageShowMessage();
+ // await locationChangedTo(child, uri);
+ log("NavigateForward complete");
+ break;
+ }
+ case Reload: {
+ log("Reload")
+ child.location.reload(true);
+ await nextReadyMessage();
+ // await once(child, "load");
+ // await nextPageShowMessage();
+ log("Reload complete");
+ break;
+ }
+ }
+ }
+
+ let played = nextPlayedOrBlockedMessage();
+ child.postMessage("play-audible", "*");
+ dump("Awaiting p\n");
+ played = await played;
+ dump("Awaited p complete\n");
+
+ SimpleTest.is(played.data.played, test_case.should_play, test_case.name);
+ child.close();
+ }
+ } catch (ex) {
+ log("exception " + ex.message);
+ }
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </pre>
+ </body>
+</html>