Commit adcc96ea authored by Vladimir Levin's avatar Vladimir Levin Committed by Commit Bot

[DL] Add suspend/resume functionality to display locking context.

This patch adds the ability to suspend and resume the context, which
when suspended prevents future tasks from running.

As of right now, the work isn't prevented from finishing since we don't
have co-operative work implemented and nothing is preventing the rest
of the system from processing the changes.

Added tests, a few of which fail due to above reason.

R=chrishtr@chromium.org

Bug: 882633
Change-Id: I256be53b3cd0071405c9ac783fe78fcd36dacb9c
Reviewed-on: https://chromium-review.googlesource.com/c/1289432Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Commit-Queue: vmpstr <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601219}
parent 7c413bef
......@@ -168,6 +168,9 @@ crbug.com/857490 virtual/paint-touchaction-rects/fast/events/touch/gesture/touch
# Display locking, currently only available via a virtual test.
crbug.com/882663 display-lock [ Skip ]
crbug.com/882663 virtual/display-lock/display-lock/context-suspend-empty.html [ Failure ]
crbug.com/882663 virtual/display-lock/display-lock/context-suspend-no-schedules.html [ Failure ]
crbug.com/882663 virtual/display-lock/display-lock/context-suspend-resume-empty.html [ Failure ]
# Sheriff 2018/05/25
crbug.com/846747 http/tests/navigation/navigation-interrupted-by-fragment.html [ Pass Timeout ]
......
......@@ -25,7 +25,8 @@ function throwingFunction(context) {
}
function finishTest(status_string) {
document.getElementById("log").innerHTML = status_string;
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
......
......@@ -32,7 +32,8 @@ function modifyDom(context) {
}
function finishTest(status_string) {
document.getElementById("log").innerHTML = status_string;
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
......
<!doctype HTML>
<style>
#parent {
width: 100px;
height: 100px;
contain: content;
}
</style>
<div id="log">PASS</div>
<div id="parent"></div>
<!doctype HTML>
<!--
Runs an acquireDisplayLock, which suspends the context.
The associated promise should never resolve.
-->
<style>
#parent {
width: 100px;
height: 100px;
contain: content;
}
</style>
<div id="log"></div>
<div id="parent"></div>
<script>
if (window.testRunner)
window.testRunner.waitUntilDone();
function finishTest(status_string) {
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
function acquire() {
document.getElementById("parent").acquireDisplayLock(
(context) => {
context.suspend();
}).then(
() => { finishTest("FAIL"); },
() => { finishTest("FAIL"); });
setTimeout(() => { finishTest("PASS"); }, 50);
}
window.onload = acquire;
</script>
<!doctype HTML>
<style>
#parent {
contain: paint;
width: 150px;
height: 150px;
background: lightblue;
}
</style>
<div id="log">PASS</div>
<div id="parent"></div>
<!doctype HTML>
<style>
#parent {
contain: paint;
width: 150px;
height: 150px;
background: lightblue;
}
</style>
<div id="log">PASS</div>
<div id="parent"></div>
<!doctype HTML>
<!--
Runs an acquireDisplayLock, which changes DOM and suspends the context.
DOM changes should never be visible.
-->
<style>
#parent {
contain: paint;
width: 150px;
height: 150px;
background: lightblue;
}
#child {
width: 50px;
height: 50px;
background: red;
}
</style>
<div id="log"></div>
<div id="parent"></div>
<script>
if (window.testRunner)
window.testRunner.waitUntilDone();
function modifyDom(context) {
let child = document.createElement("div");
child.id = "child";
document.getElementById("parent").appendChild(child);
}
function finishTest(status_string) {
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
function acquire() {
document.getElementById("parent").acquireDisplayLock(
(context) => {
modifyDom();
context.suspend();
}).then(
() => { finishTest("FAIL"); },
() => { finishTest("FAIL"); });
setTimeout(() => { finishTest("PASS"); }, 50);
}
window.onload = acquire;
</script>
<!doctype HTML>
<style>
#parent {
width: 100px;
height: 100px;
contain: content;
}
</style>
<div id="log">PASS</div>
<div id="parent"></div>
<!doctype HTML>
<!--
Runs an acquireDisplayLock, which suspends the context.
The context is then resumed which is when the promise resolves.
-->
<style>
#parent {
width: 100px;
height: 100px;
contain: content;
}
</style>
<div id="log"></div>
<div id="parent"></div>
<script>
if (window.testRunner)
window.testRunner.waitUntilDone();
function finishTest(status_string) {
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
function acquire() {
let current_status = "FAIL";
let resume_handle = undefined;
document.getElementById("parent").acquireDisplayLock(
(context) => {
resume_handle = context.suspend();
}).then(
() => { finishTest(current_status); },
() => { finishTest("FAIL - rejected"); });
setTimeout(() => {
current_status = "PASS";
context.resume();
}, 50);
}
window.onload = acquire;
</script>
<!doctype HTML>
<style>
#parent {
contain: paint;
width: 150px;
height: 150px;
background: lightblue;
}
#child {
width: 50px;
height: 50px;
background: lightgreen;
}
</style>
<div id="log">PASS</div>
<div id="parent"><div id="child"></div></div>
<!doctype HTML>
<!--
Runs an acquireDisplayLock, which suspends the context and schedules
a continuation; later resumes the context.
-->
<style>
#parent {
contain: paint;
width: 150px;
height: 150px;
background: lightblue;
}
#pass_child {
width: 50px;
height: 50px;
background: lightgreen;
}
#fail_child {
width: 50px;
height: 50px;
background: red;
}
</style>
<div id="log"></div>
<div id="parent"></div>
<script>
if (window.testRunner)
window.testRunner.waitUntilDone();
var child_class = "fail_child";
function modifyDom(context) {
let child = document.createElement("div");
child.id = child_class;
document.getElementById("parent").appendChild(child);
}
function finishTest(status_string) {
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
function acquire() {
let current_status = "FAIL";
let resume_handle = undefined;
document.getElementById("parent").acquireDisplayLock(
(context) => {
context.schedule(modifyDom);
resume_handle = context.suspend();
}).then(
() => { finishTest(current_status); },
() => { finishTest("FAIL - rejected"); });
setTimeout(() => {
current_status = "PASS";
child_class = "pass_child";
resume_handle.resume();
}, 50);
}
window.onload = acquire;
</script>
<!doctype HTML>
<!--
Runs an acquireDisplayLock, which suspends the context and schedules
a continuation, which should never run.
-->
<style>
#parent {
contain: paint;
width: 150px;
height: 150px;
background: lightblue;
}
#child {
width: 50px;
height: 50px;
background: red;
}
</style>
<div id="log"></div>
<div id="parent"></div>
<script>
if (window.testRunner)
window.testRunner.waitUntilDone();
function modifyDom(context) {
let child = document.createElement("div");
child.id = "child";
document.getElementById("parent").appendChild(child);
}
function finishTest(status_string) {
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
function acquire() {
document.getElementById("parent").acquireDisplayLock(
(context) => {
context.schedule(modifyDom);
context.suspend();
}).then(
() => { finishTest("FAIL"); },
() => { finishTest("FAIL"); });
setTimeout(() => { finishTest("PASS"); }, 50);
}
window.onload = acquire;
</script>
<!doctype HTML>
<style>
#parent {
contain: paint;
width: 150px;
height: 150px;
background: lightblue;
}
#child {
width: 50px;
height: 50px;
background: lightgreen;
}
</style>
<div id="log">PASS</div>
<div id="parent"><div id="child"></div></div>
<!doctype HTML>
<!--
Runs an acquireDisplayLock, which suspends the context twice and schedules
a continuation; later resumes the context (twice).
-->
<style>
#parent {
contain: paint;
width: 150px;
height: 150px;
background: lightblue;
}
#pass_child {
width: 50px;
height: 50px;
background: lightgreen;
}
#fail_child {
width: 50px;
height: 50px;
background: red;
}
</style>
<div id="log"></div>
<div id="parent"></div>
<script>
if (window.testRunner)
window.testRunner.waitUntilDone();
var child_class = "fail_child";
function modifyDom(context) {
let child = document.createElement("div");
child.id = child_class;
document.getElementById("parent").appendChild(child);
}
function finishTest(status_string) {
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
function acquire() {
let current_status = "FAIL";
let resume_handle1 = undefined;
let resume_handle2 = undefined;
document.getElementById("parent").acquireDisplayLock(
(context) => {
context.schedule(modifyDom);
resume_handle1 = context.suspend();
resume_handle2 = context.suspend();
}).then(
() => { finishTest(current_status); },
() => { finishTest("FAIL - rejected"); });
setTimeout(() => {
resume_handle1.resume();
setTimeout(() => {
current_status = "PASS";
child_class = "pass_child";
resume_handle2.resume();
}, 50);
}, 50);
}
window.onload = acquire;
</script>
......@@ -43,7 +43,8 @@ function addChild(id, context) {
}
function finishTest(status_string) {
document.getElementById("log").innerHTML = status_string;
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
......
......@@ -57,7 +57,8 @@ function addChild1(context) {
}
function finishTest(status_string) {
document.getElementById("log").innerHTML = status_string;
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
......
......@@ -47,7 +47,8 @@ function addChild1(context) {
}
function finishTest(status_string) {
document.getElementById("log").innerHTML = status_string;
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
......
......@@ -47,7 +47,8 @@ function addChild1(context) {
}
function finishTest(status_string) {
document.getElementById("log").innerHTML = status_string;
if (document.getElementById("log").innerHTML === "")
document.getElementById("log").innerHTML = status_string;
if (window.testRunner)
window.testRunner.notifyDone();
}
......
......@@ -101,6 +101,7 @@ core_idl_files =
"css/cssom/style_property_map.idl",
"css/cssom/style_property_map_read_only.idl",
"display_lock/display_lock_context.idl",
"display_lock/display_lock_suspended_handle.idl",
"dom/abort_controller.idl",
"dom/abort_signal.idl",
"dom/attr.idl",
......
......@@ -8,6 +8,8 @@ blink_core_sources("display_lock") {
sources = [
"display_lock_context.cc",
"display_lock_context.h",
"display_lock_suspended_handle.cc",
"display_lock_suspended_handle.h",
]
public_deps = [
......
......@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_display_lock_callback.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_suspended_handle.h"
#include "third_party/blink/renderer/platform/bindings/microtask.h"
namespace blink {
......@@ -32,11 +33,16 @@ void DisplayLockContext::ContextDestroyed(ExecutionContext*) {
}
bool DisplayLockContext::HasPendingActivity() const {
// If we don't have a task scheduled, then we should already be resolved.
// If we don't have a task scheduled, then we should be suspended or already
// be resolved.
// TODO(vmpstr): This should also be kept alive if we're doing co-operative
// work.
DCHECK(process_queue_task_scheduled_ || !resolver_);
return process_queue_task_scheduled_;
DCHECK(suspended_count_ || process_queue_task_scheduled_ || !resolver_);
// Note that if we're suspended and we have no resolver, it means that we've
// resolved either due to execution context being destroyed, or the suspended
// handle was disposed without resuming. In either case, we shouldn't keep the
// context alive.
return process_queue_task_scheduled_ || (suspended_count_ && resolver_);
}
void DisplayLockContext::ScheduleTask(V8DisplayLockCallback* callback,
......@@ -46,32 +52,32 @@ void DisplayLockContext::ScheduleTask(V8DisplayLockCallback* callback,
DCHECK(script_state);
resolver_ = ScriptPromiseResolver::Create(script_state);
}
if (!process_queue_task_scheduled_) {
DCHECK(GetExecutionContext());
DCHECK(GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI));
GetExecutionContext()
->GetTaskRunner(TaskType::kMiscPlatformAPI)
->PostTask(FROM_HERE, WTF::Bind(&DisplayLockContext::ProcessQueue,
WrapWeakPersistent(this)));
process_queue_task_scheduled_ = true;
}
ScheduleTaskIfNeeded();
}
void DisplayLockContext::schedule(V8DisplayLockCallback* callback) {
ScheduleTask(callback);
}
DisplayLockSuspendedHandle* DisplayLockContext::suspend() {
++suspended_count_;
return new DisplayLockSuspendedHandle(this);
}
void DisplayLockContext::ProcessQueue() {
// It's important to clear this before running the tasks, since the tasks can
// call ScheduleTask() which will re-schedule a PostTask() for us to continue
// the work.
process_queue_task_scheduled_ = false;
// If we've become suspended, then abort. We'll schedule a new task when we
// resume.
if (suspended_count_)
return;
// We might have cleaned up already due to exeuction context being destroyed.
if (callbacks_.IsEmpty()) {
DCHECK(!resolver_);
if (!resolver_)
return;
}
// Get a local copy of all the tasks we will run.
// TODO(vmpstr): This should possibly be subject to a budget instead.
......@@ -114,4 +120,37 @@ void DisplayLockContext::RejectAndCleanUp() {
callbacks_.clear();
}
void DisplayLockContext::Resume() {
DCHECK_GT(suspended_count_, 0u);
--suspended_count_;
ScheduleTaskIfNeeded();
}
void DisplayLockContext::NotifyWillNotResume() {
DCHECK_GT(suspended_count_, 0u);
// The promise will never reject or resolve since we're now indefinitely
// suspended.
// TODO(vmpstr): We should probably issue a console warning.
resolver_->Detach();
resolver_ = nullptr;
}
void DisplayLockContext::ScheduleTaskIfNeeded() {
// We don't need a task in the following cases:
// - One is already scheduled
// - We're suspended, meaning we will schedule one when we resume.
// - We've already resolved this context (e.g. due to execution context being
// destroyed).
if (process_queue_task_scheduled_ || suspended_count_ || !resolver_)
return;
DCHECK(GetExecutionContext());
DCHECK(GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI));
GetExecutionContext()
->GetTaskRunner(TaskType::kMiscPlatformAPI)
->PostTask(FROM_HERE, WTF::Bind(&DisplayLockContext::ProcessQueue,
WrapWeakPersistent(this)));
process_queue_task_scheduled_ = true;
}
} // namespace blink
......@@ -13,6 +13,7 @@
namespace blink {
class V8DisplayLockCallback;
class DisplayLockSuspendedHandle;
class CORE_EXPORT DisplayLockContext final
: public ScriptWrappable,
public ActiveScriptWrappable<DisplayLockContext>,
......@@ -24,7 +25,7 @@ class CORE_EXPORT DisplayLockContext final
DisplayLockContext(ExecutionContext*);
~DisplayLockContext() override;
// GC Functions.
// GC functions.
void Trace(blink::Visitor*) override;
void Dispose();
......@@ -53,17 +54,34 @@ class CORE_EXPORT DisplayLockContext final
// JavaScript interface implementation.
void schedule(V8DisplayLockCallback*);
DisplayLockSuspendedHandle* suspend();
private:
friend class DisplayLockSuspendedHandle;
// Processes the current queue of callbacks.
void ProcessQueue();
// Rejects the associated promise if one exists, and clears the current queue.
// This effectively makes the context finalized.
void RejectAndCleanUp();
// Called by the suspended handle in order to resume context operations.
void Resume();
// Called by the suspended handle informing us that it was disposed without
// resuming, meaning it will never resume.
void NotifyWillNotResume();
// Schedule a task if one is required. Specifically, this would schedule a
// task if one was not already scheduled and if we need to either process
// callbacks or to resolve the associated promise.
void ScheduleTaskIfNeeded();
HeapVector<Member<V8DisplayLockCallback>> callbacks_;
Member<ScriptPromiseResolver> resolver_;
bool process_queue_task_scheduled_ = false;
unsigned suspended_count_ = 0;
};
} // namespace blink
......
......@@ -2,8 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[RuntimeEnabled=DisplayLocking] callback DisplayLockCallback = void(DisplayLockContext context);
[RuntimeEnabled=DisplayLocking, ActiveScriptWrappable] interface DisplayLockContext {
[RuntimeEnabled=DisplayLocking]
callback DisplayLockCallback = void(DisplayLockContext context);
[RuntimeEnabled=DisplayLocking, ActiveScriptWrappable]
interface DisplayLockContext {
// Schedule continuation work for this context. Multiple calls are allowed and
// will run in the order scheduled.
void schedule(DisplayLockCallback callback);
};
// Suspend the context, preventing the lock from being released. Multiple
// calls to suspend are allowed: in order to resume the context, resume() must
// be called on each of the returned DisplayLockSuspendedHandles.
DisplayLockSuspendedHandle suspend();
};
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/display_lock/display_lock_suspended_handle.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
namespace blink {
DisplayLockSuspendedHandle::DisplayLockSuspendedHandle(
DisplayLockContext* context)
: context_(context) {}
DisplayLockSuspendedHandle::~DisplayLockSuspendedHandle() {}
void DisplayLockSuspendedHandle::Trace(blink::Visitor* visitor) {
ScriptWrappable::Trace(visitor);
visitor->Trace(context_);
}
void DisplayLockSuspendedHandle::Dispose() {
// If we're disposing the handle and we still have a valid reference to the
// context, it means that this handle was never resumed. In turn, this means
// that we will never resume the context. We should inform the context of
// this.
// TODO(vmpstr): It is possible that we want to resume the context on dispose,
// making gc observable from script. If that's the case, this should be
// changed to instead resume the context.
if (context_)
context_->NotifyWillNotResume();
context_ = nullptr;
}
void DisplayLockSuspendedHandle::resume() {
if (context_)
context_->Resume();
context_ = nullptr;
}
} // namespace blink
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_SUSPENDED_HANDLE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_SUSPENDED_HANDLE_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
namespace blink {
class DisplayLockContext;
class CORE_EXPORT DisplayLockSuspendedHandle final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
DisplayLockSuspendedHandle(DisplayLockContext* context);
~DisplayLockSuspendedHandle() override;
// GC functions.
void Trace(blink::Visitor*) override;
void Dispose();
// JavaScript interface implementation.
void resume();
private:
WeakMember<DisplayLockContext> context_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_SUSPENDED_HANDLE_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[RuntimeEnabled=DisplayLocking] interface DisplayLockSuspendedHandle {
void resume();
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment