Adblock Plus and (a little) more

Rewriting JavaScript code with JSHydra · 2010-12-07 20:15 by Wladimir Palant

I was thinking about possible ways to rewrite Adblock Plus code automatically. One way to use it would be the online tool to find redundant filters. Due to its use of Adblock Plus code (which requires JavaScript 1.7) this tool would only work in Firefox. I didn’t really want to give up the convenient features of JavaScript 1.7, neither did I want to fork my code for this web tool which I would need to sync up regularly. Automated rewriting is really the only option but it is usually too complicated.

That is, until I remembered JSHydra, a tool created by Joshua Cranmer. In its current incarnation is has an abstraction on top of SpiderMonkey’s parsing tree which is relatively easy to understand and manipulate. And there is even a decompiler that can take this data and generate JavaScript code from it. The indentation of the result still has some quirks and I also found some bugs (fixed in my private copy of the JSHydra repository). But I actually managed to automatically rewrite code and it even runs.

My rewrite script does a whole bunch of modifications:

  • Changes let foo into var foo (yes, that part is really easy)
  • Removes modules-related code (like EXPORTED_SYMBOLS declaration and Cu.import() calls)
  • Removes XPCOM-related constants (Cc and friends), these have to be emulated externally somehow
  • Converts destructuring assignment of arrays, introduces a temporary variable for that
  • Similar approach taken with for each loops, these are converted into normal for loops with a temporary variable as loop index. Yes, this will only work when iterating over arrays but that’s the only case I care about.
  • Rewrites __proto__-based inheritance chains, that’s the most tricky part. Not only does this change prototype declaration, it also has to change constructors because the “classic” inheritance model requires creating an object instance and we don’t want to run random code there. Update: As Jeff Walden pointed out below, rewriting constructors isn’t really necessary and this logic can be simplified significantly.
  • {__proto__: null} is changed into {}. Yes, that sucks but I don’t think there is a real replacement.

As a result, the redundancy check will now work in all browsers (automatically rewritten code, original code). Yes, that works even though I couldn’t find a proper replacement for __defineGetter__ which is used for lazy evaluation — in IE these properties will simply be calculated immediately rather than delayed.

Tags:

Comment [3]

  1. clouserw · 2010-12-08 00:27 · #

    This is a cool idea.

  2. Jeff Walden · 2010-12-08 08:53 · #

    The easiest way (in browsers that support it) to get prototypal inheritance going without __proto__ is to use Object.create(proto) — that gives you an object on which you can set the desired properties. You could also specify those properties as a second argument, although it seems to me direct assignment would be clearer. You can’t reimplement Object.create for browsers without it, but you could implement the prototypal inheritance bit without much real difference, with a little work.

    Reply from Wladimir Palant:

    Nice one, I didn’t know about it. Unfortunately, the only current browser which doesn’t support __proto__ happens to be Internet Explorer – and it doesn’t support Object.create() either of course :)

  3. Jeff Walden · 2010-12-08 18:50 · #

    Well, you can still fake the prototypal inheritance bit (and I wish Textile had a preformatted-text frob):

    Object.create = Object.create || function(proto, props)
    {
      assert(arguments.length == 1, "props not supported");
      function O() { }
      O.prototype = proto;
      return new O();
    };

    You could go further in supporting props too, but only if the props have a certain form and define only data properties which are enumerable/configurable/writable, which seems like either too much to expect of developers or too much deviation from the actual method for them to have to keep in mind.

    Reply from Wladimir Palant:

    Ouch, I entirely forgot that you can have two constructors share a prototype. This makes rewriting constructors unnecessary of course, thanks a lot! I didn’t really want to fake Object.create(), instead I changed my _extend function: https://hg.adblockplus.org/jshydra/rev/39fee8df80a1

    PS: Textile does have preformatted text but that isn’t part of the small feature set enabled for comments, for whatever reasons.

Commenting is closed for this article.