Hacking the Brackets shell

This is a quick hack to make use of Adobe’s Brackets shell to make your own desktop apps in HTML and javascript. If you haven’t seen Brackets, take a look ~ it’s a full-featured IDE written in javascript which installs on the desktop as a self contained app or exe. It’s powered by the Chromium Embedded Framework (CEF3), and now has a separate node server process. This where it differs from node-webkit, where Chromium and node run in the same thread. In certain scenarios the Brackets approach could offer performance benefits, but the downside is that communication (via web-sockets) needs to be set up between the two processes, making use of node modules more difficult.

The good news is that CEF extensions are included to allow most of the file system operations that an application might want to do, as long as you’re dealing with text files only, and have no need to read or write binary files or connect to sqlite, etc. The other plus is the the Brackets team have the build process extremely well gruntified, and (at least on the mac) it’s insanely quick to get up and running.

In this post, I’m going to take you through the steps to create a viable application:

  • Download and build Brackets.
  • Hack the shell to use your own icon and application name.
  • Where to look for API documentation.
  • Using the API in your code to set up a File menu.

Building Brackets

There are very detailed instructions on building Brackets on the Brackets shell github wiki, note that it’s best to clone the Brackets project as well as the shell project into adjacent folders, as the grunt files assume it to be there. Once you have the whole of brackets building OK, then you can start hacking.

Hacking the shell

A search for the string “Brackets” on the brackets-shell project root quickly reveals likely candidates for files to change:

  • /Gruntfile.js ~ one reference under build.name.
  • /appshell/config.h ~ defines APP_NAME for each OS.
  • /appshell_config.gypi ~ one reference under variables.appname.
  • /appshell/mac/English.lproj ~ 5 references, covering all mentions of the app name in the main menu.

To customise the app icon, simply replace /appshell/mac/appshell.icns with your own icon file. The /appshell/res/ folder has an appshell.ico file for Windows and various sized png’s of the icon.

If you now run grunt full-build in the brackets-shell root you should get a rebranded version of Brackets.

Exploring the API

When you build the shell, the fully packaged app appears in /installer/mac/staging/. But you also get an empty shell in /xcodebuild/Release/, with no HTML in it. When you run it, you are prompted for an html file to open, which gets loaded into the webkit window. This is really useful for experimenting and for the development process, as you don’t have to build after every change. Also, when you right click on the window, you get a “Show Dev Tools” option. This opens up a console just like the Chrome Dev Tools. If you type “appshell” at the console prompt, you get to see the API objects.

As you can see, the appshell.app object lets you work with the window and menus, and the appshell.fs object lets you work with the file system. The appshell.shellAPI object is where the shell communicates with your code, as I’ll show later.

You can get more documentation on the API from the comments in /appshell_extensions.js.

Using the API

If you want to cut to the chase, clone my project to a folder adjacent to the brackets-shell folder. Run the shell in /xcodebuild/Release/ and open the index.html in the markdown-editor project root. You should see a nice markdown editor UI with a fully functional file menu. Take a look at /hackdown.js ~ the comments in this file explain what’s going on.

Packaging the app

To create a build with your app in the shell, you need to change a file ~ In the brackets-shell project, change the Gruntfile.js thus:

  • Remove /src from copy.www.files[0].cwd.
  • Change git.www.repo to ../markdown-editor.

Then run grunt full-build in the brackets-shell folder. Your packaged app should now be in /installer/mac/staging/.

Acknowledgements

Thanks to James Taylor for the markdown editor code that I based this demo on, and Clint Berry who’s blog helped me get up and running.

Up the Workers!

Mineurs

Web Workers – exploring the API

Web workers is a technology in flux, support is variable across current browsers and very sketchy in older versions (yep, IE). In this article I’m going to explore the current state of the API, what’s useable and how to manage a fallback strategy.

Main features

Spawn a new thread ~ The whole point of workers is that they spawn a new thread, allowing long resource heavy processing to occur outside of the main page thread, and so without locking up the UI. You can spawn multiple workers and they each get a new thread.

Cannot access the DOM ~ To prevent possibly conflicting DOM updates, workers have no direct access to the DOM, operating within their own sandbox and communicating with the spawning page via messages.

Supported in all current desktop browsers ~ The API is supported in some way in the big 5 desktop browsers, and most mobile browsers with the notable exception of the android browser. This is not so much an issue as Chrome is now the default browser in most current android builds.

Getting started

This code creates a worker instance and the JavaScript file specified is run synchronously in a new thread. This means the initial run of the code completes before control is returned to the spawning thread. The URL for the code is resolved relative to the spawning page, and absolute paths are only allowed if within the same domain (protocol, host and port) as the spawning page.

It’s worth noting that in most browsers it’s possible to use the data:// protocol to pass code to the worker without an external .js file. However, for security reasons, Chrome doesn’t support it so I wouldn’t recommend it.

Communication

As you can see, communication between the spawning page and the worker is via and asynchronous messaging system. You can use addEventListener() as well:

Worker scope

Properties

  • self ~ the global worker object.
  • location ~ similar to window.location but read-only.
  • navigator ~ contains appName, appVersion, userAgent and platform.

Methods

  • postMessage() ~ used to communicate with the web page.
  • importScripts() ~ used to load external scripts.
  • setTimeout() and setInterval().
  • addEventListener() and removeEventListener().
  • close() ~ stops the worker.

Events

  • message ~ triggered when the web page is communicating with the worker.

Other

  • all ECMAScript objects, such as Object, Array, Date, JSON etc.
  • the XMLHttpRequest constructor.

The worker object

Methods

  • postMessage() ~ used to communicate with the web worker.
  • addEventListener() and removeEventListener().
  • terminate() ~ stops the worker.

Events

  • message ~ triggered when the worker is communicating with the web page.
  • error ~ triggered when the worker generates an uncaught error. The event object contains message, filename and lineno properties.

Loading external code

The importScripts() function allows you to load external code from your worked object’s scope. This might be any third party library, except of course those that rely on the DOM.

The path is relative to the worker script, and strangely absolute paths can be to any domain, unlike paths used in the Worker() constructor.

Experience has shown that third party libraries often have a reference to window even though they are not dependent on the DOM. They can still be used by simply giving self and alias of window.

Loading external code asynchronously

In the code above, all imported scripts are loaded and run synchronously, locking up the thread in the original spawning page. While not a problem for small libraries, you might like to use the following strategy for larger ones:

Workers spawning subworkers

In Firefox, Opera and IE10, a worker can spawn subworkers; sadly this isn’t possible in webkit based browsers. Use the following code within the worker to test for support:

As a fall-back, delegate spawning of new worker to the parent script.

Data transfer

Internally data is passed between worker and parent as a string, or in some cases, as we will see later, as transferrable objects. Modern browsers do a structured clone of an object passed to the postMessage() function. For that reason, postMessage will only take data that can be serialised, that means no functions. An error will be thrown if the data cannot be serialised.

Transferable objects can be used in all current browsers except Internet explorer. They’re transferred by reference and do not involve a copy, making the transfer much faster. Only an ArrayBuffer is transferable (yes it does exist, and is supported by current browsers). This is a fixed length buffer of 8-bit bytes. Once transferred, the ArrayBuffer is empty in the original scope.

NOTE: Early implementations in Chrome used alternative postMessage method ~ webkitPostMessage, to support transferable objects.

For convenience, you can use typed arrays, which are “views” on the ArrayBuffer:

Name Bytes Description
Int8Array 1 Eight-bit two’s complement signed integer
Uint8Array 1 Eight-bit unsigned integer
Uint8ClampedArray 1 Eight-bit unsigned integer (clamped to the range 0-255)
Int16Array 2 Sixteen-bit two’s complement signed integer
Uint16Array 2 Sixteen-bit unsigned integer
Int32Array 4 Thirty-two-bit two’s complement signed integer
Uint32Array 4 Thirty-two-bit unsigned integer
Float32Array 4 Thirty-two-bit IEEE floating point
Float64Array 8 Sixty-four-bit IEEE floating point

Usage:

The second argument is an array of objects that are transferable. These objects must exist within the message object. You can mix transferable and non-transferable objects in the message. If you’re using a typed array, be sure to transfer the buffer property. The following code will test for support:

Worker life cycle

A worker persists until EITHER

  • all its code has executed AND
  • it has no timers running AND
  • it has no onmessage handler AND
  • it has no event listener

OR

  • the parent page is navigated away from OR
  • the parent script terminates it OR
  • the worker closes itself

Debugging and handling errors

Unfortunately current browsers don’t support break points and stepping in worker code. However, error consoles will report errors and allow inspection of the code. You can listen for and trap ‘error’ events on the worker object. I find a convenient way to debug is to set up console.log() and alert() functions which delegate these to the parent page.

Feature support matrix

desktop browsers:

Feature Firefox Chrome Safari IE Opera
Worker support 3.5+ 3.0+ 4.0+ 10+ 10.6+
Structured clone yes yes yes 5.0+ yes
Transferable objects 18.0+ 17.0+ 6.0+ no 15.0+
Workers spawning workers yes no no yes yes

mobile browsers:

Feature iOS Safari Android
browser
Blackberry
browser
Opera
mobile
Chrome
mobile
Firefox
mobile
Worker support 5.0+ not since 2.1 7.0+ 11+ 28.0+ 22.0+
Structured clone yes ? no ? yes 8.0+
Transferable objects yes ? no ? yes 18.0+
Workers spawning workers no no ? ? no no

Worker libraries

Finally, there are a couple of interesting web worker library projects out there:

In a future post, I’ll look into these and see how they shape up.

Spritesheets in createjs – how to support IE8

It’s a pain, I know, but some corporate clients insist on supporting legacy browsers, especially Internet Explorer, largely because their own internal systems require their offices to use them. A recent project I was asked about involved some bitmap animation but had to work on IE8 ~ now I know IE8 is only two major versions ago, but bearing in mind how slow off the mark Microsoft has been in supporting HTML5, it still has to be considered “legacy”. A great way to do bitmap animation is to use CreateJS, which is a javascript library that makes using canvas easy for Flash as3 developers like myself.

CreateJS has a SpriteSheet class that works with the BitmapAnimation class to create an animation. A spritesheet consists of a single image into which all of the frames of an animation are packed, making best use of the space available, and a JSON file containing information on the positions within the image of each frame and named sequences of frames. On the CreateJS website you can download ZOË, a tool that allows you to create a spritesheet for CreateJS from a SWF file. Here’s an example of a JSON file created by ZOË:

In CreateJS, here’s how you create an animation:

All great and easy so far. Now how to support IE8 (and any other non-canvas supporting platform for that matter)? The first problem is that if you load createjs.js at all you will generate a javascript error in IE8, so you need to check for canvas support before you do so:

Having done that, you can now check for the presence of the createjs object in window to decide when to fall back to the CSS alternative I’m about to show you. I’ve created a class called CSSAnimation to handle it:

You can then create the animation thus. The following code will append the spritesheet image to the div with the id “div-ID” and animate it: