AEM’s CSSFileBuilder vs. Protocol-relative paths in ClientLibs

Protocol-relative paths

The first time I ever heard of protocol-relative URLs (properly called “network-path references”) was back in 2007 in an article by Ned Batchelder, who I know from Freenode’s #python channel. The most common use-case for these URLs are when you want your site to refer to some external resource, such as a stylesheet, web-font, image, etc. – but you cannot know at the time the site is coded whether the end-user will be using HTTPS or HTTP. (You should always be using HTTPS anyway, but we can’t always enforce that…)

However, if you use one of these protocol-relative paths in a CSS file that participates in a ClientLib, you’ll discover an un(der?)-documented behaviour. I think it would be easiest to just show you what’s going to happen if you try it, and then explain why, and what you can do about it.

Creating a ClientLib

In this example, I’m going to make a ClientLib that will contain a css.txt file, and a ‘css’ folder with one CSS file inside it. For purposes of this example, I’ll try to load a Google Webfont using a protocol relative URL – the font I selected is called “Gloria Hallelujah.”

I’m lazy, so I’m just going to drop all of this inside /apps/geometrixx. The following steps will basically echo those found in the Adobe Experience Manager documentation for creating a ClientLibrary – I’m just including them here so you don’t have to open a new tab. If you already know how to make a ClientLibrary, there’s nothing particular interesting going on here…

  1. Navigate to http://localhost:4502/crx/de/index.jsp#/apps/geometrixx and log in (if you’re not already).
  1. Right-click on ‘geometrixx’ and select ‘Create…’, then ‘Create Node…’apps-create-node
  1. Give the node a name you’ll be able to find later – I chose ‘clPathExample.’ The node type needs to be ‘cq:ClientLibraryFolder’. Click ‘Save All.’clientlib-dialog
  1. Select the new ‘clPathExample’ node. Go to the Properties pane and add a property called ‘categories’ of type String[]. Put a value in here that you’ll be able to remember – I went with ‘cl.loadfont’.
    clientlib-properties
  1. Right-click on ‘clPathExample’ and choose ‘Create …’, then ‘Create Folder.’ Name the folder ‘css’. Click ‘Save All.’css-folder-dialog
  1. Right-click on the new ‘css’ folder and chose ‘Create …’, then ‘Create File.’ Name the file ‘font.css’. Click ‘Save All.’
    font.css-file
  1. In the editor pane that opened for ‘font.css,’ enter the following text:font.css-content
    @import url(//fonts.googleapis.com/css?family=Gloria+Hallelujah);
    .example { font-family: 'Gloria Hallelujah', serif; }

This is the path to our Google Font, with a protocol-relative URL; we’re also setting up a CSS class we can use later to see it get applied. Click ‘Save All.’ Leave this editor tab open, as we’ll be using it again later.

  1. Right-click on ‘clPathExample’ and choose ‘Create …’, then ‘Create File.’ Name the file ‘css.txt’. Click ‘Save All.’css-txt-dialog
  1. In the editor pane that opens for ‘css.txt’, you have two options. You can either use the ‘#base’ method of setting the ‘relative root’:css-text-content


#base=css
font.css

or, you can put the full relative path to the font.css file as a single line:

css/font.css

At Axis41, our habit is to use #base, so I’m going to go with that for now; however, neither option seems to have any effect on the behaviour we’re discussing today. Click ‘Save All.’ You can close the css.txt editor pane at this point.

For the next step, rather than go through the whole process of embedding this ClientLib in /etc/designs, then creating a page that uses <cq:includeClientLib /> to pull it into head.jsp, we’re just going to take a short-cut for purposes of demonstration, and make a static file under /content. If you really want to go the longer route, feel free to follow along with the Adobe Experience Manager example found here; or you can just take my word for it that the behaviour we’re demonstrating doesn’t change when used through an embed.

  1. Right-click on ‘/content’ and choose ‘Create…’, then ‘Create File.’ I’m going to name my file ‘example.html’. In the new editor pane that opens for ‘example.html’, put the following HTML code:


<html>
<head>
<link href="/apps/geometrixx/clPathExample.css" rel="stylesheet">
</head>
<body>
<div class="example">
This text should be using our Google Webfont.
</div>
</body>
</html>

Again, note that we’re not following the standard best practice of linking the ClientLibrary through /etc/designs; I would not recommend doing it this way in a production environment, but doing this here shaves several paragraphs off the example.

Click ‘Save All’ and load the example page in your browser (i.e., http://localhost:4502/content/example.html).

broken-page-only

What just happened there?!

Obviously, this is not loading our Google Font. If we look at ‘View Source’, everything looks OK on ‘example.html’, so what happened? Let’s take a look at our Developer tools to see what kind of hint that can give us.

broken-network-tab

Hmm – why does the Network tab show a 404 for the font request? Let’s see if the Console tab gives us any better information…

font-not-loaded

Wait – why is it trying to load the Google Font from localhost:4052? Let’s jump right to the CSS file itself:

css-code-broken

What happened?!

The problem lies in a little piece of code known as the CssFileBuilder, which runs as part of the “Day CQ HTML Library Manager” service (com.day.cq.widget.impl.HtmlLibraryManagerImpl). Adobe hints at this behaviour in their documentation, with the following aside:

When you embed CSS files, the generated CSS code uses paths to resources that are relative to the embedding library. For example, the publicly-accessible library /etc/client/libraries/myclientlibs/publicmain embeds the /apps/myapp/clientlib client library:

I mean, reading that sentence, you’d think, “Sure – makes sense, that seems like exactly the behaviour we want,” but the fact that the CssFileBuilder tried to turn a network-relative path into a series of parent-path components back to the site root certainly came as a surprise to me. So, what exactly can we do? Well, thankfully, there’s a feature inside of CssFileBuilder – which I can’t find any documentation for, unfortunately – that allows use to override this behaviour.

Make our URL absolute

Earlier, I told you we’d be re-visiting the font.css editor in CRX DELite. Let’s go there now and make a small change to the code:

@import url(absolute://fonts.googleapis.com/css?family=Gloria+Hallelujah);
.example { font-family: 'Gloria Hallelujah', serif; }

Now, let’s try re-loading the CSS file and see what happens:

css-code-working

Perfect – exactly what we were looking for! The addition of the pseudo-scheme ‘absolute:’ stopped the ClientLibrary code from breaking our URL. Now let’s make sure everything looks OK in our ‘example.html’ file:

all-is-well

Great! Looks exactly like we had hoped. The obvious drawback of this method is that we cannot now directly reference the CSS files used in a ClientLibrary – but, at least for Axis41, that had zero practical implications, as it wasn’t something we ever found ourselves doing, anyway.

Takeaway

So now you know – if you want to use protocol relative URLs (or “network-path references,” as we should probably all be calling them) in your CSS ClientLibrary files, you have to prefix them with the pseudo-scheme ‘absolute:’ to prevent CssFileBuilder from rewriting them as relative paths to the site root.

Featured picture by Tom Eversley