#336 ✓ checked-in
db48x

if the Popcorn.plugin function's definition argument is a function, call that function each time the plugin is instantiated instead of just once when it's defined

Reported by db48x | February 14th, 2011 @ 12:25 PM | in 0.4

This lets us make the _setup/start/end functions close over instance data.

Comments and changes to this ticket

  • db48x
  • Rick

    Rick February 14th, 2011 @ 04:09 PM

    • State changed from “new” to “assigned”

    It will always be reduced to a function, then wrapped in another function that adds internal references and controls adding the plugin's function call as a track event

  • db48x

    db48x February 18th, 2011 @ 04:22 PM

    • State changed from “assigned” to “peer-review-requested”
  • Scott Downe

    Scott Downe February 18th, 2011 @ 04:42 PM

    Right, so I am liking this idea more and more.

    Popcorn.plugin("testPlugin", (function() { // initial closure for immediate execution and plugin data
      return function() { // second closure for unique instance data
        var instanceData = {};
        return {
          _setup: function( options ){},
          _start: function( event, options ) {},
          _end: function( event, options ) {}
        };
      };
    })());
    

    That little monster above covers all bases.

    I have not looked at your code db48x, but is this the idea?

  • db48x

    db48x February 18th, 2011 @ 04:45 PM

    Yes, that will work, but it's kinda pointless. You could do this just as easily:

    // code for immediate execution and plugin data here
    Popcorn.plugin("testPlugin", function() { // closure for unique instance data
      var instanceData = {};
      return {
        _setup: function( options ){},
        _start: function( event, options ) {},
        _end: function( event, options ) {}
      };
    });
    
  • Scott Downe

    Scott Downe February 18th, 2011 @ 04:50 PM

    The immediate executing function has a different use case, though.

    Setting up something that is to be shared by all tracks, like a script file, can be loaded in that.

    Where as, the non executing function is for instance data unique per track.

  • Scott Downe

    Scott Downe February 18th, 2011 @ 04:51 PM

    Popcorn.plugin("testPlugin", (function() { // initial closure for immediate execution and plugin data
      var sharedData = "everyone can use me, and change me";
      return function() { // second closure for unique instance data
        var instanceData = {};
        return {
          _setup: function( options ){},
          _start: function( event, options ) {},
          _end: function( event, options ) {}
        };
      };
    })());
    
  • db48x

    db48x February 18th, 2011 @ 04:54 PM

    How is that any different from this:

    // code for immediate execution and plugin data here
    var sharedData = "everyone can use me, and change me";
    Popcorn.plugin("testPlugin", function() { // closure for unique instance data
      var instanceData = {};
      return {
        _setup: function( options ){},
        _start: function( event, options ) {},
        _end: function( event, options ) {}
      };
    });
    
  • annasob

    annasob February 18th, 2011 @ 04:57 PM

    • State changed from “peer-review-requested” to “review-needs-work”

    popcorn.lowerthird.html error: manifest is undefined

  • Scott Downe

    Scott Downe February 18th, 2011 @ 04:59 PM

    • State changed from “review-needs-work” to “peer-review-requested”
    // code for immediate execution and plugin data here
    var sharedData = "everyone can use me, and change me";
    Popcorn.plugin("testPlugin", function() { // closure for unique instance data
      var instanceData = {};
      return {
        _setup: function( options ){},
        _start: function( event, options ) {},
        _end: function( event, options ) {}
      };
    });
    

    Is also every so slightly different, as sharedData isn't hidden to just that plugin.

  • annasob

    annasob February 18th, 2011 @ 05:00 PM

    Why did u change the state Scott?

  • Scott Downe

    Scott Downe February 18th, 2011 @ 05:03 PM

    • State changed from “peer-review-requested” to “review-needs-work”
  • Scott Downe

    Scott Downe February 18th, 2011 @ 05:04 PM

    Whoops, I didn't technically change it. I just didn't refresh, and still had the "peer-review-requested" in my drop down list from where it was originally.

  • db48x

    db48x February 18th, 2011 @ 05:04 PM

    • State changed from “review-needs-work” to “peer-review-requested”

    I just pushed a fix, so that state is now fine.

  • Scott Downe

    Scott Downe February 18th, 2011 @ 05:05 PM

    • State changed from “peer-review-requested” to “review-needs-work”
    • Milestone set to 0.4
    • Milestone order changed from “1” to “0”

    Also, I think it is safe to say this can make it into 0.4.

  • Scott Downe

    Scott Downe February 18th, 2011 @ 05:06 PM

    • State changed from “review-needs-work” to “peer-review-requested”

    lol, it happened again!

  • db48x

    db48x February 18th, 2011 @ 05:07 PM

    • Milestone cleared.

    Scott: fair enough, but I would write it like this:

    (function() {
      // code for immediate execution and plugin data here
      var sharedData = "everyone can use me, and change me";
      Popcorn.plugin("testPlugin", function() { // closure for unique instance data
        var instanceData = {};
        return {
          _setup: function( options ){},
          _start: function( event, options ) {},
          _end: function( event, options ) {}
        };
      });
    })();
    

    This way it's easier to see the difference between the two functions.

  • Scott Downe

    Scott Downe February 18th, 2011 @ 05:13 PM

    Yeah, that also works.

    wonders if the ticket status will change

  • Rick

    Rick February 18th, 2011 @ 05:38 PM

    I just want it on record that I'm opposed to changing the behaviour to one that provides "magic". I'd much prefer an "enhancement" to the api then a "change". A third boolean arg would maintain back compat. Also in the future I think we need a required 3 use case minimum before adding a feature.

  • db48x

    db48x February 18th, 2011 @ 06:02 PM

    Err, what magic are you referring to?

  • Rick

    Rick February 18th, 2011 @ 07:05 PM

    • State changed from “peer-review-requested” to “review-needs-work”

    This is in conflict with the latest 0.4 branch...

    
    git pull db48x 336-plugin-closures
    remote: Counting objects: 15, done.
    remote: Compressing objects: 100% (11/11), done.
    remote: Total 11 (delta 8), reused 0 (delta 0)
    Unpacking objects: 100% (11/11), done.
    From https://github.com/db48x/popcorn-js
     * branch            336-plugin-closures -> FETCH_HEAD
    Auto-merging popcorn.js
    CONFLICT (content): Merge conflict in popcorn.js
    Auto-merging test/popcorn.unit.js
    Automatic merge failed; fix conflicts and then commit the result.
    

    The "magic" is that something different is now happening "in place of" behaviour that is already established - when it should be "in addition to". As an end user (think: Butter) the expected behaviour of plugins has changed, even though I have not told the plugins to behave differently.

    On an unrelated note - this diff is basically impossible to review...

    https://github.com/db48x/popcorn-js/commit/32f6e14b2cdddd3dfa64c507...

  • Rick

    Rick February 18th, 2011 @ 07:10 PM

    Also... if I have a plugin that requests remote resources, wont this RE-request those resources everytime the plugin is called? The very essence of a plugin registration factory is so that the definition can be executed and _setup can be resolved.

  • db48x

    db48x February 19th, 2011 @ 07:33 AM

    • State changed from “review-needs-work” to “peer-review-requested”

    The old behavior was a bug. If any current plugins rely on that bug I will be happy to rewrite them for you; in fact the transformation is pretty trivial. I'll look at those merge conflicts while I'm at it...

    Oh, and yes, sorry about the diff. I discovered that the section of code that I was working on had lots of whitespace at the ends of the lines, but I accidentally removed them from the whole file instead of just that section. mea culpa. I recommend checking this diff out via the git command line, where you can specify -w to ignore changes that are just in the whitespace.

  • db48x

    db48x February 19th, 2011 @ 02:17 PM

    I fixed up the code and mustache plugins so that they'll work right; none of the others needed any changes. I went ahead and simplified the googlenews and webpage plugins a bit while I was looking at them.

  • Rick

    Rick February 20th, 2011 @ 06:22 PM

    I'm not trying to be a PITA, but I'm not entirely sure I understand why you refer to the current behaviour as a "bug" - whether an anonymous function expression OR immediately invoked function expression was passed as the plugin's definition argument to Popcorn.plugins( name, definition ) is entirely a matter of syntactic convenience and I promise - all the behaviours are intended. It's the returned object that the plugin factory cares about. We simply allowed the end developer several ways to deliver the object and handled the execution of anonymous function expressions internally - if that happened to be what was provided to the plugin registration.

    During that "registration" several pieces of functionality are wrapped up in a function expression declared as pluginFn which will be the actual function that is executed when the plugin is called. pluginFn accepts the object argument passed to the plugin function call.

    Within pluginFn, we take the _setup function from the original definition object and inline it to be executed everytime the plugin function called (which sounds like what is being described in the ticket title). When the plugin is called, the object that is passed to it IS the new instance of data.

    I don't agree with the policy of calling expected behaviour a "bug" without having a full understanding of the API and the decisions that were made when the API was being developed.

  • Rick

    Rick February 20th, 2011 @ 06:58 PM

    As for EOL whitespace, I've been trying to find an opportunity to complete #286, but to do so I want all outstanding commits to land to do the entire file wholesale.

  • Rick

    Rick February 20th, 2011 @ 07:12 PM

    • State changed from “peer-review-requested” to “under-review”
  • db48x

    db48x February 21st, 2011 @ 12:50 AM

    • State changed from “under-review” to “peer-review-requested”

    Thanks for undertaking the review.

    When we discussed this in November of last year, I asked you to implement exactly what I've implemented in this patch and you said that you would. Since your stated intention was to implement one thing and the actual implementation did something else, I assumed that it was the result of a bug.

    This is perhaps a bit long, but here's our entire conversation:

    [14:31] <db48xOther> so I'm looking at Popcorn.plugin
            <db48xOther> it defines a variable called definition which is a function
            <db48xOther> but never seems to call it
            <db48xOther> am I missing something?
    [14:32] <annasob> rwaldron_ ^^^^
    [14:37] <db48xOther> oh, I see
            <db48xOther> plugin[name] = definition
            <db48xOther> and I'm passing in a function, so it actually doesn't create a new definition function
    [14:41] <rwaldron_> db48xOther, that signature is deprecated
    [14:42] <rwaldron_> please use Popcorn.plugin("name", { start: fn(), end: fn() ...etc... })
            <rwaldron_> i should have boaz remove that
            * db48xOther sighs
    [14:44] <db48xOther> why is it depreciated?
    [14:52] <rwaldron_> db48xOther, it's start and end points are ambigious
    [14:53] <rwaldron_> literally making it impossible for scott's updater code to know when to run 
            <db48xOther> rwaldron_: how so?
    [14:54] <rwaldron_> because updater needs to explicitly know when to run a command
            <rwaldron_> the first plugin signature buries that in the callback
            <rwaldron_> no good
    [14:55] <db48xOther> that's only because Popcorn.plugin doesn't call the definition plugin
            <rwaldron_> guh
    [14:56] <rwaldron_> the definition function is NOT YOURS as the developer of a plugin to worry about
            <rwaldron_> thats used internally to smartly maintain scope
    [14:57] <rwaldron_> its going to be too problematic in the long run
            <rwaldron_> in the short we jsut need to update the plugins
            <db48xOther> the function I pass in is going to a variable called definition
            <rwaldron_> which really shouldnt be hard
            <db48xOther> but the plugin function also creates a definition function if I only pass in an object
    [14:58] <db48xOther> that's a different kind of definition function
            <db48xOther> an internal one
            <db48xOther> instead of checking to see if the definition variable is an object
            <db48xOther> it should check to see if it's a function
            <rwaldron_> so,. thats going to be the only way to do it
            <rwaldron_> all of the handling is going to be changed
            <db48xOther> if it's a function, it should call it and use the return value as the object that the rest of the plugin function acts on
    [14:59] <db48xOther> so it would create the same internal definition function in both cases
            <db48xOther> that would let _me_ smartly manage my scope, so that I can close over variables per instance of my plugin
            <rwaldron_> one sec
            <rwaldron_> let me see if i understand you...
            <rwaldron_> (give me a min)
            <db48xOther> ok
    [15:00] <rwaldron_> pardon the flodd
    [15:01] <rwaldron_> flood*
            <rwaldron_> Popcorn.plugin("foo", function () {
            <rwaldron_>   
            <rwaldron_>   // do stuff
            <rwaldron_>   return {
            <rwaldron_>     start: function () {},
            <rwaldron_>     end: function () {}
            <rwaldron_>   };
            <rwaldron_> });
            <rwaldron_> you want to do write the plugin like that?
    [15:02] <rwaldron_> i gotta go
            <rwaldron_> i'll be back in about an hour
    [15:03] <db48xOther> yes
            <db48xOther> where the comment is, I would put variable declarations
    [15:04] <db48xOther> the _setup, start, end and timeupdate functions can then close over those variables so that each time the user uses the plugin those variables can have different values
    [15:45] <rwaldron_> ok, i'm going to patch in db48xOther's request
            <rwaldron_> catch up in a few
    

    (I've edited it to remove parallel conversations)

  • db48x

    db48x February 21st, 2011 @ 12:51 AM

    • State changed from “peer-review-requested” to “under-review”

    bah

  • Rick

    Rick February 21st, 2011 @ 10:34 AM

    No changes were made because after this discussion, while reviewing the code I was about to change, it occurred to me that the change would be incorrect and that I had initially misunderstood the context. The original "bug" was the allowance of a function expression that had an arbitrary return. We needed some form of restrictions on what would be allowed into the plugin factory as a valid plugin definition, which boils down to:

    
    // The bare minimum required to define a plugin:
    
    {
     _setup: function() {}, 
     start: function() {}, 
     end: function() {}
    }
    

    How that object is delivered to the plugin factory is up to the developer.

    
    Popcorn.plugin( "myPlugin", {
     _setup: function() {}, 
     start: function() {}, 
     end: function() {}
    });
    
    // Is fundamentally the same as:
    
    Popcorn.plugin( "myPlugin", (function() {
    
      return {
       _setup: function() {}, 
       start: function() {}, 
       end: function() {}
      };
    
    })() );
    

    So when we wrote the API in our first code sprint, we also decided that if someone wanted to define a plugin like...

    
    Popcorn.plugin( "myPlugin", function() {
    
      return {
       _setup: function() {}, 
       start: function() {}, 
       end: function() {}
      };
    
    });
    

    That Popcorn.plugin() would detect that it was a function, execute it, capture the returned object and recursively call it back: Popcorn.plugin( name, theReturnedAndCapturedObject ) - which would illustrate a behaviour that matched the other accepted plugin syntaxes.

    I apologize if there was any confusion created by that earlier discussion.

  • Scott Downe

    Scott Downe February 22nd, 2011 @ 04:54 PM

    I have to say I am in favour of:

    Popcorn.plugin( "myPlugin", function() {
    
      var istancedTrackData;
    
      return {
       _setup: function() {}, 
       start: function() {}, 
       end: function() {}
      };
    
    });
    

    It reminds me of:

    var TestObject = function() {
    
      var used = false;
    
      this.useIt = function() {
        used = true;
      };
      this.amIUsed = function() {
        return used;
      };
    
    };
    
    var testObject1 = new TestObject();
    var testObject2 = new TestObject();
    
    testObject1.amIUsed(): // false
    testObject2.amIUsed(): // false
    
    testObject1.useIt();   // you use it, you cannot un use it :P
                           // var used is tightly bound, hidden, and protected from the use
    
    testObject1.amIUsed(): // true
    testObject2.amIUsed(): // false
    
  • Rick

    Rick February 22nd, 2011 @ 05:47 PM

    I've re-reviewed these changes and was able to successfully run them (the conflicts noted earlier have been resolved).

    In addition to my points above, updating Butter with this code causes major regressions due to the change in the handling of manifests. Manifests were expected to be part of the plugin's options instance object and each item in the manifest was transformed into an element and updated as necessary. I'm still trying to sort through that.

    In the meantime I ran the test suite and plugin unit tests for all the plugins

    Here are my results (I've provided screenshots - hopefully these are helpful)
    Attribution: Fails
    http://gyazo.com/3c92d098150e9da9162002c1e7ac3de4.png

    GoogleMaps Plugin: Fails
    http://gyazo.com/30952376ab1e4ddae4675d36ce4d1296.png

    Tagthisperson: Fails
    http://gyazo.com/0a7e4250622d5a86d6d20094fff3fbaa.png

    Twitter: Fails
    http://gyazo.com/c8cc9ec00fce8971b7c2c9ecc695b40e.png

    Webpage: doesn't finish, several errors but might be unrelated

    Wikipedia: Fails
    http://gyazo.com/8bb84a002ce7c8050fea05a1d089e116.png

    Additionally, line 714-716 is missing curly braces ( https://webmademovies.lighthouseapp.com/projects/63272/styleguide )

  • db48x

    db48x February 22nd, 2011 @ 05:49 PM

    Hrm, I haven't seen any of these errors. I'll investigate.

  • Rick

    Rick February 22nd, 2011 @ 06:12 PM

    Update with Butter regression, it appears to be caused by:

    
    // Line 688 of popcorn.js
    
    function pluginFn ( setup, options ) {
    
      if ( !options ) {
        return this;
      }
    
      // storing the plugin natives
      options._natives = setup;
    

    Previously, setup contained the manifest property as it was defined by the plugin definition's return object. Butter uses the options._natives.manifest - which it is now missing from several plugins.

  • db48x

    db48x February 22nd, 2011 @ 06:24 PM

    Previously, setup contained the manifest property as it was defined by the plugin definition's return object. Butter uses the options._natives.manifest - which it is now missing from several plugins.

    pluginFn could do this:

    if (!"manifest" in setup)
    {
      options._natives.manifest = manifest;
    }
    

    Also, I am seeing some of those errors today. They look like they might be itermittant; the wikipedia tests, for example, aren't loadin the wikipedia information until after the tests have failed. Maybe wikipedia is slow today?

  • db48x

    db48x February 22nd, 2011 @ 07:46 PM

    Basically all of these tests have the same problem. They have something visible for a few seconds, and set an interval so to check the visibility during that time slot and then after to make sure it was visible only when it should have been. This should work fine, but sometimes it fails if the browser doesn't actually run the callback function at the right time, or if the browser can't play the video at the correct rate.

    I think we should rewrite these tests so that the test uses the same timing mechanism as popcorn itself. We can use the code plugin to run the test functions instead of using setInterval.

  • Rick
  • Rick

    Rick February 22nd, 2011 @ 11:16 PM

    (from [4ec2a0e1bcbdfd3286b2e9550d81997ebe6a550a]) [#336] fix 'manifest is undefined' error for plugins that pass an object instead of a function https://github.com/rwldrn/popcorn-js/commit/4ec2a0e1bcbdfd3286b2e95...

  • Rick
  • Rick
  • Rick
  • Rick
  • Rick
  • Rick

    Rick February 22nd, 2011 @ 11:25 PM

    • State changed from “under-review” to “peer-review-requested”

    Ugh, sorry about that.

    I was able to resolve all the Butter manifest issues:

    https://github.com/rwldrn/popcorn-js/tree/336

  • Rick
  • Scott Downe

    Scott Downe February 23rd, 2011 @ 09:55 AM

    • Milestone set to 0.4
    • Milestone order changed from “2” to “0”
  • db48x

    db48x February 23rd, 2011 @ 10:22 AM

    • State changed from “peer-review-requested” to “review-looks-good”

    Yea, storing the manifest so that Butter can get at it looks good. Who wants to sr?

  • Rick

    Rick February 23rd, 2011 @ 10:32 AM

    • State changed from “review-looks-good” to “super-review-requested”

    Daniel, I'm updating this to super-review-request status ;P

  • Scott Downe

    Scott Downe February 23rd, 2011 @ 02:03 PM

    • State changed from “super-review-requested” to “under-review”
    • Assigned user changed from “db48x” to “Scott Downe”
  • Scott Downe

    Scott Downe February 23rd, 2011 @ 03:45 PM

    • State changed from “under-review” to “review-needs-work”

    This needs some unit tests.

    I just so happen to of made some unit tests: https://github.com/ScottDowne/popcorn-js/commit/4b13b2e4f130f817be0...

    Consider more unit tests for other functionality I may of missed.

    Also, I see on line 668:

    function pluginFn ( setup, options ) {
    

    I believe this is more in line with what we have been doing thus far:

    var pluginFn = function( setup, options ) {
    

    The video element seems to be broken on chrome on my machine. If you pause, you cannot play again. This make core unit tests not possible. This is happening on a simple page with one video element, this is not popcorn.

    So yeah, just the unit tests and consider changing pluginFn declaration and I will pass this.

  • Scott Downe

    Scott Downe February 23rd, 2011 @ 03:47 PM

    One more thing, some missed opportunities for whitespace here:

    +    if ( typeof definition === "object" ) {
    
    +      plugin[ name ] = function(options) { return pluginFn(definition, options); };
    
    +    }
    
    +    else if ( typeof definition === "function" ) {
    
    +      plugin[ name ] = function(options) { return pluginFn(definition(), options); };
    
    +    }
    
  • db48x

    db48x February 23rd, 2011 @ 04:29 PM

    • State changed from “review-needs-work” to “super-review-requested”

    Review comments fixed, test merged in, etc.

    https://github.com/db48x/popcorn-js/commits/336-plugin-closures

  • Scott Downe

    Scott Downe February 23rd, 2011 @ 05:01 PM

    I am getting test failures now.

    171 tests of 177 passed, 6 failed.

    The tests are:

    Popcorn Plugin: Plugin Factory (2, 6, 8)

    Popcorn Plugin: Remove Plugin (4, 15, 19)

  • db48x

    db48x February 23rd, 2011 @ 05:04 PM

    • State changed from “super-review-requested” to “review-needs-work”

    should be fixed now, except for the exec test which is itermittant

  • db48x

    db48x February 23rd, 2011 @ 05:26 PM

    • State changed from “review-needs-work” to “super-review-requested”
  • Scott Downe

    Scott Downe February 23rd, 2011 @ 05:28 PM

    • State changed from “super-review-requested” to “review-looks-good”

    Awesome, thanks for your patience.

    Tests are all passing, linting passes, demo runs, and plugins are passing tests from what I can tell.

  • Rick

    Rick February 24th, 2011 @ 09:56 AM

    • State changed from “review-looks-good” to “review-needs-work”

    A few last changes, per style guide ( most recent version here: http://gul.ly/tt )

    
    Line 688-691, current:
    
    if (!manifest)
    {
      manifest = definition.manifest || {};
    }
    
    
    Please update to (including comments):
    
    //  If `manifest` arg is undefined, check for manifest within the `definition` object
    //  If no `definition.manifest`, an empty object is a sufficient fallback
    if ( !manifest ) {
      manifest = definition.manifest || {};
    }
    
    
    Line 721-724, current:
    
    if ( !options.target ) {
      var manifestopts = "options" in manifest && manifest.options;
      options.target = manifestopts && "target" in manifestopts && manifestopts.target;
    }
    
    
    
    Please update to (including comments):
    
    if ( !options.target ) {
    
      //  Sometimes the manifest may be missing entirely 
      //  or it has an options object that doesn't have a `target` property
    
      var manifestopts = "options" in manifest && manifest.options;
      options.target = manifestopts && "target" in manifestopts && manifestopts.target;
    }
    

    I'm marking this as review-needs-work, after you make the changes, just update it to "review-looks-good". These changes won't require a complete review process

    Thanks and great work!

  • Scott Downe

    Scott Downe February 24th, 2011 @ 10:39 AM

    • Assigned user changed from “Scott Downe” to “db48x”
  • Rick

    Rick February 24th, 2011 @ 12:47 PM

    (from [4e7b59eb615715c64b781f94b8b06961ee07e3d9]) [#336] tweak the comments in the test, address Scott's other review comment, and wrap something a little more nicely https://github.com/rwldrn/popcorn-js/commit/4e7b59eb615715c64b781f9...

  • Rick
  • Rick

    Rick February 24th, 2011 @ 12:47 PM

    • State changed from “review-needs-work” to “review-looks-good”

    Daniel asked me to just go ahead with the changes in a branch, so here they are:

    https://github.com/rwldrn/popcorn-js/commit/3bed562097120ccdfed0ae5...

  • Rick
  • Rick

    Rick February 24th, 2011 @ 12:48 PM

    (from [5713de8e3dd92a6a702e9bbc1543dc0c4bc00e02]) [#336] sometimes the manifest is missing entirely, or it has an options object but that object doesn't have a target property https://github.com/rwldrn/popcorn-js/commit/5713de8e3dd92a6a702e9bb...

  • Rick
  • Rick
  • Rick
  • annasob

    annasob February 24th, 2011 @ 01:18 PM

    Confused: am I staging this off of ricks 336-last branch?

  • annasob

    annasob February 24th, 2011 @ 02:10 PM

    • State changed from “review-looks-good” to “staged”

    Great team work guys!

    So I staged db48x's 336-plugin-closures and Ricks 336-final into 0.4 commit

    I also ran all of the plugins in Chrome and FF 3.6 as well as the semantic_video and the XML and JSON parsers.

  • annasob
  • annasob

    annasob March 21st, 2011 @ 02:47 PM

    • State changed from “staged” to “checked-in”
  • annasob

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Popcorn.js is an HTML5 video framework that lets you bring elements of the web into your videos.

Popcorn.js is a project of Web Made Movies, Mozilla's Open Video Lab.

Shared Ticket Bins

Referenced by

Pages