Posts Tagged ‘webkit’

Website thumbnails

Generating website thumbnails was a pretty hot topic a few years ago with the rise of services like websnapr, thumbalizr, and, perhaps the most well-known, snap shots. The popularity of doing this has faded somewhat, most likely due to the realizations that services like snap shots are not all that helpful and more of an annoyance than anything else.

I did my own thumbnail generator app a few years ago for a CentOS system. It was painful. I believe I was using gtkmozembed and just going through hell to resolve all the dependencies to get things running on a command line system.

With the WebKit stuff I recently put up, I decided to revisit the idea under .NET, and things were surprisingly easy. This is due to the fact that WinForms 2.0 has the Control.DrawToBitmap() method which makes capturing an image of the client area of a control relatively easy. I should mention, .NET 2.0 has the WebBrowser control, but I could never get the Control.DrawToBitmap() method to work; I always got a blank (white) image. The WebBrowser control is also undesirable because it’s based on IE6, so its certainly far from up-to-date or accurate rendering, even more-so than the outdated version of WebKit I’m using.

So let’s say we’re running a WebKit instance in a Panel (panel1), we can capture what’s displayed as follows,

Bitmap bmpOut = new Bitmap(panel1.Width, panel1.Height);
panel1.DrawToBitmap(bmpOut, panel1.ClientRectangle);
bmpOut.Save(
"test.png");

Here’s an example of a capture:

website thumbnail reddit.com

There is one critical limitation; only the visible portion of the web page is captured. However, if a component to scroll and capture different portions of the page were implemented, and the final image was pieced together from these portions, a full page would be captured.

Another limitation is there there is no way to tell when the page is finished loading. Figuring this out requires digging into the depths of the WebKit codebase, I’m certainly not up for it, and it’s not for the faint of heart.

Embedding WebKit in a WinForms application

This is how to embed a WebKit instance within a WinForms application. I promised, a long time ago, to show this, but never got around to it. The process itself is pretty simple, assuming you have WebKit compiled (which can be pretty difficult) because you’ll need to be able to link with the WebKit libraries, specificially: WebKit.lib and WebKitGUID.lib (which, of course, have dependencies on all of the other libraries produced in the compilation). I’m going to be using a older version of WebKit, the same one I played with in my previous blog posts, as I don’t have time to check out the newest version of WebKit. Hopefully, the interfaces are the same or similar and what I’m about to describe here can be adapted easily to work with the latest version.

What we’re going to do is create a CLI wrapper to allow the .Net application to access the native WebKit code. To mix managed and unmanaged code, we’re going to need to create a C++/CLI application. Yes, it is going to be ugly code, but it works and (I think) much simpler than trying to P/Invoke functions in WebKit.dll.

Here’s the wrapper code (I’ve also included a compiled dll in the /Release folder):
WebKitCLIWrapper.dll.zip

WebKitNativeController.h and WebKitNativeController.cpp is adapted from the demo code provided by Apple. WebKitCLIWrapper.h (shown below) is the C++/CLI .NET code that wraps the native controller.

#pragma once

#include
"WebKitNativeController.h"

namespace WebKitCLIWrapper
{
    
using namespace System;
    
using namespace System::Collections;

    
public ref class Wrapper
    {
        
public:
            Wrapper(IntPtr hwnd, String^ htmlContent)
            {
                webKitController =
new WebKitNativeController();

                IntPtr bstrPtr = System::Runtime::InteropServices::Marshal::StringToBSTR(htmlContent);
                webKitController->Init((HWND)(
int)hwnd, (BSTR*)&bstrPtr);
                System::Runtime::InteropServices::Marshal::FreeBSTR(bstrPtr);
            }

            ~Wrapper()
            {
                webKitController->UnInit();
                webKitController->Release();
            }

            
void LoadURL(String^ url)
            {

                IntPtr bstrPtr = System::Runtime::InteropServices::Marshal::StringToBSTR(url);
                webKitController->LoadURL(*((BSTR*)&bstrPtr));
                System::Runtime::InteropServices::Marshal::FreeBSTR(bstrPtr);
            }
            
        
protected:
            WebKitNativeController*        webKitController;
    };
}

Now, we can simply include WebKitCLIWrapper.dll in any .Net project as a reference and access the wrapper to create a WebKit instance. So if your in Visual C#, create a new WinForms app, create a Panel (panel1) on a form, and add the following to the form’s Load event:

WebKitCLIWrapper.Wrapper wrap = new Wrapper(panel1.Handle, "");
wrap.LoadURL(
"http://arstechnica.com/");

However, before running the application, you need all the necessary libraries and necessary resources referenced by WebKit.dll in the app’s working directory (I like to set it to Debug).

For convenience, here’s a zip containing all you’ll need:
WebKit_dlls+resources.zip
(as I mentioned, this is an older version of WebKit)

With all the necessary files in place, launch the app, and a WebKit instance should load in the specified panel (panel1) along with the web page specified in LoadURL() call.

arstechnica.com loaded in a webkit container within a WinForms application

If you’d like to display and process a specific piece of HTML code, get rid of the LoadURL() call, place the code to be embedded in a string, and pass it as the 2nd argument to the WebKitCLIWrapper.Wrapper constructor. For example, this will load and embed a youtube video:

string embed = "<object width=\"425\" height=\"344\"><param name=\"movie\" value=\"http://www.youtube.com/v/SuBqIrgKnNg&hl=en&fs=1\"></param><param name=\"allowFullScreen\" value=\"true\"></param><embed src=\"http://www.youtube.com/v/SuBqIrgKnNg&hl=en&fs=1\" type=\"application/x-shockwave-flash\" allowfullscreen=\"true\" width=\"425\" height=\"344\"></embed></object>";
WebKitCLIWrapper.
Wrapper wrap = new Wrapper(panel1.Handle, embed);

cookie monster npr interview, displayed in a webkit instance with a WinForms panel

The wrapper interface is very bare-bones, but it’s good enough if you just need to display some web or HTML content in your app or, if your looking to build a .NET web browser, it will hopefully provide a starting point for how to interact with WebKit.

Trials and tribulations with WebKit

This post is about issues that pop up after you’ve got WebKit compiled and can get an application up and running within initialization errors.

(fyi, these are issues I’ve had on Win32. Things may be different on other platforms)

Issue 1: Safari theme doesn’t work, causes crash
If, like me, you’ve just copied the other necessary DLLs needed by WebKit from your Safari directory, SafariTheme.dll will cause you trouble (crash) unless you copy over the \SafariTheme.resources folder as well.

Note that WebKit will work fine without the Safari theme. Simply, get rid of the SafariTheme.dll file and a default theme will be used (it’s ugly, but it’s functional). Also, note that I’m not sure what the copyrights/license is for the Safari theme, there may be restrictions on using and/or distributing it.

Issue 2: “localized string not found” in context menus
Go to folder where you have the WebKit source. Navigate to WebKit\win. There you’ll find the \WebKit.resources folder, copy it to the folder with your WebKit DLL. Next, copy \English.lproj into the copied \WebKit.resources folder.

So, in the directory with your WebKit DLL, you should have:
\WebKit.resources
\WebKit.resources\English.lproj

Issue 3: loadURL() does nothing
If your using the WinLauncher application as guide, you’ll notice the loadURL() function doesn’t seem to work. This is because the timeout interval passed to the initWithURL() member is 0. A value of 60 (seconds?) seems to work well.

request->initWithURL(urlBStr, WebURLRequestUseProtocolCachePolicy, 60);

That’s it for now. In the next few days, I’ll post how to wrap WebKit and embed it within a WinForms application; screenshot below.

WebKit in C# WinForms application

Programmatically registering and un-registering a COM DLL

Playing around with WebKit a bit and stumbled across the headaches of working with a COM DLL. WebKit uses COM interfaces, so an application using a WebKit DLL must register the DLL. COM DLL registration is system-wide (i.e. all calls will be directed to a single DLL file), and this creates a problem if an application wants to use a DLL of a different version or a custom one (assuming renaming all the interfaces is not a viable option).

A simple solution to this seems to be to register and unregister the DLL programmatically. By doing so, any existing (or non-existent) registration is ignored and the exact DLL file the application needs is registered and used. The registration is still system-wide, but if other applications follow the same policy there doesn’t seem to be a problem. For applications that don’t follow this policy, it seems their calls will go to the last DLL registered.

(This is just from my own investigation and I really don’t know a whole lot about COM stuff. If anything mentioned is incorrect, please correct me.)

How to register and unregister COM DLLs can be found here. This is a bit convoluted. I actually just followed the steps listed in the second post of this thread on the MSDN forums to write my code.

To register:

HMODULE webKitMod = ::LoadLibrary(L"WebKit.dll"); FARPROC regFunc = ::GetProcAddress(webKitMod, "DllRegisterServer"); if(regFunc != NULL) { HRESULT regRet = regFunc(); if(regRet != S_OK) { // report error } } else { // report error } ::FreeLibrary(webKitMod);

To UnRegister:

HMODULE webKitMod = ::LoadLibrary(L"WebKit.dll"); FARPROC unRegFunc = ::GetProcAddress(webKitMod, "DllUnregisterServer"); if(unRegFunc != NULL) { HRESULT unRegRet = unRegFunc(); if(unRegRet != S_OK) { // report error } } else { // report error } ::FreeLibrary(webKitMod);

Compiling Webkit on Win32

Compiling WebKit on Windows is far from trivial. In the process of getting it compiled and linked successfully, I took some notes which may help others struggling to getting it done.

First, a few precautions, don’t try to be smart. Follow the instructions on the site closely, this means:
  • Don’t try to compile w/ Visual C++ 2008, it won’t work. Use VC++ 2005.
  • For VC++ 2005 Express Ed., make sure you have SP1, compilation will fail w/o it.
  • For Vista, make sure you have the additional SP update.
  • Don’t try to compile outside of cygwin directory structure.
Next, you need to get the webkit source.
  • Get the source from a nightly build (make sure to download the source and not one of the platform specific builds). You can also get the files from the subversion repository, but I had issues with this and discovered missing files. It may have been fixed, but in general, I think just getting the nightly build is simpler.
  • The source is in a .bz2 file. So you’ll need a bzip2 decompressor.
  • The bz2 file decompresses to a .tar file (wtf?!), so you’ll also need a tar decompressor.
  • Next, download the WebKit Support Libraries.
  • Put the source under your cygwin/home/<username> directory (e.g. \cygwin\home\<username>\WebKit)
  • Finally, update, by running the update-webkit script.
Preparing for compilation.

Note: I tried to build from the cygwin shell, as demonstrated on webkit.org, but it failed. Failing in this way and then having to dig thru build logs is insane, so I diverted to building from within VC++ so I could easily spot and fix errors as they occurred.

Note: I failed to realize that I didn’t have SP1 for VC++ 2005 Express when I started this whole process and didn’t figure it out until I hit a few perplexing errors related to the linker failing because it detected different versions of the compiler were used for the 2 parts of compilation on a few object files (yea, it’s weird, I have no clue what was going on internally here). I found a few quick and dirty solutions to this problem by playing around with optimization settings, but eventually hit a brick wall, where no amount of fidgeting with optimization parameters resulted in compilation/linking going forward. Installing SP1 solved the issue and may have solved the earlier issues as well without me having to play with optimization settings.

So, onwards we go,
  • I found that after opening the WebKit solution, none of the projects were loaded. This is because the project files are looking for *.vsprops files in WebKit\WebKitLibraries\tools\vsprops, but the vsprops files are actually in WebKit\WebKitLibraries\win\tools\vsprops. The simplest solution is just copy over what’s in the /win directory into the parent directory (WebKit\WebKitLibraries). Looking at WebKit\WebKitLibraries now, I copied over the win/tools folder and the files in /win. Alternatively, you can edit the project files (they’re XML files, you can open them up in a text editor).
  • Set the 2 environment variables necessary for compiling within VC++,
    • WEBKITLIBRARIESDIR = cygwin\home\<user>\WebKit\WebKitLibraries
    • WEBKITOUTPUTDIR = cygwin\home\<user>\WebKit\WebKitBuild
  • Get the Platform SDK, specifically the Windows Server 2003 Ed. of the Platform SDK. I did not have luck getting it to work with an older or newer version of the SDK. I didn’t install the R2 update; I’m not sure what effect it would have.
  • Update VC++ directories:
    • Executables >> add cygwin/bin, windows/system32, Platform SDK/bin
    • Includes >> Platform SDK/include
    • Library Files >> Platform SDK/lib
  • Update VC++ directories for the WebKit Support Library:
    • Includes >> WebKit\WebKitSupportLibrary\WebKitSupportLibrary\win\include
    • Library Files >> WebKit\WebKitSupportLibrary\WebKitSupportLibrary\win\lib
  • Update VC++ directories for JavaScriptCore/icu:
    • Includes >> WebKit\JavaScriptCore\icu
  • Update VC++ directories for WebCoreSQLite3:
    • Includes >> WebKit\WebKitLibraries\WebCoreSQLite3
  • Update VC++ directories for JavaScriptCore:
    • Includes >> WebKit\JavaScriptCore
  • I had issues with the ARRAYSIZE macro not being defined, so in WebCore/config.h, add the following:

    #ifndef ARRAYSIZE
    #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
    #endif

    right before the line,

    #endif /* PLATFORM(WIN_OS) */

  • Get rid of cygwin’s link.exe in cygwin/bin (rename it or delete it). This causes a conflict with the real linker (VC++’s link.exe). It was likely caused by the order in which VC++ executable directories were set, so if you need it, you might be able to keep it.
  • For the QtMovieWin project, edit all source files (there’s only 2) so that windows.h is included first (this is due to macro above, which will now cause a macro redefinition by the preprocessor).
  • For QtMovieWin, also rearrange the order of include, so that #include <wtf/Vector.h> is included before config.h and windows.h to prevent some min/max macro nonsense.
  • For QtMovieWin, take off prompt error reporting for linking (ERRORREPORT:PROMPT) project (see Linker >> Advanced page in project setting).
  • Make a new config file by copying WebCore/config.h and making WebCore/config_qt.h. Remove the ARRAYSIZE macro defintions from config_qt.h. Update all files in QtMovieWin project so that config_qt.h is included instead of config.h.
  • Install the QuickTime SDK.
  • Update VC++ directories:
    • Includes >> QuickTime 7.3 SDK\CIncludes
    • Library Files >> QuickTime 7.3 SDK\Libraries
  • For QtMovieWin, make sure you linker inputs are set correctly for QuickTime libraries and VC++ can find them.
  • For QtMovieWin, add “CoreFoundation.lib” to linker inputs.
  • (This may have been an issue caused by not having SP1, you might not have to do this, try compiling first) Turn off whole program optimization for all projects.
  • (This may have been an issue caused by not having SP1, you might not have to do this, try compiling first) Turn off treat warnings as errors for JavaScriptCore, WebKit, WebKitGUID, WebCore, and WTF projects.
That should do it.

Some general advice, work from within VC++ and compile as you make changes; make it a touch-and-go operation. In this way you see errors as they occur and can more easily spot and fix issues. Don’t simply follow what I’ve written above as tutorial thinking it’s a sure-fire solution for compilation, things may very likely change with different iterations of the code.