summaryrefslogtreecommitdiffstats
path: root/chrome/plugin/chrome_plugin_host.cc
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 23:55:29 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 23:55:29 +0000
commit09911bf300f1a419907a9412154760efd0b7abc3 (patch)
treef131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/plugin/chrome_plugin_host.cc
parent586acc5fe142f498261f52c66862fa417c3d52d2 (diff)
downloadchromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip
chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz
chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/plugin/chrome_plugin_host.cc')
-rw-r--r--chrome/plugin/chrome_plugin_host.cc570
1 files changed, 570 insertions, 0 deletions
diff --git a/chrome/plugin/chrome_plugin_host.cc b/chrome/plugin/chrome_plugin_host.cc
new file mode 100644
index 0000000..5a3af53
--- /dev/null
+++ b/chrome/plugin/chrome_plugin_host.cc
@@ -0,0 +1,570 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/plugin/chrome_plugin_host.h"
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_plugin_lib.h"
+#include "chrome/common/chrome_plugin_util.h"
+#include "chrome/plugin/plugin_process.h"
+#include "chrome/plugin/plugin_thread.h"
+#include "chrome/plugin/webplugin_proxy.h"
+#include "net/base/data_url.h"
+#include "webkit/glue/plugins/plugin_instance.h"
+#include "webkit/glue/resource_loader_bridge.h"
+#include "webkit/glue/resource_type.h"
+#include "webkit/glue/webkit_glue.h"
+
+namespace {
+
+using webkit_glue::ResourceLoaderBridge;
+
+// This class manages a network request made by the plugin, handling the
+// data as it comes in from the ResourceLoaderBridge and is requested by the
+// plugin.
+// NOTE: All methods must be called on the Plugin thread.
+class PluginRequestHandlerProxy
+ : public PluginHelper, public ResourceLoaderBridge::Peer {
+ public:
+ static PluginRequestHandlerProxy* FromCPRequest(CPRequest* request) {
+ return ScopableCPRequest::GetData<PluginRequestHandlerProxy*>(request);
+ }
+
+ PluginRequestHandlerProxy(ChromePluginLib* plugin,
+ ScopableCPRequest* cprequest)
+ : PluginHelper(plugin), cprequest_(cprequest), response_data_offset_(0),
+ completed_(false), sync_(false), read_buffer_(NULL) {
+ load_flags_ = PluginResponseUtils::CPLoadFlagsToNetFlags(0);
+ cprequest_->data = this; // see FromCPRequest().
+ }
+
+ ~PluginRequestHandlerProxy() {
+ if (bridge_.get() && !completed_) {
+ bridge_->Cancel();
+ }
+ }
+
+ // ResourceLoaderBridge::Peer
+ virtual void OnUploadProgress(uint64 position, uint64 size) {
+ CPRR_UploadProgressFunc upload_progress =
+ plugin_->functions().response_funcs->upload_progress;
+ if (upload_progress)
+ upload_progress(cprequest_.get(), position, size);
+ }
+
+ virtual void OnReceivedRedirect(const GURL& new_url) {
+ plugin_->functions().response_funcs->received_redirect(
+ cprequest_.get(), new_url.spec().c_str());
+ }
+
+ virtual void OnReceivedResponse(
+ const ResourceLoaderBridge::ResponseInfo& info) {
+ response_headers_ = info.headers;
+ plugin_->functions().response_funcs->start_completed(
+ cprequest_.get(), CPERR_SUCCESS);
+ }
+
+ virtual void OnReceivedData(const char* data, int len) {
+ response_data_.append(data, len);
+ if (read_buffer_) {
+ // If we had an asynchronous operation pending, read into that buffer
+ // and inform the plugin.
+ int rv = Read(read_buffer_, read_buffer_size_);
+ DCHECK(rv != CPERR_IO_PENDING);
+ read_buffer_ = NULL;
+ plugin_->functions().response_funcs->read_completed(
+ cprequest_.get(), rv);
+ }
+ }
+
+ virtual void OnCompletedRequest(const URLRequestStatus& status) {
+ completed_ = true;
+
+ if (!status.is_success()) {
+ // TODO(mpcomplete): better error codes
+ // Inform the plugin, calling the right function depending on whether
+ // we got the start_completed event or not.
+ if (response_headers_) {
+ plugin_->functions().response_funcs->start_completed(
+ cprequest_.get(), CPERR_FAILURE);
+ } else {
+ plugin_->functions().response_funcs->read_completed(
+ cprequest_.get(), CPERR_FAILURE);
+ }
+ } else if (read_buffer_) {
+ // The plugin was waiting for more data. Inform him we're done.
+ read_buffer_ = NULL;
+ plugin_->functions().response_funcs->read_completed(
+ cprequest_.get(), CPERR_SUCCESS);
+ }
+ }
+
+ virtual std::string GetURLForDebugging() {
+ return cprequest_->url;
+ }
+
+ void set_extra_headers(const std::string& headers) {
+ extra_headers_ = headers;
+ }
+ void set_load_flags(uint32 flags) {
+ load_flags_ = flags;
+ }
+ void set_sync(bool sync) {
+ sync_ = sync;
+ }
+ void AppendDataToUpload(const char* bytes, int bytes_len) {
+ upload_content_.push_back(net::UploadData::Element());
+ upload_content_.back().SetToBytes(bytes, bytes_len);
+ }
+
+ void AppendFileToUpload(const std::wstring &filepath) {
+ AppendFileRangeToUpload(filepath, 0, kuint64max);
+ }
+
+ void AppendFileRangeToUpload(const std::wstring &filepath,
+ uint64 offset, uint64 length) {
+ upload_content_.push_back(net::UploadData::Element());
+ upload_content_.back().SetToFilePathRange(filepath, offset, length);
+ }
+
+ CPError Start() {
+ bridge_.reset(
+ PluginThread::GetPluginThread()->resource_dispatcher()->CreateBridge(
+ cprequest_->method,
+ GURL(cprequest_->url),
+ GURL(cprequest_->url), // TODO(jackson): policy url?
+ GURL(), // TODO(mpcomplete): referrer?
+ extra_headers_,
+ load_flags_,
+ GetCurrentProcessId(),
+ ResourceType::OBJECT,
+ false, // TODO (jcampan): mixed-content?
+ cprequest_->context));
+ if (!bridge_.get())
+ return CPERR_FAILURE;
+
+ for (size_t i = 0; i < upload_content_.size(); ++i) {
+ switch (upload_content_[i].type()) {
+ case net::UploadData::TYPE_BYTES: {
+ const std::vector<char>& bytes = upload_content_[i].bytes();
+ bridge_->AppendDataToUpload(&bytes[0],
+ static_cast<int>(bytes.size()));
+ break;
+ }
+ case net::UploadData::TYPE_FILE: {
+ bridge_->AppendFileRangeToUpload(
+ upload_content_[i].file_path(),
+ upload_content_[i].file_range_offset(),
+ upload_content_[i].file_range_length());
+ break;
+ }
+ default: {
+ NOTREACHED() << "Unknown UploadData::Element type";
+ }
+ }
+ }
+
+ if (sync_) {
+ ResourceLoaderBridge::SyncLoadResponse response;
+ bridge_->SyncLoad(&response);
+ response_headers_ = response.headers;
+ response_data_ = response.data;
+ completed_ = true;
+ return response.status.is_success() ? CPERR_SUCCESS : CPERR_FAILURE;
+ } else {
+ if (!bridge_->Start(this)) {
+ bridge_.reset();
+ return CPERR_FAILURE;
+ }
+ return CPERR_IO_PENDING;
+ }
+ }
+
+ int GetResponseInfo(CPResponseInfoType type, void* buf, uint32 buf_size) {
+ return PluginResponseUtils::GetResponseInfo(
+ response_headers_, type, buf, buf_size);
+ }
+
+ int Read(void* buf, uint32 buf_size) {
+ uint32 avail =
+ static_cast<uint32>(response_data_.size()) - response_data_offset_;
+ uint32 count = buf_size;
+ if (count > avail)
+ count = avail;
+
+ int rv = CPERR_FAILURE;
+ if (count) {
+ // Data is ready now.
+ memcpy(buf, &response_data_[0] + response_data_offset_, count);
+ response_data_offset_ += count;
+ } else if (!completed_) {
+ read_buffer_ = buf;
+ read_buffer_size_ = buf_size;
+ DCHECK(!sync_);
+ return CPERR_IO_PENDING;
+ }
+
+ if (response_data_.size() == response_data_offset_) {
+ // Simple optimization for large requests. Generally the consumer will
+ // read the data faster than it comes in, so we can clear our buffer
+ // any time it has all been read.
+ response_data_.clear();
+ response_data_offset_ = 0;
+ }
+
+ read_buffer_ = NULL;
+ return count;
+ }
+
+ private:
+ scoped_ptr<ScopableCPRequest> cprequest_;
+ scoped_ptr<ResourceLoaderBridge> bridge_;
+ std::vector<net::UploadData::Element> upload_content_;
+ std::string extra_headers_;
+ uint32 load_flags_;
+ bool sync_;
+
+ scoped_refptr<net::HttpResponseHeaders> response_headers_;
+ std::string response_data_;
+ int response_data_offset_;
+ bool completed_;
+ void* read_buffer_;
+ uint32 read_buffer_size_;
+};
+
+//
+// Generic functions
+//
+
+void STDCALL CPB_SetKeepProcessAlive(CPID id, CPBool keep_alive) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ static bool g_keep_process_alive = false;
+ bool desired_value = keep_alive ? true : false; // smash to bool
+ if (desired_value != g_keep_process_alive) {
+ g_keep_process_alive = desired_value;
+ if (g_keep_process_alive)
+ PluginProcess::AddRefProcess();
+ else
+ PluginProcess::ReleaseProcess();
+ }
+}
+
+CPError STDCALL CPB_GetCookies(CPID id, CPBrowsingContext context,
+ const char* url, char** cookies) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ std::string cookies_str;
+ PluginThread::GetPluginThread()->Send(
+ new PluginProcessHostMsg_GetCookies(context, GURL(url), &cookies_str));
+ *cookies = CPB_StringDup(CPB_Alloc, cookies_str);
+ return CPERR_SUCCESS;
+}
+
+CPError STDCALL CPB_ShowHtmlDialogModal(
+ CPID id, CPBrowsingContext context, const char* url, int width, int height,
+ const char* json_arguments, char** json_retval) {
+ CHECK(ChromePluginLib::IsPluginThread());
+
+ WebPluginProxy* webplugin = WebPluginProxy::FromCPBrowsingContext(context);
+ if (!webplugin)
+ return CPERR_INVALID_PARAMETER;
+
+ std::string retval_str;
+ webplugin->ShowModalHTMLDialog(
+ GURL(url), width, height, json_arguments, &retval_str);
+ *json_retval = CPB_StringDup(CPB_Alloc, retval_str);
+ return CPERR_SUCCESS;
+}
+
+CPError STDCALL CPB_ShowHtmlDialog(
+ CPID id, CPBrowsingContext context, const char* url, int width, int height,
+ const char* json_arguments, void* plugin_context) {
+ // TODO(mpcomplete): support non-modal dialogs.
+ return CPERR_FAILURE;
+}
+
+CPError STDCALL CPB_GetCommandLineArguments(
+ CPID id, CPBrowsingContext context, const char* url, char** arguments) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ std::string arguments_str;
+ CPError rv = CPB_GetCommandLineArgumentsCommon(url, &arguments_str);
+ if (rv == CPERR_SUCCESS)
+ *arguments = CPB_StringDup(CPB_Alloc, arguments_str);
+ return rv;
+}
+
+CPBrowsingContext STDCALL CPB_GetBrowsingContextFromNPP(NPP npp) {
+ if (!npp)
+ return CPERR_INVALID_PARAMETER;
+
+ NPAPI::PluginInstance* instance =
+ static_cast<NPAPI::PluginInstance *>(npp->ndata);
+ WebPluginProxy* webplugin =
+ static_cast<WebPluginProxy*>(instance->webplugin());
+
+ return webplugin->GetCPBrowsingContext();
+}
+
+int STDCALL CPB_GetBrowsingContextInfo(
+ CPID id, CPBrowsingContext context, CPBrowsingContextInfoType type,
+ void* buf, uint32 buf_size) {
+ CHECK(ChromePluginLib::IsPluginThread());
+
+ switch (type) {
+ case CPBROWSINGCONTEXT_DATA_DIR_PTR: {
+ if (buf_size < sizeof(char*))
+ return sizeof(char*);
+
+ std::wstring wretval;
+ PluginThread::GetPluginThread()->Send(
+ new PluginProcessHostMsg_GetPluginDataDir(&wretval));
+ file_util::AppendToPath(&wretval, chrome::kChromePluginDataDirname);
+ *static_cast<char**>(buf) = CPB_StringDup(CPB_Alloc, WideToUTF8(wretval));
+ return CPERR_SUCCESS;
+ }
+ case CPBROWSINGCONTEXT_UI_LOCALE_PTR: {
+ if (buf_size < sizeof(char*))
+ return sizeof(char*);
+
+ std::wstring wretval = webkit_glue::GetWebKitLocale();
+ *static_cast<char**>(buf) = CPB_StringDup(CPB_Alloc, WideToUTF8(wretval));
+ return CPERR_SUCCESS;
+ }
+ }
+
+ return CPERR_FAILURE;
+}
+
+CPError STDCALL CPB_AddUICommand(CPID id, int command) {
+ // Not implemented in the plugin process
+ return CPERR_FAILURE;
+}
+
+CPError STDCALL CPB_HandleCommand(
+ CPID id, CPBrowsingContext context, int command, void *data) {
+ // Not implemented in the plugin process
+ return CPERR_FAILURE;
+}
+
+//
+// Functions related to network interception
+//
+
+void STDCALL CPB_EnableRequestIntercept(
+ CPID id, const char** schemes, uint32 num_schemes) {
+ // We ignore requests by the plugin to intercept from this process. That's
+ // handled in the browser process.
+}
+
+void STDCALL CPRR_ReceivedRedirect(CPRequest* request, const char* new_url) {
+ NOTREACHED() << "Network interception should not happen in plugin process.";
+}
+
+void STDCALL CPRR_StartCompleted(CPRequest* request, CPError result) {
+ NOTREACHED() << "Network interception should not happen in plugin process.";
+}
+
+void STDCALL CPRR_ReadCompleted(CPRequest* request, int bytes_read) {
+ NOTREACHED() << "Network interception should not happen in plugin process.";
+}
+
+void STDCALL CPRR_UploadProgress(CPRequest* request, uint64 pos, uint64 size) {
+ NOTREACHED() << "Network interception should not happen in plugin process.";
+}
+
+//
+// Functions related to serving network requests to the plugin
+//
+
+CPError STDCALL CPB_CreateRequest(CPID id, CPBrowsingContext context,
+ const char* method, const char* url,
+ CPRequest** request) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ ChromePluginLib* plugin = ChromePluginLib::FromCPID(id);
+ CHECK(plugin);
+
+ ScopableCPRequest* cprequest = new ScopableCPRequest(url, method, context);
+ PluginRequestHandlerProxy* handler =
+ new PluginRequestHandlerProxy(plugin, cprequest);
+
+ *request = cprequest;
+ return CPERR_SUCCESS;
+}
+
+CPError STDCALL CPR_StartRequest(CPRequest* request) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ PluginRequestHandlerProxy* handler =
+ PluginRequestHandlerProxy::FromCPRequest(request);
+ CHECK(handler);
+ return handler->Start();
+}
+
+void STDCALL CPR_EndRequest(CPRequest* request, CPError reason) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ PluginRequestHandlerProxy* handler =
+ PluginRequestHandlerProxy::FromCPRequest(request);
+ delete handler;
+}
+
+void STDCALL CPR_SetExtraRequestHeaders(CPRequest* request,
+ const char* headers) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ PluginRequestHandlerProxy* handler =
+ PluginRequestHandlerProxy::FromCPRequest(request);
+ CHECK(handler);
+ handler->set_extra_headers(headers);
+}
+
+void STDCALL CPR_SetRequestLoadFlags(CPRequest* request, uint32 flags) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ PluginRequestHandlerProxy* handler =
+ PluginRequestHandlerProxy::FromCPRequest(request);
+ CHECK(handler);
+
+ if (flags & CPREQUESTLOAD_SYNCHRONOUS) {
+ handler->set_sync(true);
+ }
+
+ uint32 net_flags = PluginResponseUtils::CPLoadFlagsToNetFlags(flags);
+ handler->set_load_flags(net_flags);
+}
+
+void STDCALL CPR_AppendDataToUpload(CPRequest* request, const char* bytes,
+ int bytes_len) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ PluginRequestHandlerProxy* handler =
+ PluginRequestHandlerProxy::FromCPRequest(request);
+ CHECK(handler);
+ handler->AppendDataToUpload(bytes, bytes_len);
+}
+
+CPError STDCALL CPR_AppendFileToUpload(CPRequest* request, const char* filepath,
+ uint64 offset, uint64 length) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ PluginRequestHandlerProxy* handler =
+ PluginRequestHandlerProxy::FromCPRequest(request);
+ CHECK(handler);
+
+ if (!length) length = kuint64max;
+ std::wstring wfilepath(UTF8ToWide(filepath));
+ handler->AppendFileRangeToUpload(wfilepath, offset, length);
+ return CPERR_SUCCESS;
+}
+
+int STDCALL CPR_GetResponseInfo(CPRequest* request, CPResponseInfoType type,
+ void* buf, uint32 buf_size) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ PluginRequestHandlerProxy* handler =
+ PluginRequestHandlerProxy::FromCPRequest(request);
+ CHECK(handler);
+ return handler->GetResponseInfo(type, buf, buf_size);
+}
+
+int STDCALL CPR_Read(CPRequest* request, void* buf, uint32 buf_size) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ PluginRequestHandlerProxy* handler =
+ PluginRequestHandlerProxy::FromCPRequest(request);
+ CHECK(handler);
+ return handler->Read(buf, buf_size);
+}
+
+
+CPBool STDCALL CPB_IsPluginProcessRunning(CPID id) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ return true;
+}
+
+CPProcessType STDCALL CPB_GetProcessType(CPID id) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ return CP_PROCESS_PLUGIN;
+}
+
+CPError STDCALL CPB_SendMessage(CPID id, const void *data, uint32 data_len) {
+ CHECK(ChromePluginLib::IsPluginThread());
+ const uint8* data_ptr = static_cast<const uint8*>(data);
+ std::vector<uint8> v(data_ptr, data_ptr + data_len);
+ if (!PluginThread::GetPluginThread()->Send(
+ new PluginProcessHostMsg_PluginMessage(v))) {
+ return CPERR_FAILURE;
+ }
+ return CPERR_SUCCESS;
+}
+
+} // namespace
+
+CPBrowserFuncs* GetCPBrowserFuncsForPlugin() {
+ static CPBrowserFuncs browser_funcs;
+ static CPRequestFuncs request_funcs;
+ static CPResponseFuncs response_funcs;
+ static bool initialized = false;
+ if (!initialized) {
+ initialized = true;
+
+ browser_funcs.size = sizeof(browser_funcs);
+ browser_funcs.version = CP_VERSION;
+ browser_funcs.enable_request_intercept = CPB_EnableRequestIntercept;
+ browser_funcs.create_request = CPB_CreateRequest;
+ browser_funcs.get_cookies = CPB_GetCookies;
+ browser_funcs.alloc = CPB_Alloc;
+ browser_funcs.free = CPB_Free;
+ browser_funcs.set_keep_process_alive = CPB_SetKeepProcessAlive;
+ browser_funcs.show_html_dialog = CPB_ShowHtmlDialog;
+ browser_funcs.show_html_dialog_modal = CPB_ShowHtmlDialogModal;
+ browser_funcs.is_plugin_process_running = CPB_IsPluginProcessRunning;
+ browser_funcs.get_process_type = CPB_GetProcessType;
+ browser_funcs.send_message = CPB_SendMessage;
+ browser_funcs.get_browsing_context_from_npp = CPB_GetBrowsingContextFromNPP;
+ browser_funcs.get_browsing_context_info = CPB_GetBrowsingContextInfo;
+ browser_funcs.get_command_line_arguments = CPB_GetCommandLineArguments;
+ browser_funcs.add_ui_command = CPB_AddUICommand;
+ browser_funcs.handle_command = CPB_HandleCommand;
+
+ browser_funcs.request_funcs = &request_funcs;
+ browser_funcs.response_funcs = &response_funcs;
+
+ request_funcs.size = sizeof(request_funcs);
+ request_funcs.start_request = CPR_StartRequest;
+ request_funcs.end_request = CPR_EndRequest;
+ request_funcs.set_extra_request_headers = CPR_SetExtraRequestHeaders;
+ request_funcs.set_request_load_flags = CPR_SetRequestLoadFlags;
+ request_funcs.append_data_to_upload = CPR_AppendDataToUpload;
+ request_funcs.get_response_info = CPR_GetResponseInfo;
+ request_funcs.read = CPR_Read;
+ request_funcs.append_file_to_upload = CPR_AppendFileToUpload;
+
+ response_funcs.size = sizeof(response_funcs);
+ response_funcs.received_redirect = CPRR_ReceivedRedirect;
+ response_funcs.start_completed = CPRR_StartCompleted;
+ response_funcs.read_completed = CPRR_ReadCompleted;
+ response_funcs.upload_progress = CPRR_UploadProgress;
+ }
+
+ return &browser_funcs;
+}