Subscribe to XML Feed
24 Apr 2010

Hacking Chrome with Git

Motivations

I spent some time hacking around in Chrome the other day because I have been extremely frustrated with the behavior of the downloads bar in the Mac version of Chrome. When the user initiates a download, it appends the downloads bar to the bottom of your current window, causing your window to grow by the height of the download bar.

This behavior really bothers me, since I have my window set up with about 10px of padding around all sides so I can easily see my IM window and other things that I have docked to the top/side of my desktop. When Chrome grows the window, I have to manually shrink it and then scoot the window away from the title bar again. Then, when I close the downloads tab, I need to make the window bigger so there isn’t an enormous gap below the browser window.

I submitted a bug report about this issue, but a member of the Chrome team explains that they consider this to be the correct behavior:

This is by design, as we do not want to change the size of the web content. We do attempt to restore the original window size when you close the download shelf, although that logic seems to break at times (see Issue 39266).

If that’s their position, then fine, though it does contradict other behavior of Chrome’s. Chrome will, for instance, not enlarge the window when they display the “do you want Chrome to remember your password” alert bar and other similar things. It simply pushes the content down a small amount and then pulls it back up when you close the alert. This is far preferable to me over messing with the size of my window.

The Fix

Well, Chrome is open source, so I decided that if they won’t fix it, I would. At least for my build, anyways. Turns out that it’s a one-line patch to a single controller file. Here they check to see if the view that’s initiating a redraw is the download shelf or the bookmark bar. If it is, they add the height to the view to the window. In this case, I just removed the condition for the download shelf and voila!

How I built Chrome

Hopefully I can save you some time if you’d like to perform similar surgery to your copy of Chrome. It took me a few tries to get everything right, due to some incorrect documentation and the enormous size of the Chrome checkout. Naturally I want to use git, since it’ll make my patch sets so much easier to maintain. These instructions show you how to build a specific release version with your patches using git. This assumes you’re building 5.0.375.17. Substitute a different version if you’d like.

  1. Follow the Chrome Git instructions steps 1-3. You’ll need to install depot tools as indicated in step 2.
  2. Instead of using the URL specified in step 4, use http://src.chromium.org/svn/releases/5.0.375.17, or another version of your choice.
  3. cd into ./src
  4. git config --add svn-remote.375.url http://src.chromium.org/svn/branches/375/src
  5. git config --add svn-remote.375.fetch :refs/remotes/375
  6. git svn fetch 375 (this fetches branch 375 into your local git repo)
  7. Look in http://src.chromium.org/svn/releases/5.0.375.17/DEPS and find in the deps dict where there is src alone. In this case, the value is /branches/375/src@45275. This means that version 375.17 is svn commit 45275. Remember that number.
  8. git checkout refs/remotes/375
  9. git log and search for 45275. Once you find the commit, note the git sha hash. In this case, it is 1bb2782587c269be10f842a468e2d26b38003daa.
  10. git checkout -b 375.17 1bb2782587c269be10f842a468e2d26b38003daa (make a checkout of the exact version of the release)
  11. Do steps 5-6 of the Chrome git instructions.
  12. Apply your patches

You now have a specific version of Chrome and all its dependencies checked out and ready to build. You can now follow the Mac OS X build instructions, or just open src/chrome/chrome.xcodeproj and choose “chrome” from the build menu in the upper left. Choose “Release” if you want to build Chrome for actual use. Note that this took over an hour on my fancy new i7 MacBook Pro with 4GB of RAM. It also used enough resources that my computer was nearly unusable for part of the compile.

Chrome Build Process

A few notes about the Chrome build process: The git repo stores only the code for the browser itself. General libraries, both Google internal and external, are fetched by using the gclient sync command. This is somewhat similar to git submodules, in that it allows Google to pin specific revisions of dependencies to a release. The DEPS file stores all the revisions of the external repositories that are necessary to duplicate a build. The tricky part was to make sure that the git checkout and the dependencies both corresponded to the same release version.

I haven’t yet had to switch my build to use a different release. As soon as that happens, I will update this page to give instructions on how to do so.

Download

If you’d like, you can download a copy of the current dev channel of Chromium with my fix applied. Or you can follow the instructions above and patch it yourself.

The Future

The Chrome guys are working on eliminating the http:// scheme from standard HTTP urls. It turns out that this caused a lot of bugs when they released it into the dev channel so they reverted the change, but they have said that they will eventually revive this “feature”. I found it really annoying and my custom chrome build will likely do away with http hiding once it becomes part of Chrome. I’m happy to work with someone who’s also annoyed by this to set up an automatic build to release a new dev channel build with custom patches to fix some of these “features”… Let me know.

View Comments
17 Apr 2010

Ruby Brew at Great Lakes Ruby Bash

ruby brew

John Roos of Roos Roast designed an excellent blend of coffee for Great Lakes Ruby Bash. John started Roos Roast while selling cars at Dunning Subaru. I met him there when I bought my WRX, when he gave me a free pound of coffee with my car. Not long after that, he quit selling cars and started roasting coffee full-time.

We have enough coffee to make 300 cups during the conference, plus we have 6 half-pound bags of coffee to raffle off for the attendees.

Huge thanks to John for doing this for us. If there’s enough demand, he might be convinced to roast more batches. Call him up or stop by his roasting operation and demand some Ruby Brew!

If you’re not in the Ann Arbor area, you can order his coffee by phone or online.

Working on this project with John was a lot of fun. I watched as he carved the Ruby Brew logo into a rubber stamp block. He also put me to work a bit. I stamped 300 of our cups, and weighed and ground our coffee for the conference.

Thanks, John!

View Comments
18 Nov 2009

Method of the Month 1: Ruby's sort vs. sort_by

Ruby’s Array has 2 methods for sorting: sort and sort_by. Both methods sort your array (obviously) but they do it in slightly different ways.

Background

This article is based on a talk I gave at the Ann Arbor Ruby Brigade in August. The idea for this talk was conjured up at eRubyCon during a discussion between Gayle Craig and me. We decided we would each give a brief talk about the differences between sort and sort_by at our next user group meetings. Amazingly, we both followed through.

sort

A Ruby array’s sort method is pretty straightforward. If called without a block, it uses each objects’ comparison operator (<=>) to return a new array with all the elements sorted. If called with a block, you may specify your own comparator as shown below:

[1, 4, 3, 7, 2, 9].sort                   # => [1, 2, 3, 4, 7, 9]
[1, 4, 3, 7, 2, 9].sort {|a, b| b <=> a}  # => [9, 7, 4, 3, 2, 1]

Note that the block for sort accepts two parameters. The return value of the block must be -1, 0, or 1. -1 indicates that the first item is less than the second, 0 indicates they are equal, and 1 indicates that the first item is greater than the second one.

sort_by

The sort_by method is slightly different. Its block only takes one parameter instead of two. Your array is then sorted by the results of each of the block calls. For example:

[1, 4, 3, 7, 2, 9].sort_by {|i| -i}             # => [9, 7, 4, 3, 2, 1]
%w{ abcd ab abc abcde }.sort_by {|i| i.length}  # => ["ab", "abc", "abcd", "abcde"]

This is accomplished using something called a Schwartzian Transform.

The Schwartzian Transform

All a schwartzian transform does is turn each record of your array into a tuple with the first element being the item on which you wish to sort. That array of tuples is then sorted. Since the first item in the tuple gets precedence, it is effectively the same as sorting the array by the results of the sort_by block. Once sorted, the first element of the tuple is removed, leaving your original array in sorted order. Below is an example implementation in pure ruby:

module Enumerable
  # This is essentially the implementation of sort_by
  # It's written in C in MRI, so it's a little faster
  def my_sort_by
    self.map {|i| [yield(i), i]}.sort.map {|j| j[1]}
  end
end

Why choose one over the other?

If you’re sorting a small array, it really doesn’t matter which sort method you choose. Use whichever is easiest to code and understand. For larger arrays, however, the performance differences between sort and sort_by can be substantial.

You generally want to use sort_by over sort whenever comparisons are relatively expensive. Note that in Ruby, method dispatch is often enough to count as an expensive comparison. Unless you’re sorting things that end up as C primitives in MRI (like an Integer) you’ll likely want to use sort_by.

The performance varies so greatly because of the Schwartzian Transform. Using sort, each pairwise comparison that must be performed calls the <=> operator. In the case of sort_by, the comparison “calculation” is only done once at the beginning, and then the array is sorted on an Integer primitive much more quickly.

I hope this was helpful to someone. It’s probably more than anyone has written on the difference between these two methods.

View Comments
07 Apr 2009

Walk Like a Duck

Introduction

In a duck-typed language like Ruby, it’s very important that you actually use duck typing. This is especially important when you’re designing a library or other code that could interact with objects that you don’t control. I have come across a few libraries lately that don’t follow duck typing conventions and have caused unexpected behavior when I’ve used them.

In this article, I’m going to pick on Shoulda. Thoughtbots, please do not take offense! I’m a fan, and I figured you guys could take it.

Duck Typing

it's a duck!

Before I get into the details, I’ll say a word or two about duck typing for those who may need a refresher. The principle of duck typing basically says that if something “walks like a duck and talks like a duck” it probably is a duck. What this really means is that when you’re interacting with other objects, you should not care what they are, but rather how they behave. If you have an object that responds to #each, who cares if it’s an Array or Set or a custom collection?

Implementing Duck Typing

You should rarely ever use #is_a? on an object. You should be using #respond_to? instead. The whole point in duck typing is that you don’t care what kind of object you’ve received. The only thing you care about is that the object does what you want it to do. Let me show you an example from Shoulda.

The following code is from lib/shoulda/assertions.rb in Shoulda’s git repository. assert_contains is a custom assertion for Shoulda that allows you to easily test a collection for the presence of a particular element.

def assert_contains(collection, x, extra_msg = "")
  collection = [collection] unless collection.is_a?(Array)
  msg = "#{x.inspect} not found in #{collection.to_a.inspect} #{extra_msg}"
  case x
  when Regexp
    assert(collection.detect { |e| e =~ x }, msg)
  else         
    assert(collection.include?(x), msg)
  end
end

If you look at the first line of the method definition, you’ll notice that they are calling is_a?(Array) on the “collection” this is passed into the function. The code I was writing was using a Set instead of an Array. The trouble is, if you pass anything other than an array, that line will wrap whatever you passed in an array. So I ended up with an Array with a Set inside of it, which caused the rest of the assertion code to fail.

The solution to this problem is very easy. If you look at my fork, all I’ve done is change that one line (and add some tests, of course). Now we only wrap the collection in an array if it doesn’t respond to include?. It’ll work with an Array still. It’ll work with a Set. It’ll work with your custom data structure that can detect the presence of objects. That’s the power of duck typing.

def assert_contains(collection, x, extra_msg = "")
  collection = [collection] unless collection.respond_to? :include?
  msg = "#{x.inspect} not found in #{collection.to_a.inspect} #{extra_msg}"
  case x
  when Regexp
    assert(collection.detect { |e| e =~ x }, msg)
  else         
    assert(collection.include?(x), msg)
  end
end

Conclusion

Whenever you’re checking for an actual class of an object rather than examining how it behaves, take a second to think if you can duck type it instead. It will make your code more generic, and make it easier for others to use.

Again, thank you to Thoughtbot for Shoulda and I’m sorry I singled you out. Shoulda is great, and just happened to be in the wrong place at the wrong time for me to start a rant about duck typing.

View Comments

Older Posts

Automatically log in on merb-auth account creation 17 Mar 2009 Comments
Fix Missing Disqus Comment Form 04 Mar 2009 Comments
tasteb.in is up! 01 Mar 2009 Comments
Play in the Sandbox 14 Feb 2009 Comments
How to learn Ruby 05 Feb 2009 Comments
Sinatra block parameters! 04 Feb 2009 Comments
Run specs with autotest, er, autospec! 26 Jan 2009 Comments
Gearing up for CodeMash 06 Jan 2009 Comments
Commit a linear git history to subversion 04 Jan 2009 Comments
Use Capistrano to set your production database password 03 Jan 2009 Comments
Resolutions 02 Jan 2009 Comments
Ruby Hoedown Videos Online 22 Aug 2008 Comments
Make RubyMate work with MacPorts Ruby 18 Jun 2008 Comments
Fix empty sake tasks 10 Feb 2008 Comments
Rails 2.0 is out 07 Dec 2007 Comments
Using other MySQL Storage Engines 03 Dec 2007 Comments
RubyInject 27 Nov 2007 Comments
shoulda 29 Sep 2007 Comments
Test behavior, not implementation! 21 Sep 2007 Comments
I can has patch? 18 Sep 2007 Comments
Caboose Sample App 10 Sep 2007 Comments
Railsify 07 Sep 2007 Comments
Massive list of Rails development tips from Peepcode 07 Sep 2007 Comments
Predefined models? No different than any others. 31 Aug 2007 Comments
Installing a Rails stack on Mac OS X with MacPorts 28 Aug 2007 Comments
First Post 27 Aug 2007 Comments