Bug 1534012 - Use a low priority ThrottledEventQueue for postMessages during page load r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D27386

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Sean Feng 2019-05-09 14:43:40 +00:00
parent c729145871
commit 87884c39a6
16 changed files with 180 additions and 5 deletions

View File

@ -298,6 +298,45 @@ bool TabGroup::IsBackground() const {
return mForegroundCount == 0;
}
nsresult TabGroup::QueuePostMessageEvent(
already_AddRefed<nsIRunnable>&& aRunnable) {
if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
if (!mPostMessageEventQueue) {
nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
mPostMessageEventQueue = ThrottledEventQueue::Create(
target, nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS);
nsresult rv = mPostMessageEventQueue->SetIsPaused(false);
MOZ_ALWAYS_SUCCEEDS(rv);
}
// Ensure the queue is enabled
if (mPostMessageEventQueue->IsPaused()) {
nsresult rv = mPostMessageEventQueue->SetIsPaused(false);
MOZ_ALWAYS_SUCCEEDS(rv);
}
if (mPostMessageEventQueue) {
mPostMessageEventQueue->Dispatch(std::move(aRunnable),
NS_DISPATCH_NORMAL);
return NS_OK;
}
}
return NS_ERROR_FAILURE;
}
void TabGroup::FlushPostMessageEvents() {
if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
if (mPostMessageEventQueue) {
nsresult rv = mPostMessageEventQueue->SetIsPaused(true);
MOZ_ALWAYS_SUCCEEDS(rv);
nsCOMPtr<nsIRunnable> event;
while ((event = mPostMessageEventQueue->GetEvent())) {
Dispatch(TaskCategory::Other, event.forget());
}
}
}
}
uint32_t TabGroup::Count(bool aActiveOnly) const {
if (!aActiveOnly) {
return mDocGroups.Count();

View File

@ -23,6 +23,7 @@ class nsPIDOMWindowOuter;
namespace mozilla {
class AbstractThread;
class ThrottledEventQueue;
namespace dom {
class Document;
class BrowserChild;
@ -143,6 +144,10 @@ class TabGroup final : public SchedulerGroup,
// can be throttled.
static bool HasOnlyThrottableTabs();
nsresult QueuePostMessageEvent(already_AddRefed<nsIRunnable>&& aRunnable);
void FlushPostMessageEvents();
private:
virtual AbstractThread* AbstractMainThreadForImpl(
TaskCategory aCategory) override;
@ -166,6 +171,10 @@ class TabGroup final : public SchedulerGroup,
uint32_t mForegroundCount;
static LinkedList<TabGroup>* sTabGroups;
// A queue to store postMessage events during page load, the queue will be
// flushed once the page is loaded
RefPtr<mozilla::ThrottledEventQueue> mPostMessageEventQueue;
};
} // namespace dom

View File

@ -2482,6 +2482,18 @@ void nsPIDOMWindowInner::SetAudioCapture(bool aCapture) {
}
void nsPIDOMWindowInner::SetActiveLoadingState(bool aIsLoading) {
if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
if (!aIsLoading) {
Document* doc = GetExtantDoc();
if (doc) {
if (doc->IsTopLevelContentDocument()) {
mozilla::dom::TabGroup* tabGroup = doc->GetDocGroup()->GetTabGroup();
tabGroup->FlushPostMessageEvents();
}
}
}
}
if (!nsGlobalWindowInner::Cast(this)->IsChromeWindow()) {
mTimeoutManager->SetLoading(aIsLoading);
}

View File

@ -6057,6 +6057,19 @@ void nsGlobalWindowOuter::PostMessageMozOuter(JSContext* aCx,
return;
}
if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
if (mDoc) {
Document* doc = mDoc->GetTopLevelContentDocument();
if (doc && doc->GetReadyStateEnum() < Document::READYSTATE_COMPLETE) {
// As long as the top level is loading, we can dispatch events to the
// queue because the queue will be flushed eventually
mozilla::dom::TabGroup* tabGroup = TabGroup();
aError = tabGroup->QueuePostMessageEvent(event.forget());
return;
}
}
}
aError = Dispatch(TaskCategory::Other, event.forget());
}

View File

@ -0,0 +1,39 @@
<script>
let runnable1 = {
run() {
window.opener.callOrder.push("Runnable1");
}
}
let runnable2 = {
run() {
window.opener.callOrder.push("Runnable2");
}
}
let runnable3 = {
run() {
window.opener.callOrder.push("Runnable3");
}
}
window.onmessage = function () {
window.opener.callOrder.push("PostMessage");
if (window.loadCount == 1) {
window.loadCount += 1;
location.reload();
} else {
window.opener.onDone();
}
};
// Pushed to normal queue
SpecialPowers.Services.tm.dispatchToMainThread(runnable1);
// Pushed to idle queue
window.postMessage("bar", "*");
// Pushed to normal queue
SpecialPowers.Services.tm.dispatchToMainThread(runnable2);
// Pushed to normal queue
SpecialPowers.Services.tm.dispatchToMainThread(runnable3);
</script>

View File

@ -3,6 +3,7 @@ support-files =
child_ip_address.html
file_crossdomainprops_inner.html
file_location.html
file_separate_post_message_queue.html
framed_location.html
idn_child.html
innerWidthHeight_script.html
@ -19,3 +20,4 @@ support-files =
[test_location_setters.html]
[test_setting_document.domain_idn.html]
[test_setting_document.domain_to_shortened_ipaddr.html]
[test_separate_post_message_queue.html]

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for using separate event queue for post messages during page load behaviors</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var tab;
var callOrder = [];
function onDone() {
tab.close();
isDeeply(callOrder, ["Runnable1", "Runnable2", "Runnable3", "PostMessage",
"Runnable1", "Runnable2", "Runnable3", "PostMessage"], "Runnables should be fired prior to PostMessage");
SimpleTest.finish();
}
SpecialPowers.pushPrefEnv({"set":[["dom.separate_event_queue_for_post_message.enabled", true]]}, function () {
tab = window.open('file_separate_post_message_queue.html');
tab.loadCount = 1;
});
</script>
</pre>
</body>
</html>

View File

@ -22,6 +22,7 @@
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/DocGroup.h"
#include "nsPresContext.h"
#include "nsIFrame.h"
#include "nsIWritablePropertyBag2.h"

View File

@ -726,6 +726,12 @@ VARCACHE_PREF(
bool, false
)
VARCACHE_PREF(
"dom.separate_event_queue_for_post_message.enabled",
dom_separate_event_queue_for_post_message_enabled,
bool, true
)
//---------------------------------------------------------------------------
// Extension prefs
//---------------------------------------------------------------------------

View File

@ -1,4 +1,5 @@
[sandboxed-iframe.html]
prefs: [dom.separate_event_queue_for_post_message.enabled:false] # Bug in WPT https://github.com/web-platform-tests/wpt/issues/16540
disabled:
if (os == "mac"): https://bugzilla.mozilla.org/show_bug.cgi?id=1433190
[Blob URL parses correctly]

View File

@ -128,7 +128,7 @@ function assert_iframe_with_csp(t, url, csp, shouldBlock, urlId, blockedURI) {
// Delay the check until after the postMessage has a chance to execute.
setTimeout(t.step_func_done(function () {
assert_equals(loaded[urlId], undefined);
}), 1);
}), 500);
assert_throws("SecurityError", () => {
var x = i.contentWindow.location.href;
});

View File

@ -30,6 +30,7 @@
#include "nsIDOMWindow.h"
#include "nsPIDOMWindow.h"
#include "nsGlobalWindow.h"
#include "nsIStringBundle.h"
#include "nsIScriptSecurityManager.h"

View File

@ -89,6 +89,10 @@ bool ThreadEventQueue<InnerQueueT>::PutEventInternal(
aPriority = EventQueuePriority::Input;
} else if (prio == nsIRunnablePriority::PRIORITY_MEDIUMHIGH) {
aPriority = EventQueuePriority::MediumHigh;
} else if (prio == nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS) {
aPriority = EventQueuePriority::DeferredTimers;
} else if (prio == nsIRunnablePriority::PRIORITY_IDLE) {
aPriority = EventQueuePriority::Idle;
}
}
}

View File

@ -173,11 +173,14 @@ class ThrottledEventQueue::Inner final : public nsISupports {
{
MutexAutoLock lock(mMutex);
// We only check the name of an executor runnable when we know there is
// something in the queue, so this should never fail.
event = mEventQueue.PeekEvent(lock);
MOZ_ALWAYS_TRUE(event);
// It is possible that mEventQueue wasn't empty when the executor
// was added to the queue, but someone processed events from mEventQueue
// before the executor, this is why mEventQueue is empty here
if (!event) {
aName.AssignLiteral("no runnables left in the ThrottledEventQueue");
return NS_OK;
}
}
if (nsCOMPtr<nsINamed> named = do_QueryInterface(event)) {
@ -265,6 +268,11 @@ class ThrottledEventQueue::Inner final : public nsISupports {
return mEventQueue.Count(lock);
}
already_AddRefed<nsIRunnable> GetEvent() {
MutexAutoLock lock(mMutex);
return mEventQueue.GetEvent(nullptr, lock);
}
void AwaitIdle() const {
// Any thread, except the main thread or our base target. Blocking the
// main thread is forbidden. Blocking the base target is guaranteed to
@ -372,6 +380,11 @@ bool ThrottledEventQueue::IsEmpty() const { return mInner->IsEmpty(); }
uint32_t ThrottledEventQueue::Length() const { return mInner->Length(); }
// Get the next runnable from the queue
already_AddRefed<nsIRunnable> ThrottledEventQueue::GetEvent() {
return mInner->GetEvent();
}
void ThrottledEventQueue::AwaitIdle() const { return mInner->AwaitIdle(); }
nsresult ThrottledEventQueue::SetIsPaused(bool aIsPaused) {

View File

@ -77,6 +77,8 @@ class ThrottledEventQueue final : public nsISerialEventTarget {
// Determine how many events are pending in the queue.
uint32_t Length() const;
already_AddRefed<nsIRunnable> GetEvent();
// Block the current thread until the queue is empty. This may not be called
// on the main thread or the base target. The ThrottledEventQueue must not be
// paused.

View File

@ -25,5 +25,7 @@ interface nsIRunnablePriority : nsISupports
const unsigned short PRIORITY_INPUT = 1;
const unsigned short PRIORITY_HIGH = 2;
const unsigned short PRIORITY_MEDIUMHIGH = 3;
const unsigned short PRIORITY_IDLE = 4;
const unsigned short PRIORITY_DEFERRED_TIMERS = 5;
readonly attribute unsigned long priority;
};