Tuesday, September 9, 2008

Write your own plugin for Google Chrome

If you want to write your own plugin for google chrome, this is the place to begin.

My first Google Chrome plugin
I have decided to spend a fair amount of time today on the Google Chrome code. My objective was to understand the functioning of the plugin framework. I have not been able to find any useful information on the web except the Plugin Architecture design document. This article will describe what I have learned and explain how to build a basic plugin that can be tested using the Google Chrome that you have downloaded from the Google website (so no custom modifications required.)

I have found several posts on the web that stated that the Chrome team was working on building the infrastructure that will enable the addition of plugins and extensions. For those who do not yet understand the difference between these two concepts, I will quote Nathan Marz:
Quote:
A software extension in an entity that adds significant unintended functionality to the software.
A software plugin is an entity that adds intended functionality to a plugin in a controlled manner.
But as he rightly adds, "[s]ometimes there is a gray area between these two distinctions." Right now, I don't know enough about Google Chrome to dispute that the difference is merely conceptual. I hope that someone can clarify.

The first thing that I learned is that the current release of Google does not offer the ability to load additional plugins. It seems that Google is planning to offer the ability to add plugins via the registration of these plugins under HKEY_CURRENT_USER\\Software\\Google\\Chrome\\Plugi ns. When Google Chrome starts, it will look at the key Path which seems to be the path of the DLL. There is an additional key (LoadOnStartup) which indicates if the plugin should be loaded during startup (as you might have guessed )
So since this logic is disabled in the current release, how are we going to enable the loading of the plugin that we will build? Well, we will name it gears.dll and replace the existing gears.dll. It is not nice as we might want to use gears.dll as well. Unfortunately, unless the Google Chrome team unable the startup load, we will have to do so. Indeed, it seems that the loading of gears.dll is hardcoded.

Ok, let's build this plugin. As you know, a plugin is not more than a DLL with several functions that have been exported. In the case of Google Chrome, just one function needs to be exported but let's do some boring stuff before starting coding. First, you have to follow the instructions available on dev.chromium.org that indicates how to build Google Chrome. The reason you need to do that is that you will use several of the static libraries that this build will produce when you compile your plugin (base.lib, icuuc.lib and googleurl.lib.)

There is a very important file that describes the Chrome Plugin API. It can be found at "chrome/common/chrome_plugin_api.h". Basically what happen, is that you "implement" this API. There are two sets of functions that needs to be implemented. One set relates to the functions that are offered by the plugin to the browser (the host) - the CPP functions, CPP for Chrome Plug-in: Plug-in Defined, but I am not sure - and the other set relates to the function that are offered by the browser to the plugin - the CPB functions, CPB for Chrome Plug-in: Browser Defined. In other terms the browser tells you: "this is a set of services that I want you to provide me with, I will interact with you via a call to a table of functions which have the following signatures. If you believe that I will need to use a particular service when the plugin is being used, please implement it." Technically, this table is a structure with members that are functions pointer. There is another set of functions which can be implemented by the plugin and/or the browser - the CPR and CPRR functions.

The only function that you need to export is

PHP Code:
CPError STDCALL CP_Initialize(CPID id, const CPBrowserFuncs* bfuncs, CPPluginFuncs* pfuncs)
This function is called by the host when the plugin is being loaded. As you can notice, the browser provides us with the two tables of function pointers. Once we do the mapping, then if the browser needs to interact with the plugin it will used these shared tables.

Our code will make use of two files: test.cpp and test.h (I used Visual Studio 2008.) I have borrowed close to 90% of the code from a chrome plugin file that is part of the source that you downloaded: "chrome/test/chrome_plugin/test_chrome_plugin.h" and "chrome/test/chrome_plugin/test_chrome_plugin.cc".

Let's code the CP_Initialize function:

PHP Code:
test.cpp:
CPError STDCALL CP_Initialize(CPID id, const CPBrowserFuncs* bfuncs,
CPPluginFuncs* pfuncs) {
if (bfuncs == NULL || pfuncs == NULL)
return CPERR_FAILURE;

if (CP_GET_MAJOR_VERSION(bfuncs->version) > CP_MAJOR_VERSION)
return CPERR_INVALID_VERSION;

if (bfuncs->size < sizeof(CPBrowserFuncs) ||
pfuncs->size < sizeof(CPPluginFuncs))
return CPERR_INVALID_VERSION;
CPERR_FAILURE and CPERR_INVALID_VERSION are defined in "chrome/common/chrome_plugin_api.h". They respectively indicate a generic failure and an incompatibility of API version used by the browser and the plugin. I don't think that I need to explain these verifications as they are pretty obvious, let's continue:

PHP Code:
pfuncs->version = CP_VERSION;
pfuncs->shutdown = CPP_Shutdown;
pfuncs->should_intercept_request = CPP_ShouldInterceptRequest;

static CPRequestFuncs 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;
pfuncs->request_funcs = &request_funcs;

static CPResponseFuncs response_funcs;
response_funcs.received_redirect = CPRR_ReceivedRedirect;
response_funcs.start_completed = CPRR_StartCompleted;
response_funcs.read_completed = CPRR_ReadCompleted;
pfuncs->response_funcs = &response_funcs;

g_cpid = id;
g_cpbrowser_funcs = *bfuncs;
g_cprequest_funcs = *bfuncs->request_funcs;
g_cpresponse_funcs = *bfuncs->response_funcs;
g_cpbrowser_funcs = *bfuncs;
We started the mapping of the functions. As you can see, we will only implement two functions among the CPP functions (made available to the host): CPP_Shutdown (called when the browser is unloading the plugin) and CPP_ShouldInterceptRequest (indicates if the plugin is interested in handling a request.) We want to be able to issue some requests via the browser therefore we implement several CPR functions. We will also process several intercepted requests so we implement some CPRR functions. Finally, we store a reference to these structures as we will use them later.

PHP Code:
test.cpp:
const char* protocols[] = {kChromeTestPluginProtocol};
g_cpbrowser_funcs.enable_request_intercept(g_cpid, protocols, 1);
return CPERR_SUCCESS;
We end this function by informing the browser that the plugin wants to intercept a list of protocols. Let's define these protocols in the header file.

PHP Code:
test.h
const char kChromeTestPluginProtocol[] = "cptest";
So let's suppose that a user enters "cptest:sync" in the location bar. The browser knows that the plugin has requested to be able to process these requests. The browser will use our set of CPRR functions. We have implemented three of these functions: CPRR_ReceivedRedirect, CPRR_StartCompleted and CPRR_ReadCompleted. If you read carefully the description of these functions, you will find that it is CPRR_ReceivedRedirect which is then called. Then we read the data: call to CPR_Read which is followed by a call to CPRR_ReadCompleted. We can start issuing our request. I will stop here but I encourage you to use your debugger to understand the sequence of events.

As you can see in the code, the following requests are implemented: cptest:sync, cptest:async and cptest:blank. Once you install the plugin, you should try them. You will find the source code here.

Finally, if you want to see your plugin listed when you type about : plugins in your location bar, do not forget to add a VERSIONINFO Resource.

If you have any question related to this article, you can always reply to this post. Otherwise, use the other forums.

source : http://www.paperfrag.com/showthread.php?p=1

No comments: