How does Backport work?

Overview

For the terminally-inquisitive, here's how backport works...

All of the API features are locked so they can't be altered by scripts. However, Javascript has many tricks up it's sleeves that allow us to overcome such obstacles.

Where does the JS API live?

In order to understand how Backport.js works, you first need to understand where the JS API features live within the Javascript environment. It took quite a lot of digging, but eventually it was confirmed that the API was exactly where it was supposed to be; on the "global object".

There's a huge amount of confusion about just what exactly is the "global object", in particular the fact that it's not the same as the "global scope" catches most people out. I must admit that I'm still quite confused by it myself at times, but hopefully I can help explain things a little...

What is the scope chain?

When you reference something (like a variable, constant or function) within one of your functions, Javascript goes to great lengths to find it for you.

It checks what's known as the "Scope chain" from left to right to see if your variable is in there. Here's a very simplistic view of what the scope chain looks like:

Block scope (if applicable) → Function scope → Closure scope chain → Global scope → Global object

The global object is referenced by this from the global scope, it's the point of last resort for Javascript when it's looking for something. And it's where the JS API lives.

Because we can't alter the actual JS API (it's locked) there's only one option left - to override it further up the scope chain.

Most of your code runs in the global scope, such as your Event handlers and other top-level functions that they call. Unfortunately you can't define overrides in there because Javascript will find the original API features living in the global scope, see that they are locked, and throw a big fat error at you.

To make matters worse, we can't define overrides further up the scope chain, because we'd have to do it in lots of places. The only common point of reference, aside from the global object, is the global scope.

So, we need to find a way to create stuff in the global scope without the global object throwing errors at us.

Separating the global scope and global obeject

To do that, we need to make the global object disappear while we define our overrides. Luckily, Javascript's bag of tricks comes to the rescue: null

Null is a bit weird when compared to pretty much everything else in Javascript. It doesn't have any properties, or a prototype chain, or a scope.

So, by using Javascript's call() method, can we scope something to null and free ourselves from that troublesome error-throwing global object? Unfortunately, no. The call method simply adds a new scope to the start of our scope chain that contains one variable: this – and that variable is pointing to some other object and it's associated properties and prototype chain. The rest of our scope chain is unchanged, so any code we have in our functions will still have the dreaded global object lurking around waiting to throw errors at us.

Eval to the rescue!

Luckily, Javascript has another trick up it's sleeve: eval()

Evaluates a string of JavaScript code without reference to a particular object.

More accurately, eval() lets you run some fresh Javascript code in the global scope. There's just one problem, it brings a friend with it: The global object.

The global scope talks to it's friend via it's this variable. If only there was a way to change the value of this to something that doesn't lead back to the global object...

eval.call(null,someCode);

And there we have it: A way to run some code in the global scope without the troublesome global object joining the party.

It should be noted that it's only possible to do this because the JS environment in Warzone isn't running in strict mode. If it was, the call() method would spot our sneaky trick and scope everything back to the global object. Let's hope that Warzone never switches it's JS environment to strict mode!!

The backport() function in Backport.js is just an abstraction layer that writes some code for us and runs it in the global scope after hiding the global object. It enables us to inject stuff in to the global scope, without getting errors thrown at us. And, once we have something in the global scope, Javascript will find it first and not bother looking for it in the global object where the JS API lives.

Hang on...

But doesn't that mean that we can no longer get access to the stuff on the global object - the original JS API functions? It does, but luckily prototype chains come to the rescue.

From the global scope, you can still get to the global object via this. And to make live easier, backport keeps a handy reference to the global object as well: backport.global

So, let's assume we've used backport to delete the deprecated droidFromId() function (by setting it to undefined in the global scope), we can still access it (assuming it's still in the JS API) like this:

var gameObj = backport.global.droidFromId(someId);

Furthermore, to make things a little more descriptive, any functions that we override (to expose upgraded/extended alternatives) have a .native property that points back to the original JS API function.

For more information, see Accessing native API features.