Copy CSS styles without additional HTTP requests

I’m working on an editor product for Plone that has a realtime preview in an iframe. In order to style the preview, I needed the same stylesheets loaded in that iframe as were in the parent window, but iframes don’t directly provide a way to do that. Enter Javascript.

A straightforward approach (and the first one I tried) would be to copy the stylesheet elements from the parent window to the iframe. To do that, I put this code in the <head> of the child document:

window.onload = function() {
  // Get a ref to the parent document's stylesheets.
  var parentdoc_ss = window.parent.document.styleSheets;
  var head = document.head;
  for (var i=0; i<parentdoc_ss.length; i++) {
    // For each stylesheet in the parent document, append a clone of it to the child.
    // NOTE: if you don't clone them, they'll disappear from the parent. Fun!
    document.head.appendChild(parentdoc_ss[i].ownerNode.cloneNode(true));
  }
}

The problem with that approach is that it can cause an additional HTTP request for each stylesheet. In practice, the original stylesheets may already be cached in the browser, on the server, or both. But even so, let’s see if we can copy the styles without making any HTTP requests.

window.onload = function() {
  // Get a ref to the parent document's stylesheets.
  var parentdoc_ss = window.parent.document.styleSheets;
  // Grab a stylesheet from the current document.
  if (document.styleSheets.length<1) {
    // If one doesn't exist, create it.
    document.head.appendChild(document.createElement("style"));
  }
  var ss = document.styleSheets[document.styleSheets.length-1];
  var rule_index = 0; // We'll need this to help us add the rules in order.
  for (var i=0; i<parentdoc_ss.length; i++) {
    for (var j=0; j<parentdoc_ss[i].cssRules.length; j++) {
      // Loop through the rules in each of the parent document's stylesheets.
      /* NOTE: IE doesn't support the cssRules property. It has "rules" instead.
              This is just a PoC so I'm not going to fix it for IE, but I'm confident it can be done.
      */
      var r = parentdoc_ss[i].cssRules[j];
      if (r.type == CSSRule.IMPORT_RULE) {
        // If the current rule is an @import, copy the rules from the stylesheet it imports.
        for (var k=0; k<r.styleSheet.cssRules.length; k++) {
          /* FIXME: Assuming a max depth of 1 import for now.
                    This should really be done recursively, but it's a PoC, so hey.
          */
          // Insert the rule from the parent doc's stylesheet into ours.
          ss.insertRule(r.styleSheet.cssRules[k].cssText, rule_index++);
        }
      } else {
        // Insert the rule from the parent doc's stylesheet into ours.
        ss.insertRule(r.cssText, rule_index++);
      }
    }
  }
}

This whole time I’ve been referencing iframes, but it really should work for any documents that share the same origin. While I only tested on Firefox 4, I wouldn’t be surprised if it worked on WebKit, too. It would definitely need tweaking for Internet Explorer.

While it’s easy enough to manipulate individual styles with Javascript, even with JQuery, I had to do a fair amount of research and trial-and-error before I had this nailed down on Firefox. I haven’t found any other documentation for copying styles without reloading the stylesheets themselves, so I hope someone finds this useful, at least for theory. If you have any suggestions or find a better approach, please leave a comment.

Plone with a clean development environment on Leopard

I still have a G5 PowerMac. It runs Mac OS X 10.5 Leopard. I know it’s old, but since it’s a PowerPC I can’t upgrade the MacOS. It’s still very relevant for doing Plone development, as Apple has a long history of installing old libraries anyway, so managing that stuff is just part of life with Apple. Here’s how I set it up to do development for Plone in a clean way:

When you install Plone using the installers with default options you get a virtual environment. Changes you make to your buildout in there generally don’t affect your operating system. When you want to develop a Plone product or do core development, however, you’ll be working from a Subversion working copy, not a normally-installed Plone setup. The development environment instructions gets you set up with some nice tools, but there are a couple of additional steps you can do to keep your development environment nice and tidy.

Things you’ll need to have a sane development situation on OS X:

  • XCode Tools – Version 3.1.4 seems to be the last PPC-compatible version. It’s there, but you’ll need to register and dig for it. XCode 3.1.4 includes…
  • Subversion – version 1.4.4, [update: Plone now uses git] which seems to be just new enough to work. If you are reading this later on and 1.4.4 doesn’t work anymore, install a new version using…
  • Homebrew – or another package manager of some kind. I’ve used MacPorts, Fink, Gentoo Prefix, and now I’m on the PowerPC branch of Homebrew. I got this idea from David Glick. However, I almost gave up when complications arose from removing MacPorts. I’m sure you can adapt these instructions to whatever package manager you want. I like Homebrew because it doesn’t duplicate what the OS provides, such as…
  • Python – but Plone doesn’t work with Python 2.5, so you’ll need to install Python manually, because Homebrew doesn’t duplicate what the OS provides. You’ll want Python 2.6 for Plone 4 and Python 2.4 for Plone 3. For 2.4 you’ll want to compile it with a special MACOSX_DEPLOYMENT_TARGET=10.5 flag. Whatever Python you have, you’ll want…
  • PIP – You can get by with easy_install, but despite OS X having surprisingly good segregation for site-packages, who wants to live without uninstall? Just easy_install pip, and never use easy_install again. Then use pip to install…
  • virtualenv – Creates silos in which to put Plone setups so they don’t contaminate your system and your system doesn’t contaminate your Plone. You can pip install this with the system Python and point it to a specific Python version later, when you use it.

How to set up a virtualized Plone development environment:

Set up a virtual environment.
$ virtualenv -v -p /usr/local/bin/python2.6 --no-site-packages --distribute ./py26env
Check out the Plone development buildout.
$ svn co  https://svn.plone.org/svn/plone/buildouts/plone-coredev/branches/4.1/ ./plone41devel

[Update: Plone now uses git.]

Change directories into your working copy.
$ cd ./plone41devel
Invoke bootstrap.py with the virtualenv’s python.
$ ../py26env/bin/python bootstrap.py && bin/buildout

Set up a local buildout configuration

The buildout configuration files for Plone are in Subversion. If you are a core developer and make a change you run the risk of accidentally committing that change to the core. A file named ‘local.cfg’ should be in svn:ignore, which means you can create it and edit it to your heart’s content without worrying about it getting caught up in your feverish fixing. It needs to hook back into the real buildout.cfg though, so it should look something like this:

[buildout]
extends =
	buildout.cfg

Then you just run bin/buildout -c local.cfg and after that you can use bin/develop rb.

Fixing PIL and lxml for OS X

Plone 4.1 needs PIL and lxml to be truly happy, but OS X makes it particularly challenging to install these. Fortunately, others have solved this problem, so I’m just going to give you my local.cfg that takes care of it. PIL is easy: Alex Clark’s Pillow does all the work for us. The real trick is the redefinition of the [instance] part with an arbitrary dependency on [lxml]. That’s necessary to force the lxml part to be run before anything else, which is the only way it will work.

[buildout]
extends =
	buildout.cfg
eggs +=
	Pillow
parts +=
	lxml

[lxml]
recipe = z3c.recipe.staticlxml
egg = lxml
build-libxslt = true
build-libxml2 = true
static-build = true
libxml2-url = ftp://xmlsoft.org/libxslt/LATEST_LIBXML2
libxslt-url = ftp://xmlsoft.org/libxslt/LATEST_LIBXSLT

[instance]
recipe = plone.recipe.zope2instance
user = admin:admin
http-address = 8080
eggs = ${buildout:eggs}
environment-vars =
	zope_i18n_compile_mo_files true
	_dummy ${lxml:egg}

Finally, if you want to check out a package, you can modify [sources] in local.cfg. Just add a sources part:

[buildout]
extends =
…
[sources]
my.product = svn svn://uri

and run bin/develop co my.product && bin/develop rb to check out and install most eggifiable repositories.