// Copyright (c) 2010 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.
package org.chromium.sdk.tests.system;
import org.chromium.sdk.DebugContext;
import org.chromium.sdk.DebugEventListener;
import org.chromium.sdk.Script;
import org.chromium.sdk.TabDebugEventListener;
/**
* Listens to various asynchronous debug events and let main thread to approve them and
* synchronize with them. Unless {@link #setDefaultReceiver} is called,
* listener always blocks waiting for the main thread to synchronize by {@link #expectEvent}.
*/
class StateManager {
private final StateManager.Monitor monitor = new Monitor();
TabDebugEventListener getTabListener() {
return tabDebugEventListener;
}
/**
* Called from the main thread. Waits for a particular event to happen and returns its data.
* The event mask is specified by receiver parameter. Receiver has several visit* methods
* with the similar semantics: it returns null if event should be skipped, non-null if
* event is accepted and the data should be passed to the main thread function or may throw
* an exception if test assumptions are broken.
*/
<RES> RES expectEvent(EventVisitor<RES> receiver) throws SmokeException {
RES res;
do {
res = expectOneEvent(receiver);
} while (res == null);
return res;
}
private <RES> RES expectOneEvent(EventVisitor<RES> receiver) throws SmokeException {
synchronized (monitor) {
if (monitor.pendingException != null) {
throw new SmokeException(monitor.pendingException);
}
monitor.defaultReceiver = null;
if (monitor.pendingEvent == null) {
try {
monitor.wait(TIMEOUT_MS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (monitor.pendingEvent == null) {
throw new SmokeException("Timeout waiting for event with " + receiver);
}
}
RES res = monitor.pendingEvent.accept(receiver);
monitor.pendingEvent = null;
monitor.notify();
return res;
}
}
/**
* Called from the main thread. Allows non-blocking processing of events in listener.
* The default receiver accepts all events but its return value is ignored unless it threw an
* exception.
*/
void setDefaultReceiver(EventVisitor<?> receiver) throws SmokeException {
synchronized (monitor) {
if (monitor.pendingException != null) {
throw new SmokeException(monitor.pendingException);
}
monitor.defaultReceiver = receiver;
if (receiver != null && monitor.pendingEvent != null) {
monitor.pendingEvent.accept(receiver);
monitor.pendingEvent = null;
monitor.notify();
}
}
}
/**
* Called from Dispatch thread by listener implementation.
*/
private void processEvent(Event event) {
synchronized (monitor) {
if (monitor.defaultReceiver != null) {
try {
event.accept(monitor.defaultReceiver);
} catch (SmokeException e) {
monitor.pendingException = e;
}
return;
}
monitor.pendingEvent = event;
monitor.notify();
try {
monitor.wait(TIMEOUT_MS);
if (monitor.pendingEvent != null) {
throw new RuntimeException("Timeout waiting for event processing");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private final TabDebugEventListener tabDebugEventListener = new TabDebugEventListener() {
public void closed() {
processEvent(new Event() {
public <RES> RES accept(EventVisitor<RES> visitor) throws SmokeException {
return visitor.visitClosed();
}
});
}
public void navigated(final String newUrl) {
processEvent(new Event() {
public <RES> RES accept(EventVisitor<RES> visitor) throws SmokeException {
return visitor.visitNavigated(newUrl);
}
});
}
public DebugEventListener getDebugEventListener() {
return debugEventListener;
}
};
private final DebugEventListener debugEventListener = new DebugEventListener() {
public void disconnected() {
processEvent(new Event() {
public <RES> RES accept(EventVisitor<RES> visitor) throws SmokeException {
return visitor.visitDisconnected();
}
});
}
public void resumed() {
processEvent(new Event() {
public <RES> RES accept(EventVisitor<RES> visitor) throws SmokeException {
return visitor.visitResumed();
}
});
}
public void scriptLoaded(final Script newScript) {
processEvent(new Event() {
public <RES> RES accept(EventVisitor<RES> visitor) throws SmokeException {
return visitor.visitScriptLoaded(newScript);
}
});
}
public void scriptCollected(final Script script) {
processEvent(new Event() {
public <RES> RES accept(EventVisitor<RES> visitor) throws SmokeException {
return visitor.visitScriptCollected(script);
}
});
}
public void suspended(final DebugContext context) {
processEvent(new Event() {
public <RES> RES accept(EventVisitor<RES> visitor) throws SmokeException {
return visitor.visitSuspended(context);
}
});
}
public VmStatusListener getVmStatusListener() {
return null;
}
public void scriptContentChanged(Script newScript) {
}
};
/**
* An internal implementation of event from {@link DebugEventListener}
* and {@link TabDebugEventListener}.
*/
private interface Event {
<RES> RES accept(EventVisitor<RES> visitor) throws SmokeException;
}
private static class Monitor {
EventVisitor<?> defaultReceiver = null;
Event pendingEvent = null;
SmokeException pendingException = null;
}
private static final int TIMEOUT_MS = 10000;
}