blob: f3af96fd47027aa5ed0add8004f574d73ed2bb82 [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COBALT_WEBDRIVER_UTIL_CALL_ON_MESSAGE_LOOP_H_
#define COBALT_WEBDRIVER_UTIL_CALL_ON_MESSAGE_LOOP_H_
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/message_loop_proxy.h"
#include "base/synchronization/waitable_event.h"
namespace cobalt {
namespace webdriver {
namespace util {
namespace internal {
template <class ReturnValue>
class CallOnMessageLoopHelper {
public:
typedef base::Callback<ReturnValue(void)> CallbackType;
CallOnMessageLoopHelper(
const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
const CallbackType& callback)
: completed_event_(true, false), success_(false) {
DCHECK_NE(base::MessageLoopProxy::current(), message_loop_proxy);
scoped_ptr<DeletionSignaler> dt(new DeletionSignaler(&completed_event_));
// Note that while MessageLoopProxy::PostTask returns false
// after the message loop has gone away, it still can return true
// even if tasks are posted during shutdown and will never be run,
// so we ignore this return value.
message_loop_proxy->PostTask(
FROM_HERE,
base::Bind(&CallOnMessageLoopHelper::Call, base::Unretained(this),
callback, base::Passed(&dt)));
}
// Waits for result, filling |out| with the return value if successful.
// Returns true on success or false if the message loop went away
// before the task was executed.
bool WaitForResult(ReturnValue* out) {
completed_event_.Wait();
if (success_) {
*out = result_;
}
return success_;
}
~CallOnMessageLoopHelper() {
// We must ensure that we've waited for completion otherwise
// DeletionSignaler will have a use-after-free.
completed_event_.Wait();
}
private:
// DeletionSignaler signals an event when the destructor is called.
// This allows us to use the base::Passed mechanism to signal our
// completed_event_ both when Call() has been invoked and when
// the message loop has been deleted.
class DeletionSignaler {
public:
base::WaitableEvent* to_signal_;
explicit DeletionSignaler(base::WaitableEvent* to_signal)
: to_signal_(to_signal) {}
~DeletionSignaler() { to_signal_->Signal(); }
private:
DISALLOW_COPY_AND_ASSIGN(DeletionSignaler);
};
void Call(const CallbackType& callback,
scoped_ptr<DeletionSignaler> dt ALLOW_UNUSED) {
result_ = callback.Run();
success_ = true;
}
base::WaitableEvent completed_event_;
ReturnValue result_;
bool success_;
};
// Used with CallWeakOnMessageLoop.
template <typename T, typename ReturnValue>
base::optional<ReturnValue> RunWeak(const base::Callback<T*()>& get_weak,
const base::Callback<ReturnValue(T*)>& cb) {
T* weak_object = get_weak.Run();
if (weak_object) {
return cb.Run(weak_object);
} else {
return base::nullopt;
}
}
} // namespace internal
// Call the base::Callback on the specified message loop and wait for it to
// complete. Returns true if successful, or false if the underlying
// PostTask failed. This can happen if a WebModule shuts down due to a page
// navigation.
//
// On success, |out| is set to the result.
template <class ReturnValue>
bool TryCallOnMessageLoop(
const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
const base::Callback<ReturnValue(void)>& callback, ReturnValue* out) {
internal::CallOnMessageLoopHelper<ReturnValue> call_helper(message_loop_proxy,
callback);
return call_helper.WaitForResult(out);
}
// Tries to call |callback| on messageloop |message_loop_proxy|,
// retrying a few times if the WebModule thread appears to have gone
// away (perhaps because of navigation). If failure persists,
// returns a CommandResult of |window_disappeared_code|.
template <typename ReturnValue>
util::CommandResult<ReturnValue> CallOnMessageLoopWithRetry(
const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
const base::Callback<util::CommandResult<ReturnValue>(void)>& callback,
protocol::Response::StatusCode window_disappeared_code) {
util::CommandResult<ReturnValue> result;
const int kRetryAttempts = 5;
bool success = false;
for (int i = 0; !success && i < kRetryAttempts; i++) {
success = TryCallOnMessageLoop(message_loop_proxy, callback, &result);
}
if (!success) {
result = util::CommandResult<ReturnValue>(window_disappeared_code);
}
return result;
}
// Supports a common pattern in the various XXXDriver classes.
// On the provided message loop, calls RunWeak which will run the callback |cb|
// if |get_weak| returns a non-NULL pointer. RunWeak will return the result of
// the callback, or base::nullopt if |get_weak| returned NULL and the callback
// wasn't run.
// If the return value from RunWeak is valid, return a CommandResult that wraps
// the value. Otherwise return |no_such_object_code| to indicate the correct
// error.
template <typename T, typename ReturnValue>
util::CommandResult<ReturnValue> CallWeakOnMessageLoopAndReturnResult(
const scoped_refptr<base::MessageLoopProxy>& message_loop,
const base::Callback<T*()>& get_weak,
const base::Callback<ReturnValue(T*)>& cb,
protocol::Response::StatusCode no_such_object_code) {
typedef util::CommandResult<ReturnValue> CommandResult;
typedef base::optional<ReturnValue> InternalResult;
InternalResult result;
bool success = util::TryCallOnMessageLoop(
message_loop,
base::Bind(&internal::RunWeak<T, ReturnValue>, get_weak, cb), &result);
if (success && result) {
return CommandResult(result.value());
} else {
return CommandResult(no_such_object_code);
}
}
} // namespace util
} // namespace webdriver
} // namespace cobalt
#endif // COBALT_WEBDRIVER_UTIL_CALL_ON_MESSAGE_LOOP_H_