blob: a7a98c690199d5b8b4d66ab8c7c41cc0eccd5655 [file] [log] [blame]
// Copyright (c) 2011 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 "ppapi/proxy/ppb_file_chooser_proxy.h"
#include <queue>
#include "ppapi/c/dev/ppb_file_chooser_dev.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/private/ppb_proxy_private.h"
#include "ppapi/proxy/host_dispatcher.h"
#include "ppapi/proxy/plugin_dispatcher.h"
#include "ppapi/proxy/plugin_resource.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/ppb_file_ref_proxy.h"
#include "ppapi/proxy/serialized_var.h"
namespace pp {
namespace proxy {
class FileChooser : public PluginResource {
public:
FileChooser(const HostResource& resource);
virtual ~FileChooser();
virtual FileChooser* AsFileChooser();
PP_CompletionCallback current_show_callback_;
// All files returned by the current show callback that haven't yet been
// given to the plugin. The plugin will repeatedly call us to get the next
// file, and we'll vend those out of this queue, removing them when ownership
// has transferred to the plugin.
std::queue<PP_Resource> file_queue_;
private:
DISALLOW_COPY_AND_ASSIGN(FileChooser);
};
FileChooser::FileChooser(const HostResource& resource)
: PluginResource(resource),
current_show_callback_(PP_MakeCompletionCallback(NULL, NULL)) {
}
FileChooser::~FileChooser() {
// Always need to fire completion callbacks to prevent a leak in the plugin.
if (current_show_callback_.func) {
// TODO(brettw) the callbacks at this level should be refactored with a
// more automatic tracking system like we have in the renderer.
MessageLoop::current()->PostTask(FROM_HERE, NewRunnableFunction(
current_show_callback_.func, current_show_callback_.user_data,
static_cast<int32_t>(PP_ERROR_ABORTED)));
}
// Any existing files we haven't transferred ownership to the plugin need
// to be freed.
PluginResourceTracker* tracker = PluginResourceTracker::GetInstance();
while (!file_queue_.empty()) {
tracker->ReleaseResource(file_queue_.front());
file_queue_.pop();
}
}
FileChooser* FileChooser::AsFileChooser() {
return this;
}
namespace {
PP_Resource Create(PP_Instance instance,
const PP_FileChooserOptions_Dev* options) {
Dispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
if (!dispatcher)
return 0;
HostResource result;
dispatcher->Send(new PpapiHostMsg_PPBFileChooser_Create(
INTERFACE_ID_PPB_FILE_CHOOSER, instance,
options->mode,
options->accept_mime_types ? options->accept_mime_types : std::string(),
&result));
if (result.is_null())
return 0;
linked_ptr<FileChooser> object(new FileChooser(result));
return PluginResourceTracker::GetInstance()->AddResource(object);
}
PP_Bool IsFileChooser(PP_Resource resource) {
FileChooser* object = PluginResource::GetAs<FileChooser>(resource);
return BoolToPPBool(!!object);
}
int32_t Show(PP_Resource chooser, struct PP_CompletionCallback callback) {
FileChooser* object = PluginResource::GetAs<FileChooser>(chooser);
if (!object)
return PP_ERROR_BADRESOURCE;
Dispatcher* dispatcher = PluginDispatcher::GetForInstance(object->instance());
if (!dispatcher)
return PP_ERROR_BADARGUMENT;
if (object->current_show_callback_.func)
return PP_ERROR_INPROGRESS; // Can't show more than once.
object->current_show_callback_ = callback;
dispatcher->Send(new PpapiHostMsg_PPBFileChooser_Show(
INTERFACE_ID_PPB_FILE_CHOOSER,
object->host_resource()));
return PP_OK_COMPLETIONPENDING;
}
PP_Resource GetNextChosenFile(PP_Resource chooser) {
FileChooser* object = PluginResource::GetAs<FileChooser>(chooser);
if (!object || object->file_queue_.empty())
return 0;
// Return the next resource in the queue. These resource have already been
// addrefed (they're currently owned by the FileChooser) and returning them
// transfers ownership of that reference to the plugin.
PP_Resource next = object->file_queue_.front();
object->file_queue_.pop();
return next;
}
const PPB_FileChooser_Dev file_chooser_interface = {
&Create,
&IsFileChooser,
&Show,
&GetNextChosenFile
};
InterfaceProxy* CreateFileChooserProxy(Dispatcher* dispatcher,
const void* target_interface) {
return new PPB_FileChooser_Proxy(dispatcher, target_interface);
}
} // namespace
PPB_FileChooser_Proxy::PPB_FileChooser_Proxy(Dispatcher* dispatcher,
const void* target_interface)
: InterfaceProxy(dispatcher, target_interface),
callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
}
PPB_FileChooser_Proxy::~PPB_FileChooser_Proxy() {
}
const InterfaceProxy::Info* PPB_FileChooser_Proxy::GetInfo() {
static const Info info = {
&file_chooser_interface,
PPB_FILECHOOSER_DEV_INTERFACE,
INTERFACE_ID_PPB_FILE_CHOOSER,
false,
&CreateFileChooserProxy,
};
return &info;
}
bool PPB_FileChooser_Proxy::OnMessageReceived(const IPC::Message& msg) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PPB_FileChooser_Proxy, msg)
// Plugin -> host messages.
IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBFileChooser_Create, OnMsgCreate)
IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBFileChooser_Show, OnMsgShow)
// Host -> plugin messages.
IPC_MESSAGE_HANDLER(PpapiMsg_PPBFileChooser_ChooseComplete,
OnMsgChooseComplete)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void PPB_FileChooser_Proxy::OnMsgCreate(PP_Instance instance,
int mode,
const std::string& accept_mime_types,
HostResource* result) {
PP_FileChooserOptions_Dev options;
options.mode = static_cast<PP_FileChooserMode_Dev>(mode);
options.accept_mime_types = accept_mime_types.c_str();
result->SetHostResource(
instance, ppb_file_chooser_target()->Create(instance, &options));
}
void PPB_FileChooser_Proxy::OnMsgShow(const HostResource& chooser) {
CompletionCallback callback = callback_factory_.NewCallback(
&PPB_FileChooser_Proxy::OnShowCallback, chooser);
int32_t result = ppb_file_chooser_target()->Show(
chooser.host_resource(), callback.pp_completion_callback());
if (result != PP_OK_COMPLETIONPENDING)
callback.Run(result);
}
void PPB_FileChooser_Proxy::OnMsgChooseComplete(
const HostResource& chooser,
int32_t result_code,
const std::vector<PPBFileRef_CreateInfo>& chosen_files) {
PP_Resource plugin_resource =
PluginResourceTracker::GetInstance()->PluginResourceForHostResource(
chooser);
if (!plugin_resource)
return;
FileChooser* object = PluginResource::GetAs<FileChooser>(plugin_resource);
if (!object)
return;
// Convert each of the passed in file infos to resources. These will be owned
// by the FileChooser object until they're passed to the plugin.
DCHECK(object->file_queue_.empty());
for (size_t i = 0; i < chosen_files.size(); i++) {
object->file_queue_.push(
PPB_FileRef_Proxy::DeserializeFileRef(chosen_files[i]));
}
// Notify the plugin of the new data. We have to swap out the callback
// because the plugin may trigger deleting the object from the callback, and
// the FileChooser object will attempt to call the callback in its destructor
// with the ABORTED status.
PP_CompletionCallback callback = object->current_show_callback_;
object->current_show_callback_ = PP_MakeCompletionCallback(NULL, NULL);
PP_RunCompletionCallback(&callback, result_code);
// DANGER: May delete |object|!
}
void PPB_FileChooser_Proxy::OnShowCallback(int32_t result,
const HostResource& chooser) {
std::vector<PPBFileRef_CreateInfo> files;
if (result == PP_OK) {
// Jump through some hoops to get the FileRef proxy. Since we know we're
// in the host at this point, we can ask the host dispatcher for it.
DCHECK(!dispatcher()->IsPlugin());
HostDispatcher* host_disp = static_cast<HostDispatcher*>(dispatcher());
PPB_FileRef_Proxy* file_ref_proxy = static_cast<PPB_FileRef_Proxy*>(
host_disp->GetOrCreatePPBInterfaceProxy(INTERFACE_ID_PPB_FILE_REF));
// Convert the returned files to the serialized info.
while (PP_Resource cur_file_resource =
ppb_file_chooser_target()->GetNextChosenFile(
chooser.host_resource())) {
PPBFileRef_CreateInfo cur_create_info;
file_ref_proxy->SerializeFileRef(cur_file_resource, &cur_create_info);
files.push_back(cur_create_info);
}
}
dispatcher()->Send(new PpapiMsg_PPBFileChooser_ChooseComplete(
INTERFACE_ID_PPB_FILE_CHOOSER, chooser, result, files));
}
} // namespace proxy
} // namespace pp