Getting started with contributing to Processing.js or Popcorn.js

Posted in open source, video, webmademovies on March 14, 2012 by scottdowne

So, the first two open source projects I was involved in have a fairly similar work flow, and I decided to write down the steps to get started in these projects.

Both Processing and Popcorn have fairly good intro sites, and I would start there. Popcorn also have secondary site that is useful. Also might want to check out the original Processing. Those sites provide a general introduction on the projects, and not something I will be covering here.

Both projects have four primary things, an issue tracker, a source repository, revision control, and a developer community.

An issue tracker is where bugs and new features get tracked, assigned, and reviewed. Both projects use something called lighthouse as their issue tracker. These can be found here for popcorn and here for processing. Issues can be anything from whitespace fixes, to documentation changes to complete rewrites. The pages I liked are the recent activity pages. You may want to look under milestones to get a list of potential tickets.

A source repository is a place where the code is stored and updated. Both projects use github as their source repository. These can be found here for popcorn and here for processing. Those are our release repos, in that they only get updated once for each release. If you want the unreleased code, you have to get that from a separate repo. These can be found here for popcorn and here for processing. We call these development branches.

Both projects use git for revision control. It goes hand in hand with github, but I do feel like it is a different thing. Source repository is where the code is kept, and revision control are the tools that interact with it. This is how I like to think of it, as it helps me understand it the way I need to work with it. I might not be technically correct with the definitions, though. Kinda like how the alphabet is not actually a song, but a song helps to understand it.

Both projects use irc for their developer community. This is the place you can go to get real time help from the people that have the answers. Popcorn uses the channel #popcorn and processing #processing.js. These can be found on the server irc.mozilla.org. More information about connecting to irc can be found here or here.

Now that that is out of the way, you will want to create a github account if you don’t already have one, then fork one of the projects from their github page. You will probably want to fork one of the development repos.

From there, you will need to get that github repo onto your local machine. I used this post when I started this, and I recommend it to others.

Next step is to find a bug on one of the lighthouse pages. Maybe comment in the bug that you are interested in working on it, so others know not to work on it as well.

You will want to create a branch on your local git repo named after your bug number. So, if my bug is popcorn bug #927 I will want to create a branch called t927. We add the t because code commits can be shorthanded to numbers, and there may be collisions in branch names and commits. Always good practice to but a letter in it. You can do this by running this command:

git branch t927 develop
git checkout t927

What this does is created a branch named t927 that is based off the branch develop. Then, moves you over to that branch.

Next you can go about changing the code in your local git repo. Once done, you will want to run some git commands.

1. git status: this shows the files that have changed, that you may need to commit.

2. git add: this adds those files. If you notice running git status after a git add, things will of changed.

3. git commit: this commits those added files. Again, running git status after this, you should see even more changes. I like to run this command like this:

git commit -m “[#927] usefull message about what I did”

This will add a message to the commit, so future developers can see why certain lines were changed, and even track it back to the original lighthouse ticket.

4. git push: this pushes the changes into your forked github repo. An example of this command for when I do it is:

git push git@github.com:ScottDowne/popcorn-js.git t927

This command takes the ssh url of my repo, and a name to name for the branch. After doing this, I could find my branch here: https://github.com/ScottDowne/popcorn-js/tree/t927 and my changes here: https://github.com/ScottDowne/popcorn-js/commits/t927

From here you will want to take one of those two urls and drop it back intp the lighthouse ticket, as a comment, and change the status to peer-review-requested. Both projects have a two step review process. Peer-review and super-review. Once both reviews passed, the repo manager will pull your changes into develop, and then on release day, everything including your changes will be pulled into master.

I’ve got a working @font-face ready event in Firefox

Posted in open source on March 13, 2012 by scottdowne

So to continue on from my last post, I was able to get a loaded or ready event for @font-face.

I mentioned in my last post I found a good place to create an event, just did not know how to create an event. I looked around mxr, starting with domcontentloaded, trying to find something similar. It was actually much easier than I thought to setup a custom event inside the c++. All you need to do is call nsContentUtils::DispatchTrustedEvent, and pass in a object that you want the event on, the second parameter is similar to the first, but it needs to be casted (have not quite figured this one out yet), third is a NS_LITERAL_STRING(“nameofevent”), and the last two are two trues (also don’t know what yet). I needed to get access to the document as well, but that was easy too. Final diff looks like this:

diff --git a/layout/style/nsFontFaceLoader.cpp b/layout/style/nsFontFaceLoader.cpp
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -247,16 +247,21 @@ nsFontFaceLoader::OnStreamComplete(nsISt
   // when new font loaded, need to reflow
   if (fontUpdate) {
     // Update layout for the presence of the new font.  Since this is
     // asynchronous, reflows will coalesce.
     ctx->UserFontSetUpdated();
     LOG(("fontdownloader (%p) reflow\n", this));
   }
 
+  nsIDocument* aDocument = ctx->Document();
+     nsContentUtils::DispatchTrustedEvent(aDocument, static_cast<nsIDocument*>(aDocument),
+                                        NS_LITERAL_STRING("fontready"),
+                                        true, true);
+
   return NS_SUCCESS_ADOPTED_DATA;
 }
 
 void
 nsFontFaceLoader::Cancel()
 {
   mFontEntry->mLoadingState = gfxProxyFontEntry::NOT_LOADING;
   mFontSet = nsnull;

And using it on a page like this, you receive the console.log.

<html>
  <head>
    <style>
      @font-face {
        font-family: dekar;
        src: url('Dekar.otf');
      }
      div {
        font-family: dekar;
      }
    </style>
    <script>
      document.addEventListener( "fontready", function( e ) {
        console.log( "We be ready, captain!" );
      }, false );
    </script>
  </head>
  <body>
    <div>
      Hello world!
    </div>
  </body>
</html>

I was pleasantly surprised the event was so easy to create.

This is just some preliminary work, really. This week I hope to get some eyes on it.

Starting to create a @font-face loaded event in Firefox

Posted in open source on March 11, 2012 by scottdowne

I am about to sit down and work on the beginning on what may become a JavaScript font API.

I am going to try something new with this blog post, and write it along side whatever it is I am working on, instead of afterwards. In hopes things will be more fresh in my mind, and to document my thoughts and process as I do things.

With regards to the font API, I am going to start hacking on it, dive right in, and learn something from it. My initial goal will be to somehow create a loaded((meta)data) event for fonts. For now, I am more concerned in making this work, than making it right. Making it right can come later.

First thing I did was head over to mxr and search for “fontface”. I bounced around a bit on there until I found the file nsFontFaceLoader.cpp.

Next I needed to setup some breakpoints, and in order to do this I had to have a webpage that would initiat the code path I am looking for. I started by downloading a font that I could initiat with @font-face, then setup a little html page that looked like this:

<html>
<head>
<style>
@font-face {
	font-family: dekar;
	src: url('Dekar.otf');
}
div {
  font-family: dekar;
}
</style>
</head>
<body>
<div>
Hello world!
</div>
</body>
</html>

I put a break point on the line nsUserFontSet::StartLoad(gfxProxyFontEntry *aProxy, const gfxFontFaceSrc *aFontFaceSrc), and ran the page. My theory was confirmed and my breakpoint was fired right away. All this tells me is I am close to the code I care about. I have found the code that loads the font, but what I really care about is the code right after the font is done loading.

So, this Start Load function is on the nsUserFontSet, and inside it sets up a nsFontFaceLoader object. This object might be what I care about, the thing that is loading. So next thing I am going to do is check what options this object has. I found a few functions like cancel, that I am not really interested in, but I did find a OnStreamComplete, which looks pretty interesting.

So now I am going to add another breakpoint, and leave my old one. Make sure both fire, with the newsest one firing last. That is exactly what it did. Now, this does not mean it is what I want yet, but probably pretty close. I noticed that at this point, if I view the webpage, the font-face has still not displayed, while I sit on my breakpoint. This is a good sign that what I have, is what I need. So next I am going to put breakpoints all over this block, and find the exact moment where the font on the page updates. So, bad news, I went all the way through this function, and the font did not update. Maybe I have the wrong spot after all. It might be some caching nonsense going on, though.

I kept looking around this function, and found a call to UserFontSetUpdated that seems to be related to styling reloads, but, seems to be after the reload, so probably too late. If I look a bit north of that, I see a “userFontSet->OnLoadComplete”, so I will send this over to mxr and see what I find there. Found the file gfx/thebes/gfxUserFontSet.cpp, with the OnLoadComplete function. I am pretty sure this is what I will need.

Next, I need to figure out how the firefox event system works, and create a load event of sorts attached to something… and call the event inside the OnLoadComplete. Probably all for tonight.

Popcorn.js 1.2 new features

Posted in open source, video, webmademovies on March 10, 2012 by scottdowne

A lot of new features landed in this release.

In no particular order:

1. Players have a _teardown function a lot like a plugin. Example:

Popcorn.player( "youtube", {
  _setup: function( options ) {},
  _teardown: function( options ) {}
});

So _setup is called when a new youtube player is created by doing Popcorn.youtube( “id”, “url” ), now _teardown will be called when destroy is called on the created player by doing p.destroy(). All track events and event listeners are cleaned before the _teardown, the idea it to just reverse what you did in _setup.

2. Popcorn.smart is a new function that tries to find the appropriate player for you. Example:

Popcorn.smart( "id", "url" );

Pretty simple, but if id is a video element, and url a valid HTML5 media, a regular old HTML5 video will be setup with popcorn. If the id is a div element, and the url a youtube, it will instead setup a youtube player.

3. Popcorn _canPlayType is a new player feature that is used by Popcorn.smart.

When you create a player, you can define a canPlayType function. Example:

Popcorn.player( "youtube", {
  _setup: function( options ) {},
  _canPlayType: function( nodeName, url ) {}
})

The _canPlayType accepts a node name that the player should be playable on. This is usually a html element, and a url, which is the url to the media to be loaded into the node. This function then return true if it can play, false otherwise. If this function does not exist on a player, undefined is returned instead.

To call this function, you do this Popcorn.youtube.canPlayType( “div”, “youtubeurl” );

4. Track event toString functions. Now, once you have a reference to a track event, you can call toString on it. This will return a string of the default data on any track event, usually the start, end and target. You can also add a toString method to the plugin when the plugin is being written, to better handle what to do with non default data. Example:

  Popcorn.plugin( "something" , {
    _setup: function( options ) {
      options.toString = function(){
        return options.start + ", " + options.end + ": " + options.foo;
    },
    start: function( event, options ){
    },
    end: function( event, options ){
    },
    _teardown: function( options ) {
    }
  });

5. We have moved players parsers and locale out of the core, and into modules. You include them much like you include a plugin, and from there on in, everything else is the same. Example:

<script src="popcorn.js"></script>
<script src="popcorn.player.js"></script>
<script src="popcorn.youtube.js"></script>
<script src="popcorn.subtitle.js"></script>

<script>
Popcorn( function() {

  Popcorn.youtube( "id", "url" ).subtitle({
    start: 10,
    end: 20,
    text: "hello world"
  });
});
</script>

6. We now have a shim to support ie8. This is only supported for the youtube player. Some plugins do work, like subtitle and footnote, but that was just a coincidence, and not something we are testing for, yet. We will improve our support for this over time. To include the shim, you have to include it before you include popcorn, like so:

<script src="popcorn.ie8.js"></script>
<script src="popcorn.js"></script>
<script src="popcorn.player.js"></script>
<script src="popcorn.youtube.js"></script>

7. All our plugins have had their manifests updated to supply options flags. This is useful for programmatically reading plugins dynamically. If someone uploads a plugin, and a program reads it in, it may want to know this information regarding options and mandatory data. Popcorn-maker is going to be using this soon. The idea will be when you click a track event to edit it, these will be a collapsible container with all the optional options. This makes a new plugin far less overwhelming, and easier to get at the important data quickly.

8. You can now supply events into a custom players option object. This is useful for listening to things that can happen during the creation process of the player. Example:

Popcorn.youtube( "id", "url", {
  events: {
    "error": function( e ) {}
  }
})

The error function will be called if for whatever reason, the player did not setup properly. This is also useful for knowing if popcorn.smart failed. Example:

Popcorn.smart( "invalidid", "nonsenseurl", {
  events: {
    "error": function( e ) {}
  }
})

So, the above id and url do not match any players, and is not a valid HTML5 media, the error function will be called. You can also put any other event in here, like canplaythrough, loadeddata, play, etc.

OSD 0.7

Posted in open source on March 10, 2012 by scottdowne

This project release was a pretty small release.

I took a r- with some nits, and turned it into an r+.

I mentioned in my last release I tried to get the nits into the last release, but ended up running out of time due to a strange error.

The error ended up being pretty simple, just not something I knew to look for. The answer came to me chatting over lunch with David Humphrey. I was changing a function that was returning a nsresult to return void. I have done this many times, so I know I had to change it in three places. The cpp file, the dot h file, and the return statement itself inside the function. Still would not build, and the error message was not being very clear. I was looking at my code, and not seeing anything I thought was wrong, so I thought it was something wrong with the build process. This was obviously a time sink…

David said right away I am probably returning inside a macro. Ended up being NS_ENSURE_TRUE would return a nsresult. I switched it to a check and return of my own, fixed up the other nits, built it, made a patch, submitted it, and got an r+. This is just a one time cost, though. I will never have to hit this again, and will always know to check for macros when I get weird errors. Also, it is hard to find macros on mxr so now I know to use dxr for this. I have found the macro I was snagged on, on dxr. Can clearly see the return there. Again, this is one of those one time costs, and gotchya moment that will not happen again. Pretty good experience if you ask me.

Now I need tests and to figure out the next step of review. Until then, I am going to look into building a fonts API! Just starting on this, but will have more about what it is about for my next release.

OSD700 project release 0.6

Posted in open source on February 26, 2012 by scottdowne

Continuing on from my OSD 700 release 0.5 I now I have the next logical release, as 0.6.

My project is to implement hash change media fragment refresh on media documents. Example: Open a media document in a new tab, change the hash tag in the URI to a new number, and hit enter. This is called a media fragment, and changes should update the video’s position. If you do not have my changes though, this will not do anything.

I have gone through a few iterations for this patch, and finally have something that is passing review. It has some nits that I am fixing and was hoping to have fixed for this release, but I hit some issues in the build process and could not wait any longer. Whenever I try to build, it fails saying files were modified externally during the build.

What I am doing in this final commit is similar to what I was trying in my last post. I am inserting a script into the media document’s head, that listens for a hash change and updates the video. If you look in my last post you will see the script I insert, but now it is a simple inline script:

window.addEventListener('hashchange',function(e){document.querySelector('audio,video').src=e.newURL;},false);

That’s it, I just set the media element’s source to the new URI in a hash change event. The advantages to this is we can use the parse media fragment c++ code and our changes are pretty modular enough that we are highly unlikely to break anything.

I had a lot of fun on this release. Felt more productive. During the last bit leading up to this release I got to go back and forth with Boris Zbarsky a couple times, getting some review minuses, and felt I was getting a better handle on the code, knew what he was talking about, and how to implement it. Slowly getting over the learning curve. Every project or codebase I have been in has this moment, and is pretty exciting!

I still need to figure out testing though, and figure out why my builds are failing. I am doing a clobber build now, so we’ll see soon.

First review of my first Firefox patch

Posted in open source on February 20, 2012 by scottdowne

So I had a my first Firefox patch 677122 reviewed. It was a review-needs-work, but that’s fine.

I had to get some help to find someone to review it. I asked in #introduction, and was told Boris Zbarsky was a good candidate, but also to check the log of the file I changed and look for previous reviewers. I saw Boris Zbarsky as a pretty active reviewer, only to cement this advice. I ran the command “hg log path/to/file > output.txt” to check the file for previous reviewers.

My patch failed because there is simply a better approach. I was reloading the page if we had a new hash in a synthetic document, but that would require the video to constantly be refreshed. He suggested I add a script to media documents to listen for haschange events, and do the work there. This is awesome to me, as JavaScript is something I am more familiar with, and I get to do some regular expression work, which I always enjoy.

I have two of the three requirements with this new method already done. Did not take much time at all to write some JavaScript to do this.

I have this:

window.addEventListener( "hashchange", function( event ) {

  var match = /#t=(\d*)(,)?(\d*)/.exec( event.target.location.hash );

  var start,
      end;

  var media = document.getElementsByTagName( "video" )[ 0 ] || document.getElementsByTagName( "audio" )[ 0 ];

  if ( !media || !match || ( match[ 2 ] && !match[ 3 ] ) ) {

    return;
  }

  start = match[ 1 ] || 0;
  end = match[ 3 ];

  media.currentTime = start;

  if ( end ) {

    var listener = function( event ) {

      console.log( this.currentTime, end );
      if ( this.currentTime >= end ) {

        this.pause();
        media.removeEventListener( "timeupdate", listener, false );
      }
    };

    media.addEventListener( "timeupdate", listener, false );
  }

  media.play();
}, false );

This is full of early return and regex awesomeness. It first sets up a listener for hash change events (I just noticed I may need to wait for the page to finish loading), next I check the hash and get a reference to the media element. I check that both of these are valid, if not, return. Then I apply the start time, and setup a listener for the end time. This may need tweaking as time goes on, but it is something to work with.

I also have the code needed to insert my script into the media document creation:

  nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::script, nsnull,
                                           kNameSpaceID_XHTML,
                                           nsIDOMNode::ELEMENT_NODE);
  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);

  nsRefPtr<nsGenericHTMLElement> script = NS_NewHTMLScriptElement(nodeInfo.forget());
  NS_ENSURE_TRUE(script, NS_ERROR_OUT_OF_MEMORY);

  head->AppendChildTo(script, false);

This gets the node info for a script node, creates a script with the node info, and appends it to the head element. This works, and am currently writing this post in my build.

The last part is to figure out how to get all my JavaScript into that script element.

Follow

Get every new post delivered to your Inbox.